summaryrefslogtreecommitdiffstats
path: root/src/qdoc/qdoc/src
diff options
context:
space:
mode:
Diffstat (limited to 'src/qdoc/qdoc/src')
-rw-r--r--src/qdoc/qdoc/src/qdoc/access.h15
-rw-r--r--src/qdoc/qdoc/src/qdoc/aggregate.cpp762
-rw-r--r--src/qdoc/qdoc/src/qdoc/aggregate.h118
-rw-r--r--src/qdoc/qdoc/src/qdoc/atom.cpp458
-rw-r--r--src/qdoc/qdoc/src/qdoc/atom.h223
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.cpp126
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.h17
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.cpp125
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.h17
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.cpp96
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.h20
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef.h207
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef_members.qdocinc162
-rw-r--r--src/qdoc/qdoc/src/qdoc/clang/AST/LLVM_LICENSE.txt279
-rw-r--r--src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h491
-rw-r--r--src/qdoc/qdoc/src/qdoc/clang/AST/qt_attribution.json20
-rw-r--r--src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp1904
-rw-r--r--src/qdoc/qdoc/src/qdoc/clangcodeparser.h94
-rw-r--r--src/qdoc/qdoc/src/qdoc/classnode.cpp260
-rw-r--r--src/qdoc/qdoc/src/qdoc/classnode.h69
-rw-r--r--src/qdoc/qdoc/src/qdoc/codechunk.cpp104
-rw-r--r--src/qdoc/qdoc/src/qdoc/codechunk.h74
-rw-r--r--src/qdoc/qdoc/src/qdoc/codemarker.cpp448
-rw-r--r--src/qdoc/qdoc/src/qdoc/codemarker.h67
-rw-r--r--src/qdoc/qdoc/src/qdoc/codeparser.cpp139
-rw-r--r--src/qdoc/qdoc/src/qdoc/codeparser.h142
-rw-r--r--src/qdoc/qdoc/src/qdoc/collectionnode.cpp104
-rw-r--r--src/qdoc/qdoc/src/qdoc/collectionnode.h126
-rw-r--r--src/qdoc/qdoc/src/qdoc/comparisoncategory.cpp28
-rw-r--r--src/qdoc/qdoc/src/qdoc/comparisoncategory.h54
-rw-r--r--src/qdoc/qdoc/src/qdoc/config.cpp1439
-rw-r--r--src/qdoc/qdoc/src/qdoc/config.h409
-rw-r--r--src/qdoc/qdoc/src/qdoc/cppcodemarker.cpp594
-rw-r--r--src/qdoc/qdoc/src/qdoc/cppcodemarker.h35
-rw-r--r--src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp1021
-rw-r--r--src/qdoc/qdoc/src/qdoc/cppcodeparser.h111
-rw-r--r--src/qdoc/qdoc/src/qdoc/doc.cpp426
-rw-r--r--src/qdoc/qdoc/src/qdoc/doc.h93
-rw-r--r--src/qdoc/qdoc/src/qdoc/docbookgenerator.cpp4771
-rw-r--r--src/qdoc/qdoc/src/qdoc/docbookgenerator.h170
-rw-r--r--src/qdoc/qdoc/src/qdoc/docparser.cpp2846
-rw-r--r--src/qdoc/qdoc/src/qdoc/docparser.h176
-rw-r--r--src/qdoc/qdoc/src/qdoc/docprivate.cpp30
-rw-r--r--src/qdoc/qdoc/src/qdoc/docprivate.h76
-rw-r--r--src/qdoc/qdoc/src/qdoc/docutilities.h28
-rw-r--r--src/qdoc/qdoc/src/qdoc/editdistance.cpp68
-rw-r--r--src/qdoc/qdoc/src/qdoc/editdistance.h17
-rw-r--r--src/qdoc/qdoc/src/qdoc/enumitem.h36
-rw-r--r--src/qdoc/qdoc/src/qdoc/enumnode.cpp82
-rw-r--r--src/qdoc/qdoc/src/qdoc/enumnode.h49
-rw-r--r--src/qdoc/qdoc/src/qdoc/examplenode.h42
-rw-r--r--src/qdoc/qdoc/src/qdoc/externalpagenode.cpp28
-rw-r--r--src/qdoc/qdoc/src/qdoc/externalpagenode.h25
-rw-r--r--src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.cpp161
-rw-r--r--src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.h24
-rw-r--r--src/qdoc/qdoc/src/qdoc/functionnode.cpp473
-rw-r--r--src/qdoc/qdoc/src/qdoc/functionnode.h202
-rw-r--r--src/qdoc/qdoc/src/qdoc/generator.cpp2216
-rw-r--r--src/qdoc/qdoc/src/qdoc/generator.h212
-rw-r--r--src/qdoc/qdoc/src/qdoc/headernode.cpp43
-rw-r--r--src/qdoc/qdoc/src/qdoc/headernode.h46
-rw-r--r--src/qdoc/qdoc/src/qdoc/helpprojectwriter.cpp769
-rw-r--r--src/qdoc/qdoc/src/qdoc/helpprojectwriter.h106
-rw-r--r--src/qdoc/qdoc/src/qdoc/htmlgenerator.cpp3733
-rw-r--r--src/qdoc/qdoc/src/qdoc/htmlgenerator.h181
-rw-r--r--src/qdoc/qdoc/src/qdoc/importrec.h33
-rw-r--r--src/qdoc/qdoc/src/qdoc/location.cpp423
-rw-r--r--src/qdoc/qdoc/src/qdoc/location.h92
-rw-r--r--src/qdoc/qdoc/src/qdoc/macro.h27
-rw-r--r--src/qdoc/qdoc/src/qdoc/main.cpp720
-rw-r--r--src/qdoc/qdoc/src/qdoc/manifestwriter.cpp405
-rw-r--r--src/qdoc/qdoc/src/qdoc/manifestwriter.h46
-rw-r--r--src/qdoc/qdoc/src/qdoc/namespacenode.cpp190
-rw-r--r--src/qdoc/qdoc/src/qdoc/namespacenode.h48
-rw-r--r--src/qdoc/qdoc/src/qdoc/node.cpp1412
-rw-r--r--src/qdoc/qdoc/src/qdoc/node.h344
-rw-r--r--src/qdoc/qdoc/src/qdoc/openedlist.cpp172
-rw-r--r--src/qdoc/qdoc/src/qdoc/openedlist.h47
-rw-r--r--src/qdoc/qdoc/src/qdoc/pagenode.cpp117
-rw-r--r--src/qdoc/qdoc/src/qdoc/pagenode.h82
-rw-r--r--src/qdoc/qdoc/src/qdoc/parameters.cpp542
-rw-r--r--src/qdoc/qdoc/src/qdoc/parameters.h113
-rw-r--r--src/qdoc/qdoc/src/qdoc/parsererror.cpp90
-rw-r--r--src/qdoc/qdoc/src/qdoc/parsererror.h26
-rw-r--r--src/qdoc/qdoc/src/qdoc/propertynode.cpp135
-rw-r--r--src/qdoc/qdoc/src/qdoc/propertynode.h93
-rw-r--r--src/qdoc/qdoc/src/qdoc/proxynode.cpp54
-rw-r--r--src/qdoc/qdoc/src/qdoc/proxynode.h23
-rw-r--r--src/qdoc/qdoc/src/qdoc/puredocparser.cpp74
-rw-r--r--src/qdoc/qdoc/src/qdoc/puredocparser.h31
-rw-r--r--src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.cpp177
-rw-r--r--src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.h28
-rw-r--r--src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp1685
-rw-r--r--src/qdoc/qdoc/src/qdoc/qdocdatabase.h395
-rw-r--r--src/qdoc/qdoc/src/qdoc/qdocindexfiles.cpp1458
-rw-r--r--src/qdoc/qdoc/src/qdoc/qdocindexfiles.h73
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlcodemarker.cpp175
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlcodemarker.h38
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlcodeparser.cpp143
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlcodeparser.h38
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.cpp794
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.h139
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlpropertynode.cpp175
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlpropertynode.h72
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmltypenode.cpp153
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmltypenode.h65
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlvisitor.cpp729
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlvisitor.h93
-rw-r--r--src/qdoc/qdoc/src/qdoc/quoter.cpp338
-rw-r--r--src/qdoc/qdoc/src/qdoc/quoter.h45
-rw-r--r--src/qdoc/qdoc/src/qdoc/relatedclass.cpp46
-rw-r--r--src/qdoc/qdoc/src/qdoc/relatedclass.h34
-rw-r--r--src/qdoc/qdoc/src/qdoc/sections.cpp992
-rw-r--r--src/qdoc/qdoc/src/qdoc/sections.h206
-rw-r--r--src/qdoc/qdoc/src/qdoc/sharedcommentnode.cpp56
-rw-r--r--src/qdoc/qdoc/src/qdoc/sharedcommentnode.h51
-rw-r--r--src/qdoc/qdoc/src/qdoc/singleton.h32
-rw-r--r--src/qdoc/qdoc/src/qdoc/sourcefileparser.h82
-rw-r--r--src/qdoc/qdoc/src/qdoc/tagfilewriter.cpp306
-rw-r--r--src/qdoc/qdoc/src/qdoc/tagfilewriter.h33
-rw-r--r--src/qdoc/qdoc/src/qdoc/template_declaration.h502
-rw-r--r--src/qdoc/qdoc/src/qdoc/text.cpp341
-rw-r--r--src/qdoc/qdoc/src/qdoc/text.h87
-rw-r--r--src/qdoc/qdoc/src/qdoc/tokenizer.cpp788
-rw-r--r--src/qdoc/qdoc/src/qdoc/tokenizer.h179
-rw-r--r--src/qdoc/qdoc/src/qdoc/topic.h29
-rw-r--r--src/qdoc/qdoc/src/qdoc/tree.cpp1357
-rw-r--r--src/qdoc/qdoc/src/qdoc/tree.h183
-rw-r--r--src/qdoc/qdoc/src/qdoc/typedefnode.cpp55
-rw-r--r--src/qdoc/qdoc/src/qdoc/typedefnode.h54
-rw-r--r--src/qdoc/qdoc/src/qdoc/utilities.cpp254
-rw-r--r--src/qdoc/qdoc/src/qdoc/utilities.h28
-rw-r--r--src/qdoc/qdoc/src/qdoc/variablenode.cpp23
-rw-r--r--src/qdoc/qdoc/src/qdoc/variablenode.h44
-rw-r--r--src/qdoc/qdoc/src/qdoc/webxmlgenerator.cpp903
-rw-r--r--src/qdoc/qdoc/src/qdoc/webxmlgenerator.h60
-rw-r--r--src/qdoc/qdoc/src/qdoc/xmlgenerator.cpp487
-rw-r--r--src/qdoc/qdoc/src/qdoc/xmlgenerator.h54
138 files changed, 46905 insertions, 0 deletions
diff --git a/src/qdoc/qdoc/src/qdoc/access.h b/src/qdoc/qdoc/src/qdoc/access.h
new file mode 100644
index 000000000..96cad686b
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/access.h
@@ -0,0 +1,15 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QtCore/qglobal.h>
+
+#ifndef ACCESS_H
+#define ACCESS_H
+
+QT_BEGIN_NAMESPACE
+
+enum class Access : unsigned char { Public, Protected, Private };
+
+QT_END_NAMESPACE
+
+#endif // ACCESS_H
diff --git a/src/qdoc/qdoc/src/qdoc/aggregate.cpp b/src/qdoc/qdoc/src/qdoc/aggregate.cpp
new file mode 100644
index 000000000..bf210ba17
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/aggregate.cpp
@@ -0,0 +1,762 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "aggregate.h"
+
+#include "functionnode.h"
+#include "parameters.h"
+#include "typedefnode.h"
+#include "qdocdatabase.h"
+#include "qmlpropertynode.h"
+#include "qmltypenode.h"
+#include "sharedcommentnode.h"
+#include <vector>
+
+using namespace Qt::Literals::StringLiterals;
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class Aggregate
+ */
+
+/*! \fn Aggregate::Aggregate(NodeType type, Aggregate *parent, const QString &name)
+ The constructor should never be called directly. It is only called
+ by the constructors of subclasses of Aggregate. Those constructors
+ pass the node \a type they want to create, the \a parent of the new
+ node, and its \a name.
+ */
+
+/*!
+ Recursively set all non-related members in the list of children to
+ \nullptr, after which each aggregate can safely delete all children
+ in their list. Aggregate's destructor calls this only on the root
+ namespace node.
+ */
+void Aggregate::dropNonRelatedMembers()
+{
+ for (auto &child : m_children) {
+ if (!child)
+ continue;
+ if (child->parent() != this)
+ child = nullptr;
+ else if (child->isAggregate())
+ static_cast<Aggregate*>(child)->dropNonRelatedMembers();
+ }
+}
+
+/*!
+ Destroys this Aggregate; deletes each child.
+ */
+Aggregate::~Aggregate()
+{
+ // If this is the root, clear non-related children first
+ if (isNamespace() && name().isEmpty())
+ dropNonRelatedMembers();
+
+ m_enumChildren.clear();
+ m_nonfunctionMap.clear();
+ m_functionMap.clear();
+ qDeleteAll(m_children.begin(), m_children.end());
+ m_children.clear();
+}
+
+/*!
+ If \a genus is \c{Node::DontCare}, find the first node in
+ this node's child list that has the given \a name. If this
+ node is a QML type, be sure to also look in the children
+ of its property group nodes. Return the matching node or \c nullptr.
+
+ If \a genus is either \c{Node::CPP} or \c {Node::QML}, then
+ find all this node's children that have the given \a name,
+ and return the one that satisfies the \a genus requirement.
+ */
+Node *Aggregate::findChildNode(const QString &name, Node::Genus genus, int findFlags) const
+{
+ if (genus == Node::DontCare) {
+ Node *node = m_nonfunctionMap.value(name);
+ if (node)
+ return node;
+ } else {
+ const NodeList &nodes = m_nonfunctionMap.values(name);
+ for (auto *node : nodes) {
+ if (genus & node->genus()) {
+ if (findFlags & TypesOnly) {
+ if (!node->isTypedef() && !node->isClassNode()
+ && !node->isQmlType() && !node->isEnumType())
+ continue;
+ } else if (findFlags & IgnoreModules && node->isModule())
+ continue;
+ return node;
+ }
+ }
+ }
+ if (genus != Node::DontCare && !(genus & this->genus()))
+ return nullptr;
+
+ auto it = m_functionMap.find(name);
+ return it != m_functionMap.end() ? (*(*it).begin()) : nullptr;
+}
+
+/*!
+ Find all the child nodes of this node that are named
+ \a name and return them in \a nodes.
+ */
+void Aggregate::findChildren(const QString &name, NodeVector &nodes) const
+{
+ nodes.clear();
+ const auto &functions = m_functionMap.value(name);
+ nodes.reserve(functions.size() + m_nonfunctionMap.count(name));
+ for (auto f : functions)
+ nodes.emplace_back(f);
+ auto [it, end] = m_nonfunctionMap.equal_range(name);
+ while (it != end) {
+ nodes.emplace_back(*it);
+ ++it;
+ }
+}
+
+/*!
+ This function searches for a child node of this Aggregate,
+ such that the child node has the spacified \a name and the
+ function \a isMatch returns true for the node. The function
+ passed must be one of the isXxx() functions in class Node
+ that tests the node type.
+ */
+Node *Aggregate::findNonfunctionChild(const QString &name, bool (Node::*isMatch)() const)
+{
+ const NodeList &nodes = m_nonfunctionMap.values(name);
+ for (auto *node : nodes) {
+ if ((node->*(isMatch))())
+ return node;
+ }
+ return nullptr;
+}
+
+/*!
+ Find a function node that is a child of this node, such that
+ the function node has the specified \a name and \a parameters.
+ If \a parameters is empty but no matching function is found
+ that has no parameters, return the first non-internal primary
+ function or overload, whether it has parameters or not.
+
+ \sa normalizeOverloads()
+ */
+FunctionNode *Aggregate::findFunctionChild(const QString &name, const Parameters &parameters)
+{
+ auto map_it = m_functionMap.find(name);
+ if (map_it == m_functionMap.end())
+ return nullptr;
+
+ auto match_it = std::find_if((*map_it).begin(), (*map_it).end(),
+ [&parameters](const FunctionNode *fn) {
+ if (fn->isInternal())
+ return false;
+ if (parameters.count() != fn->parameters().count())
+ return false;
+ for (int i = 0; i < parameters.count(); ++i)
+ if (parameters.at(i).type() != fn->parameters().at(i).type())
+ return false;
+ return true;
+ });
+
+ if (match_it != (*map_it).end())
+ return *match_it;
+
+ // Assumes that overloads are already normalized; i.e, if there's
+ // an active function, it'll be found at the start of the list.
+ auto *fn = (*(*map_it).begin());
+ return (parameters.isEmpty() && !fn->isInternal()) ? fn : nullptr;
+}
+
+/*!
+ Returns the function node that is a child of this node, such
+ that the function described has the same name and signature
+ as the function described by the function node \a clone.
+
+ Returns \nullptr if no matching function was found.
+ */
+FunctionNode *Aggregate::findFunctionChild(const FunctionNode *clone)
+{
+ auto funcs_it = m_functionMap.find(clone->name());
+ if (funcs_it == m_functionMap.end())
+ return nullptr;
+
+ auto func_it = std::find_if((*funcs_it).begin(), (*funcs_it).end(),
+ [clone](const FunctionNode *fn) {
+ return compare(clone, fn) == 0;
+ });
+
+ return func_it != (*funcs_it).end() ? *func_it : nullptr;
+}
+
+/*!
+ Mark all child nodes that have no documentation as having
+ private access and internal status. qdoc will then ignore
+ them for documentation purposes.
+ */
+void Aggregate::markUndocumentedChildrenInternal()
+{
+ for (auto *child : std::as_const(m_children)) {
+ if (!child->hasDoc() && !child->isDontDocument()) {
+ if (!child->docMustBeGenerated()) {
+ if (child->isFunction()) {
+ if (static_cast<FunctionNode *>(child)->hasAssociatedProperties())
+ continue;
+ } else if (child->isTypedef()) {
+ if (static_cast<TypedefNode *>(child)->hasAssociatedEnum())
+ continue;
+ }
+ child->setAccess(Access::Private);
+ child->setStatus(Node::Internal);
+ }
+ }
+ if (child->isAggregate()) {
+ static_cast<Aggregate *>(child)->markUndocumentedChildrenInternal();
+ }
+ }
+}
+
+/*!
+ Adopts each non-aggregate C++ node (function/macro, typedef, enum, variable,
+ or a shared comment node with genus Node::CPP) in the global scope to the
+ aggregate specified in the node's documentation using the \\relates command.
+
+ If the target Aggregate is not found in the primary tree, creates a new
+ ProxyNode to use as the parent.
+*/
+void Aggregate::resolveRelates()
+{
+ Q_ASSERT(name().isEmpty()); // Must be called on the root namespace
+ auto *database = QDocDatabase::qdocDB();
+
+ for (auto *node : m_children) {
+ if (node->isRelatedNonmember() || node->isAggregate())
+ continue;
+ if (node->genus() != Node::CPP)
+ continue;
+
+ const auto &relates_args = node->doc().metaCommandArgs("relates"_L1);
+ if (relates_args.isEmpty())
+ continue;
+
+ auto *aggregate = database->findRelatesNode(relates_args[0].first.split("::"_L1));
+ if (!aggregate)
+ aggregate = new ProxyNode(this, relates_args[0].first);
+ else if (node->parent() == aggregate)
+ continue;
+
+ aggregate->adoptChild(node);
+ node->setRelatedNonmember(true);
+ }
+}
+
+/*!
+ Sorts the lists of overloads in the function map and assigns overload
+ numbers.
+
+ For sorting, active functions take precedence over internal ones, as well
+ as ones marked as \\overload - the latter ones typically do not contain
+ full documentation, so selecting them as the \e primary function
+ would cause unnecessary warnings to be generated.
+
+ Otherwise, the order is set as determined by FunctionNode::compare().
+ */
+void Aggregate::normalizeOverloads()
+{
+ for (auto map_it = m_functionMap.begin(); map_it != m_functionMap.end(); ++map_it) {
+ if ((*map_it).size() > 1) {
+ std::sort((*map_it).begin(), (*map_it).end(),
+ [](const FunctionNode *f1, const FunctionNode *f2) -> bool {
+ if (f1->isInternal() != f2->isInternal())
+ return f2->isInternal();
+ if (f1->isOverload() != f2->isOverload())
+ return f2->isOverload();
+ // Prioritize documented over undocumented
+ if (f1->hasDoc() != f2->hasDoc())
+ return f1->hasDoc();
+ return (compare(f1, f2) < 0);
+ });
+ // Set overload numbers
+ signed short n{0};
+ for (auto *fn : (*map_it))
+ fn->setOverloadNumber(n++);
+ }
+ }
+
+ for (auto *node : std::as_const(m_children)) {
+ if (node->isAggregate())
+ static_cast<Aggregate *>(node)->normalizeOverloads();
+ }
+}
+
+/*!
+ Returns a const reference to the list of child nodes of this
+ aggregate that are not function nodes. Duplicate nodes are
+ removed from the list.
+ */
+const NodeList &Aggregate::nonfunctionList()
+{
+ m_nonfunctionList = m_nonfunctionMap.values();
+ std::sort(m_nonfunctionList.begin(), m_nonfunctionList.end(), Node::nodeNameLessThan);
+ m_nonfunctionList.erase(std::unique(m_nonfunctionList.begin(), m_nonfunctionList.end()),
+ m_nonfunctionList.end());
+ return m_nonfunctionList;
+}
+
+/*! \fn bool Aggregate::isAggregate() const
+ Returns \c true because this node is an instance of Aggregate,
+ which means it can have children.
+ */
+
+/*!
+ Finds the enum type node that has \a enumValue as one of
+ its enum values and returns a pointer to it. Returns 0 if
+ no enum type node is found that has \a enumValue as one
+ of its values.
+ */
+const EnumNode *Aggregate::findEnumNodeForValue(const QString &enumValue) const
+{
+ for (const auto *node : m_enumChildren) {
+ const auto *en = static_cast<const EnumNode *>(node);
+ if (en->hasItem(enumValue))
+ return en;
+ }
+ return nullptr;
+}
+
+/*!
+ Adds the \a child to this node's child map using \a title
+ as the key. The \a child is not added to the child list
+ again, because it is presumed to already be there. We just
+ want to be able to find the child by its \a title.
+ */
+void Aggregate::addChildByTitle(Node *child, const QString &title)
+{
+ m_nonfunctionMap.insert(title, child);
+}
+
+/*!
+ Adds the \a child to this node's child list and sets the child's
+ parent pointer to this Aggregate. It then mounts the child with
+ mountChild().
+
+ The \a child is then added to this Aggregate's searchable maps
+ and lists.
+
+ \note This function does not test the child's parent pointer
+ for null before changing it. If the child's parent pointer
+ is not null, then it is being reparented. The child becomes
+ a child of this Aggregate, but it also remains a child of
+ the Aggregate that is it's old parent. But the child will
+ only have one parent, and it will be this Aggregate. The is
+ because of the \c relates command.
+
+ \sa mountChild(), dismountChild()
+ */
+void Aggregate::addChild(Node *child)
+{
+ m_children.append(child);
+ child->setParent(this);
+ child->setUrl(QString());
+ child->setIndexNodeFlag(isIndexNode());
+
+ if (child->isFunction()) {
+ m_functionMap[child->name()].emplace_back(static_cast<FunctionNode *>(child));
+ } else if (!child->name().isEmpty()) {
+ m_nonfunctionMap.insert(child->name(), child);
+ if (child->isEnumType())
+ m_enumChildren.append(child);
+ }
+}
+
+/*!
+ This Aggregate becomes the adoptive parent of \a child. The
+ \a child knows this Aggregate as its parent, but its former
+ parent continues to have pointers to the child in its child
+ list and in its searchable data structures. But the child is
+ also added to the child list and searchable data structures
+ of this Aggregate.
+ */
+void Aggregate::adoptChild(Node *child)
+{
+ if (child->parent() != this) {
+ m_children.append(child);
+ child->setParent(this);
+ if (child->isFunction()) {
+ m_functionMap[child->name()].emplace_back(static_cast<FunctionNode *>(child));
+ } else if (!child->name().isEmpty()) {
+ m_nonfunctionMap.insert(child->name(), child);
+ if (child->isEnumType())
+ m_enumChildren.append(child);
+ }
+ if (child->isSharedCommentNode()) {
+ auto *scn = static_cast<SharedCommentNode *>(child);
+ for (Node *n : scn->collective())
+ adoptChild(n);
+ }
+ }
+}
+
+/*!
+ If this node has a child that is a QML property named \a n, return a
+ pointer to that child. Otherwise, return \nullptr.
+ */
+QmlPropertyNode *Aggregate::hasQmlProperty(const QString &n) const
+{
+ NodeType goal = Node::QmlProperty;
+ for (auto *child : std::as_const(m_children)) {
+ if (child->nodeType() == goal) {
+ if (child->name() == n)
+ return static_cast<QmlPropertyNode *>(child);
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ If this node has a child that is a QML property named \a n and that
+ also matches \a attached, return a pointer to that child.
+ */
+QmlPropertyNode *Aggregate::hasQmlProperty(const QString &n, bool attached) const
+{
+ NodeType goal = Node::QmlProperty;
+ for (auto *child : std::as_const(m_children)) {
+ if (child->nodeType() == goal) {
+ if (child->name() == n && child->isAttached() == attached)
+ return static_cast<QmlPropertyNode *>(child);
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Returns \c true if this aggregate has multiple function
+ overloads matching the name of \a fn.
+
+ \note Assumes \a fn is a member of this aggregate.
+*/
+bool Aggregate::hasOverloads(const FunctionNode *fn) const
+{
+ auto it = m_functionMap.find(fn->name());
+ return !(it == m_functionMap.end()) && (it.value().size() > 1);
+}
+
+/*
+ When deciding whether to include a function in the function
+ index, if the function is marked private, don't include it.
+ If the function is marked obsolete, don't include it. If the
+ function is marked internal, don't include it. Or if the
+ function is a destructor or any kind of constructor, don't
+ include it. Otherwise include it.
+ */
+static bool keep(FunctionNode *fn)
+{
+ if (fn->isPrivate() || fn->isDeprecated() || fn->isInternal() || fn->isSomeCtor() || fn->isDtor())
+ return false;
+ return true;
+}
+
+/*!
+ Insert all functions declared in this aggregate into the
+ \a functionIndex. Call the function recursively for each
+ child that is an aggregate.
+
+ Only include functions that are in the public API and
+ that are not constructors or destructors.
+ */
+void Aggregate::findAllFunctions(NodeMapMap &functionIndex)
+{
+ for (auto functions : m_functionMap) {
+ std::for_each(functions.begin(), functions.end(),
+ [&functionIndex](FunctionNode *fn) {
+ if (keep(fn))
+ functionIndex[fn->name()].insert(fn->parent()->fullDocumentName(), fn);
+ }
+ );
+ }
+
+ for (Node *node : std::as_const(m_children)) {
+ if (node->isAggregate() && !node->isPrivate() && !node->isDontDocument())
+ static_cast<Aggregate *>(node)->findAllFunctions(functionIndex);
+ }
+}
+
+/*!
+ For each child of this node, if the child is a namespace node,
+ insert the child into the \a namespaces multimap. If the child
+ is an aggregate, call this function recursively for that child.
+
+ When the function called with the root node of a tree, it finds
+ all the namespace nodes in that tree and inserts them into the
+ \a namespaces multimap.
+
+ The root node of a tree is a namespace, but it has no name, so
+ it is not inserted into the map. So, if this function is called
+ for each tree in the qdoc database, it finds all the namespace
+ nodes in the database.
+ */
+void Aggregate::findAllNamespaces(NodeMultiMap &namespaces)
+{
+ for (auto *node : std::as_const(m_children)) {
+ if (node->isAggregate() && !node->isPrivate()) {
+ if (node->isNamespace() && !node->name().isEmpty())
+ namespaces.insert(node->name(), node);
+ static_cast<Aggregate *>(node)->findAllNamespaces(namespaces);
+ }
+ }
+}
+
+/*!
+ Returns true if this aggregate contains at least one child
+ that is marked obsolete. Otherwise returns false.
+ */
+bool Aggregate::hasObsoleteMembers() const
+{
+ for (const auto *node : m_children)
+ if (!node->isPrivate() && node->isDeprecated()) {
+ if (node->isFunction() || node->isProperty() || node->isEnumType() || node->isTypedef()
+ || node->isTypeAlias() || node->isVariable() || node->isQmlProperty())
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Finds all the obsolete C++ classes and QML types in this
+ aggregate and all the C++ classes and QML types with obsolete
+ members, and inserts them into maps used elsewhere for
+ generating documentation.
+ */
+void Aggregate::findAllObsoleteThings()
+{
+ for (auto *node : std::as_const(m_children)) {
+ if (!node->isPrivate()) {
+ if (node->isDeprecated()) {
+ if (node->isClassNode())
+ QDocDatabase::obsoleteClasses().insert(node->qualifyCppName(), node);
+ else if (node->isQmlType())
+ QDocDatabase::obsoleteQmlTypes().insert(node->qualifyQmlName(), node);
+ } else if (node->isClassNode()) {
+ auto *a = static_cast<Aggregate *>(node);
+ if (a->hasObsoleteMembers())
+ QDocDatabase::classesWithObsoleteMembers().insert(node->qualifyCppName(), node);
+ } else if (node->isQmlType()) {
+ auto *a = static_cast<Aggregate *>(node);
+ if (a->hasObsoleteMembers())
+ QDocDatabase::qmlTypesWithObsoleteMembers().insert(node->qualifyQmlName(),
+ node);
+ } else if (node->isAggregate()) {
+ static_cast<Aggregate *>(node)->findAllObsoleteThings();
+ }
+ }
+ }
+}
+
+/*!
+ Finds all the C++ classes, QML types, QML basic types, and examples
+ in this aggregate and inserts them into appropriate maps for later
+ use in generating documentation.
+ */
+void Aggregate::findAllClasses()
+{
+ for (auto *node : std::as_const(m_children)) {
+ if (!node->isPrivate() && !node->isInternal() && !node->isDontDocument()
+ && node->tree()->camelCaseModuleName() != QString("QDoc")) {
+ if (node->isClassNode()) {
+ QDocDatabase::cppClasses().insert(node->qualifyCppName().toLower(), node);
+ } else if (node->isQmlType()) {
+ QString name = node->name().toLower();
+ QDocDatabase::qmlTypes().insert(name, node);
+ // also add to the QML basic type map
+ if (node->isQmlBasicType())
+ QDocDatabase::qmlBasicTypes().insert(name, node);
+ } else if (node->isExample()) {
+ // use the module index title as key for the example map
+ QString title = node->tree()->indexTitle();
+ if (!QDocDatabase::examples().contains(title, node))
+ QDocDatabase::examples().insert(title, node);
+ } else if (node->isAggregate()) {
+ static_cast<Aggregate *>(node)->findAllClasses();
+ }
+ }
+ }
+}
+
+/*!
+ Find all the attribution pages in this node and insert them
+ into \a attributions.
+ */
+void Aggregate::findAllAttributions(NodeMultiMap &attributions)
+{
+ for (auto *node : std::as_const(m_children)) {
+ if (!node->isPrivate()) {
+ if (node->isPageNode() && static_cast<PageNode*>(node)->isAttribution())
+ attributions.insert(node->tree()->indexTitle(), node);
+ else if (node->isAggregate())
+ static_cast<Aggregate *>(node)->findAllAttributions(attributions);
+ }
+ }
+}
+
+/*!
+ Finds all the nodes in this node where a \e{since} command appeared
+ in the qdoc comment and sorts them into maps according to the kind
+ of node.
+
+ This function is used for generating the "New Classes... in x.y"
+ section on the \e{What's New in Qt x.y} page.
+ */
+void Aggregate::findAllSince()
+{
+ for (auto *node : std::as_const(m_children)) {
+ if (node->isRelatedNonmember() && node->parent() != this)
+ continue;
+ QString sinceString = node->since();
+ // Insert a new entry into each map for each new since string found.
+ if (node->isInAPI() && !sinceString.isEmpty()) {
+ // operator[] will insert a default-constructed value into the
+ // map if key is not found, which is what we want here.
+ auto &nsmap = QDocDatabase::newSinceMaps()[sinceString];
+ auto &ncmap = QDocDatabase::newClassMaps()[sinceString];
+ auto &nqcmap = QDocDatabase::newQmlTypeMaps()[sinceString];
+
+ if (node->isFunction()) {
+ // Insert functions into the general since map.
+ auto *fn = static_cast<FunctionNode *>(node);
+ if (!fn->isDeprecated() && !fn->isSomeCtor() && !fn->isDtor())
+ nsmap.insert(fn->name(), fn);
+ } else if (node->isClassNode()) {
+ // Insert classes into the since and class maps.
+ QString name = node->qualifyWithParentName();
+ nsmap.insert(name, node);
+ ncmap.insert(name, node);
+ } else if (node->isQmlType()) {
+ // Insert QML elements into the since and element maps.
+ QString name = node->qualifyWithParentName();
+ nsmap.insert(name, node);
+ nqcmap.insert(name, node);
+ } else if (node->isQmlProperty()) {
+ // Insert QML properties into the since map.
+ nsmap.insert(node->name(), node);
+ } else {
+ // Insert external documents into the general since map.
+ QString name = node->qualifyWithParentName();
+ nsmap.insert(name, node);
+ }
+ }
+ // Enum values - a special case as EnumItem is not a Node subclass
+ if (node->isInAPI() && node->isEnumType()) {
+ for (const auto &val : static_cast<EnumNode *>(node)->items()) {
+ sinceString = val.since();
+ if (sinceString.isEmpty())
+ continue;
+ // Insert to enum value map
+ QDocDatabase::newEnumValueMaps()[sinceString].insert(
+ node->name() + "::" + val.name(), node);
+ // Ugly hack: Insert into general map with an empty key -
+ // we need something in there to mark the corresponding
+ // section populated. See Sections class constructor.
+ QDocDatabase::newSinceMaps()[sinceString].replace(QString(), node);
+ }
+ }
+
+ // Recursively find child nodes with since commands.
+ if (node->isAggregate())
+ static_cast<Aggregate *>(node)->findAllSince();
+ }
+}
+
+/*!
+ Resolves the inheritance information for all QML type children
+ of this aggregate.
+*/
+void Aggregate::resolveQmlInheritance()
+{
+ NodeMap previousSearches;
+ for (auto *child : std::as_const(m_children)) {
+ if (!child->isQmlType())
+ continue;
+ static_cast<QmlTypeNode *>(child)->resolveInheritance(previousSearches);
+ }
+}
+
+/*!
+ Returns a word representing the kind of Aggregate this node is.
+ Currently only works for class, struct, and union, but it can
+ easily be extended. If \a cap is true, the word is capitalised.
+ */
+QString Aggregate::typeWord(bool cap) const
+{
+ if (cap) {
+ switch (nodeType()) {
+ case Node::Class:
+ return QLatin1String("Class");
+ case Node::Struct:
+ return QLatin1String("Struct");
+ case Node::Union:
+ return QLatin1String("Union");
+ default:
+ break;
+ }
+ } else {
+ switch (nodeType()) {
+ case Node::Class:
+ return QLatin1String("class");
+ case Node::Struct:
+ return QLatin1String("struct");
+ case Node::Union:
+ return QLatin1String("union");
+ default:
+ break;
+ }
+ }
+ return QString();
+}
+
+/*! \fn int Aggregate::count() const
+ Returns the number of children in the child list.
+ */
+
+/*! \fn const NodeList &Aggregate::childNodes() const
+ Returns a const reference to the child list.
+ */
+
+/*! \fn NodeList::ConstIterator Aggregate::constBegin() const
+ Returns a const iterator pointing at the beginning of the child list.
+ */
+
+/*! \fn NodeList::ConstIterator Aggregate::constEnd() const
+ Returns a const iterator pointing at the end of the child list.
+ */
+
+/*! \fn QmlTypeNode *Aggregate::qmlBaseNode() const
+ If this Aggregate is a QmlTypeNode, this function returns a pointer to
+ the QmlTypeNode that is its base type. Otherwise it returns \c nullptr.
+ A QmlTypeNode doesn't always have a base type, so even when this Aggregate
+ is aQmlTypeNode, the pointer returned can be \c nullptr.
+ */
+
+/*! \fn FunctionMap &Aggregate::functionMap()
+ Returns a reference to this Aggregate's function map, which
+ is a map of all the children of this Aggregate that are
+ FunctionNodes.
+ */
+
+/*! \fn void Aggregate::appendToRelatedByProxy(const NodeList &t)
+ Appends the list of node pointers to the list of elements that are
+ related to this Aggregate but are documented in a different module.
+
+ \sa relatedByProxy()
+ */
+
+/*! \fn NodeList &Aggregate::relatedByProxy()
+ Returns a reference to a list of node pointers where each element
+ points to a node in an index file for some other module, such that
+ whatever the node represents was documented in that other module,
+ but it is related to this Aggregate, so when the documentation for
+ this Aggregate is written, it will contain links to elements in the
+ other module.
+ */
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/aggregate.h b/src/qdoc/qdoc/src/qdoc/aggregate.h
new file mode 100644
index 000000000..a02633e04
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/aggregate.h
@@ -0,0 +1,118 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef AGGREGATE_H
+#define AGGREGATE_H
+
+#include "pagenode.h"
+
+#include <optional>
+#include <vector>
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class FunctionNode;
+class QmlTypeNode;
+class QmlPropertyNode;
+
+class Aggregate : public PageNode
+{
+public:
+ using FunctionMap = QMap<QString, std::vector<FunctionNode*>>;
+
+ [[nodiscard]] Node *findChildNode(const QString &name, Node::Genus genus,
+ int findFlags = 0) const;
+ Node *findNonfunctionChild(const QString &name, bool (Node::*)() const);
+ void findChildren(const QString &name, NodeVector &nodes) const;
+ FunctionNode *findFunctionChild(const QString &name, const Parameters &parameters);
+ FunctionNode *findFunctionChild(const FunctionNode *clone);
+
+ void resolveRelates();
+ void normalizeOverloads();
+ void markUndocumentedChildrenInternal();
+
+ [[nodiscard]] bool isAggregate() const override { return true; }
+ [[nodiscard]] const EnumNode *findEnumNodeForValue(const QString &enumValue) const;
+
+ [[nodiscard]] qsizetype count() const { return m_children.size(); }
+ [[nodiscard]] const NodeList &childNodes() const { return m_children; }
+ const NodeList &nonfunctionList();
+ [[nodiscard]] NodeList::ConstIterator constBegin() const { return m_children.constBegin(); }
+ [[nodiscard]] NodeList::ConstIterator constEnd() const { return m_children.constEnd(); }
+
+ inline void setIncludeFile(const QString& include) { m_includeFile.emplace(include); }
+ // REMARK: Albeit not enforced at the API boundaries,
+ // downstream-user can assume that if there is a QString that was
+ // set, then that string is not empty.
+ [[nodiscard]] inline const std::optional<QString>& includeFile() const { return m_includeFile; }
+
+ [[nodiscard]] QmlPropertyNode *hasQmlProperty(const QString &) const;
+ [[nodiscard]] QmlPropertyNode *hasQmlProperty(const QString &, bool attached) const;
+ virtual QmlTypeNode *qmlBaseNode() const { return nullptr; }
+ void addChildByTitle(Node *child, const QString &title);
+ void addChild(Node *child);
+ void adoptChild(Node *child);
+
+ FunctionMap &functionMap() { return m_functionMap; }
+ void findAllFunctions(NodeMapMap &functionIndex);
+ void findAllNamespaces(NodeMultiMap &namespaces);
+ void findAllAttributions(NodeMultiMap &attributions);
+ [[nodiscard]] bool hasObsoleteMembers() const;
+ void findAllObsoleteThings();
+ void findAllClasses();
+ void findAllSince();
+ void resolveQmlInheritance();
+ bool hasOverloads(const FunctionNode *fn) const;
+ void appendToRelatedByProxy(const NodeList &t) { m_relatedByProxy.append(t); }
+ NodeList &relatedByProxy() { return m_relatedByProxy; }
+ [[nodiscard]] QString typeWord(bool cap) const;
+
+protected:
+ Aggregate(NodeType type, Aggregate *parent, const QString &name)
+ : PageNode(type, parent, name) {}
+ ~Aggregate() override;
+
+private:
+ friend class Node;
+ void dropNonRelatedMembers();
+
+protected:
+ NodeList m_children {};
+ NodeList m_relatedByProxy {};
+ FunctionMap m_functionMap {};
+
+private:
+ // REMARK: The member indicates the name of a file where the
+ // aggregate can be found from, for example, an header file that
+ // declares a class.
+ // For aggregates such as classes we expect this to always be set
+ // to a non-empty string after the code-parsing phase.
+ // Indeed, currently, by default, QDoc always generates such a
+ // string using the name of the aggregate if no include file can
+ // be propagated from some of the parents.
+ //
+ // Nonetheless, we are still forced to make this an optional, as
+ // this will not be true for all Aggregates.
+ //
+ // For example, for namespaces, we don't seem to set an include
+ // file and indeed doing so wouldn't be particularly meaningful.
+ //
+ // It is possible to assume in later code, especially the
+ // generation phase, that at least some classes of aggregates
+ // always have a value set here but we should, for the moment,
+ // still check for the possibility of something not to be there,
+ // or warn if we decide to ignore that, to be compliant with the
+ // current interface, whose change would require deep changes to
+ // QDoc internal structures.
+ std::optional<QString> m_includeFile{};
+ NodeList m_enumChildren {};
+ NodeMultiMap m_nonfunctionMap {};
+ NodeList m_nonfunctionList {};
+};
+
+QT_END_NAMESPACE
+
+#endif // AGGREGATE_H
diff --git a/src/qdoc/qdoc/src/qdoc/atom.cpp b/src/qdoc/qdoc/src/qdoc/atom.cpp
new file mode 100644
index 000000000..f887c4ec5
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/atom.cpp
@@ -0,0 +1,458 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "atom.h"
+
+#include "location.h"
+#include "qdocdatabase.h"
+
+#include <QtCore/qregularexpression.h>
+
+#include <cstdio>
+
+QT_BEGIN_NAMESPACE
+
+/*! \class Atom
+ \brief The Atom class is the fundamental unit for representing
+ documents internally.
+
+ Atoms have a \i type and are completed by a \i string whose
+ meaning depends on the \i type. For example, the string
+ \quotation
+ \i italic text looks nicer than \bold bold text
+ \endquotation
+ is represented by the following atoms:
+ \quotation
+ (FormattingLeft, ATOM_FORMATTING_ITALIC)
+ (String, "italic")
+ (FormattingRight, ATOM_FORMATTING_ITALIC)
+ (String, " text is more attractive than ")
+ (FormattingLeft, ATOM_FORMATTING_BOLD)
+ (String, "bold")
+ (FormattingRight, ATOM_FORMATTING_BOLD)
+ (String, " text")
+ \endquotation
+
+ \also Text
+*/
+
+/*! \enum Atom::AtomType
+
+ \value AnnotatedList
+ \value AutoLink
+ \value BaseName
+ \value BriefLeft
+ \value BriefRight
+ \value C
+ \value CaptionLeft
+ \value CaptionRight
+ \value Code
+ \value CodeBad
+ \value CodeQuoteArgument
+ \value CodeQuoteCommand
+ \value DetailsLeft
+ \value DetailsRight
+ \value DivLeft
+ \value DivRight
+ \value ExampleFileLink
+ \value ExampleImageLink
+ \value FormatElse
+ \value FormatEndif
+ \value FormatIf
+ \value FootnoteLeft
+ \value FootnoteRight
+ \value FormattingLeft
+ \value FormattingRight
+ \value GeneratedList
+ \value Image
+ \value ImageText
+ \value ImportantNote
+ \value InlineImage
+ \value Keyword
+ \value LineBreak
+ \value Link
+ \value LinkNode
+ \value ListLeft
+ \value ListItemNumber
+ \value ListTagLeft
+ \value ListTagRight
+ \value ListItemLeft
+ \value ListItemRight
+ \value ListRight
+ \value NavAutoLink
+ \value NavLink
+ \value Nop
+ \value Note
+ \value ParaLeft
+ \value ParaRight
+ \value Qml
+ \value QuotationLeft
+ \value QuotationRight
+ \value RawString
+ \value SectionLeft
+ \value SectionRight
+ \value SectionHeadingLeft
+ \value SectionHeadingRight
+ \value SidebarLeft
+ \value SidebarRight
+ \value SinceList
+ \value SinceTagLeft
+ \value SinceTagRight
+ \value String
+ \value TableLeft
+ \value TableRight
+ \value TableHeaderLeft
+ \value TableHeaderRight
+ \value TableRowLeft
+ \value TableRowRight
+ \value TableItemLeft
+ \value TableItemRight
+ \value TableOfContents
+ \value Target
+ \value UnhandledFormat
+ \value UnknownCommand
+*/
+
+static const struct
+{
+ const char *english;
+ int no;
+} atms[] = { { "AnnotatedList", Atom::AnnotatedList },
+ { "AutoLink", Atom::AutoLink },
+ { "BaseName", Atom::BaseName },
+ { "br", Atom::BR },
+ { "BriefLeft", Atom::BriefLeft },
+ { "BriefRight", Atom::BriefRight },
+ { "C", Atom::C },
+ { "CaptionLeft", Atom::CaptionLeft },
+ { "CaptionRight", Atom::CaptionRight },
+ { "Code", Atom::Code },
+ { "CodeBad", Atom::CodeBad },
+ { "CodeQuoteArgument", Atom::CodeQuoteArgument },
+ { "CodeQuoteCommand", Atom::CodeQuoteCommand },
+ { "ComparesLeft", Atom::ComparesLeft },
+ { "ComparesRight", Atom::ComparesRight },
+ { "DetailsLeft", Atom::DetailsLeft },
+ { "DetailsRight", Atom::DetailsRight },
+ { "DivLeft", Atom::DivLeft },
+ { "DivRight", Atom::DivRight },
+ { "ExampleFileLink", Atom::ExampleFileLink },
+ { "ExampleImageLink", Atom::ExampleImageLink },
+ { "FootnoteLeft", Atom::FootnoteLeft },
+ { "FootnoteRight", Atom::FootnoteRight },
+ { "FormatElse", Atom::FormatElse },
+ { "FormatEndif", Atom::FormatEndif },
+ { "FormatIf", Atom::FormatIf },
+ { "FormattingLeft", Atom::FormattingLeft },
+ { "FormattingRight", Atom::FormattingRight },
+ { "GeneratedList", Atom::GeneratedList },
+ { "hr", Atom::HR },
+ { "Image", Atom::Image },
+ { "ImageText", Atom::ImageText },
+ { "ImportantLeft", Atom::ImportantLeft },
+ { "ImportantRight", Atom::ImportantRight },
+ { "InlineImage", Atom::InlineImage },
+ { "Keyword", Atom::Keyword },
+ { "LegaleseLeft", Atom::LegaleseLeft },
+ { "LegaleseRight", Atom::LegaleseRight },
+ { "LineBreak", Atom::LineBreak },
+ { "Link", Atom::Link },
+ { "LinkNode", Atom::LinkNode },
+ { "ListLeft", Atom::ListLeft },
+ { "ListItemNumber", Atom::ListItemNumber },
+ { "ListTagLeft", Atom::ListTagLeft },
+ { "ListTagRight", Atom::ListTagRight },
+ { "ListItemLeft", Atom::ListItemLeft },
+ { "ListItemRight", Atom::ListItemRight },
+ { "ListRight", Atom::ListRight },
+ { "NavAutoLink", Atom::NavAutoLink },
+ { "NavLink", Atom::NavLink },
+ { "Nop", Atom::Nop },
+ { "NoteLeft", Atom::NoteLeft },
+ { "NoteRight", Atom::NoteRight },
+ { "ParaLeft", Atom::ParaLeft },
+ { "ParaRight", Atom::ParaRight },
+ { "Qml", Atom::Qml },
+ { "QuotationLeft", Atom::QuotationLeft },
+ { "QuotationRight", Atom::QuotationRight },
+ { "RawString", Atom::RawString },
+ { "SectionLeft", Atom::SectionLeft },
+ { "SectionRight", Atom::SectionRight },
+ { "SectionHeadingLeft", Atom::SectionHeadingLeft },
+ { "SectionHeadingRight", Atom::SectionHeadingRight },
+ { "SidebarLeft", Atom::SidebarLeft },
+ { "SidebarRight", Atom::SidebarRight },
+ { "SinceList", Atom::SinceList },
+ { "SinceTagLeft", Atom::SinceTagLeft },
+ { "SinceTagRight", Atom::SinceTagRight },
+ { "SnippetCommand", Atom::SnippetCommand },
+ { "SnippetIdentifier", Atom::SnippetIdentifier },
+ { "SnippetLocation", Atom::SnippetLocation },
+ { "String", Atom::String },
+ { "TableLeft", Atom::TableLeft },
+ { "TableRight", Atom::TableRight },
+ { "TableHeaderLeft", Atom::TableHeaderLeft },
+ { "TableHeaderRight", Atom::TableHeaderRight },
+ { "TableRowLeft", Atom::TableRowLeft },
+ { "TableRowRight", Atom::TableRowRight },
+ { "TableItemLeft", Atom::TableItemLeft },
+ { "TableItemRight", Atom::TableItemRight },
+ { "TableOfContents", Atom::TableOfContents },
+ { "Target", Atom::Target },
+ { "UnhandledFormat", Atom::UnhandledFormat },
+ { "WarningLeft", Atom::WarningLeft },
+ { "WarningRight", Atom::WarningRight },
+ { "UnknownCommand", Atom::UnknownCommand },
+ { nullptr, 0 } };
+
+/*! \fn Atom::Atom(AtomType type, const QString &string)
+
+ Constructs an atom of the specified \a type with the single
+ parameter \a string and does not put the new atom in a list.
+*/
+
+/*! \fn Atom::Atom(AtomType type, const QString &p1, const QString &p2)
+
+ Constructs an atom of the specified \a type with the two
+ parameters \a p1 and \a p2 and does not put the new atom
+ in a list.
+*/
+
+/*! \fn Atom(Atom *previous, AtomType type, const QString &string)
+
+ Constructs an atom of the specified \a type with the single
+ parameter \a string and inserts the new atom into the list
+ after the \a previous atom.
+*/
+
+/*! \fn Atom::Atom(Atom *previous, AtomType type, const QString &p1, const QString &p2)
+
+ Constructs an atom of the specified \a type with the two
+ parameters \a p1 and \a p2 and inserts the new atom into
+ the list after the \a previous atom.
+*/
+
+/*! \fn void Atom::appendChar(QChar ch)
+
+ Appends \a ch to the string parameter of this atom.
+
+ \also string()
+*/
+
+/*! \fn void Atom::concatenateString(const QString &string)
+
+ Appends \a string to the string parameter of this atom.
+
+ \also string()
+*/
+
+/*! \fn void Atom::chopString()
+
+ \also string()
+*/
+
+/*!
+ Starting from this Atom, searches the linked list for the
+ atom of specified type \a t and returns it. Returns \nullptr
+ if no such atom is found.
+*/
+const Atom *Atom::find(AtomType t) const
+{
+ const auto *a{this};
+ while (a && a->type() != t)
+ a = a->next();
+ return a;
+}
+
+/*!
+ Starting from this Atom, searches the linked list for the
+ atom of specified type \a t and string \a s, and returns it.
+ Returns \nullptr if no such atom is found.
+*/
+const Atom *Atom::find(AtomType t, const QString &s) const
+{
+ const auto *a{this};
+ while (a && (a->type() != t || a->string() != s))
+ a = a->next();
+ return a;
+}
+
+/*! \fn Atom *Atom::next()
+ Return the next atom in the atom list.
+ \also type(), string()
+*/
+
+/*!
+ Return the next Atom in the list if it is of AtomType \a t.
+ Otherwise return 0.
+ */
+const Atom *Atom::next(AtomType t) const
+{
+ return (m_next && (m_next->type() == t)) ? m_next : nullptr;
+}
+
+/*!
+ Return the next Atom in the list if it is of AtomType \a t
+ and its string part is \a s. Otherwise return 0.
+ */
+const Atom *Atom::next(AtomType t, const QString &s) const
+{
+ return (m_next && (m_next->type() == t) && (m_next->string() == s)) ? m_next : nullptr;
+}
+
+/*! \fn const Atom *Atom::next() const
+ Return the next atom in the atom list.
+ \also type(), string()
+*/
+
+/*! \fn AtomType Atom::type() const
+ Return the type of this atom.
+ \also string(), next()
+*/
+
+/*!
+ Return the type of this atom as a string. Return "Invalid" if
+ type() returns an impossible value.
+
+ This is only useful for debugging.
+
+ \also type()
+*/
+QString Atom::typeString() const
+{
+ static bool deja = false;
+
+ if (!deja) {
+ int i = 0;
+ while (atms[i].english != nullptr) {
+ if (atms[i].no != i)
+ Location::internalError(QStringLiteral("QDoc::Atom: atom %1 missing").arg(i));
+ ++i;
+ }
+ deja = true;
+ }
+
+ int i = static_cast<int>(type());
+ if (i < 0 || i > static_cast<int>(Last))
+ return QLatin1String("Invalid");
+ return QLatin1String(atms[i].english);
+}
+
+/*! \fn const QString &Atom::string() const
+
+ Returns the string parameter that together with the type
+ characterizes this atom.
+
+ \also type(), next()
+*/
+
+/*!
+ For a link atom, returns the string representing the link text
+ if one exist in the list of atoms.
+*/
+QString Atom::linkText() const
+{
+ Q_ASSERT(m_type == Atom::Link);
+ QString result;
+
+ if (next() && next()->string() == ATOM_FORMATTING_LINK) {
+ auto *atom = next()->next();
+ while (atom && atom->type() != Atom::FormattingRight) {
+ result += atom->string();
+ atom = atom->next();
+ }
+ return result;
+ }
+
+ return string();
+}
+
+/*!
+ The only constructor for LinkAtom. It creates an Atom of
+ type Atom::Link. \a p1 being the link target. \a p2 is the
+ parameters in square brackets. Normally there is just one
+ word in the square brackets, but there can be up to three
+ words separated by spaces. The constructor splits \a p2 on
+ the space character.
+ */
+LinkAtom::LinkAtom(const QString &p1, const QString &p2, Location location)
+ : Atom(Atom::Link, p1),
+ location(location),
+ m_resolved(false),
+ m_genus(Node::DontCare),
+ m_domain(nullptr),
+ m_squareBracketParams(p2)
+{
+ // nada.
+}
+
+/*!
+ This function resolves the parameters that were enclosed in
+ square brackets. If the parameters have already been resolved,
+ it does nothing and returns immediately.
+ */
+void LinkAtom::resolveSquareBracketParams()
+{
+ if (m_resolved)
+ return;
+ const QStringList params = m_squareBracketParams.toLower().split(QLatin1Char(' '));
+ for (const auto &param : params) {
+ if (!m_domain) {
+ m_domain = QDocDatabase::qdocDB()->findTree(param);
+ if (m_domain) {
+ continue;
+ }
+ }
+
+ if (param == "qml") {
+ m_genus = Node::QML;
+ continue;
+ }
+ if (param == "cpp") {
+ m_genus = Node::CPP;
+ continue;
+ }
+ if (param == "doc") {
+ m_genus = Node::DOC;
+ continue;
+ }
+ if (param == "api") {
+ m_genus = Node::API;
+ continue;
+ }
+ break;
+ }
+ m_resolved = true;
+}
+
+/*!
+ Standard copy constructor of LinkAtom \a t.
+ */
+LinkAtom::LinkAtom(const LinkAtom &t)
+ : Atom(Link, t.string()),
+ location(t.location),
+ m_resolved(t.m_resolved),
+ m_genus(t.m_genus),
+ m_domain(t.m_domain),
+ m_squareBracketParams(t.m_squareBracketParams)
+{
+ // nothing
+}
+
+/*!
+ Special copy constructor of LinkAtom \a t, where
+ where the new LinkAtom will not be the first one
+ in the list.
+ */
+LinkAtom::LinkAtom(Atom *previous, const LinkAtom &t)
+ : Atom(previous, Link, t.string()),
+ location(t.location),
+ m_resolved(t.m_resolved),
+ m_genus(t.m_genus),
+ m_domain(t.m_domain),
+ m_squareBracketParams(t.m_squareBracketParams)
+{
+ previous->m_next = this;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/atom.h b/src/qdoc/qdoc/src/qdoc/atom.h
new file mode 100644
index 000000000..7483c829e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/atom.h
@@ -0,0 +1,223 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef ATOM_H
+#define ATOM_H
+
+#include "node.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class Tree;
+class LinkAtom;
+
+class Atom
+{
+public:
+ enum AtomType {
+ AnnotatedList,
+ AutoLink,
+ BaseName,
+ BR,
+ BriefLeft,
+ BriefRight,
+ C,
+ CaptionLeft,
+ CaptionRight,
+ Code,
+ CodeBad,
+ CodeQuoteArgument,
+ CodeQuoteCommand,
+ ComparesLeft,
+ ComparesRight,
+ DetailsLeft,
+ DetailsRight,
+ DivLeft,
+ DivRight,
+ ExampleFileLink,
+ ExampleImageLink,
+ FootnoteLeft,
+ FootnoteRight,
+ FormatElse,
+ FormatEndif,
+ FormatIf,
+ FormattingLeft,
+ FormattingRight,
+ GeneratedList,
+ HR,
+ Image,
+ ImageText,
+ ImportantLeft,
+ ImportantRight,
+ InlineImage,
+ Keyword,
+ LegaleseLeft,
+ LegaleseRight,
+ LineBreak,
+ Link,
+ LinkNode,
+ ListLeft,
+ ListItemNumber,
+ ListTagLeft,
+ ListTagRight,
+ ListItemLeft,
+ ListItemRight,
+ ListRight,
+ NavAutoLink,
+ NavLink,
+ Nop,
+ NoteLeft,
+ NoteRight,
+ ParaLeft,
+ ParaRight,
+ Qml,
+ QuotationLeft,
+ QuotationRight,
+ RawString,
+ SectionLeft,
+ SectionRight,
+ SectionHeadingLeft,
+ SectionHeadingRight,
+ SidebarLeft,
+ SidebarRight,
+ SinceList,
+ SinceTagLeft,
+ SinceTagRight,
+ SnippetCommand,
+ SnippetIdentifier,
+ SnippetLocation,
+ String,
+ TableLeft,
+ TableRight,
+ TableHeaderLeft,
+ TableHeaderRight,
+ TableRowLeft,
+ TableRowRight,
+ TableItemLeft,
+ TableItemRight,
+ TableOfContents,
+ Target,
+ UnhandledFormat,
+ WarningLeft,
+ WarningRight,
+ UnknownCommand,
+ Last = UnknownCommand
+ };
+
+ friend class LinkAtom;
+
+ explicit Atom(AtomType type, const QString &string = "") : m_type(type), m_strs(string) { }
+
+ Atom(AtomType type, const QString &p1, const QString &p2) : m_type(type), m_strs(p1)
+ {
+ if (!p2.isEmpty())
+ m_strs << p2;
+ }
+
+ Atom(Atom *previous, AtomType type, const QString &string)
+ : m_next(previous->m_next), m_type(type), m_strs(string)
+ {
+ previous->m_next = this;
+ }
+
+ Atom(Atom *previous, AtomType type, const QString &p1, const QString &p2)
+ : m_next(previous->m_next), m_type(type), m_strs(p1)
+ {
+ if (!p2.isEmpty())
+ m_strs << p2;
+ previous->m_next = this;
+ }
+
+ virtual ~Atom() = default;
+
+ void appendChar(QChar ch) { m_strs[0] += ch; }
+ void concatenateString(const QString &string) { m_strs[0] += string; }
+ void append(const QString &string) { m_strs << string; }
+ void chopString() { m_strs[0].chop(1); }
+ void setString(const QString &string) { m_strs[0] = string; }
+ Atom *next() { return m_next; }
+ void setNext(Atom *newNext) { m_next = newNext; }
+
+ [[nodiscard]] const Atom *find(AtomType t) const;
+ [[nodiscard]] const Atom *find(AtomType t, const QString &s) const;
+ [[nodiscard]] const Atom *next() const { return m_next; }
+ [[nodiscard]] const Atom *next(AtomType t) const;
+ [[nodiscard]] const Atom *next(AtomType t, const QString &s) const;
+ [[nodiscard]] AtomType type() const { return m_type; }
+ [[nodiscard]] QString typeString() const;
+ [[nodiscard]] const QString &string() const { return m_strs[0]; }
+ [[nodiscard]] const QString &string(int i) const { return m_strs[i]; }
+ [[nodiscard]] qsizetype count() const { return m_strs.size(); }
+ [[nodiscard]] QString linkText() const;
+ [[nodiscard]] const QStringList &strings() const { return m_strs; }
+
+ [[nodiscard]] virtual bool isLinkAtom() const { return false; }
+ virtual Node::Genus genus() { return Node::DontCare; }
+ virtual Tree *domain() { return nullptr; }
+ virtual void resolveSquareBracketParams() {}
+
+protected:
+ Atom *m_next = nullptr;
+ AtomType m_type {};
+ QStringList m_strs {};
+};
+
+class LinkAtom : public Atom
+{
+public:
+ LinkAtom(const QString &p1, const QString &p2, Location location = Location());
+ LinkAtom(const LinkAtom &t);
+ LinkAtom(Atom *previous, const LinkAtom &t);
+ ~LinkAtom() override = default;
+
+ [[nodiscard]] bool isLinkAtom() const override { return true; }
+ Node::Genus genus() override
+ {
+ resolveSquareBracketParams();
+ return m_genus;
+ }
+ Tree *domain() override
+ {
+ resolveSquareBracketParams();
+ return m_domain;
+ }
+ void resolveSquareBracketParams() override;
+
+public:
+ Location location;
+
+protected:
+ bool m_resolved {};
+ Node::Genus m_genus {};
+ Tree *m_domain {};
+ QString m_squareBracketParams {};
+};
+
+#define ATOM_FORMATTING_BOLD "bold"
+#define ATOM_FORMATTING_INDEX "index"
+#define ATOM_FORMATTING_ITALIC "italic"
+#define ATOM_FORMATTING_LINK "link"
+#define ATOM_FORMATTING_PARAMETER "parameter"
+#define ATOM_FORMATTING_SPAN "span "
+#define ATOM_FORMATTING_SUBSCRIPT "subscript"
+#define ATOM_FORMATTING_SUPERSCRIPT "superscript"
+#define ATOM_FORMATTING_TELETYPE "teletype"
+#define ATOM_FORMATTING_TRADEMARK "trademark"
+#define ATOM_FORMATTING_UICONTROL "uicontrol"
+#define ATOM_FORMATTING_UNDERLINE "underline"
+
+#define ATOM_LIST_BULLET "bullet"
+#define ATOM_LIST_TAG "tag"
+#define ATOM_LIST_VALUE "value"
+#define ATOM_LIST_LOWERALPHA "loweralpha"
+#define ATOM_LIST_LOWERROMAN "lowerroman"
+#define ATOM_LIST_NUMERIC "numeric"
+#define ATOM_LIST_UPPERALPHA "upperalpha"
+#define ATOM_LIST_UPPERROMAN "upperroman"
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.cpp b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.cpp
new file mode 100644
index 000000000..799582139
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.cpp
@@ -0,0 +1,126 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "directorypath.h"
+
+/*!
+ * \class DirectoryPath
+ *
+ * \brief Represents a path to a directory that was known to exist on the
+ * filesystem.
+ *
+ * An instance of this type guarantees that, at the time of creation
+ * of the instance, the contained path represented an existing,
+ * readable, executable directory.
+ *
+ * The type is intended to be used whenever a user-provided path to a
+ * directory is encountered the first time, validating that it can be
+ * used later on for the duration of a QDoc execution and
+ * canonicalizing the original path.
+ *
+ * Such a usage example could be during the configuration process,
+ * when encountering the paths that defines where QDoc should search
+ * for images or other files.
+ *
+ * Similarly, it is intended to be used at the API boundaries,
+ * internally, to relieve the called element of the requirement to
+ * check the validity of a path when a directory is required and to
+ * ensure that a single format of the path is encountered.
+ *
+ * Do note that the guarantees provided by this type do not
+ * necessarily hold after the time of creation of an instance.
+ * Indeed, the underlying filesystem may have changed.
+ *
+ * It is possible to renew the contract by obtaining a new instance:
+ *
+ * \code
+ * DirectoryPath old...
+ *
+ * ...
+ *
+ * auto current{DirectoryPath:refine(old.value())};
+ * \endcode
+ *
+ * QDoc itself will not generally perform destructive operations on
+ * its input files during an execution and, as such, it is never
+ * required to renew a contract. Ensuring that the underlying input
+ * files are indeed immutable is out-of-scope for QDoc and it is
+ * allowed to consider a case where the contract was invalidated as
+ * undefined behavior.
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {wrapped_type_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_equality_operator_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_less_than_operator_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_strictly_less_than_operator_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_greater_than_operator_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_strictly_greater_than_operator_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {refine_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {value_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {copy_constructor_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {copy_assignment_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {move_constructor_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {move_assignment_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {conversion_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_equal_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_unequal_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_less_than_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_less_than_or_equal_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_greater_than_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_greater_than_or_equal_documentation} {DirectoryPath}
+ */
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.h b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.h
new file mode 100644
index 000000000..7ec1dd415
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.h
@@ -0,0 +1,17 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "../refined_typedef.h"
+
+#include <optional>
+
+#include <QtCore/qstring.h>
+#include <QtCore/qfileinfo.h>
+
+QDOC_REFINED_TYPEDEF(QString, DirectoryPath) {
+ QFileInfo info{value};
+
+ return (info.isDir() && info.isReadable() && info.isExecutable()) ? std::optional(DirectoryPath{info.canonicalFilePath()}) : std::nullopt;
+}
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.cpp b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.cpp
new file mode 100644
index 000000000..d8964b6a6
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.cpp
@@ -0,0 +1,125 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "filepath.h"
+
+/*!
+ * \class FilePath
+ *
+ * \brief Represents a path to a file that was known to exist on the
+ * filesystem.
+ *
+ * An instance of this type guarantees that, at the time of creation
+ * of the instance, the contained path represented an existing,
+ * readable file.
+ *
+ * The type is intended to be used whenever a user-provided path to a
+ * file is encountered the first time, validating that it can be
+ * used later on for the duration of a QDoc execution and
+ * canonicalizing the original path.
+ *
+ * Such a usage example could be when resolving a file whose path is
+ * provided by the user.
+ *
+ * Similarly, it is intended to be used at the API boundaries,
+ * internally, to relieve the called element of the requirement to
+ * check the validity of a path when a file is required and to
+ * ensure that a single format of the path is encountered.
+ *
+ * Do note that the guarantees provided by this type do not
+ * necessarily hold after the time of creation of an instance.
+ * Indeed, the underlying filesystem may have changed.
+ *
+ * It is possible to renew the contract by obtaining a new instance:
+ *
+ * \code
+ * FilePath old...
+ *
+ * ...
+ *
+ * auto current{FilePath::refine(old.value())};
+ * \endcode
+ *
+ * QDoc itself will not generally perform destructive operations on
+ * its input files during an execution and, as such, it is never
+ * required to renew a contract. Ensuring that the underlying input
+ * files are indeed immutable is out-of-scope for QDoc and it is
+ * allowed to consider a case where the contract was invalidated as
+ * undefined behavior.
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {wrapped_type_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_equality_operator_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_less_than_operator_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_strictly_less_than_operator_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_greater_than_operator_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_strictly_greater_than_operator_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {refine_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {value_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {copy_constructor_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {copy_assignment_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {move_constructor_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {move_assignment_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {conversion_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_equal_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_unequal_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_less_than_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_less_than_or_equal_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_greater_than_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_greater_than_or_equal_documentation} {FilePath}
+ */
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.h b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.h
new file mode 100644
index 000000000..e8a9b2d35
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.h
@@ -0,0 +1,17 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "qdoc/boundaries/refined_typedef.h"
+
+#include <optional>
+
+#include <QtCore/qstring.h>
+#include <QtCore/qfileinfo.h>
+
+QDOC_REFINED_TYPEDEF(QString, FilePath) {
+ QFileInfo info{value};
+
+ return (info.isFile() && info.isReadable()) ? std::optional(FilePath{info.canonicalFilePath()}) : std::nullopt;
+}
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.cpp b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.cpp
new file mode 100644
index 000000000..4d5eca512
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.cpp
@@ -0,0 +1,96 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "resolvedfile.h"
+
+/*!
+ * \class ResolvedFile
+ *
+ * \brief Represents a file that is reachable by QDoc based on its
+ * current configuration.
+ *
+ * Instances of this type are, generally, intended to be generated by
+ * any process that needs to query the filesystem for the presence of
+ * some files based on a user-inputted path to ensure their
+ * availability.
+ *
+ * Such an example might be when QDoc is searching for a file whose
+ * path is provided by the user, such as the one in a snippet command,
+ * that should represent a file that is reachable with the current
+ * configuration.
+ *
+ * On the other side, logic that requires access to files that are
+ * known to be user-provided, such as the quoting of snippets, can use
+ * this type at the API boundary to signal that the file should be
+ * accessible so that they avoid the need to search for the file
+ * themselves.
+ *
+ * Do note that, semantically, this type doesn't actually guarantee
+ * anything about its origin and only guarantees whatever its members
+ * guarantee.
+ *
+ * The reasoning behind this lack of enforcement is to allow for an
+ * easier testing.
+ * As many parts of QDoc might require the presence of an instance of
+ * this type, we want to be able to construct those instances without
+ * the need to pass trough whichever valid generator for them.
+ *
+ * Nonetheless, inside QDoc, any boundary that requires an instance of
+ * this type can consider it guaranteed that the instance was
+ * generated trough some appropriate logic, and consider it a bug if
+ * such is not the case.
+ *
+ * An instance of this type provides two pieces of information.
+ *
+ * The path to the file that is considered resolved, accessible trough
+ * the get_path() method and the string that was used to resolve the
+ * file in the first place, accessible trough the get_query() method.
+ *
+ * The first should be used by consumer who needs to interact with the
+ * file itself, such as reading from it or copying it.
+ *
+ * The second is provided for context and can be used when consumers
+ * need to know what the user-inputted path was in the first place,
+ * for example when presenting debug information.
+ *
+ * It is not semantically guaranteed that this two pieces of
+ * information are actually related. Any such instance for which this
+ * is true should be considered malformed. Inside QDoc, tough,
+ * consumer of this type can consider it guaranteed that no malformed
+ * instance will be passed to them, and consider it a bug if it
+ * happens otherwise.
+ */
+
+/*!
+ * \fn ResolvedFile::ResolvedFile(QString query, FilePath filepath)
+ *
+ * Constructs an instance of this type from \a query and \a filepath.
+ *
+ * \a query should represent the user-inputted path that was used to
+ * resolve the file that this instance represents.
+ *
+ * \a filepath should represent the file that is found querying the
+ * filesystem trough \a query using an appropriate logic for resolving
+ * files.
+ *
+ * An instance that is built from \a query and \a filepath is
+ * guaranteed to return a value that is equivalent to \a query when
+ * get_query() is called and a value that is equivalent to \a
+ * filepath.value() when get_path() is called.
+ */
+
+/*!
+ * \fn const QString& ResolvedFile::get_query() const
+ *
+ * Returns a string representing the user-inputted path that was used
+ * to resolve the file.
+ */
+
+/*!
+ * \fn const QString& ResolvedFile::get_path() const
+ *
+ * Returns a string representing the canonicalized path to the file
+ * that was resolved.
+ *
+ * Access to this file is to be considered guranteed to be available.
+ */
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.h b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.h
new file mode 100644
index 000000000..e21120ab1
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.h
@@ -0,0 +1,20 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "qdoc/boundaries/filesystem/filepath.h"
+
+#include <QString>
+
+struct ResolvedFile {
+public:
+ ResolvedFile(QString query, FilePath filepath) : query{query}, filepath{filepath} {}
+
+ [[nodiscard]] const QString& get_query() const { return query; }
+ [[nodiscard]] const QString& get_path() const { return filepath.value(); }
+
+private:
+ QString query;
+ FilePath filepath;
+};
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef.h b/src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef.h
new file mode 100644
index 000000000..4f3a13b7c
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef.h
@@ -0,0 +1,207 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QtCore/qglobal.h>
+
+#include <functional>
+#include <optional>
+#include <type_traits>
+
+// TODO: Express the documentation such that QDoc would be able to see
+// it and process it correctly. This probably means that we would like
+// to associate the definition with a namespace, albeit we could use
+// the header file too, and put the documentation in an empty cpp
+// file. This is delayed as there currently isn't much namespacing for
+// anything in QDoc and such a namespacing should be added gradually
+// and attentively.
+
+// TODO: Review the semantics for construction and optmize it. Should we copy the
+// value? Should we only allow rvalues?
+
+// TODO: There is an high chance that we will need to "compose"
+// refinitions later on when dealing, for example, with the basics of
+// user-provided paths.
+// For example, when requiring that each user-inputted path is purged
+// as per QFileInfo definition of purging.
+// For example, it might be that instead of passing QString around we
+// might pass some Path type that is a purged QString.
+// Then, any other refinement over paths will need to use that as a
+// base type.
+// To avoid the clutter that comes from that, if such will be the
+// case, we will need to change the definition of refine and value if
+// the passed in type was refined already.
+// That is, such that if we have:
+//
+// QDOC_REFINE_TYPE(QString, Path) { ... }
+// QDOC_REFINE_TYPE(Path, Foo) { ... }
+//
+// Foo refines a QString and Foo.value returns a QString. This should
+// in general be trivial as long as we add a way to identify, such as
+// a tag that refinements derive from, what type was declared through
+// QDOC_REFINED_TYPEDEF and what type was not.
+
+// TODO: Provide a way to generate a standard documentation for all
+// members of a type generated by QDOC_REFINED_TYPEDEF without having
+// to copy-paste include command everywhere.
+// The main problem of doing this is that the preprocessor strips
+// comments away, making it impossible to generate comments, and hence
+// QDoc documentation, with the preprocessor.
+
+/*!
+ * \macro QDOC_REFINED_TYPEDEF(_type, _name)
+ * \relates refined_typedef.hpp
+ *
+ * Declares a wrapper type for \c {_type}, with identifier \c {_name},
+ * that represents a subset of \c {_type} for which some conditions
+ * hold.
+ *
+ * For example:
+ *
+ * \code
+ QDOC_REFINED_TYPEDEF(std::size_t, Fin5) {
+ return (value < 5) : std::make_optional<Fin5>{value} : std::nullopt;
+ }
+ * \endcode
+ *
+ * Represents the subset of \c {std::size_t} that contains the value 0, 1,
+ * 2, 3 and 4, that is, the general finite set of cardinality 5.
+ *
+ * As the example shows, usages of the macro require some type, an
+ * identifier and some code.
+ *
+ * The type that is provided is the type that will be wrapped.
+ * Do note that we expect a type with no-qualifiers and that is not a
+ * pointer type. Types passed with those kind of qualifiers will be
+ * simplified to their base type.
+ *
+ * That is, for example, \c {int*}, \c {const int}, \c {const int&},
+ * \c {int&} all counts as \c {int}.
+ *
+ * The identifier that is passed is used as the name for the newly
+ * declared type that wraps the original type.
+ *
+ * The code block that is passed will be run when an instance of the
+ * newly created wrapper type is being obtained.
+ * If the wrapper type is T, the codeblock must return a \c
+ * {std::optional<T>}.
+ * The code block should perform any check that ensures that the
+ * guarantees provided by the wrapper type holds and return a value if
+ * such is the case, otherwise returning \c {std::nullopt}.
+ *
+ * Inside the code block, the identifier \e {value} is implicitly
+ * bound to an element of the wrapped type that the instance is being
+ * built from and for which the guarantees provided by the wrapper
+ * type must hold.
+ *
+ * When a call to QDOC_REFINED_TYPEDEF is successful, a type with the
+ * provided identifier is declared.
+ *
+ * Let T be a type declared trough a call of QDOC_REFINED_TYPEDEF and
+ * W be the type that it wraps.
+ *
+ * An instance of T can be obtained by calling T::refine with an
+ * element of W.
+ *
+ * If the element of W respects the guarantees that T provides, then
+ * the call will return an optional that contains an instance of T,
+ * othewise it will return an empty optional.
+ *
+ * When an instance of T is obtained, it will wrap the element of W that
+ * was used to obtain it.
+ *
+ * The wrapped value can be accessed trough the \c {value} method.
+ *
+ * For example, considering \c {Fin5}, we could obtain an instance of
+ * it as follows:
+ *
+ * \code
+ * auto instance = *(Fin5::refine(std::size_t{1}));
+ * \endcode
+ *
+ * With that instance available we can retrieve the original value as
+ * follows:
+ *
+ * \code
+ * instance.value(); // The value 1
+ * \endcode
+ */
+
+#define QDOC_REFINED_TYPEDEF(_type, _name) \
+ struct _name { \
+ public: \
+ using wrapped_type = std::remove_reference_t<std::remove_cv_t<std::remove_pointer_t<_type>>>; \
+ \
+ inline static constexpr auto has_equality_operator_v = std::is_invocable_r_v<bool, std::equal_to<>, wrapped_type, wrapped_type>; \
+ inline static constexpr auto has_less_than_operator_v = std::is_invocable_r_v<bool, std::less_equal<>, wrapped_type, wrapped_type>; \
+ inline static constexpr auto has_strictly_less_than_operator_v = std::is_invocable_r_v<bool, std::less<>, wrapped_type, wrapped_type>; \
+ inline static constexpr auto has_greater_than_operator_v = std::is_invocable_r_v<bool, std::greater_equal<>, wrapped_type, wrapped_type>; \
+ inline static constexpr auto has_strictly_greater_than_operator_v = std::is_invocable_r_v<bool, std::greater<>, wrapped_type, wrapped_type>; \
+ \
+ public: \
+ static std::optional<_name> refine(wrapped_type value); \
+ \
+ [[nodiscard]] const wrapped_type& value() const noexcept { return _value; } \
+ \
+ _name(const _name&) = default; \
+ _name& operator=(const _name&) = default; \
+ \
+ _name(_name&&) = default; \
+ _name& operator=(_name&&) = default; \
+ \
+ operator wrapped_type() const { return _value; } \
+ \
+ public: \
+ \
+ template< \
+ typename = std::enable_if_t< \
+ has_equality_operator_v \
+ > \
+ > \
+ bool operator==(const _name& rhs) const noexcept { return _value == rhs._value; } \
+ \
+ template< \
+ typename = std::enable_if_t< \
+ has_equality_operator_v \
+ > \
+ > \
+ bool operator!=(const _name& rhs) const noexcept { return !(_value == rhs._value); } \
+ \
+ template< \
+ typename = std::enable_if_t< \
+ has_less_than_operator_v \
+ > \
+ > \
+ bool operator<=(const _name& rhs) const noexcept { return _value <= rhs._value; } \
+ \
+ \
+ template< \
+ typename = std::enable_if_t< \
+ has_strictly_less_than_operator_v \
+ > \
+ > \
+ bool operator<(const _name& rhs) const noexcept { return _value < rhs._value; } \
+ \
+ template< \
+ typename = std::enable_if_t< \
+ has_greater_than_operator_v \
+ > \
+ > \
+ bool operator>=(const _name& rhs) const noexcept { return _value >= rhs._value; } \
+ \
+ template< \
+ typename = std::enable_if_t< \
+ has_strictly_greater_than_operator_v \
+ > \
+ > \
+ bool operator>(const _name& rhs) const noexcept { return _value > rhs._value; } \
+ \
+ private: \
+ _name(wrapped_type value) : _value{std::move(value)} {} \
+ \
+ private: \
+ wrapped_type _value; \
+ }; \
+ \
+ inline std::optional<_name> _name::refine(wrapped_type value)
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef_members.qdocinc b/src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef_members.qdocinc
new file mode 100644
index 000000000..ab956f49f
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef_members.qdocinc
@@ -0,0 +1,162 @@
+//! [wrapped_type_documentation]
+\typealias \1::wrapped_type
+
+The type that is wrapped by this type.
+
+This type is always deprived of qualifiers and is never a pointer
+type.
+//! [wrapped_type_documentation]
+
+//! [has_equality_operator_documentation]
+\variable \1::has_equality_operator_v
+
+True when the wrapped_type can be compared for equality.
+
+When this is the case, \1 can be compared for equality and inequality.
+//! [has_equality_operator_documentation]
+
+//! [has_less_than_operator_documentation]
+\variable \1::has_less_than_operator_v
+
+True when the wrapped_type can be compared for lesserness.
+
+When this is the case, \1 can be compared for lesserness.
+//! [has_less_than_operator_documentation]
+
+//! [has_strictly_less_than_operator_documentation]
+\variable \1::has_strictly_less_than_operator_v
+
+True when the wrapped_type can be compared for strict lesserness.
+
+When this is the case, \1 can be compared for strict lesserness.
+//! [has_stricly_less_than_operator_documentation]
+
+//! [has_greater_than_operator_documentation]
+\variable \1::has_greater_than_operator_v
+
+True when the wrapped_type can be compared for greaterness.
+
+When this is the case, \1 can be compared for greaterness.
+//! [has_less_than_operator_documentation]
+
+//! [has_strictly_greater_than_operator_documentation]
+\variable \1::has_strictly_greater_than_operator_v
+
+True when the wrapped_type can be compared for strict greaterness.
+
+When this is the case, \1 can be compared for strict greaterness.
+//! [has_stricly_greater_than_operator_documentation]
+
+//! [refine_documentation]
+\fn static std::optional<\1> \1::refine(wrapped_type value)
+
+Returns an instance of \1 wrapping \a value if \a value respects the
+guarantees that are required by \1.
+
+If such is not the case, \c {std::nullopt} is returned instead.
+//! [refine_documentation]
+
+//! [value_documentation]
+\fn const wrapped_type& \1::value() const noexcept
+
+Returns a const reference to the value that is wrapped by this
+instance.
+//! [value_documentation]
+
+//! [copy_constructor_documentation]
+\fn \1::\1(const \1& other)
+
+Copy-constructs an instance of \1 from \a other.
+
+This constructor is generated by the compiler.
+//! [copy_constructor_documentation]
+
+//! [copy_assignment_documentation]
+\fn \1::operator=(const \1& other)
+
+Copy-assigns to this instance of \1 from \a other.
+
+This constructor is generated by the compiler.
+//! [copy_assignment_documentation]
+
+//! [move_constructor_documentation]
+\fn \1::\1(\1&& other)
+
+Move-constructs an instance of \1 from \a other.
+
+The only valid operations on an instance that was moved-from are
+destruction and reassignment.
+
+This constructor is generated by the compiler.
+//! [move_constructor_documentation]
+
+//! [move_assignment_documentation]
+\fn \1::operator=(\1&& other)
+
+Move-assigns to this instance of \1 from \a other.
+
+The only valid operations on an instance that was moved-from are
+destruction and reassignment.
+
+This constructor is generated by the compiler.
+//! [move_assignment_documentation]
+
+//! [conversion_documentation]
+\fn operator wrapped_type() const
+
+Converts this instance to its wrapped value.
+//! [conversion_documentation]
+
+//! [operator_equal_documentation]
+\fn bool operator=(const \1& rhs) const noexcept
+
+Returns true if the value wrapped by this instance and \a rhs compare
+equal.
+
+Returns false otherwise.
+//! [operator_equal_documentation]
+
+//! [operator_unequal_documentation]
+\fn bool operator!=(const \1& rhs) const noexcept
+
+Returns true if the value wrapped by this instance and \a rhs do not
+compare equal.
+
+Returns false otherwise.
+//! [operator_unequal_documentation]
+
+//! [operator_less_than_documentation]
+\fn bool operator<(const \1& rhs) const noexcept
+
+Returns true if the value wrapped by this instance compares less than
+the value wrapped by \a rhs.
+
+Returns false otherwise.
+//! [operator_less_than_documentation]
+
+//! [operator_less_than_or_equal_documentation]
+\fn bool operator<=(const \1& rhs) const noexcept
+
+Returns true if the value wrapped by this instance compares less than
+or equal than the value wrapped by \a rhs.
+
+Returns false otherwise.
+//! [operator_less_than_or_equal_documentation]
+
+//! [operator_greater_than_documentation]
+\fn bool operator>(const \1& rhs) const noexcept
+
+Returns true if the value wrapped by this instance compares greater
+than the value wrapped by \a rhs.
+
+Returns false otherwise.
+//! [operator_greater_than_documentation]
+
+//! [operator_greater_than_or_equal_documentation]
+\fn bool operator>=(const \1& rhs) const noexcept
+
+Returns true if the value wrapped by this instance compares greater
+than or equal or equal than the value wrapped by \a rhs.
+
+Returns false otherwise.
+//! [operator_greater_than_or_equal_documentation]
diff --git a/src/qdoc/qdoc/src/qdoc/clang/AST/LLVM_LICENSE.txt b/src/qdoc/qdoc/src/qdoc/clang/AST/LLVM_LICENSE.txt
new file mode 100644
index 000000000..fa6ac5400
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/clang/AST/LLVM_LICENSE.txt
@@ -0,0 +1,279 @@
+==============================================================================
+The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
+==============================================================================
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+---- LLVM Exceptions to the Apache 2.0 License ----
+
+As an exception, if, as a result of your compiling your source code, portions
+of this Software are embedded into an Object form of such source code, you
+may redistribute such embedded portions in such Object form without complying
+with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
+
+In addition, if you combine or link compiled forms of this Software with
+software that is licensed under the GPLv2 ("Combined Software") and if a
+court of competent jurisdiction determines that the patent provision (Section
+3), the indemnity provision (Section 9) or other Section of the License
+conflicts with the conditions of the GPLv2, you may retroactively and
+prospectively choose to deem waived or otherwise exclude such Section(s) of
+the License, but only in their entirety and only with respect to the Combined
+Software.
+
+==============================================================================
+Software from third parties included in the LLVM Project:
+==============================================================================
+The LLVM Project contains third party software which is under different license
+terms. All such code will be identified clearly using at least one of two
+mechanisms:
+1) It will be in a separate directory tree with its own `LICENSE.txt` or
+ `LICENSE` file at the top containing the specific license and restrictions
+ which apply to that software, or
+2) It will contain specific license and restriction terms at the top of every
+ file.
+
+==============================================================================
+Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy):
+==============================================================================
+University of Illinois/NCSA
+Open Source License
+
+Copyright (c) 2003-2019 University of Illinois at Urbana-Champaign.
+All rights reserved.
+
+Developed by:
+
+ LLVM Team
+
+ University of Illinois at Urbana-Champaign
+
+ http://llvm.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal with
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimers.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimers in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the names of the LLVM Team, University of Illinois at
+ Urbana-Champaign, nor the names of its contributors may be used to
+ endorse or promote products derived from this Software without specific
+ prior written permission.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
+SOFTWARE.
+
diff --git a/src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h b/src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h
new file mode 100644
index 000000000..c6d331ea8
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h
@@ -0,0 +1,491 @@
+//===------- QualTypeNames.cpp - Generate Complete QualType Names ---------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+// Those directives indirectly includes "clang/AST/Attrs.h" which
+// includes "clang/AST/Attrs.inc".
+// "clang/AST/Attrs.inc", produces some "C4267" warnings specifically
+// on MSVC 2019.
+// This in turn blocks CI integrations for configuration with that
+// compiler that treats warnings as errors.
+// As that header is not under our control, we disable the warning
+// completely when going through those includes.
+#include <QtCore/qcompilerdetection.h>
+
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_MSVC(4267)
+
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/DeclarationName.h"
+#include "clang/AST/GlobalDecl.h"
+#include "clang/AST/Mangle.h"
+
+QT_WARNING_POP
+
+#include <stdio.h>
+#include <memory>
+
+namespace clang {
+
+namespace TypeName {
+
+inline QualType getFullyQualifiedType(QualType QT, const ASTContext &Ctx,
+ bool WithGlobalNsPrefix);
+
+/// Create a NestedNameSpecifier for Namesp and its enclosing
+/// scopes.
+///
+/// \param[in] Ctx - the AST Context to be used.
+/// \param[in] Namesp - the NamespaceDecl for which a NestedNameSpecifier
+/// is requested.
+/// \param[in] WithGlobalNsPrefix - Indicate whether the global namespace
+/// specifier "::" should be prepended or not.
+static inline NestedNameSpecifier *createNestedNameSpecifier(
+ const ASTContext &Ctx,
+ const NamespaceDecl *Namesp,
+ bool WithGlobalNsPrefix);
+
+/// Create a NestedNameSpecifier for TagDecl and its enclosing
+/// scopes.
+///
+/// \param[in] Ctx - the AST Context to be used.
+/// \param[in] TD - the TagDecl for which a NestedNameSpecifier is
+/// requested.
+/// \param[in] FullyQualify - Convert all template arguments into fully
+/// qualified names.
+/// \param[in] WithGlobalNsPrefix - Indicate whether the global namespace
+/// specifier "::" should be prepended or not.
+static inline NestedNameSpecifier *createNestedNameSpecifier(
+ const ASTContext &Ctx, const TypeDecl *TD,
+ bool FullyQualify, bool WithGlobalNsPrefix);
+
+static inline NestedNameSpecifier *createNestedNameSpecifierForScopeOf(
+ const ASTContext &Ctx, const Decl *decl,
+ bool FullyQualified, bool WithGlobalNsPrefix);
+
+static inline NestedNameSpecifier *getFullyQualifiedNestedNameSpecifier(
+ const ASTContext &Ctx, NestedNameSpecifier *scope, bool WithGlobalNsPrefix);
+
+static inline bool getFullyQualifiedTemplateName(const ASTContext &Ctx,
+ TemplateName &TName,
+ bool WithGlobalNsPrefix) {
+ bool Changed = false;
+ NestedNameSpecifier *NNS = nullptr;
+
+ TemplateDecl *ArgTDecl = TName.getAsTemplateDecl();
+ // ArgTDecl won't be NULL because we asserted that this isn't a
+ // dependent context very early in the call chain.
+ assert(ArgTDecl != nullptr);
+ QualifiedTemplateName *QTName = TName.getAsQualifiedTemplateName();
+
+ if (QTName && !QTName->hasTemplateKeyword()) {
+ NNS = QTName->getQualifier();
+ NestedNameSpecifier *QNNS = getFullyQualifiedNestedNameSpecifier(
+ Ctx, NNS, WithGlobalNsPrefix);
+ if (QNNS != NNS) {
+ Changed = true;
+ NNS = QNNS;
+ } else {
+ NNS = nullptr;
+ }
+ } else {
+ NNS = createNestedNameSpecifierForScopeOf(
+ Ctx, ArgTDecl, true, WithGlobalNsPrefix);
+ }
+ if (NNS) {
+ TemplateName UnderlyingTN(ArgTDecl);
+ if (UsingShadowDecl *USD = TName.getAsUsingShadowDecl())
+ UnderlyingTN = TemplateName(USD);
+ TName =
+ Ctx.getQualifiedTemplateName(NNS,
+ /*TemplateKeyword=*/false, UnderlyingTN);
+ Changed = true;
+ }
+ return Changed;
+}
+
+static inline bool getFullyQualifiedTemplateArgument(const ASTContext &Ctx,
+ TemplateArgument &Arg,
+ bool WithGlobalNsPrefix) {
+ bool Changed = false;
+
+ // Note: we do not handle TemplateArgument::Expression, to replace it
+ // we need the information for the template instance decl.
+
+ if (Arg.getKind() == TemplateArgument::Template) {
+ TemplateName TName = Arg.getAsTemplate();
+ Changed = getFullyQualifiedTemplateName(Ctx, TName, WithGlobalNsPrefix);
+ if (Changed) {
+ Arg = TemplateArgument(TName);
+ }
+ } else if (Arg.getKind() == TemplateArgument::Type) {
+ QualType SubTy = Arg.getAsType();
+ // Check if the type needs more desugaring and recurse.
+ QualType QTFQ = getFullyQualifiedType(SubTy, Ctx, WithGlobalNsPrefix);
+ if (QTFQ != SubTy) {
+ Arg = TemplateArgument(QTFQ);
+ Changed = true;
+ }
+ }
+ return Changed;
+}
+
+static inline const Type *getFullyQualifiedTemplateType(const ASTContext &Ctx,
+ const Type *TypePtr,
+ bool WithGlobalNsPrefix) {
+ // DependentTemplateTypes exist within template declarations and
+ // definitions. Therefore we shouldn't encounter them at the end of
+ // a translation unit. If we do, the caller has made an error.
+ assert(!isa<DependentTemplateSpecializationType>(TypePtr));
+ // In case of template specializations, iterate over the arguments
+ // and fully qualify them as well.
+ if (const auto *TST = dyn_cast<const TemplateSpecializationType>(TypePtr)) {
+ bool MightHaveChanged = false;
+ SmallVector<TemplateArgument, 4> FQArgs;
+ // Cheap to copy and potentially modified by
+ // getFullyQualifedTemplateArgument.
+ for (TemplateArgument Arg : TST->template_arguments()) {
+ MightHaveChanged |= getFullyQualifiedTemplateArgument(
+ Ctx, Arg, WithGlobalNsPrefix);
+ FQArgs.push_back(Arg);
+ }
+
+ // If a fully qualified arg is different from the unqualified arg,
+ // allocate new type in the AST.
+ if (MightHaveChanged) {
+ QualType QT = Ctx.getTemplateSpecializationType(
+ TST->getTemplateName(), FQArgs,
+ TST->getCanonicalTypeInternal());
+ // getTemplateSpecializationType returns a fully qualified
+ // version of the specialization itself, so no need to qualify
+ // it.
+ return QT.getTypePtr();
+ }
+ } else if (const auto *TSTRecord = dyn_cast<const RecordType>(TypePtr)) {
+ // We are asked to fully qualify and we have a Record Type,
+ // which can point to a template instantiation with no sugar in any of
+ // its template argument, however we still need to fully qualify them.
+
+ if (const auto *TSTDecl =
+ dyn_cast<ClassTemplateSpecializationDecl>(TSTRecord->getDecl())) {
+ const TemplateArgumentList &TemplateArgs = TSTDecl->getTemplateArgs();
+
+ bool MightHaveChanged = false;
+ SmallVector<TemplateArgument, 4> FQArgs;
+ for (unsigned int I = 0, E = TemplateArgs.size(); I != E; ++I) {
+ // cheap to copy and potentially modified by
+ // getFullyQualifedTemplateArgument
+ TemplateArgument Arg(TemplateArgs[I]);
+ MightHaveChanged |= getFullyQualifiedTemplateArgument(
+ Ctx, Arg, WithGlobalNsPrefix);
+ FQArgs.push_back(Arg);
+ }
+
+ // If a fully qualified arg is different from the unqualified arg,
+ // allocate new type in the AST.
+ if (MightHaveChanged) {
+ TemplateName TN(TSTDecl->getSpecializedTemplate());
+ QualType QT = Ctx.getTemplateSpecializationType(
+ TN, FQArgs,
+ TSTRecord->getCanonicalTypeInternal());
+ // getTemplateSpecializationType returns a fully qualified
+ // version of the specialization itself, so no need to qualify
+ // it.
+ return QT.getTypePtr();
+ }
+ }
+ }
+ return TypePtr;
+}
+
+static inline NestedNameSpecifier *createOuterNNS(const ASTContext &Ctx, const Decl *D,
+ bool FullyQualify,
+ bool WithGlobalNsPrefix) {
+ const DeclContext *DC = D->getDeclContext();
+ if (const auto *NS = dyn_cast<NamespaceDecl>(DC)) {
+ while (NS && NS->isInline()) {
+ // Ignore inline namespace;
+ NS = dyn_cast<NamespaceDecl>(NS->getDeclContext());
+ }
+ if (NS && NS->getDeclName()) {
+ return createNestedNameSpecifier(Ctx, NS, WithGlobalNsPrefix);
+ }
+ return nullptr; // no starting '::', no anonymous
+ } else if (const auto *TD = dyn_cast<TagDecl>(DC)) {
+ return createNestedNameSpecifier(Ctx, TD, FullyQualify, WithGlobalNsPrefix);
+ } else if (const auto *TDD = dyn_cast<TypedefNameDecl>(DC)) {
+ return createNestedNameSpecifier(
+ Ctx, TDD, FullyQualify, WithGlobalNsPrefix);
+ } else if (WithGlobalNsPrefix && DC->isTranslationUnit()) {
+ return NestedNameSpecifier::GlobalSpecifier(Ctx);
+ }
+ return nullptr; // no starting '::' if |WithGlobalNsPrefix| is false
+}
+
+/// Return a fully qualified version of this name specifier.
+static inline NestedNameSpecifier *getFullyQualifiedNestedNameSpecifier(
+ const ASTContext &Ctx, NestedNameSpecifier *Scope,
+ bool WithGlobalNsPrefix) {
+ switch (Scope->getKind()) {
+ case NestedNameSpecifier::Global:
+ // Already fully qualified
+ return Scope;
+ case NestedNameSpecifier::Namespace:
+ return TypeName::createNestedNameSpecifier(
+ Ctx, Scope->getAsNamespace(), WithGlobalNsPrefix);
+ case NestedNameSpecifier::NamespaceAlias:
+ // Namespace aliases are only valid for the duration of the
+ // scope where they were introduced, and therefore are often
+ // invalid at the end of the TU. So use the namespace name more
+ // likely to be valid at the end of the TU.
+ return TypeName::createNestedNameSpecifier(
+ Ctx,
+ Scope->getAsNamespaceAlias()->getNamespace()->getCanonicalDecl(),
+ WithGlobalNsPrefix);
+ case NestedNameSpecifier::Identifier:
+ // A function or some other construct that makes it un-namable
+ // at the end of the TU. Skip the current component of the name,
+ // but use the name of it's prefix.
+ return getFullyQualifiedNestedNameSpecifier(
+ Ctx, Scope->getPrefix(), WithGlobalNsPrefix);
+ case NestedNameSpecifier::Super:
+ case NestedNameSpecifier::TypeSpec:
+ case NestedNameSpecifier::TypeSpecWithTemplate: {
+ const Type *Type = Scope->getAsType();
+ // Find decl context.
+ const TagDecl *TD = nullptr;
+ if (const TagType *TagDeclType = Type->getAs<TagType>()) {
+ TD = TagDeclType->getDecl();
+ } else {
+ TD = Type->getAsCXXRecordDecl();
+ }
+ if (TD) {
+ return TypeName::createNestedNameSpecifier(Ctx, TD,
+ true /*FullyQualified*/,
+ WithGlobalNsPrefix);
+ } else if (const auto *TDD = dyn_cast<TypedefType>(Type)) {
+ return TypeName::createNestedNameSpecifier(Ctx, TDD->getDecl(),
+ true /*FullyQualified*/,
+ WithGlobalNsPrefix);
+ }
+ return Scope;
+ }
+ }
+ llvm_unreachable("bad NNS kind");
+}
+
+/// Create a nested name specifier for the declaring context of
+/// the type.
+static inline NestedNameSpecifier *createNestedNameSpecifierForScopeOf(
+ const ASTContext &Ctx, const Decl *Decl,
+ bool FullyQualified, bool WithGlobalNsPrefix) {
+ assert(Decl);
+
+ const DeclContext *DC = Decl->getDeclContext()->getRedeclContext();
+ const auto *Outer = dyn_cast_or_null<NamedDecl>(DC);
+ const auto *OuterNS = dyn_cast_or_null<NamespaceDecl>(DC);
+ if (Outer && !(OuterNS && OuterNS->isAnonymousNamespace())) {
+ if (OuterNS) {
+ return createNestedNameSpecifier(Ctx, OuterNS, WithGlobalNsPrefix);
+ } else if (const auto *TD = dyn_cast<TagDecl>(Outer)) {
+ return createNestedNameSpecifier(
+ Ctx, TD, FullyQualified, WithGlobalNsPrefix);
+ } else if (isa<TranslationUnitDecl>(Outer)) {
+ // Context is the TU. Nothing needs to be done.
+ return nullptr;
+ } else {
+ // Decl's context was neither the TU, a namespace, nor a
+ // TagDecl, which means it is a type local to a scope, and not
+ // accessible at the end of the TU.
+ return nullptr;
+ }
+ } else if (WithGlobalNsPrefix && DC->isTranslationUnit()) {
+ return NestedNameSpecifier::GlobalSpecifier(Ctx);
+ }
+ return nullptr;
+}
+
+/// Create a nested name specifier for the declaring context of
+/// the type.
+static inline NestedNameSpecifier *createNestedNameSpecifierForScopeOf(
+ const ASTContext &Ctx, const Type *TypePtr,
+ bool FullyQualified, bool WithGlobalNsPrefix) {
+ if (!TypePtr) return nullptr;
+
+ Decl *Decl = nullptr;
+ // There are probably other cases ...
+ if (const auto *TDT = dyn_cast<TypedefType>(TypePtr)) {
+ Decl = TDT->getDecl();
+ } else if (const auto *TagDeclType = dyn_cast<TagType>(TypePtr)) {
+ Decl = TagDeclType->getDecl();
+ } else if (const auto *TST = dyn_cast<TemplateSpecializationType>(TypePtr)) {
+ Decl = TST->getTemplateName().getAsTemplateDecl();
+ } else {
+ Decl = TypePtr->getAsCXXRecordDecl();
+ }
+
+ if (!Decl) return nullptr;
+
+ return createNestedNameSpecifierForScopeOf(
+ Ctx, Decl, FullyQualified, WithGlobalNsPrefix);
+}
+
+inline NestedNameSpecifier *createNestedNameSpecifier(const ASTContext &Ctx,
+ const NamespaceDecl *Namespace,
+ bool WithGlobalNsPrefix) {
+ while (Namespace && Namespace->isInline()) {
+ // Ignore inline namespace;
+ Namespace = dyn_cast<NamespaceDecl>(Namespace->getDeclContext());
+ }
+ if (!Namespace) return nullptr;
+
+ bool FullyQualified = true; // doesn't matter, DeclContexts are namespaces
+ return NestedNameSpecifier::Create(
+ Ctx,
+ createOuterNNS(Ctx, Namespace, FullyQualified, WithGlobalNsPrefix),
+ Namespace);
+}
+
+inline NestedNameSpecifier *createNestedNameSpecifier(const ASTContext &Ctx,
+ const TypeDecl *TD,
+ bool FullyQualify,
+ bool WithGlobalNsPrefix) {
+ const Type *TypePtr = TD->getTypeForDecl();
+ if (isa<const TemplateSpecializationType>(TypePtr) ||
+ isa<const RecordType>(TypePtr)) {
+ // We are asked to fully qualify and we have a Record Type (which
+ // may point to a template specialization) or Template
+ // Specialization Type. We need to fully qualify their arguments.
+
+ TypePtr = getFullyQualifiedTemplateType(Ctx, TypePtr, WithGlobalNsPrefix);
+ }
+
+ return NestedNameSpecifier::Create(
+ Ctx, createOuterNNS(Ctx, TD, FullyQualify, WithGlobalNsPrefix),
+ false /*No TemplateKeyword*/, TypePtr);
+}
+
+/// Return the fully qualified type, including fully-qualified
+/// versions of any template parameters.
+inline QualType getFullyQualifiedType(QualType QT, const ASTContext &Ctx,
+ bool WithGlobalNsPrefix = false) {
+ // In case of myType* we need to strip the pointer first, fully
+ // qualify and attach the pointer once again.
+ if (isa<PointerType>(QT.getTypePtr())) {
+ // Get the qualifiers.
+ Qualifiers Quals = QT.getQualifiers();
+ QT = getFullyQualifiedType(QT->getPointeeType(), Ctx, WithGlobalNsPrefix);
+ QT = Ctx.getPointerType(QT);
+ // Add back the qualifiers.
+ QT = Ctx.getQualifiedType(QT, Quals);
+ return QT;
+ }
+
+ if (auto *MPT = dyn_cast<MemberPointerType>(QT.getTypePtr())) {
+ // Get the qualifiers.
+ Qualifiers Quals = QT.getQualifiers();
+ // Fully qualify the pointee and class types.
+ QT = getFullyQualifiedType(QT->getPointeeType(), Ctx, WithGlobalNsPrefix);
+ QualType Class = getFullyQualifiedType(QualType(MPT->getClass(), 0), Ctx,
+ WithGlobalNsPrefix);
+ QT = Ctx.getMemberPointerType(QT, Class.getTypePtr());
+ // Add back the qualifiers.
+ QT = Ctx.getQualifiedType(QT, Quals);
+ return QT;
+ }
+
+ // In case of myType& we need to strip the reference first, fully
+ // qualify and attach the reference once again.
+ if (isa<ReferenceType>(QT.getTypePtr())) {
+ // Get the qualifiers.
+ bool IsLValueRefTy = isa<LValueReferenceType>(QT.getTypePtr());
+ Qualifiers Quals = QT.getQualifiers();
+ QT = getFullyQualifiedType(QT->getPointeeType(), Ctx, WithGlobalNsPrefix);
+ // Add the r- or l-value reference type back to the fully
+ // qualified one.
+ if (IsLValueRefTy)
+ QT = Ctx.getLValueReferenceType(QT);
+ else
+ QT = Ctx.getRValueReferenceType(QT);
+ // Add back the qualifiers.
+ QT = Ctx.getQualifiedType(QT, Quals);
+ return QT;
+ }
+
+ // Remove the part of the type related to the type being a template
+ // parameter (we won't report it as part of the 'type name' and it
+ // is actually make the code below to be more complex (to handle
+ // those)
+ while (isa<SubstTemplateTypeParmType>(QT.getTypePtr())) {
+ // Get the qualifiers.
+ Qualifiers Quals = QT.getQualifiers();
+
+ QT = cast<SubstTemplateTypeParmType>(QT.getTypePtr())->desugar();
+
+ // Add back the qualifiers.
+ QT = Ctx.getQualifiedType(QT, Quals);
+ }
+
+ NestedNameSpecifier *Prefix = nullptr;
+ // Local qualifiers are attached to the QualType outside of the
+ // elaborated type. Retrieve them before descending into the
+ // elaborated type.
+ Qualifiers PrefixQualifiers = QT.getLocalQualifiers();
+ QT = QualType(QT.getTypePtr(), 0);
+#if LIBCLANG_VERSION_MAJOR >= 18
+ constexpr ElaboratedTypeKeyword ETK_None = ElaboratedTypeKeyword::None;
+#endif
+ ElaboratedTypeKeyword Keyword = ETK_None;
+ if (const auto *ETypeInput = dyn_cast<ElaboratedType>(QT.getTypePtr())) {
+ QT = ETypeInput->getNamedType();
+ assert(!QT.hasLocalQualifiers());
+ Keyword = ETypeInput->getKeyword();
+ }
+
+ // We don't consider the alias introduced by `using a::X` as a new type.
+ // The qualified name is still a::X.
+ if (const auto *UT = QT->getAs<UsingType>()) {
+ QT = Ctx.getQualifiedType(UT->getUnderlyingType(), PrefixQualifiers);
+ return getFullyQualifiedType(QT, Ctx, WithGlobalNsPrefix);
+ }
+
+ // Create a nested name specifier if needed.
+ Prefix = createNestedNameSpecifierForScopeOf(Ctx, QT.getTypePtr(),
+ true /*FullyQualified*/,
+ WithGlobalNsPrefix);
+
+ // In case of template specializations iterate over the arguments and
+ // fully qualify them as well.
+ if (isa<const TemplateSpecializationType>(QT.getTypePtr()) ||
+ isa<const RecordType>(QT.getTypePtr())) {
+ // We are asked to fully qualify and we have a Record Type (which
+ // may point to a template specialization) or Template
+ // Specialization Type. We need to fully qualify their arguments.
+
+ const Type *TypePtr = getFullyQualifiedTemplateType(
+ Ctx, QT.getTypePtr(), WithGlobalNsPrefix);
+ QT = QualType(TypePtr, 0);
+ }
+ if (Prefix || Keyword != ETK_None) {
+ QT = Ctx.getElaboratedType(Keyword, Prefix, QT);
+ }
+ QT = Ctx.getQualifiedType(QT, PrefixQualifiers);
+ return QT;
+}
+
+inline std::string getFullyQualifiedName(QualType QT,
+ const ASTContext &Ctx,
+ const PrintingPolicy &Policy,
+ bool WithGlobalNsPrefix = false) {
+ QualType FQQT = getFullyQualifiedType(QT, Ctx, WithGlobalNsPrefix);
+ return FQQT.getAsString(Policy);
+}
+
+} // end namespace TypeName
+} // end namespace clang
diff --git a/src/qdoc/qdoc/src/qdoc/clang/AST/qt_attribution.json b/src/qdoc/qdoc/src/qdoc/clang/AST/qt_attribution.json
new file mode 100644
index 000000000..13219767d
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/clang/AST/qt_attribution.json
@@ -0,0 +1,20 @@
+[
+ {
+ "Id": "llvm_clang_typename_namespace",
+ "Name": "QualTypeNames",
+ "QDocModule": "qdoc",
+ "QtUsage": "Used to have access to a version of clang::TypeName::getFullyQualifiedName that does not expand templates to an instance.",
+ "QtParts": [
+ "tools"
+ ],
+ "Files": "QualTypeNames.h",
+
+ "Description": "A part of Clang's C++ API that deals with fully qualifying types.",
+ "Homepage": "https://github.com/llvm/llvm-project",
+ "Version": "16.0",
+ "License": "Apache License 2.0",
+ "LicenseId": "Apache-2.0 WITH LLVM-exception",
+ "LicenseFile": "LLVM_LICENSE.txt",
+ "Copyright": "Copyright assigned to LLVM project contributors."
+ }
+]
diff --git a/src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp b/src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp
new file mode 100644
index 000000000..a414b55a3
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp
@@ -0,0 +1,1904 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "clangcodeparser.h"
+#include "cppcodeparser.h"
+
+#include "access.h"
+#include "classnode.h"
+#include "codechunk.h"
+#include "config.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "namespacenode.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "typedefnode.h"
+#include "variablenode.h"
+#include "utilities.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qelapsedtimer.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qscopedvaluerollback.h>
+#include <QtCore/qtemporarydir.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qvarlengtharray.h>
+
+#include <clang-c/Index.h>
+
+#include <clang/AST/Decl.h>
+#include <clang/AST/DeclFriend.h>
+#include <clang/AST/DeclTemplate.h>
+#include <clang/AST/Expr.h>
+#include <clang/AST/Type.h>
+#include <clang/AST/TypeLoc.h>
+#include <clang/Basic/SourceLocation.h>
+#include <clang/Frontend/ASTUnit.h>
+#include <clang/Lex/Lexer.h>
+#include <llvm/Support/Casting.h>
+
+#include "clang/AST/QualTypeNames.h"
+#include "template_declaration.h"
+
+#include <cstdio>
+
+QT_BEGIN_NAMESPACE
+
+struct CompilationIndex {
+ CXIndex index = nullptr;
+
+ operator CXIndex() {
+ return index;
+ }
+
+ ~CompilationIndex() {
+ clang_disposeIndex(index);
+ }
+};
+
+struct TranslationUnit {
+ CXTranslationUnit tu = nullptr;
+
+ operator CXTranslationUnit() {
+ return tu;
+ }
+
+ operator bool() {
+ return tu;
+ }
+
+ ~TranslationUnit() {
+ clang_disposeTranslationUnit(tu);
+ }
+};
+
+// We're printing diagnostics in ClangCodeParser::printDiagnostics,
+// so avoid clang itself printing them.
+static const auto kClangDontDisplayDiagnostics = 0;
+
+static CXTranslationUnit_Flags flags_ = static_cast<CXTranslationUnit_Flags>(0);
+
+constexpr const char fnDummyFileName[] = "/fn_dummyfile.cpp";
+
+#ifndef QT_NO_DEBUG_STREAM
+template<class T>
+static QDebug operator<<(QDebug debug, const std::vector<T> &v)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ const size_t size = v.size();
+ debug << "std::vector<>[" << size << "](";
+ for (size_t i = 0; i < size; ++i) {
+ if (i)
+ debug << ", ";
+ debug << v[i];
+ }
+ debug << ')';
+ return debug;
+}
+#endif // !QT_NO_DEBUG_STREAM
+
+static void printDiagnostics(const CXTranslationUnit &translationUnit)
+{
+ if (!lcQdocClang().isDebugEnabled())
+ return;
+
+ static const auto displayOptions = CXDiagnosticDisplayOptions::CXDiagnostic_DisplaySourceLocation
+ | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayColumn
+ | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayOption;
+
+ for (unsigned i = 0, numDiagnostics = clang_getNumDiagnostics(translationUnit); i < numDiagnostics; ++i) {
+ auto diagnostic = clang_getDiagnostic(translationUnit, i);
+ auto formattedDiagnostic = clang_formatDiagnostic(diagnostic, displayOptions);
+ qCDebug(lcQdocClang) << clang_getCString(formattedDiagnostic);
+ clang_disposeString(formattedDiagnostic);
+ clang_disposeDiagnostic(diagnostic);
+ }
+}
+
+/*!
+ * Returns the underlying Decl that \a cursor represents.
+ *
+ * This can be used to drop back down from a LibClang's CXCursor to
+ * the underlying C++ AST that Clang provides.
+ *
+ * It should be used when LibClang does not expose certain
+ * functionalities that are available in the C++ AST.
+ *
+ * The CXCursor should represent a declaration. Usages of this
+ * function on CXCursors that do not represent a declaration may
+ * produce undefined results.
+ */
+static const clang::Decl* get_cursor_declaration(CXCursor cursor) {
+ assert(clang_isDeclaration(clang_getCursorKind(cursor)));
+
+ return static_cast<const clang::Decl*>(cursor.data[0]);
+}
+
+
+/*!
+ * Returns a string representing the name of \a type as if it was
+ * referred to at the end of the translation unit that it was parsed
+ * from.
+ *
+ * For example, given the following code:
+ *
+ * \code
+ * namespace foo {
+ * template<typename T>
+ * struct Bar {
+ * using Baz = const T&;
+ *
+ * void bam(Baz);
+ * };
+ * }
+ * \endcode
+ *
+ * Given a parsed translation unit and an AST node, say \e {decl},
+ * representing the parameter declaration of the first argument of \c {bam},
+ * calling \c{get_fully_qualified_name(decl->getType(), * decl->getASTContext())}
+ * would result in the string \c {foo::Bar<T>::Baz}.
+ *
+ * This should generally be used every time the stringified
+ * representation of a type is acquired as part of parsing with Clang,
+ * so as to ensure a consistent behavior and output.
+ */
+static std::string get_fully_qualified_type_name(clang::QualType type, const clang::ASTContext& declaration_context) {
+ return clang::TypeName::getFullyQualifiedName(
+ type,
+ declaration_context,
+ declaration_context.getPrintingPolicy()
+ );
+}
+
+/*
+ * Retrieves expression as written in the original source code.
+ *
+ * declaration_context should be the ASTContext of the declaration
+ * from which the expression was extracted from.
+ *
+ * If the expression contains a leading equal sign it will be removed.
+ *
+ * Leading and trailing spaces will be similarly removed from the expression.
+ */
+static std::string get_expression_as_string(const clang::Expr* expression, const clang::ASTContext& declaration_context) {
+ QString default_value = QString::fromStdString(clang::Lexer::getSourceText(
+ clang::CharSourceRange::getTokenRange(expression->getSourceRange()),
+ declaration_context.getSourceManager(),
+ declaration_context.getLangOpts()
+ ).str());
+
+ if (default_value.startsWith("="))
+ default_value.remove(0, 1);
+
+ default_value = default_value.trimmed();
+
+ return default_value.toStdString();
+}
+
+/*
+ * Retrieves the default value of the passed in type template parameter as a string.
+ *
+ * The default value of a type template parameter is always a type,
+ * and its stringified representation will be return as the fully
+ * qualified version of the type.
+ *
+ * If the parameter has no default value the empty string will be returned.
+ */
+static std::string get_default_value_initializer_as_string(const clang::TemplateTypeParmDecl* parameter) {
+ return (parameter && parameter->hasDefaultArgument()) ?
+ get_fully_qualified_type_name(parameter->getDefaultArgument(), parameter->getASTContext()) :
+ "";
+
+}
+
+/*
+ * Retrieves the default value of the passed in non-type template parameter as a string.
+ *
+ * The default value of a non-type template parameter is an expression
+ * and its stringified representation will be return as it was written
+ * in the original code.
+ *
+ * If the parameter as no default value the empty string will be returned.
+ */
+static std::string get_default_value_initializer_as_string(const clang::NonTypeTemplateParmDecl* parameter) {
+ return (parameter && parameter->hasDefaultArgument()) ?
+ get_expression_as_string(parameter->getDefaultArgument(), parameter->getASTContext()) : "";
+
+}
+
+/*
+ * Retrieves the default value of the passed in template template parameter as a string.
+ *
+ * The default value of a template template parameter is a template
+ * name and its stringified representation will be returned as a fully
+ * qualified version of that name.
+ *
+ * If the parameter as no default value the empty string will be returned.
+ */
+static std::string get_default_value_initializer_as_string(const clang::TemplateTemplateParmDecl* parameter) {
+ std::string default_value{};
+
+ if (parameter && parameter->hasDefaultArgument()) {
+ const clang::TemplateName template_name = parameter->getDefaultArgument().getArgument().getAsTemplate();
+
+ llvm::raw_string_ostream ss{default_value};
+ template_name.print(ss, parameter->getASTContext().getPrintingPolicy(), clang::TemplateName::Qualified::Fully);
+ }
+
+ return default_value;
+}
+
+/*
+ * Retrieves the default value of the passed in function parameter as
+ * a string.
+ *
+ * The default value of a function parameter is an expression and its
+ * stringified representation will be returned as it was written in
+ * the original code.
+ *
+ * If the parameter as no default value or Clang was not able to yet
+ * parse it at this time the empty string will be returned.
+ */
+static std::string get_default_value_initializer_as_string(const clang::ParmVarDecl* parameter) {
+ if (!parameter || !parameter->hasDefaultArg() || parameter->hasUnparsedDefaultArg())
+ return "";
+
+ return get_expression_as_string(
+ parameter->hasUninstantiatedDefaultArg() ? parameter->getUninstantiatedDefaultArg() : parameter->getDefaultArg(),
+ parameter->getASTContext()
+ );
+}
+
+/*
+ * Retrieves the default value of the passed in declaration, based on
+ * its concrete type, as a string.
+ *
+ * If the declaration is a nullptr or the concrete type of the
+ * declaration is not a supported one, the returned string will be the
+ * empty string.
+ */
+static std::string get_default_value_initializer_as_string(const clang::NamedDecl* declaration) {
+ if (!declaration) return "";
+
+ if (auto type_template_parameter = llvm::dyn_cast<clang::TemplateTypeParmDecl>(declaration))
+ return get_default_value_initializer_as_string(type_template_parameter);
+
+ if (auto non_type_template_parameter = llvm::dyn_cast<clang::NonTypeTemplateParmDecl>(declaration))
+ return get_default_value_initializer_as_string(non_type_template_parameter);
+
+ if (auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(declaration)) {
+ return get_default_value_initializer_as_string(template_template_parameter);
+ }
+
+ if (auto function_parameter = llvm::dyn_cast<clang::ParmVarDecl>(declaration)) {
+ return get_default_value_initializer_as_string(function_parameter);
+ }
+
+ return "";
+}
+
+/*!
+ Call clang_visitChildren on the given cursor with the lambda as a callback
+ T can be any functor that is callable with a CXCursor parameter and returns a CXChildVisitResult
+ (in other word compatible with function<CXChildVisitResult(CXCursor)>
+ */
+template<typename T>
+bool visitChildrenLambda(CXCursor cursor, T &&lambda)
+{
+ CXCursorVisitor visitor = [](CXCursor c, CXCursor,
+ CXClientData client_data) -> CXChildVisitResult {
+ return (*static_cast<T *>(client_data))(c);
+ };
+ return clang_visitChildren(cursor, visitor, &lambda);
+}
+
+/*!
+ convert a CXString to a QString, and dispose the CXString
+ */
+static QString fromCXString(CXString &&string)
+{
+ QString ret = QString::fromUtf8(clang_getCString(string));
+ clang_disposeString(string);
+ return ret;
+}
+
+/*
+ * Returns an intermediate representation that models the the given
+ * template declaration.
+ */
+static RelaxedTemplateDeclaration get_template_declaration(const clang::TemplateDecl* template_declaration) {
+ assert(template_declaration);
+
+ RelaxedTemplateDeclaration template_declaration_ir{};
+
+ auto template_parameters = template_declaration->getTemplateParameters();
+ for (auto template_parameter : template_parameters->asArray()) {
+ auto kind{RelaxedTemplateParameter::Kind::TypeTemplateParameter};
+ std::string type{};
+
+ if (auto non_type_template_parameter = llvm::dyn_cast<clang::NonTypeTemplateParmDecl>(template_parameter)) {
+ kind = RelaxedTemplateParameter::Kind::NonTypeTemplateParameter;
+ type = get_fully_qualified_type_name(non_type_template_parameter->getType(), non_type_template_parameter->getASTContext());
+
+ // REMARK: QDoc uses this information to match a user
+ // provided documentation (for example from an "\fn"
+ // command) with a `Node` that was extracted from the
+ // code-base.
+ //
+ // Due to how QDoc obtains an AST for documentation that
+ // is provided by the user, there might be a mismatch in
+ // the type of certain non type template parameters.
+ //
+ // QDoc generally builds a fake out-of-line definition for
+ // a callable provided through an "\fn" command, when it
+ // needs to match it.
+ // In that context, certain type names may be dependent
+ // names, while they may not be when the element they
+ // represent is extracted from the code-base.
+ //
+ // This in turn makes their stringified representation
+ // different in the two contextes, as a dependent name may
+ // require the "typename" keyword to precede it.
+ //
+ // Since QDoc uses a very simplified model, and it
+ // generally doesn't need care about the exact name
+ // resolution rules for C++, since it passes by
+ // Clang-validated data, we remove the "typename" keyword
+ // if it prefixes the type representation, so that it
+ // doesn't impact the matching procedure..
+
+ // KLUDGE: Waiting for C++20 to avoid the conversion.
+ // Doesn't really impact performance in a
+ // meaningful way so it can be kept while waiting.
+ if (QString::fromStdString(type).startsWith("typename ")) type.erase(0, std::string("typename ").size());
+ }
+
+ auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(template_parameter);
+ if (template_template_parameter) kind = RelaxedTemplateParameter::Kind::TemplateTemplateParameter;
+
+ template_declaration_ir.parameters.push_back({
+ kind,
+ template_parameter->isTemplateParameterPack(),
+ {
+ type,
+ template_parameter->getNameAsString(),
+ get_default_value_initializer_as_string(template_parameter)
+ },
+ (template_template_parameter ?
+ std::optional<TemplateDeclarationStorage>(TemplateDeclarationStorage{
+ get_template_declaration(template_template_parameter).parameters
+ }) : std::nullopt)
+ });
+ }
+
+ return template_declaration_ir;
+}
+
+/*!
+ convert a CXSourceLocation to a qdoc Location
+ */
+static Location fromCXSourceLocation(CXSourceLocation location)
+{
+ unsigned int line, column;
+ CXString file;
+ clang_getPresumedLocation(location, &file, &line, &column);
+ Location l(fromCXString(std::move(file)));
+ l.setColumnNo(column);
+ l.setLineNo(line);
+ return l;
+}
+
+/*!
+ convert a CX_CXXAccessSpecifier to Node::Access
+ */
+static Access fromCX_CXXAccessSpecifier(CX_CXXAccessSpecifier spec)
+{
+ switch (spec) {
+ case CX_CXXPrivate:
+ return Access::Private;
+ case CX_CXXProtected:
+ return Access::Protected;
+ case CX_CXXPublic:
+ return Access::Public;
+ default:
+ return Access::Public;
+ }
+}
+
+/*!
+ Returns the spelling in the file for a source range
+ */
+
+struct FileCacheEntry
+{
+ QByteArray fileName;
+ QByteArray content;
+};
+
+static inline QString fromCache(const QByteArray &cache,
+ unsigned int offset1, unsigned int offset2)
+{
+ return QString::fromUtf8(cache.mid(offset1, offset2 - offset1));
+}
+
+static QString readFile(CXFile cxFile, unsigned int offset1, unsigned int offset2)
+{
+ using FileCache = QList<FileCacheEntry>;
+ static FileCache cache;
+
+ CXString cxFileName = clang_getFileName(cxFile);
+ const QByteArray fileName = clang_getCString(cxFileName);
+ clang_disposeString(cxFileName);
+
+ for (const auto &entry : std::as_const(cache)) {
+ if (fileName == entry.fileName)
+ return fromCache(entry.content, offset1, offset2);
+ }
+
+ QFile file(QString::fromUtf8(fileName));
+ if (file.open(QIODeviceBase::ReadOnly)) { // binary to match clang offsets
+ FileCacheEntry entry{fileName, file.readAll()};
+ cache.prepend(entry);
+ while (cache.size() > 5)
+ cache.removeLast();
+ return fromCache(entry.content, offset1, offset2);
+ }
+ return {};
+}
+
+static QString getSpelling(CXSourceRange range)
+{
+ auto start = clang_getRangeStart(range);
+ auto end = clang_getRangeEnd(range);
+ CXFile file1, file2;
+ unsigned int offset1, offset2;
+ clang_getFileLocation(start, &file1, nullptr, nullptr, &offset1);
+ clang_getFileLocation(end, &file2, nullptr, nullptr, &offset2);
+
+ if (file1 != file2 || offset2 <= offset1)
+ return QString();
+
+ return readFile(file1, offset1, offset2);
+}
+
+/*!
+ Returns the function name from a given cursor representing a
+ function declaration. This is usually clang_getCursorSpelling, but
+ not for the conversion function in which case it is a bit more complicated
+ */
+QString functionName(CXCursor cursor)
+{
+ if (clang_getCursorKind(cursor) == CXCursor_ConversionFunction) {
+ // For a CXCursor_ConversionFunction we don't want the spelling which would be something
+ // like "operator type-parameter-0-0" or "operator unsigned int". we want the actual name as
+ // spelled;
+ auto conversion_declaration =
+ static_cast<const clang::CXXConversionDecl*>(get_cursor_declaration(cursor));
+
+ return QLatin1String("operator ") + QString::fromStdString(get_fully_qualified_type_name(
+ conversion_declaration->getConversionType(),
+ conversion_declaration->getASTContext()
+ ));
+ }
+
+ QString name = fromCXString(clang_getCursorSpelling(cursor));
+
+ // Remove template stuff from constructor and destructor but not from operator<
+ auto ltLoc = name.indexOf('<');
+ if (ltLoc > 0 && !name.startsWith("operator<"))
+ name = name.left(ltLoc);
+ return name;
+}
+
+/*!
+ Reconstruct the qualified path name of a function that is
+ being overridden.
+ */
+static QString reconstructQualifiedPathForCursor(CXCursor cur)
+{
+ QString path;
+ auto kind = clang_getCursorKind(cur);
+ while (!clang_isInvalid(kind) && kind != CXCursor_TranslationUnit) {
+ switch (kind) {
+ case CXCursor_Namespace:
+ case CXCursor_StructDecl:
+ case CXCursor_ClassDecl:
+ case CXCursor_UnionDecl:
+ case CXCursor_ClassTemplate:
+ path.prepend("::");
+ path.prepend(fromCXString(clang_getCursorSpelling(cur)));
+ break;
+ case CXCursor_FunctionDecl:
+ case CXCursor_FunctionTemplate:
+ case CXCursor_CXXMethod:
+ case CXCursor_Constructor:
+ case CXCursor_Destructor:
+ case CXCursor_ConversionFunction:
+ path = functionName(cur);
+ break;
+ default:
+ break;
+ }
+ cur = clang_getCursorSemanticParent(cur);
+ kind = clang_getCursorKind(cur);
+ }
+ return path;
+}
+
+/*!
+ Find the node from the QDocDatabase \a qdb that corrseponds to the declaration
+ represented by the cursor \a cur, if it exists.
+ */
+static Node *findNodeForCursor(QDocDatabase *qdb, CXCursor cur)
+{
+ auto kind = clang_getCursorKind(cur);
+ if (clang_isInvalid(kind))
+ return nullptr;
+ if (kind == CXCursor_TranslationUnit)
+ return qdb->primaryTreeRoot();
+
+ Node *p = findNodeForCursor(qdb, clang_getCursorSemanticParent(cur));
+ if (p == nullptr)
+ return nullptr;
+ if (!p->isAggregate())
+ return nullptr;
+ auto parent = static_cast<Aggregate *>(p);
+
+ QString name = fromCXString(clang_getCursorSpelling(cur));
+ switch (kind) {
+ case CXCursor_Namespace:
+ return parent->findNonfunctionChild(name, &Node::isNamespace);
+ case CXCursor_StructDecl:
+ case CXCursor_ClassDecl:
+ case CXCursor_UnionDecl:
+ case CXCursor_ClassTemplate:
+ return parent->findNonfunctionChild(name, &Node::isClassNode);
+ case CXCursor_FunctionDecl:
+ case CXCursor_FunctionTemplate:
+ case CXCursor_CXXMethod:
+ case CXCursor_Constructor:
+ case CXCursor_Destructor:
+ case CXCursor_ConversionFunction: {
+ NodeVector candidates;
+ parent->findChildren(functionName(cur), candidates);
+ if (candidates.isEmpty())
+ return nullptr;
+
+ CXType funcType = clang_getCursorType(cur);
+ auto numArg = clang_getNumArgTypes(funcType);
+ bool isVariadic = clang_isFunctionTypeVariadic(funcType);
+ QVarLengthArray<QString, 20> args;
+
+ std::optional<RelaxedTemplateDeclaration> relaxed_template_declaration{std::nullopt};
+ if (kind == CXCursor_FunctionTemplate)
+ relaxed_template_declaration = get_template_declaration(
+ get_cursor_declaration(cur)->getAsFunction()->getDescribedFunctionTemplate()
+ );
+
+ for (Node *candidate : std::as_const(candidates)) {
+ if (!candidate->isFunction(Node::CPP))
+ continue;
+
+ auto fn = static_cast<FunctionNode *>(candidate);
+
+ if (!fn->templateDecl() && relaxed_template_declaration)
+ continue;
+
+ if (fn->templateDecl() && !relaxed_template_declaration)
+ continue;
+
+ if (fn->templateDecl() && relaxed_template_declaration &&
+ !are_template_declarations_substitutable(*fn->templateDecl(), *relaxed_template_declaration))
+ continue;
+
+ const Parameters &parameters = fn->parameters();
+
+ if (parameters.count() != numArg + isVariadic)
+ continue;
+
+ if (fn->isConst() != bool(clang_CXXMethod_isConst(cur)))
+ continue;
+
+ if (isVariadic && parameters.last().type() != QLatin1String("..."))
+ continue;
+
+ if (fn->isRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_LValue))
+ continue;
+
+ if (fn->isRefRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_RValue))
+ continue;
+
+ auto function_declaration = get_cursor_declaration(cur)->getAsFunction();
+
+ bool different = false;
+ for (int i = 0; i < numArg; ++i) {
+ CXType argType = clang_getArgType(funcType, i);
+
+ if (args.size() <= i)
+ args.append(QString::fromStdString(get_fully_qualified_type_name(
+ function_declaration->getParamDecl(i)->getOriginalType(),
+ function_declaration->getASTContext()
+ )));
+
+ QString recordedType = parameters.at(i).type();
+ QString typeSpelling = args.at(i);
+
+ different = recordedType != typeSpelling;
+
+ // Retry with a canonical type spelling
+ if (different && (argType.kind == CXType_Typedef || argType.kind == CXType_Elaborated)) {
+ QStringView canonicalType = parameters.at(i).canonicalType();
+ if (!canonicalType.isEmpty()) {
+ different = canonicalType !=
+ QString::fromStdString(get_fully_qualified_type_name(
+ function_declaration->getParamDecl(i)->getOriginalType().getCanonicalType(),
+ function_declaration->getASTContext()
+ ));
+ }
+ }
+
+ if (different) {
+ break;
+ }
+ }
+
+ if (!different)
+ return fn;
+ }
+ return nullptr;
+ }
+ case CXCursor_EnumDecl:
+ return parent->findNonfunctionChild(name, &Node::isEnumType);
+ case CXCursor_FieldDecl:
+ case CXCursor_VarDecl:
+ return parent->findNonfunctionChild(name, &Node::isVariable);
+ case CXCursor_TypedefDecl:
+ return parent->findNonfunctionChild(name, &Node::isTypedef);
+ default:
+ return nullptr;
+ }
+}
+
+static void setOverridesForFunction(FunctionNode *fn, CXCursor cursor)
+{
+ CXCursor *overridden;
+ unsigned int numOverridden = 0;
+ clang_getOverriddenCursors(cursor, &overridden, &numOverridden);
+ for (uint i = 0; i < numOverridden; ++i) {
+ QString path = reconstructQualifiedPathForCursor(overridden[i]);
+ if (!path.isEmpty()) {
+ fn->setOverride(true);
+ fn->setOverridesThis(path);
+ break;
+ }
+ }
+ clang_disposeOverriddenCursors(overridden);
+}
+
+class ClangVisitor
+{
+public:
+ ClangVisitor(QDocDatabase *qdb, const std::set<Config::HeaderFilePath> &allHeaders)
+ : qdb_(qdb), parent_(qdb->primaryTreeRoot())
+ {
+ std::transform(allHeaders.cbegin(), allHeaders.cend(), std::inserter(allHeaders_, allHeaders_.begin()),
+ [](const auto& header_file_path) { return header_file_path.filename; });
+ }
+
+ QDocDatabase *qdocDB() { return qdb_; }
+
+ CXChildVisitResult visitChildren(CXCursor cursor)
+ {
+ auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
+ auto loc = clang_getCursorLocation(cur);
+ if (clang_Location_isFromMainFile(loc))
+ return visitSource(cur, loc);
+ CXFile file;
+ clang_getFileLocation(loc, &file, nullptr, nullptr, nullptr);
+ bool isInteresting = false;
+ auto it = isInterestingCache_.find(file);
+ if (it != isInterestingCache_.end()) {
+ isInteresting = *it;
+ } else {
+ QFileInfo fi(fromCXString(clang_getFileName(file)));
+ // Match by file name in case of PCH/installed headers
+ isInteresting = allHeaders_.find(fi.fileName()) != allHeaders_.end();
+ isInterestingCache_[file] = isInteresting;
+ }
+ if (isInteresting) {
+ return visitHeader(cur, loc);
+ }
+
+ return CXChildVisit_Continue;
+ });
+ return ret ? CXChildVisit_Break : CXChildVisit_Continue;
+ }
+
+ /*
+ Not sure about all the possibilities, when the cursor
+ location is not in the main file.
+ */
+ CXChildVisitResult visitFnArg(CXCursor cursor, Node **fnNode, bool &ignoreSignature)
+ {
+ auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
+ auto loc = clang_getCursorLocation(cur);
+ if (clang_Location_isFromMainFile(loc))
+ return visitFnSignature(cur, loc, fnNode, ignoreSignature);
+ return CXChildVisit_Continue;
+ });
+ return ret ? CXChildVisit_Break : CXChildVisit_Continue;
+ }
+
+ Node *nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc);
+
+private:
+ /*!
+ SimpleLoc represents a simple location in the main source file,
+ which can be used as a key in a QMap.
+ */
+ struct SimpleLoc
+ {
+ unsigned int line {}, column {};
+ friend bool operator<(const SimpleLoc &a, const SimpleLoc &b)
+ {
+ return a.line != b.line ? a.line < b.line : a.column < b.column;
+ }
+ };
+ /*!
+ \variable ClangVisitor::declMap_
+ Map of all the declarations in the source file so we can match them
+ with a documentation comment.
+ */
+ QMap<SimpleLoc, CXCursor> declMap_;
+
+ QDocDatabase *qdb_;
+ Aggregate *parent_;
+ std::set<QString> allHeaders_;
+ QHash<CXFile, bool> isInterestingCache_; // doing a canonicalFilePath is slow, so keep a cache.
+
+ /*!
+ Returns true if the symbol should be ignored for the documentation.
+ */
+ bool ignoredSymbol(const QString &symbolName)
+ {
+ if (symbolName == QLatin1String("QPrivateSignal"))
+ return true;
+ // Ignore functions generated by property macros
+ if (symbolName.startsWith("_qt_property_"))
+ return true;
+ // Ignore template argument deduction guides
+ if (symbolName.startsWith("<deduction guide"))
+ return true;
+ return false;
+ }
+
+ CXChildVisitResult visitSource(CXCursor cursor, CXSourceLocation loc);
+ CXChildVisitResult visitHeader(CXCursor cursor, CXSourceLocation loc);
+ CXChildVisitResult visitFnSignature(CXCursor cursor, CXSourceLocation loc, Node **fnNode,
+ bool &ignoreSignature);
+ void processFunction(FunctionNode *fn, CXCursor cursor);
+ bool parseProperty(const QString &spelling, const Location &loc);
+ void readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor);
+ Aggregate *getSemanticParent(CXCursor cursor);
+};
+
+/*!
+ Visits a cursor in the .cpp file.
+ This fills the declMap_
+ */
+CXChildVisitResult ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc)
+{
+ auto kind = clang_getCursorKind(cursor);
+ if (clang_isDeclaration(kind)) {
+ SimpleLoc l;
+ clang_getPresumedLocation(loc, nullptr, &l.line, &l.column);
+ declMap_.insert(l, cursor);
+ return CXChildVisit_Recurse;
+ }
+ return CXChildVisit_Continue;
+}
+
+/*!
+ If the semantic and lexical parent cursors of \a cursor are
+ not the same, find the Aggregate node for the semantic parent
+ cursor and return it. Otherwise return the current parent.
+ */
+Aggregate *ClangVisitor::getSemanticParent(CXCursor cursor)
+{
+ CXCursor sp = clang_getCursorSemanticParent(cursor);
+ CXCursor lp = clang_getCursorLexicalParent(cursor);
+ if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) {
+ Node *spn = findNodeForCursor(qdb_, sp);
+ if (spn && spn->isAggregate()) {
+ return static_cast<Aggregate *>(spn);
+ }
+ }
+ return parent_;
+}
+
+CXChildVisitResult ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation, Node **fnNode,
+ bool &ignoreSignature)
+{
+ switch (clang_getCursorKind(cursor)) {
+ case CXCursor_Namespace:
+ return CXChildVisit_Recurse;
+ case CXCursor_FunctionDecl:
+ case CXCursor_FunctionTemplate:
+ case CXCursor_CXXMethod:
+ case CXCursor_Constructor:
+ case CXCursor_Destructor:
+ case CXCursor_ConversionFunction: {
+ ignoreSignature = false;
+ if (ignoredSymbol(functionName(cursor))) {
+ *fnNode = nullptr;
+ ignoreSignature = true;
+ } else {
+ *fnNode = findNodeForCursor(qdb_, cursor);
+ if (*fnNode) {
+ if ((*fnNode)->isFunction(Node::CPP)) {
+ auto *fn = static_cast<FunctionNode *>(*fnNode);
+ readParameterNamesAndAttributes(fn, cursor);
+ }
+ } else { // Possibly an implicitly generated special member
+ QString name = functionName(cursor);
+ if (ignoredSymbol(name))
+ return CXChildVisit_Continue;
+ Aggregate *semanticParent = getSemanticParent(cursor);
+ if (semanticParent && semanticParent->isClass()) {
+ auto *candidate = new FunctionNode(nullptr, name);
+ processFunction(candidate, cursor);
+ if (!candidate->isSpecialMemberFunction()) {
+ delete candidate;
+ return CXChildVisit_Continue;
+ }
+ candidate->setDefault(true);
+ semanticParent->addChild(*fnNode = candidate);
+ }
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return CXChildVisit_Continue;
+}
+
+CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
+{
+ auto kind = clang_getCursorKind(cursor);
+
+ switch (kind) {
+ case CXCursor_TypeAliasTemplateDecl:
+ case CXCursor_TypeAliasDecl: {
+ QString aliasDecl = getSpelling(clang_getCursorExtent(cursor)).simplified();
+ QStringList typeAlias = aliasDecl.split(QLatin1Char('='));
+ if (typeAlias.size() == 2) {
+ typeAlias[0] = typeAlias[0].trimmed();
+ const QLatin1String usingString("using ");
+ qsizetype usingPos = typeAlias[0].indexOf(usingString);
+ if (usingPos != -1) {
+ typeAlias[0].remove(0, usingPos + usingString.size());
+ typeAlias[0] = typeAlias[0].split(QLatin1Char(' ')).first();
+ typeAlias[1] = typeAlias[1].trimmed();
+ auto *ta = new TypeAliasNode(parent_, typeAlias[0], typeAlias[1]);
+ ta->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
+ ta->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+
+ if (kind == CXCursor_TypeAliasTemplateDecl) {
+ auto template_decl = llvm::dyn_cast<clang::TemplateDecl>(get_cursor_declaration(cursor));
+ ta->setTemplateDecl(get_template_declaration(template_decl));
+ }
+ }
+ }
+ return CXChildVisit_Continue;
+ }
+ case CXCursor_StructDecl:
+ case CXCursor_UnionDecl:
+ if (fromCXString(clang_getCursorSpelling(cursor)).isEmpty()) // anonymous struct or union
+ return CXChildVisit_Continue;
+ Q_FALLTHROUGH();
+ case CXCursor_ClassTemplate:
+ Q_FALLTHROUGH();
+ case CXCursor_ClassDecl: {
+ if (!clang_isCursorDefinition(cursor))
+ return CXChildVisit_Continue;
+
+ if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
+ return CXChildVisit_Continue;
+
+ QString className = fromCXString(clang_getCursorSpelling(cursor));
+
+ Aggregate *semanticParent = getSemanticParent(cursor);
+ if (semanticParent && semanticParent->findNonfunctionChild(className, &Node::isClassNode)) {
+ return CXChildVisit_Continue;
+ }
+
+ CXCursorKind actualKind = (kind == CXCursor_ClassTemplate) ?
+ clang_getTemplateCursorKind(cursor) : kind;
+
+ Node::NodeType type = Node::Class;
+ if (actualKind == CXCursor_StructDecl)
+ type = Node::Struct;
+ else if (actualKind == CXCursor_UnionDecl)
+ type = Node::Union;
+
+ auto *classe = new ClassNode(type, semanticParent, className);
+ classe->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
+ classe->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+
+ if (kind == CXCursor_ClassTemplate) {
+ auto template_declaration = llvm::dyn_cast<clang::TemplateDecl>(get_cursor_declaration(cursor));
+ classe->setTemplateDecl(get_template_declaration(template_declaration));
+ }
+
+ QScopedValueRollback<Aggregate *> setParent(parent_, classe);
+ return visitChildren(cursor);
+ }
+ case CXCursor_CXXBaseSpecifier: {
+ if (!parent_->isClassNode())
+ return CXChildVisit_Continue;
+ auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor));
+ auto type = clang_getCursorType(cursor);
+ auto baseCursor = clang_getTypeDeclaration(type);
+ auto baseNode = findNodeForCursor(qdb_, baseCursor);
+ auto classe = static_cast<ClassNode *>(parent_);
+ if (baseNode == nullptr || !baseNode->isClassNode()) {
+ QString bcName = reconstructQualifiedPathForCursor(baseCursor);
+ classe->addUnresolvedBaseClass(access,
+ bcName.split(QLatin1String("::"), Qt::SkipEmptyParts));
+ return CXChildVisit_Continue;
+ }
+ auto baseClasse = static_cast<ClassNode *>(baseNode);
+ classe->addResolvedBaseClass(access, baseClasse);
+ return CXChildVisit_Continue;
+ }
+ case CXCursor_Namespace: {
+ QString namespaceName = fromCXString(clang_getCursorDisplayName(cursor));
+ NamespaceNode *ns = nullptr;
+ if (parent_)
+ ns = static_cast<NamespaceNode *>(
+ parent_->findNonfunctionChild(namespaceName, &Node::isNamespace));
+ if (!ns) {
+ ns = new NamespaceNode(parent_, namespaceName);
+ ns->setAccess(Access::Public);
+ ns->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+ }
+ QScopedValueRollback<Aggregate *> setParent(parent_, ns);
+ return visitChildren(cursor);
+ }
+ case CXCursor_FunctionTemplate:
+ Q_FALLTHROUGH();
+ case CXCursor_FunctionDecl:
+ case CXCursor_CXXMethod:
+ case CXCursor_Constructor:
+ case CXCursor_Destructor:
+ case CXCursor_ConversionFunction: {
+ if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
+ return CXChildVisit_Continue;
+ QString name = functionName(cursor);
+ if (ignoredSymbol(name))
+ return CXChildVisit_Continue;
+ // constexpr constructors generate also a global instance; ignore
+ if (kind == CXCursor_Constructor && parent_ == qdb_->primaryTreeRoot())
+ return CXChildVisit_Continue;
+
+ auto *fn = new FunctionNode(parent_, name);
+ CXSourceRange range = clang_Cursor_getCommentRange(cursor);
+ if (!clang_Range_isNull(range)) {
+ QString comment = getSpelling(range);
+ if (comment.startsWith("//!")) {
+ qsizetype tag = comment.indexOf(QChar('['));
+ if (tag > 0) {
+ qsizetype end = comment.indexOf(QChar(']'), ++tag);
+ if (end > 0)
+ fn->setTag(comment.mid(tag, end - tag));
+ }
+ }
+ }
+
+ processFunction(fn, cursor);
+
+ if (kind == CXCursor_FunctionTemplate) {
+ auto template_declaration = get_cursor_declaration(cursor)->getAsFunction()->getDescribedFunctionTemplate();
+ fn->setTemplateDecl(get_template_declaration(template_declaration));
+ }
+
+ return CXChildVisit_Continue;
+ }
+#if CINDEX_VERSION >= 36
+ case CXCursor_FriendDecl: {
+ return visitChildren(cursor);
+ }
+#endif
+ case CXCursor_EnumDecl: {
+ auto *en = static_cast<EnumNode *>(findNodeForCursor(qdb_, cursor));
+ if (en && en->items().size())
+ return CXChildVisit_Continue; // Was already parsed, probably in another TU
+
+ QString enumTypeName = fromCXString(clang_getCursorSpelling(cursor));
+
+ if (clang_Cursor_isAnonymous(cursor)) {
+ enumTypeName = "anonymous";
+ if (parent_ && (parent_->isClassNode() || parent_->isNamespace())) {
+ Node *n = parent_->findNonfunctionChild(enumTypeName, &Node::isEnumType);
+ if (n)
+ en = static_cast<EnumNode *>(n);
+ }
+ }
+ if (!en) {
+ en = new EnumNode(parent_, enumTypeName, clang_EnumDecl_isScoped(cursor));
+ en->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
+ en->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+ }
+
+ // Enum values
+ visitChildrenLambda(cursor, [&](CXCursor cur) {
+ if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl)
+ return CXChildVisit_Continue;
+
+ QString value;
+ visitChildrenLambda(cur, [&](CXCursor cur) {
+ if (clang_isExpression(clang_getCursorKind(cur))) {
+ value = getSpelling(clang_getCursorExtent(cur));
+ return CXChildVisit_Break;
+ }
+ return CXChildVisit_Continue;
+ });
+ if (value.isEmpty()) {
+ QLatin1String hex("0x");
+ if (!en->items().isEmpty() && en->items().last().value().startsWith(hex)) {
+ value = hex + QString::number(clang_getEnumConstantDeclValue(cur), 16);
+ } else {
+ value = QString::number(clang_getEnumConstantDeclValue(cur));
+ }
+ }
+
+ en->addItem(EnumItem(fromCXString(clang_getCursorSpelling(cur)), value));
+ return CXChildVisit_Continue;
+ });
+ return CXChildVisit_Continue;
+ }
+ case CXCursor_FieldDecl:
+ case CXCursor_VarDecl: {
+ if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
+ return CXChildVisit_Continue;
+
+ auto value_declaration =
+ llvm::dyn_cast<clang::ValueDecl>(get_cursor_declaration(cursor));
+ assert(value_declaration);
+
+ auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor));
+ auto var = new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
+
+ var->setAccess(access);
+ var->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+ var->setLeftType(QString::fromStdString(get_fully_qualified_type_name(
+ value_declaration->getType(),
+ value_declaration->getASTContext()
+ )));
+ var->setStatic(kind == CXCursor_VarDecl && parent_->isClassNode());
+
+ return CXChildVisit_Continue;
+ }
+ case CXCursor_TypedefDecl: {
+ if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
+ return CXChildVisit_Continue;
+ auto *td = new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
+ td->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
+ td->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+ // Search to see if this is a Q_DECLARE_FLAGS (if the type is QFlags<ENUM>)
+ visitChildrenLambda(cursor, [&](CXCursor cur) {
+ if (clang_getCursorKind(cur) != CXCursor_TemplateRef
+ || fromCXString(clang_getCursorSpelling(cur)) != QLatin1String("QFlags"))
+ return CXChildVisit_Continue;
+ // Found QFlags<XXX>
+ visitChildrenLambda(cursor, [&](CXCursor cur) {
+ if (clang_getCursorKind(cur) != CXCursor_TypeRef)
+ return CXChildVisit_Continue;
+ auto *en =
+ findNodeForCursor(qdb_, clang_getTypeDeclaration(clang_getCursorType(cur)));
+ if (en && en->isEnumType())
+ static_cast<EnumNode *>(en)->setFlagsType(td);
+ return CXChildVisit_Break;
+ });
+ return CXChildVisit_Break;
+ });
+ return CXChildVisit_Continue;
+ }
+ default:
+ if (clang_isDeclaration(kind) && parent_->isClassNode()) {
+ // may be a property macro or a static_assert
+ // which is not exposed from the clang API
+ parseProperty(getSpelling(clang_getCursorExtent(cursor)),
+ fromCXSourceLocation(loc));
+ }
+ return CXChildVisit_Continue;
+ }
+}
+
+void ClangVisitor::readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor)
+{
+ Parameters &parameters = fn->parameters();
+ // Visit the parameters and attributes
+ int i = 0;
+ visitChildrenLambda(cursor, [&](CXCursor cur) {
+ auto kind = clang_getCursorKind(cur);
+ if (kind == CXCursor_AnnotateAttr) {
+ QString annotation = fromCXString(clang_getCursorDisplayName(cur));
+ if (annotation == QLatin1String("qt_slot")) {
+ fn->setMetaness(FunctionNode::Slot);
+ } else if (annotation == QLatin1String("qt_signal")) {
+ fn->setMetaness(FunctionNode::Signal);
+ }
+ if (annotation == QLatin1String("qt_invokable"))
+ fn->setInvokable(true);
+ } else if (kind == CXCursor_CXXOverrideAttr) {
+ fn->setOverride(true);
+ } else if (kind == CXCursor_ParmDecl) {
+ if (i >= parameters.count())
+ return CXChildVisit_Break; // Attributes comes before parameters so we can break.
+
+ if (QString name = fromCXString(clang_getCursorSpelling(cur)); !name.isEmpty())
+ parameters[i].setName(name);
+
+ const clang::ParmVarDecl* parameter_declaration = llvm::dyn_cast<const clang::ParmVarDecl>(get_cursor_declaration(cur));
+ Q_ASSERT(parameter_declaration);
+
+ std::string default_value = get_default_value_initializer_as_string(parameter_declaration);
+
+ if (!default_value.empty())
+ parameters[i].setDefaultValue(QString::fromStdString(default_value));
+
+ ++i;
+ }
+ return CXChildVisit_Continue;
+ });
+}
+
+void ClangVisitor::processFunction(FunctionNode *fn, CXCursor cursor)
+{
+ CXCursorKind kind = clang_getCursorKind(cursor);
+ CXType funcType = clang_getCursorType(cursor);
+ fn->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
+ fn->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+ fn->setStatic(clang_CXXMethod_isStatic(cursor));
+ fn->setConst(clang_CXXMethod_isConst(cursor));
+ fn->setVirtualness(!clang_CXXMethod_isVirtual(cursor)
+ ? FunctionNode::NonVirtual
+ : clang_CXXMethod_isPureVirtual(cursor)
+ ? FunctionNode::PureVirtual
+ : FunctionNode::NormalVirtual);
+
+ // REMARK: We assume that the following operations and casts are
+ // generally safe.
+ // Callers of those methods will generally check at the LibClang
+ // level the kind of cursor we are dealing with and will pass on
+ // only valid cursors that are of a function kind and that are at
+ // least a declaration.
+ //
+ // Failure to do so implies a bug in the call chain and should be
+ // dealt with as such.
+ const clang::Decl* declaration = get_cursor_declaration(cursor);
+
+ assert(declaration);
+
+ const clang::FunctionDecl* function_declaration = declaration->getAsFunction();
+
+ if (kind == CXCursor_Constructor
+ // a constructor template is classified as CXCursor_FunctionTemplate
+ || (kind == CXCursor_FunctionTemplate && fn->name() == parent_->name()))
+ fn->setMetaness(FunctionNode::Ctor);
+ else if (kind == CXCursor_Destructor)
+ fn->setMetaness(FunctionNode::Dtor);
+ else
+ fn->setReturnType(QString::fromStdString(get_fully_qualified_type_name(
+ function_declaration->getReturnType(),
+ function_declaration->getASTContext()
+ )));
+
+ const clang::CXXConstructorDecl* constructor_declaration = llvm::dyn_cast<const clang::CXXConstructorDecl>(function_declaration);
+
+ if (constructor_declaration && constructor_declaration->isCopyConstructor()) fn->setMetaness(FunctionNode::CCtor);
+ else if (constructor_declaration && constructor_declaration->isMoveConstructor()) fn->setMetaness(FunctionNode::MCtor);
+
+ const clang::CXXConversionDecl* conversion_declaration = llvm::dyn_cast<const clang::CXXConversionDecl>(function_declaration);
+
+ if (function_declaration->isConstexpr()) fn->markConstexpr();
+ if (
+ (constructor_declaration && constructor_declaration->isExplicit()) ||
+ (conversion_declaration && conversion_declaration->isExplicit())
+ ) fn->markExplicit();
+
+ const clang::CXXMethodDecl* method_declaration = llvm::dyn_cast<const clang::CXXMethodDecl>(function_declaration);
+
+ if (method_declaration && method_declaration->isCopyAssignmentOperator()) fn->setMetaness(FunctionNode::CAssign);
+ else if (method_declaration && method_declaration->isMoveAssignmentOperator()) fn->setMetaness(FunctionNode::MAssign);
+
+ const clang::FunctionType* function_type = function_declaration->getFunctionType();
+ const clang::FunctionProtoType* function_prototype = static_cast<const clang::FunctionProtoType*>(function_type);
+
+ if (function_prototype) {
+ clang::FunctionProtoType::ExceptionSpecInfo exception_specification = function_prototype->getExceptionSpecInfo();
+
+ if (exception_specification.Type != clang::ExceptionSpecificationType::EST_None) {
+ const std::string exception_specification_spelling =
+ exception_specification.NoexceptExpr ? get_expression_as_string(
+ exception_specification.NoexceptExpr,
+ function_declaration->getASTContext()
+ ) : "";
+
+ if (exception_specification_spelling != "false")
+ fn->markNoexcept(QString::fromStdString(exception_specification_spelling));
+ }
+ }
+
+ CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(funcType);
+ if (refQualKind == CXRefQualifier_LValue)
+ fn->setRef(true);
+ else if (refQualKind == CXRefQualifier_RValue)
+ fn->setRefRef(true);
+ // For virtual functions, determine what it overrides
+ // (except for destructor for which we do not want to classify as overridden)
+ if (!fn->isNonvirtual() && kind != CXCursor_Destructor)
+ setOverridesForFunction(fn, cursor);
+
+ Parameters &parameters = fn->parameters();
+ parameters.clear();
+ parameters.reserve(function_declaration->getNumParams());
+
+ for (clang::ParmVarDecl* const parameter_declaration : function_declaration->parameters()) {
+ clang::QualType parameter_type = parameter_declaration->getOriginalType();
+
+ parameters.append(QString::fromStdString(get_fully_qualified_type_name(
+ parameter_type,
+ parameter_declaration->getASTContext()
+ )));
+
+ if (!parameter_type.isCanonical())
+ parameters.last().setCanonicalType(QString::fromStdString(get_fully_qualified_type_name(
+ parameter_type.getCanonicalType(),
+ parameter_declaration->getASTContext()
+ )));
+ }
+
+ if (parameters.count() > 0) {
+ if (parameters.last().type().endsWith(QLatin1String("QPrivateSignal"))) {
+ parameters.pop_back(); // remove the QPrivateSignal argument
+ parameters.setPrivateSignal();
+ }
+ }
+
+ if (clang_isFunctionTypeVariadic(funcType))
+ parameters.append(QStringLiteral("..."));
+ readParameterNamesAndAttributes(fn, cursor);
+
+ if (declaration->getFriendObjectKind() != clang::Decl::FOK_None)
+ fn->setRelatedNonmember(true);
+}
+
+bool ClangVisitor::parseProperty(const QString &spelling, const Location &loc)
+{
+ if (!spelling.startsWith(QLatin1String("Q_PROPERTY"))
+ && !spelling.startsWith(QLatin1String("QDOC_PROPERTY"))
+ && !spelling.startsWith(QLatin1String("Q_OVERRIDE")))
+ return false;
+
+ qsizetype lpIdx = spelling.indexOf(QChar('('));
+ qsizetype rpIdx = spelling.lastIndexOf(QChar(')'));
+ if (lpIdx <= 0 || rpIdx <= lpIdx)
+ return false;
+
+ QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1);
+ signature = signature.simplified();
+ QStringList parts = signature.split(QChar(' '), Qt::SkipEmptyParts);
+
+ static const QStringList attrs =
+ QStringList() << "READ" << "MEMBER" << "WRITE"
+ << "NOTIFY" << "CONSTANT" << "FINAL"
+ << "REQUIRED" << "BINDABLE" << "DESIGNABLE"
+ << "RESET" << "REVISION" << "SCRIPTABLE"
+ << "STORED" << "USER";
+
+ // Find the location of the first attribute. All preceding parts
+ // represent the property type + name.
+ auto it = std::find_if(parts.cbegin(), parts.cend(),
+ [](const QString &attr) -> bool {
+ return attrs.contains(attr);
+ });
+
+ if (it == parts.cend() || std::distance(parts.cbegin(), it) < 2)
+ return false;
+
+ QStringList typeParts;
+ std::copy(parts.cbegin(), it, std::back_inserter(typeParts));
+ parts.erase(parts.cbegin(), it);
+ QString name = typeParts.takeLast();
+
+ // Move the pointer operator(s) from name to type
+ while (!name.isEmpty() && name.front() == QChar('*')) {
+ typeParts.last().push_back(name.front());
+ name.removeFirst();
+ }
+
+ // Need at least READ or MEMBER + getter/member name
+ if (parts.size() < 2 || name.isEmpty())
+ return false;
+
+ auto *property = new PropertyNode(parent_, name);
+ property->setAccess(Access::Public);
+ property->setLocation(loc);
+ property->setDataType(typeParts.join(QChar(' ')));
+
+ int i = 0;
+ while (i < parts.size()) {
+ const QString &key = parts.at(i++);
+ // Keywords with no associated values
+ if (key == "CONSTANT") {
+ property->setConstant();
+ } else if (key == "REQUIRED") {
+ property->setRequired();
+ }
+ if (i < parts.size()) {
+ QString value = parts.at(i++);
+ if (key == "READ") {
+ qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Getter);
+ } else if (key == "WRITE") {
+ qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Setter);
+ property->setWritable(true);
+ } else if (key == "MEMBER") {
+ property->setWritable(true);
+ } else if (key == "STORED") {
+ property->setStored(value.toLower() == "true");
+ } else if (key == "BINDABLE") {
+ property->setPropertyType(PropertyNode::PropertyType::BindableProperty);
+ qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Bindable);
+ } else if (key == "RESET") {
+ qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Resetter);
+ } else if (key == "NOTIFY") {
+ qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Notifier);
+ }
+ }
+ }
+ return true;
+}
+
+/*!
+ Given a comment at location \a loc, return a Node for this comment
+ \a nextCommentLoc is the location of the next comment so the declaration
+ must be inbetween.
+ Returns nullptr if no suitable declaration was found between the two comments.
+ */
+Node *ClangVisitor::nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
+{
+ ClangVisitor::SimpleLoc docloc;
+ clang_getPresumedLocation(loc, nullptr, &docloc.line, &docloc.column);
+ auto decl_it = declMap_.upperBound(docloc);
+ if (decl_it == declMap_.end())
+ return nullptr;
+
+ unsigned int declLine = decl_it.key().line;
+ unsigned int nextCommentLine;
+ clang_getPresumedLocation(nextCommentLoc, nullptr, &nextCommentLine, nullptr);
+ if (nextCommentLine < declLine)
+ return nullptr; // there is another comment before the declaration, ignore it.
+
+ // make sure the previous decl was finished.
+ if (decl_it != declMap_.begin()) {
+ CXSourceLocation prevDeclEnd = clang_getRangeEnd(clang_getCursorExtent(*(std::prev(decl_it))));
+ unsigned int prevDeclLine;
+ clang_getPresumedLocation(prevDeclEnd, nullptr, &prevDeclLine, nullptr);
+ if (prevDeclLine >= docloc.line) {
+ // The previous declaration was still going. This is only valid if the previous
+ // declaration is a parent of the next declaration.
+ auto parent = clang_getCursorLexicalParent(*decl_it);
+ if (!clang_equalCursors(parent, *(std::prev(decl_it))))
+ return nullptr;
+ }
+ }
+ auto *node = findNodeForCursor(qdb_, *decl_it);
+ // borrow the parameter name from the definition
+ if (node && node->isFunction(Node::CPP))
+ readParameterNamesAndAttributes(static_cast<FunctionNode *>(node), *decl_it);
+ return node;
+}
+
+ClangCodeParser::ClangCodeParser(
+ QDocDatabase* qdb,
+ Config& config,
+ const std::vector<QByteArray>& include_paths,
+ const QList<QByteArray>& defines,
+ std::optional<std::reference_wrapper<const PCHFile>> pch
+) : m_qdb{qdb},
+ m_includePaths{include_paths},
+ m_defines{defines},
+ m_pch{pch}
+{
+ m_allHeaders = config.getHeaderFiles();
+}
+
+static const char *defaultArgs_[] = {
+/*
+ https://bugreports.qt.io/browse/QTBUG-94365
+ An unidentified bug in Clang 15.x causes parsing failures due to errors in
+ the AST. This replicates only with C++20 support enabled - avoid the issue
+ by using C++17 with Clang 15.
+ */
+#if LIBCLANG_VERSION_MAJOR == 15
+ "-std=c++17",
+#else
+ "-std=c++20",
+#endif
+#ifndef Q_OS_WIN
+ "-fPIC",
+#else
+ "-fms-compatibility-version=19",
+#endif
+ "-DQ_QDOC",
+ "-DQ_CLANG_QDOC",
+ "-DQT_DISABLE_DEPRECATED_UP_TO=0",
+ "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
+ "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
+ "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
+ "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
+ "-Wno-constant-logical-operand",
+ "-Wno-macro-redefined",
+ "-Wno-nullability-completeness",
+ "-fvisibility=default",
+ "-ferror-limit=0",
+ ("-I" CLANG_RESOURCE_DIR)
+};
+
+/*!
+ Load the default arguments and the defines into \a args.
+ Clear \a args first.
+ */
+void getDefaultArgs(const QList<QByteArray>& defines, std::vector<const char*>& args)
+{
+ args.clear();
+ args.insert(args.begin(), std::begin(defaultArgs_), std::end(defaultArgs_));
+
+ // Add the defines from the qdocconf file.
+ for (const auto &p : std::as_const(defines))
+ args.push_back(p.constData());
+}
+
+static QList<QByteArray> includePathsFromHeaders(const std::set<Config::HeaderFilePath> &allHeaders)
+{
+ QList<QByteArray> result;
+ for (const auto& [header_path, _] : allHeaders) {
+ const QByteArray path = "-I" + header_path.toLatin1();
+ const QByteArray parent =
+ "-I" + QDir::cleanPath(header_path + QLatin1String("/../")).toLatin1();
+ }
+
+ return result;
+}
+
+/*!
+ Load the include paths into \a moreArgs. If no include paths
+ were provided, try to guess reasonable include paths.
+ */
+void getMoreArgs(
+ const std::vector<QByteArray>& include_paths,
+ const std::set<Config::HeaderFilePath>& all_headers,
+ std::vector<const char*>& args
+) {
+ if (include_paths.empty()) {
+ /*
+ The include paths provided are inadequate. Make a list
+ of reasonable places to look for include files and use
+ that list instead.
+ */
+ qCWarning(lcQdoc) << "No include paths passed to qdoc; guessing reasonable include paths";
+
+ QString basicIncludeDir = QDir::cleanPath(QString(Config::installDir + "/../include"));
+ args.emplace_back(QByteArray("-I" + basicIncludeDir.toLatin1()).constData());
+
+ auto include_paths_from_headers = includePathsFromHeaders(all_headers);
+ args.insert(args.end(), include_paths_from_headers.begin(), include_paths_from_headers.end());
+ } else {
+ std::copy(include_paths.begin(), include_paths.end(), std::back_inserter(args));
+ }
+}
+
+/*!
+ Building the PCH must be possible when there are no .cpp
+ files, so it is moved here to its own member function, and
+ it is called after the list of header files is complete.
+ */
+std::optional<PCHFile> buildPCH(
+ QDocDatabase* qdb,
+ QString module_header,
+ const std::set<Config::HeaderFilePath>& all_headers,
+ const std::vector<QByteArray>& include_paths,
+ const QList<QByteArray>& defines
+) {
+ static std::vector<const char*> arguments{};
+
+ if (module_header.isEmpty()) return std::nullopt;
+
+ getDefaultArgs(defines, arguments);
+ getMoreArgs(include_paths, all_headers, arguments);
+
+ flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
+ | CXTranslationUnit_SkipFunctionBodies
+ | CXTranslationUnit_KeepGoing);
+
+ CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
+
+ QTemporaryDir pch_directory{QDir::tempPath() + QLatin1String("/qdoc_pch")};
+ if (!pch_directory.isValid()) return std::nullopt;
+
+ const QByteArray module = module_header.toUtf8();
+ QByteArray header;
+
+ qCDebug(lcQdoc) << "Build and visit PCH for" << module_header;
+ // A predicate for std::find_if() to locate a path to the module's header
+ // (e.g. QtGui/QtGui) to be used as pre-compiled header
+ struct FindPredicate
+ {
+ enum SearchType { Any, Module };
+ QByteArray &candidate_;
+ const QByteArray &module_;
+ SearchType type_;
+ FindPredicate(QByteArray &candidate, const QByteArray &module,
+ SearchType type = Any)
+ : candidate_(candidate), module_(module), type_(type)
+ {
+ }
+
+ bool operator()(const QByteArray &p) const
+ {
+ if (type_ != Any && !p.endsWith(module_))
+ return false;
+ candidate_ = p + "/";
+ candidate_.append(module_);
+ if (p.startsWith("-I"))
+ candidate_ = candidate_.mid(2);
+ return QFile::exists(QString::fromUtf8(candidate_));
+ }
+ };
+
+ // First, search for an include path that contains the module name, then any path
+ QByteArray candidate;
+ auto it = std::find_if(include_paths.begin(), include_paths.end(),
+ FindPredicate(candidate, module, FindPredicate::Module));
+ if (it == include_paths.end())
+ it = std::find_if(include_paths.begin(), include_paths.end(),
+ FindPredicate(candidate, module, FindPredicate::Any));
+ if (it != include_paths.end())
+ header = candidate;
+
+ if (header.isEmpty()) {
+ qWarning() << "(qdoc) Could not find the module header in include paths for module"
+ << module << " (include paths: " << include_paths << ")";
+ qWarning() << " Artificial module header built from header dirs in qdocconf "
+ "file";
+ }
+ arguments.push_back("-xc++");
+
+ TranslationUnit tu;
+
+ QString tmpHeader = pch_directory.path() + "/" + module;
+ if (QFile tmpHeaderFile(tmpHeader); tmpHeaderFile.open(QIODevice::Text | QIODevice::WriteOnly)) {
+ QTextStream out(&tmpHeaderFile);
+ if (header.isEmpty()) {
+ for (const auto& [header_path, header_name] : all_headers) {
+ if (!header_name.endsWith(QLatin1String("_p.h"))
+ && !header_name.startsWith(QLatin1String("moc_"))) {
+ QString line = QLatin1String("#include \"") + header_path
+ + QLatin1String("/") + header_name + QLatin1String("\"");
+ out << line << "\n";
+
+ }
+ }
+ } else {
+ QFileInfo headerFile(header);
+ if (!headerFile.exists()) {
+ qWarning() << "Could not find module header file" << header;
+ return std::nullopt;
+ }
+ out << QLatin1String("#include \"") + header + QLatin1String("\"");
+ }
+ }
+
+ CXErrorCode err =
+ clang_parseTranslationUnit2(index, tmpHeader.toLatin1().data(), arguments.data(),
+ static_cast<int>(arguments.size()), nullptr, 0,
+ flags_ | CXTranslationUnit_ForSerialization, &tu.tu);
+ qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << tmpHeader << arguments
+ << ") returns" << err;
+
+ printDiagnostics(tu);
+
+ if (err || !tu) {
+ qCCritical(lcQdoc) << "Could not create PCH file for " << module_header;
+ return std::nullopt;
+ }
+
+ QByteArray pch_name = pch_directory.path().toUtf8() + "/" + module + ".pch";
+ auto error = clang_saveTranslationUnit(tu, pch_name.constData(),
+ clang_defaultSaveOptions(tu));
+ if (error) {
+ qCCritical(lcQdoc) << "Could not save PCH file for" << module_header;
+ return std::nullopt;
+ }
+
+ // Visit the header now, as token from pre-compiled header won't be visited
+ // later
+ CXCursor cur = clang_getTranslationUnitCursor(tu);
+ ClangVisitor visitor(qdb, all_headers);
+ visitor.visitChildren(cur);
+ qCDebug(lcQdoc) << "PCH built and visited for" << module_header;
+
+ return std::make_optional(PCHFile{std::move(pch_directory), pch_name});
+}
+
+static float getUnpatchedVersion(QString t)
+{
+ if (t.count(QChar('.')) > 1)
+ t.truncate(t.lastIndexOf(QChar('.')));
+ return t.toFloat();
+}
+
+/*!
+ Get ready to parse the C++ cpp file identified by \a filePath
+ and add its parsed contents to the database. \a location is
+ used for reporting errors.
+
+ Call matchDocsAndStuff() to do all the parsing and tree building.
+ */
+ParsedCppFileIR ClangCodeParser::parse_cpp_file(const QString &filePath)
+{
+ flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
+ | CXTranslationUnit_SkipFunctionBodies
+ | CXTranslationUnit_KeepGoing);
+
+ CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
+
+ getDefaultArgs(m_defines, m_args);
+ if (m_pch && !filePath.endsWith(".mm")) {
+ m_args.push_back("-w");
+ m_args.push_back("-include-pch");
+ m_args.push_back((*m_pch).get().name.constData());
+ }
+ getMoreArgs(m_includePaths, m_allHeaders, m_args);
+
+ TranslationUnit tu;
+ CXErrorCode err =
+ clang_parseTranslationUnit2(index, filePath.toLocal8Bit(), m_args.data(),
+ static_cast<int>(m_args.size()), nullptr, 0, flags_, &tu.tu);
+ qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << filePath << m_args
+ << ") returns" << err;
+ printDiagnostics(tu);
+
+ if (err || !tu) {
+ qWarning() << "(qdoc) Could not parse source file" << filePath << " error code:" << err;
+ return {};
+ }
+
+ ParsedCppFileIR parse_result{};
+
+ CXCursor tuCur = clang_getTranslationUnitCursor(tu);
+ ClangVisitor visitor(m_qdb, m_allHeaders);
+ visitor.visitChildren(tuCur);
+
+ CXToken *tokens;
+ unsigned int numTokens = 0;
+ const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
+ clang_tokenize(tu, clang_getCursorExtent(tuCur), &tokens, &numTokens);
+
+ for (unsigned int i = 0; i < numTokens; ++i) {
+ if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
+ continue;
+ QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i]));
+ if (!comment.startsWith("/*!"))
+ continue;
+
+ auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
+ auto loc = fromCXSourceLocation(commentLoc);
+ auto end_loc = fromCXSourceLocation(clang_getRangeEnd(clang_getTokenExtent(tu, tokens[i])));
+ Doc::trimCStyleComment(loc, comment);
+
+ // Doc constructor parses the comment.
+ Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
+ if (hasTooManyTopics(doc))
+ continue;
+
+ if (doc.topicsUsed().isEmpty()) {
+ Node *n = nullptr;
+ if (i + 1 < numTokens) {
+ // Try to find the next declaration.
+ CXSourceLocation nextCommentLoc = commentLoc;
+ while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
+ ++i; // already skip all the tokens that are not comments
+ nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
+ n = visitor.nodeForCommentAtLocation(commentLoc, nextCommentLoc);
+ }
+
+ if (n) {
+ parse_result.tied.emplace_back(TiedDocumentation{doc, n});
+ } else if (CodeParser::isWorthWarningAbout(doc)) {
+ bool future = false;
+ if (doc.metaCommandsUsed().contains(COMMAND_SINCE)) {
+ QString sinceVersion = doc.metaCommandArgs(COMMAND_SINCE).at(0).first;
+ if (getUnpatchedVersion(sinceVersion) >
+ getUnpatchedVersion(Config::instance().get(CONFIG_VERSION).asString()))
+ future = true;
+ }
+ if (!future) {
+ doc.location().warning(
+ QStringLiteral("Cannot tie this documentation to anything"),
+ QStringLiteral("qdoc found a /*! ... */ comment, but there was no "
+ "topic command (e.g., '\\%1', '\\%2') in the "
+ "comment and no function definition following "
+ "the comment.")
+ .arg(COMMAND_FN, COMMAND_PAGE));
+ }
+ }
+ } else {
+ parse_result.untied.emplace_back(UntiedDocumentation{doc, QStringList()});
+
+ CXCursor cur = clang_getCursor(tu, commentLoc);
+ while (true) {
+ CXCursorKind kind = clang_getCursorKind(cur);
+ if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
+ break;
+ if (kind == CXCursor_Namespace) {
+ parse_result.untied.back().context << fromCXString(clang_getCursorSpelling(cur));
+ }
+ cur = clang_getCursorLexicalParent(cur);
+ }
+ }
+ }
+
+ clang_disposeTokens(tu, tokens, numTokens);
+ m_namespaceScope.clear();
+ s_fn.clear();
+
+ return parse_result;
+}
+
+/*!
+ Use clang to parse the function signature from a function
+ command. \a location is used for reporting errors. \a fnSignature
+ is the string to parse. It is always a function decl.
+ \a idTag is the optional bracketed argument passed to \\fn, or
+ an empty string.
+ \a context is a string list representing the scope (namespaces)
+ under which the function is declared.
+
+ Returns a variant that's either a Node instance tied to the
+ function declaration, or a parsing failure for later processing.
+ */
+std::variant<Node*, FnMatchError> FnCommandParser::operator()(const Location &location, const QString &fnSignature,
+ const QString &idTag, QStringList context)
+{
+ Node *fnNode = nullptr;
+ /*
+ If the \fn command begins with a tag, then don't try to
+ parse the \fn command with clang. Use the tag to search
+ for the correct function node. It is an error if it can
+ not be found. Return 0 in that case.
+ */
+ if (!idTag.isEmpty()) {
+ fnNode = m_qdb->findFunctionNodeForTag(idTag);
+ if (!fnNode) {
+ location.error(
+ QStringLiteral("tag \\fn [%1] not used in any include file in current module").arg(idTag));
+ } else {
+ /*
+ The function node was found. Use the formal
+ parameter names from the \fn command, because
+ they will be the names used in the documentation.
+ */
+ auto *fn = static_cast<FunctionNode *>(fnNode);
+ QStringList leftParenSplit = fnSignature.mid(fnSignature.indexOf(fn->name())).split('(');
+ if (leftParenSplit.size() > 1) {
+ QStringList rightParenSplit = leftParenSplit[1].split(')');
+ if (!rightParenSplit.empty()) {
+ QString params = rightParenSplit[0];
+ if (!params.isEmpty()) {
+ QStringList commaSplit = params.split(',');
+ Parameters &parameters = fn->parameters();
+ if (parameters.count() == commaSplit.size()) {
+ for (int i = 0; i < parameters.count(); ++i) {
+ QStringList blankSplit = commaSplit[i].split(' ', Qt::SkipEmptyParts);
+ if (blankSplit.size() > 1) {
+ QString pName = blankSplit.last();
+ // Remove any non-letters from the start of parameter name
+ auto it = std::find_if(std::begin(pName), std::end(pName),
+ [](const QChar &c) { return c.isLetter(); });
+ parameters[i].setName(
+ pName.remove(0, std::distance(std::begin(pName), it)));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return fnNode;
+ }
+ auto flags = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
+ | CXTranslationUnit_SkipFunctionBodies
+ | CXTranslationUnit_KeepGoing);
+
+ CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
+
+ getDefaultArgs(m_defines, m_args);
+
+ if (m_pch) {
+ m_args.push_back("-w");
+ m_args.push_back("-include-pch");
+ m_args.push_back((*m_pch).get().name.constData());
+ }
+
+ TranslationUnit tu;
+ QByteArray s_fn{};
+ for (const auto &ns : std::as_const(context))
+ s_fn.prepend("namespace " + ns.toUtf8() + " {");
+ s_fn += fnSignature.toUtf8();
+ if (!s_fn.endsWith(";"))
+ s_fn += "{ }";
+ s_fn.append(context.size(), '}');
+
+ const char *dummyFileName = fnDummyFileName;
+ CXUnsavedFile unsavedFile { dummyFileName, s_fn.constData(),
+ static_cast<unsigned long>(s_fn.size()) };
+ CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, m_args.data(),
+ int(m_args.size()), &unsavedFile, 1, flags, &tu.tu);
+ qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << dummyFileName << m_args
+ << ") returns" << err;
+ printDiagnostics(tu);
+ if (err || !tu) {
+ location.error(QStringLiteral("clang could not parse \\fn %1").arg(fnSignature));
+ return fnNode;
+ } else {
+ /*
+ Always visit the tu if one is constructed, because
+ it might be possible to find the correct node, even
+ if clang detected diagnostics. Only bother to report
+ the diagnostics if they stop us finding the node.
+ */
+ CXCursor cur = clang_getTranslationUnitCursor(tu);
+ ClangVisitor visitor(m_qdb, m_allHeaders);
+ bool ignoreSignature = false;
+ visitor.visitFnArg(cur, &fnNode, ignoreSignature);
+
+ if (!fnNode) {
+ unsigned diagnosticCount = clang_getNumDiagnostics(tu);
+ const auto &config = Config::instance();
+ if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
+ return FnMatchError{ fnSignature, location };
+ }
+ }
+ }
+ return fnNode;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/clangcodeparser.h b/src/qdoc/qdoc/src/qdoc/clangcodeparser.h
new file mode 100644
index 000000000..d7568f9a4
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/clangcodeparser.h
@@ -0,0 +1,94 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CLANGCODEPARSER_H
+#define CLANGCODEPARSER_H
+
+#include "codeparser.h"
+#include "parsererror.h"
+#include "config.h"
+
+#include <QtCore/qtemporarydir.h>
+#include <QtCore/QStringList>
+
+#include <optional>
+
+typedef struct CXTranslationUnitImpl *CXTranslationUnit;
+
+class CppCodeParser;
+
+QT_BEGIN_NAMESPACE
+
+struct ParsedCppFileIR {
+ std::vector<UntiedDocumentation> untied;
+ std::vector<TiedDocumentation> tied;
+};
+
+struct PCHFile {
+ QTemporaryDir dir;
+ QByteArray name;
+};
+
+std::optional<PCHFile> buildPCH(
+ QDocDatabase* qdb,
+ QString module_header,
+ const std::set<Config::HeaderFilePath>& all_headers,
+ const std::vector<QByteArray>& include_paths,
+ const QList<QByteArray>& defines
+);
+
+struct FnCommandParser {
+ FnCommandParser(
+ QDocDatabase* qdb,
+ const std::set<Config::HeaderFilePath>& all_headers,
+ const QList<QByteArray>& defines,
+ std::optional<std::reference_wrapper<const PCHFile>> pch
+ ) : m_qdb{qdb},
+ m_allHeaders{all_headers},
+ m_defines{defines},
+ m_args{},
+ m_pch{pch}
+ {}
+
+ std::variant<Node*, FnMatchError> operator()(
+ const Location &location,
+ const QString &fnSignature,
+ const QString &idTag,
+ QStringList context
+ );
+
+private:
+ QDocDatabase* m_qdb;
+ const std::set<Config::HeaderFilePath>& m_allHeaders; // file name->path
+ QList<QByteArray> m_defines {};
+ std::vector<const char *> m_args {};
+ std::optional<std::reference_wrapper<const PCHFile>> m_pch;
+};
+
+class ClangCodeParser
+{
+public:
+ ClangCodeParser(
+ QDocDatabase* qdb,
+ Config&,
+ const std::vector<QByteArray>& include_paths,
+ const QList<QByteArray>& defines,
+ std::optional<std::reference_wrapper<const PCHFile>> pch
+ );
+
+ ParsedCppFileIR parse_cpp_file(const QString &filePath);
+
+private:
+ QDocDatabase* m_qdb{};
+ std::set<Config::HeaderFilePath> m_allHeaders {}; // file name->path
+ const std::vector<QByteArray>& m_includePaths;
+ QList<QByteArray> m_defines {};
+ std::vector<const char *> m_args {};
+ QStringList m_namespaceScope {};
+ QByteArray s_fn;
+ std::optional<std::reference_wrapper<const PCHFile>> m_pch;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/classnode.cpp b/src/qdoc/qdoc/src/qdoc/classnode.cpp
new file mode 100644
index 000000000..1b132f91e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/classnode.cpp
@@ -0,0 +1,260 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "classnode.h"
+
+#include "functionnode.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "qmltypenode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class ClassNode
+ \brief The ClassNode represents a C++ class.
+
+ It is also used to represent a C++ struct or union. There are some
+ actual uses for structs, but I don't think any unions have been
+ documented yet.
+ */
+
+/*!
+ Adds the base class \a node to this class's list of base
+ classes. The base class has the specified \a access. This
+ is a resolved base class.
+ */
+void ClassNode::addResolvedBaseClass(Access access, ClassNode *node)
+{
+ m_bases.append(RelatedClass(access, node));
+ node->m_derived.append(RelatedClass(access, this));
+}
+
+/*!
+ Adds the derived class \a node to this class's list of derived
+ classes. The derived class inherits this class with \a access.
+ */
+void ClassNode::addDerivedClass(Access access, ClassNode *node)
+{
+ m_derived.append(RelatedClass(access, node));
+}
+
+/*!
+ Add an unresolved base class to this class node's list of
+ base classes. The unresolved base class will be resolved
+ before the generate phase of qdoc. In an unresolved base
+ class, the pointer to the base class node is 0.
+ */
+void ClassNode::addUnresolvedBaseClass(Access access, const QStringList &path)
+{
+ m_bases.append(RelatedClass(access, path));
+}
+
+/*!
+ Search the child list to find the property node with the
+ specified \a name.
+ */
+PropertyNode *ClassNode::findPropertyNode(const QString &name)
+{
+ Node *n = findNonfunctionChild(name, &Node::isProperty);
+
+ if (n)
+ return static_cast<PropertyNode *>(n);
+
+ PropertyNode *pn = nullptr;
+
+ const QList<RelatedClass> &bases = baseClasses();
+ if (!bases.isEmpty()) {
+ for (const RelatedClass &base : bases) {
+ ClassNode *cn = base.m_node;
+ if (cn) {
+ pn = cn->findPropertyNode(name);
+ if (pn)
+ break;
+ }
+ }
+ }
+ const QList<RelatedClass> &ignoredBases = ignoredBaseClasses();
+ if (!ignoredBases.isEmpty()) {
+ for (const RelatedClass &base : ignoredBases) {
+ ClassNode *cn = base.m_node;
+ if (cn) {
+ pn = cn->findPropertyNode(name);
+ if (pn)
+ break;
+ }
+ }
+ }
+
+ return pn;
+}
+
+/*!
+ \a fn is an overriding function in this class or in a class
+ derived from this class. Find the node for the function that
+ \a fn overrides in this class's children or in one of this
+ class's base classes. Return a pointer to the overridden
+ function or return 0.
+
+ This should be revised because clang provides the path to the
+ overridden function. mws 15/12/2018
+ */
+FunctionNode *ClassNode::findOverriddenFunction(const FunctionNode *fn)
+{
+ for (auto &bc : m_bases) {
+ ClassNode *cn = bc.m_node;
+ if (cn == nullptr) {
+ cn = QDocDatabase::qdocDB()->findClassNode(bc.m_path);
+ bc.m_node = cn;
+ }
+ if (cn != nullptr) {
+ FunctionNode *result = cn->findFunctionChild(fn);
+ if (result != nullptr && !result->isInternal() && !result->isNonvirtual()
+ && result->hasDoc())
+ return result;
+ result = cn->findOverriddenFunction(fn);
+ if (result != nullptr && !result->isNonvirtual())
+ return result;
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ \a fn is an overriding function in this class or in a class
+ derived from this class. Find the node for the property that
+ \a fn overrides in this class's children or in one of this
+ class's base classes. Return a pointer to the overridden
+ property or return 0.
+ */
+PropertyNode *ClassNode::findOverriddenProperty(const FunctionNode *fn)
+{
+ for (auto &baseClass : m_bases) {
+ ClassNode *cn = baseClass.m_node;
+ if (cn == nullptr) {
+ cn = QDocDatabase::qdocDB()->findClassNode(baseClass.m_path);
+ baseClass.m_node = cn;
+ }
+ if (cn != nullptr) {
+ const NodeList &children = cn->childNodes();
+ for (const auto &child : children) {
+ if (child->isProperty()) {
+ auto *pn = static_cast<PropertyNode *>(child);
+ if (pn->name() == fn->name() || pn->hasAccessFunction(fn->name())) {
+ if (pn->hasDoc())
+ return pn;
+ }
+ }
+ }
+ PropertyNode *result = cn->findOverriddenProperty(fn);
+ if (result != nullptr)
+ return result;
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Returns true if the class or struct represented by this class
+ node must be documented. If this function returns true, then
+ qdoc must find a qdoc comment for this class. If it returns
+ false, then the class need not be documented.
+ */
+bool ClassNode::docMustBeGenerated() const
+{
+ if (!hasDoc() || isPrivate() || isInternal() || isDontDocument())
+ return false;
+ if (declLocation().fileName().endsWith(QLatin1String("_p.h")) && !hasDoc())
+ return false;
+
+ return true;
+}
+
+/*!
+ A base class of this class node was private or internal.
+ That node's list of \a bases is traversed in this function.
+ Each of its public base classes is promoted to be a base
+ class of this node for documentation purposes. For each
+ private or internal class node in \a bases, this function
+ is called recursively with the list of base classes from
+ that private or internal class node.
+ */
+void ClassNode::promotePublicBases(const QList<RelatedClass> &bases)
+{
+ if (!bases.isEmpty()) {
+ for (qsizetype i = bases.size() - 1; i >= 0; --i) {
+ ClassNode *bc = bases.at(i).m_node;
+ if (bc == nullptr)
+ bc = QDocDatabase::qdocDB()->findClassNode(bases.at(i).m_path);
+ if (bc != nullptr) {
+ if (bc->isPrivate() || bc->isInternal())
+ promotePublicBases(bc->baseClasses());
+ else
+ m_bases.append(bases.at(i));
+ }
+ }
+ }
+}
+
+/*!
+ Remove private and internal bases classes from this class's list
+ of base classes. When a base class is removed from the list, add
+ its base classes to this class's list of base classes.
+ */
+void ClassNode::removePrivateAndInternalBases()
+{
+ int i;
+ i = 0;
+ QSet<ClassNode *> found;
+
+ // Remove private and duplicate base classes.
+ while (i < m_bases.size()) {
+ ClassNode *bc = m_bases.at(i).m_node;
+ if (bc == nullptr)
+ bc = QDocDatabase::qdocDB()->findClassNode(m_bases.at(i).m_path);
+ if (bc != nullptr
+ && (bc->isPrivate() || bc->isInternal() || bc->isDontDocument()
+ || found.contains(bc))) {
+ RelatedClass rc = m_bases.at(i);
+ m_bases.removeAt(i);
+ m_ignoredBases.append(rc);
+ promotePublicBases(bc->baseClasses());
+ } else {
+ ++i;
+ }
+ found.insert(bc);
+ }
+
+ i = 0;
+ while (i < m_derived.size()) {
+ ClassNode *dc = m_derived.at(i).m_node;
+ if (dc != nullptr && (dc->isPrivate() || dc->isInternal() || dc->isDontDocument())) {
+ m_derived.removeAt(i);
+ const QList<RelatedClass> &dd = dc->derivedClasses();
+ for (qsizetype j = dd.size() - 1; j >= 0; --j)
+ m_derived.insert(i, dd.at(j));
+ } else {
+ ++i;
+ }
+ }
+}
+
+/*!
+ */
+void ClassNode::resolvePropertyOverriddenFromPtrs(PropertyNode *pn)
+{
+ for (const auto &baseClass : std::as_const(baseClasses())) {
+ ClassNode *cn = baseClass.m_node;
+ if (cn) {
+ Node *n = cn->findNonfunctionChild(pn->name(), &Node::isProperty);
+ if (n) {
+ auto *baseProperty = static_cast<PropertyNode *>(n);
+ cn->resolvePropertyOverriddenFromPtrs(baseProperty);
+ pn->setOverriddenFrom(baseProperty);
+ } else
+ cn->resolvePropertyOverriddenFromPtrs(pn);
+ }
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/classnode.h b/src/qdoc/qdoc/src/qdoc/classnode.h
new file mode 100644
index 000000000..1ac944a34
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/classnode.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CLASSNODE_H
+#define CLASSNODE_H
+
+#include "aggregate.h"
+#include "relatedclass.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class FunctionNode;
+class PropertyNode;
+class QmlTypeNode;
+
+class ClassNode : public Aggregate
+{
+public:
+ ClassNode(NodeType type, Aggregate *parent, const QString &name) : Aggregate(type, parent, name)
+ {
+ }
+ [[nodiscard]] bool isFirstClassAggregate() const override { return true; }
+ [[nodiscard]] bool isClassNode() const override { return true; }
+ [[nodiscard]] bool isRelatableType() const override { return true; }
+ [[nodiscard]] bool isWrapper() const override { return m_wrapper; }
+ void setWrapper() override { m_wrapper = true; }
+
+ void addResolvedBaseClass(Access access, ClassNode *node);
+ void addDerivedClass(Access access, ClassNode *node);
+ void addUnresolvedBaseClass(Access access, const QStringList &path);
+ void removePrivateAndInternalBases();
+ void resolvePropertyOverriddenFromPtrs(PropertyNode *pn);
+
+ QList<RelatedClass> &baseClasses() { return m_bases; }
+ QList<RelatedClass> &derivedClasses() { return m_derived; }
+ QList<RelatedClass> &ignoredBaseClasses() { return m_ignoredBases; }
+
+ [[nodiscard]] const QList<RelatedClass> &baseClasses() const { return m_bases; }
+
+ [[nodiscard]] bool isAbstract() const override { return m_abstract; }
+ void setAbstract(bool b) override { m_abstract = b; }
+ PropertyNode *findPropertyNode(const QString &name);
+ FunctionNode *findOverriddenFunction(const FunctionNode *fn);
+ PropertyNode *findOverriddenProperty(const FunctionNode *fn);
+ [[nodiscard]] bool docMustBeGenerated() const override;
+
+ void insertQmlNativeType(QmlTypeNode *qmlTypeNode) { m_nativeTypeForQml << qmlTypeNode; }
+ bool isQmlNativeType() { return !m_nativeTypeForQml.empty(); }
+ const QSet<QmlTypeNode *> &qmlNativeTypes() { return m_nativeTypeForQml; }
+
+private:
+ void promotePublicBases(const QList<RelatedClass> &bases);
+
+private:
+ QList<RelatedClass> m_bases {};
+ QList<RelatedClass> m_derived {};
+ QList<RelatedClass> m_ignoredBases {};
+ bool m_abstract { false };
+ bool m_wrapper { false };
+ QSet<QmlTypeNode *> m_nativeTypeForQml;
+};
+
+QT_END_NAMESPACE
+
+#endif // CLASSNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/codechunk.cpp b/src/qdoc/qdoc/src/qdoc/codechunk.cpp
new file mode 100644
index 000000000..889e091af
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/codechunk.cpp
@@ -0,0 +1,104 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "codechunk.h"
+
+QT_BEGIN_NAMESPACE
+
+enum { Other, Alnum, Gizmo, Comma, LBrace, RBrace, RAngle, Colon, Paren };
+
+// entries 128 and above are Other
+static const int charCategory[256] = { Other, Other, Other, Other, Other, Other, Other, Other,
+ Other, Other, Other, Other, Other, Other, Other, Other,
+ Other, Other, Other, Other, Other, Other, Other, Other,
+ Other, Other, Other, Other, Other, Other, Other, Other,
+ // ! " # $ % & '
+ Other, Other, Other, Other, Other, Gizmo, Gizmo, Other,
+ // ( ) * + , - . /
+ Paren, Paren, Gizmo, Gizmo, Comma, Other, Other, Gizmo,
+ // 0 1 2 3 4 5 6 7
+ Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // 8 9 : ; < = > ?
+ Alnum, Alnum, Colon, Other, Other, Gizmo, RAngle, Gizmo,
+ // @ A B C D E F G
+ Other, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // H I J K L M N O
+ Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // P Q R S T U V W
+ Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // X Y Z [ \ ] ^ _
+ Alnum, Alnum, Alnum, Other, Other, Other, Gizmo, Alnum,
+ // ` a b c d e f g
+ Other, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // h i j k l m n o
+ Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // p q r s t u v w
+ Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // x y z { | } ~
+ Alnum, Alnum, Alnum, LBrace, Gizmo, RBrace, Other, Other };
+
+static const bool needSpace[9][9] = {
+ /* [ a + , { } > : ) */
+ /* [ */ { false, false, false, false, false, true, false, false, false },
+ /* a */ { false, true, true, false, false, true, false, false, false },
+ /* + */ { false, true, false, false, false, true, false, true, false },
+ /* , */ { true, true, true, true, true, true, true, true, false },
+ /* { */ { false, false, false, false, false, false, false, false, false },
+ /* } */ { false, false, false, false, false, false, false, false, false },
+ /* > */ { true, true, true, false, true, true, true, false, false },
+ /* : */ { false, false, true, true, true, true, true, false, false },
+ /* ( */ { false, false, false, false, false, false, false, false, false },
+};
+
+static int category(QChar ch)
+{
+ return charCategory[static_cast<int>(ch.toLatin1())];
+}
+
+/*!
+ \class CodeChunk
+
+ \brief The CodeChunk class represents a tiny piece of C++ code.
+
+ \note I think this class should be eliminated (mws 11/12/2018
+
+ The class provides conversion between a list of lexemes and a string. It adds
+ spaces at the right place for consistent style. The tiny pieces of code it
+ represents are data types, enum values, and default parameter values.
+
+ Apart from the piece of code itself, there are two bits of metainformation
+ stored in CodeChunk: the base and the hotspot. The base is the part of the
+ piece that may be a hypertext link. The base of
+
+ QMap<QString, QString>
+
+ is QMap.
+
+ The hotspot is the place the variable name should be inserted in the case of a
+ variable (or parameter) declaration. The hotspot of
+
+ char * []
+
+ is between '*' and '[]'.
+*/
+
+/*!
+ Appends \a lexeme to the current string contents, inserting
+ a space if appropriate.
+ */
+void CodeChunk::append(const QString &lexeme)
+{
+ if (!m_str.isEmpty() && !lexeme.isEmpty()) {
+ /*
+ Should there be a space or not between the code chunk so far and the
+ new lexeme?
+ */
+ int cat1 = category(m_str.at(m_str.size() - 1));
+ int cat2 = category(lexeme[0]);
+ if (needSpace[cat1][cat2])
+ m_str += QLatin1Char(' ');
+ }
+ m_str += lexeme;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/codechunk.h b/src/qdoc/qdoc/src/qdoc/codechunk.h
new file mode 100644
index 000000000..00ad26c6d
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/codechunk.h
@@ -0,0 +1,74 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CODECHUNK_H
+#define CODECHUNK_H
+
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+// ### get rid of that class
+
+class CodeChunk
+{
+public:
+ CodeChunk() : m_hotspot(-1) { }
+
+ void append(const QString &lexeme);
+ void appendHotspot()
+ {
+ if (m_hotspot == -1)
+ m_hotspot = m_str.size();
+ }
+
+ [[nodiscard]] bool isEmpty() const { return m_str.isEmpty(); }
+ void clear() { m_str.clear(); }
+ [[nodiscard]] QString toString() const { return m_str; }
+ [[nodiscard]] QString left() const
+ {
+ return m_str.left(m_hotspot == -1 ? m_str.size() : m_hotspot);
+ }
+ [[nodiscard]] QString right() const
+ {
+ return m_str.mid(m_hotspot == -1 ? m_str.size() : m_hotspot);
+ }
+
+private:
+ QString m_str {};
+ qsizetype m_hotspot {};
+};
+
+inline bool operator==(const CodeChunk &c, const CodeChunk &d)
+{
+ return c.toString() == d.toString();
+}
+
+inline bool operator!=(const CodeChunk &c, const CodeChunk &d)
+{
+ return !(c == d);
+}
+
+inline bool operator<(const CodeChunk &c, const CodeChunk &d)
+{
+ return c.toString() < d.toString();
+}
+
+inline bool operator>(const CodeChunk &c, const CodeChunk &d)
+{
+ return d < c;
+}
+
+inline bool operator<=(const CodeChunk &c, const CodeChunk &d)
+{
+ return !(c > d);
+}
+
+inline bool operator>=(const CodeChunk &c, const CodeChunk &d)
+{
+ return !(c < d);
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/codemarker.cpp b/src/qdoc/qdoc/src/qdoc/codemarker.cpp
new file mode 100644
index 000000000..28f84a946
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/codemarker.cpp
@@ -0,0 +1,448 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "codemarker.h"
+
+#include "classnode.h"
+#include "config.h"
+#include "functionnode.h"
+#include "node.h"
+#include "propertynode.h"
+#include "qmlpropertynode.h"
+
+#include <QtCore/qobjectdefs.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+QString CodeMarker::s_defaultLang;
+QList<CodeMarker *> CodeMarker::s_markers;
+
+
+/*!
+ When a code marker constructs itself, it puts itself into
+ the static list of code markers. All the code markers in
+ the static list get initialized in initialize(), which is
+ not called until after the qdoc configuration file has
+ been read.
+ */
+CodeMarker::CodeMarker()
+{
+ s_markers.prepend(this);
+}
+
+/*!
+ When a code marker destroys itself, it removes itself from
+ the static list of code markers.
+ */
+CodeMarker::~CodeMarker()
+{
+ s_markers.removeAll(this);
+}
+
+/*!
+ A code market performs no initialization by default. Marker-specific
+ initialization is performed in subclasses.
+ */
+void CodeMarker::initializeMarker() {}
+
+/*!
+ Terminating a code marker is trivial.
+ */
+void CodeMarker::terminateMarker()
+{
+ // nothing.
+}
+
+/*!
+ All the code markers in the static list are initialized
+ here, after the qdoc configuration file has been loaded.
+ */
+void CodeMarker::initialize()
+{
+ s_defaultLang = Config::instance().get(CONFIG_LANGUAGE).asString();
+ for (const auto &marker : std::as_const(s_markers))
+ marker->initializeMarker();
+}
+
+/*!
+ All the code markers in the static list are terminated here.
+ */
+void CodeMarker::terminate()
+{
+ for (const auto &marker : std::as_const(s_markers))
+ marker->terminateMarker();
+}
+
+CodeMarker *CodeMarker::markerForCode(const QString &code)
+{
+ CodeMarker *defaultMarker = markerForLanguage(s_defaultLang);
+ if (defaultMarker != nullptr && defaultMarker->recognizeCode(code))
+ return defaultMarker;
+
+ for (const auto &marker : std::as_const(s_markers)) {
+ if (marker->recognizeCode(code))
+ return marker;
+ }
+
+ return defaultMarker;
+}
+
+CodeMarker *CodeMarker::markerForFileName(const QString &fileName)
+{
+ CodeMarker *defaultMarker = markerForLanguage(s_defaultLang);
+ qsizetype dot = -1;
+ while ((dot = fileName.lastIndexOf(QLatin1Char('.'), dot)) != -1) {
+ QString ext = fileName.mid(dot + 1);
+ if (defaultMarker != nullptr && defaultMarker->recognizeExtension(ext))
+ return defaultMarker;
+ for (const auto &marker : std::as_const(s_markers)) {
+ if (marker->recognizeExtension(ext))
+ return marker;
+ }
+ --dot;
+ }
+ return defaultMarker;
+}
+
+CodeMarker *CodeMarker::markerForLanguage(const QString &lang)
+{
+ for (const auto &marker : std::as_const(s_markers)) {
+ if (marker->recognizeLanguage(lang))
+ return marker;
+ }
+ return nullptr;
+}
+
+const Node *CodeMarker::nodeForString(const QString &string)
+{
+#if QT_POINTER_SIZE == 4
+ const quintptr n = string.toUInt();
+#else
+ const quintptr n = string.toULongLong();
+#endif
+ return reinterpret_cast<const Node *>(n);
+}
+
+QString CodeMarker::stringForNode(const Node *node)
+{
+ return QString::number(reinterpret_cast<quintptr>(node));
+}
+
+/*!
+ Returns a string representing the \a node status, set using \preliminary, \since,
+ and \deprecated commands.
+
+ If a string is returned, it is one of:
+ \list
+ \li \c {"preliminary"}
+ \li \c {"since <version_since>, deprecated in <version_deprecated>"}
+ \li \c {"since <version_since>, until <version_deprecated>"}
+ \li \c {"since <version_since>"}
+ \li \c {"since <version_since>, deprecated"}
+ \li \c {"deprecated in <version_deprecated>"}
+ \li \c {"until <version_deprecated>"}
+ \li \c {"deprecated"}
+ \endlist
+
+ If \a node has no related status information, returns std::nullopt.
+*/
+static std::optional<QString> nodeStatusAsString(const Node *node)
+{
+ if (node->isPreliminary())
+ return std::optional(u"preliminary"_s);
+
+ QStringList result;
+ if (const auto &since = node->since(); !since.isEmpty())
+ result << "since %1"_L1.arg(since);
+ if (const auto &deprecated = node->deprecatedSince(); !deprecated.isEmpty()) {
+ if (node->isDeprecated())
+ result << "deprecated in %1"_L1.arg(deprecated);
+ else
+ result << "until %1"_L1.arg(deprecated);
+ } else if (node->isDeprecated()) {
+ result << u"deprecated"_s;
+ }
+
+ return result.isEmpty() ? std::nullopt : std::optional(result.join(u", "_s));
+}
+
+/*!
+ Returns the 'extra' synopsis string for \a node with status information,
+ using a specified section \a style.
+*/
+QString CodeMarker::extraSynopsis(const Node *node, Section::Style style)
+{
+ if (style != Section::Summary && style != Section::Details)
+ return {};
+
+ QStringList extra;
+ if (style == Section::Details) {
+ switch (node->nodeType()) {
+ case Node::Function: {
+ const auto *func = static_cast<const FunctionNode *>(node);
+ if (func->isStatic()) {
+ extra << "static";
+ } else if (!func->isNonvirtual()) {
+ if (func->isFinal())
+ extra << "final";
+ if (func->isOverride())
+ extra << "override";
+ if (func->isPureVirtual())
+ extra << "pure";
+ extra << "virtual";
+ }
+
+ if (func->isExplicit()) extra << "explicit";
+ if (func->isConstexpr()) extra << "constexpr";
+ if (auto noexcept_info = func->getNoexcept()) {
+ extra << (QString("noexcept") + (!(*noexcept_info).isEmpty() ? "(...)" : ""));
+ }
+
+ if (func->access() == Access::Protected)
+ extra << "protected";
+ else if (func->access() == Access::Private)
+ extra << "private";
+
+ if (func->isSignal()) {
+ if (func->parameters().isPrivateSignal())
+ extra << "private";
+ extra << "signal";
+ } else if (func->isSlot())
+ extra << "slot";
+ else if (func->isDefault())
+ extra << "default";
+ else if (func->isInvokable())
+ extra << "invokable";
+ }
+ break;
+ case Node::TypeAlias:
+ extra << "alias";
+ break;
+ case Node::Property: {
+ auto propertyNode = static_cast<const PropertyNode *>(node);
+ if (propertyNode->propertyType() == PropertyNode::PropertyType::BindableProperty)
+ extra << "bindable";
+ if (!propertyNode->isWritable())
+ extra << "read-only";
+ }
+ break;
+ case Node::QmlProperty: {
+ auto qmlProperty = static_cast<const QmlPropertyNode *>(node);
+ if (qmlProperty->isDefault())
+ extra << u"default"_s;
+ // Call non-const overloads to ensure attributes are fetched from
+ // associated C++ properties
+ else if (const_cast<QmlPropertyNode *>(qmlProperty)->isReadOnly())
+ extra << u"read-only"_s;
+ else if (const_cast<QmlPropertyNode *>(qmlProperty)->isRequired())
+ extra << u"required"_s;
+ else if (!qmlProperty->defaultValue().isEmpty()) {
+ extra << u"default: "_s + qmlProperty->defaultValue();
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ // Add status for both Summary and Details
+ if (auto status = nodeStatusAsString(node)) {
+ if (!extra.isEmpty())
+ extra.last() += ','_L1;
+ extra << *status;
+ }
+
+ QString extraStr = extra.join(QLatin1Char(' '));
+ if (!extraStr.isEmpty()) {
+ extraStr.prepend(style == Section::Details ? '[' : '(');
+ extraStr.append(style == Section::Details ? ']' : ')');
+ }
+
+ return extraStr;
+}
+
+static const QString samp = QLatin1String("&amp;");
+static const QString slt = QLatin1String("&lt;");
+static const QString sgt = QLatin1String("&gt;");
+static const QString squot = QLatin1String("&quot;");
+
+QString CodeMarker::protect(const QString &str)
+{
+ qsizetype n = str.size();
+ QString marked;
+ marked.reserve(n * 2 + 30);
+ const QChar *data = str.constData();
+ for (int i = 0; i != n; ++i) {
+ switch (data[i].unicode()) {
+ case '&':
+ marked += samp;
+ break;
+ case '<':
+ marked += slt;
+ break;
+ case '>':
+ marked += sgt;
+ break;
+ case '"':
+ marked += squot;
+ break;
+ default:
+ marked += data[i];
+ }
+ }
+ return marked;
+}
+
+void CodeMarker::appendProtectedString(QString *output, QStringView str)
+{
+ qsizetype n = str.size();
+ output->reserve(output->size() + n * 2 + 30);
+ const QChar *data = str.constData();
+ for (int i = 0; i != n; ++i) {
+ switch (data[i].unicode()) {
+ case '&':
+ *output += samp;
+ break;
+ case '<':
+ *output += slt;
+ break;
+ case '>':
+ *output += sgt;
+ break;
+ case '"':
+ *output += squot;
+ break;
+ default:
+ *output += data[i];
+ }
+ }
+}
+
+QString CodeMarker::typified(const QString &string, bool trailingSpace)
+{
+ QString result;
+ QString pendingWord;
+
+ for (int i = 0; i <= string.size(); ++i) {
+ QChar ch;
+ if (i != string.size())
+ ch = string.at(i);
+
+ QChar lower = ch.toLower();
+ if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) || ch.digitValue() >= 0
+ || ch == QLatin1Char('_') || ch == QLatin1Char(':')) {
+ pendingWord += ch;
+ } else {
+ if (!pendingWord.isEmpty()) {
+ bool isProbablyType = (pendingWord != QLatin1String("const"));
+ if (isProbablyType)
+ result += QLatin1String("<@type>");
+ result += pendingWord;
+ if (isProbablyType)
+ result += QLatin1String("</@type>");
+ }
+ pendingWord.clear();
+
+ switch (ch.unicode()) {
+ case '\0':
+ break;
+ case '&':
+ result += QLatin1String("&amp;");
+ break;
+ case '<':
+ result += QLatin1String("&lt;");
+ break;
+ case '>':
+ result += QLatin1String("&gt;");
+ break;
+ default:
+ result += ch;
+ }
+ }
+ }
+ if (trailingSpace && string.size()) {
+ if (!string.endsWith(QLatin1Char('*')) && !string.endsWith(QLatin1Char('&')))
+ result += QLatin1Char(' ');
+ }
+ return result;
+}
+
+QString CodeMarker::taggedNode(const Node *node)
+{
+ QString tag;
+ const QString &name = node->name();
+
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ tag = QLatin1String("@namespace");
+ break;
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ tag = QLatin1String("@class");
+ break;
+ case Node::Enum:
+ tag = QLatin1String("@enum");
+ break;
+ case Node::TypeAlias:
+ case Node::Typedef:
+ tag = QLatin1String("@typedef");
+ break;
+ case Node::Function:
+ tag = QLatin1String("@function");
+ break;
+ case Node::Property:
+ tag = QLatin1String("@property");
+ break;
+ case Node::QmlType:
+ tag = QLatin1String("@property");
+ break;
+ case Node::Page:
+ tag = QLatin1String("@property");
+ break;
+ default:
+ tag = QLatin1String("@unknown");
+ break;
+ }
+ return (QLatin1Char('<') + tag + QLatin1Char('>') + protect(name) + QLatin1String("</") + tag
+ + QLatin1Char('>'));
+}
+
+QString CodeMarker::taggedQmlNode(const Node *node)
+{
+ QString tag;
+ if (node->isFunction()) {
+ const auto *fn = static_cast<const FunctionNode *>(node);
+ switch (fn->metaness()) {
+ case FunctionNode::QmlSignal:
+ tag = QLatin1String("@signal");
+ break;
+ case FunctionNode::QmlSignalHandler:
+ tag = QLatin1String("@signalhandler");
+ break;
+ case FunctionNode::QmlMethod:
+ tag = QLatin1String("@method");
+ break;
+ default:
+ tag = QLatin1String("@unknown");
+ break;
+ }
+ } else if (node->isQmlProperty()) {
+ tag = QLatin1String("@property");
+ } else {
+ tag = QLatin1String("@unknown");
+ }
+ return QLatin1Char('<') + tag + QLatin1Char('>') + protect(node->name()) + QLatin1String("</")
+ + tag + QLatin1Char('>');
+}
+
+QString CodeMarker::linkTag(const Node *node, const QString &body)
+{
+ return QLatin1String("<@link node=\"") + stringForNode(node) + QLatin1String("\">") + body
+ + QLatin1String("</@link>");
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/codemarker.h b/src/qdoc/qdoc/src/qdoc/codemarker.h
new file mode 100644
index 000000000..af668b650
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/codemarker.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CODEMARKER_H
+#define CODEMARKER_H
+
+#include "atom.h"
+#include "sections.h"
+
+QT_BEGIN_NAMESPACE
+
+class CodeMarker
+{
+public:
+ CodeMarker();
+ virtual ~CodeMarker();
+
+ virtual void initializeMarker();
+ virtual void terminateMarker();
+ virtual bool recognizeCode(const QString & /*code*/) { return true; }
+ virtual bool recognizeExtension(const QString & /*extension*/) { return true; }
+ virtual bool recognizeLanguage(const QString & /*language*/) { return false; }
+ [[nodiscard]] virtual Atom::AtomType atomType() const { return Atom::Code; }
+ virtual QString markedUpCode(const QString &code, const Node * /*relative*/,
+ const Location & /*location*/)
+ {
+ return protect(code);
+ }
+ virtual QString markedUpSynopsis(const Node * /*node*/, const Node * /*relative*/,
+ Section::Style /*style*/)
+ {
+ return QString();
+ }
+ virtual QString markedUpQmlItem(const Node *, bool) { return QString(); }
+ virtual QString markedUpName(const Node * /*node*/) { return QString(); }
+ virtual QString markedUpEnumValue(const QString & /*enumValue*/, const Node * /*relative*/)
+ {
+ return QString();
+ }
+ virtual QString markedUpInclude(const QString & /*include*/) { return QString(); }
+
+ static void initialize();
+ static void terminate();
+ static CodeMarker *markerForCode(const QString &code);
+ static CodeMarker *markerForFileName(const QString &fileName);
+ static CodeMarker *markerForLanguage(const QString &lang);
+ static const Node *nodeForString(const QString &string);
+ static QString stringForNode(const Node *node);
+ static QString extraSynopsis(const Node *node, Section::Style style);
+
+ QString typified(const QString &string, bool trailingSpace = false);
+
+protected:
+ static QString protect(const QString &string);
+ static void appendProtectedString(QString *output, QStringView str);
+ QString taggedNode(const Node *node);
+ QString taggedQmlNode(const Node *node);
+ QString linkTag(const Node *node, const QString &body);
+
+private:
+ static QString s_defaultLang;
+ static QList<CodeMarker *> s_markers;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/codeparser.cpp b/src/qdoc/qdoc/src/qdoc/codeparser.cpp
new file mode 100644
index 000000000..ad35e6d65
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/codeparser.cpp
@@ -0,0 +1,139 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "codeparser.h"
+
+#include "config.h"
+#include "generator.h"
+#include "node.h"
+#include "qdocdatabase.h"
+
+#include <QtCore/qregularexpression.h>
+
+QT_BEGIN_NAMESPACE
+
+QList<CodeParser *> CodeParser::s_parsers;
+
+/*!
+ The constructor adds this code parser to the static
+ list of code parsers.
+ */
+CodeParser::CodeParser()
+{
+ m_qdb = QDocDatabase::qdocDB();
+ s_parsers.prepend(this);
+}
+
+/*!
+ The destructor removes this code parser from the static
+ list of code parsers.
+ */
+CodeParser::~CodeParser()
+{
+ s_parsers.removeAll(this);
+}
+
+/*!
+ Terminating a code parser is trivial.
+ */
+void CodeParser::terminateParser()
+{
+ // nothing.
+}
+
+/*!
+ All the code parsers in the static list are initialized here,
+ after the qdoc configuration variables have been set.
+ */
+void CodeParser::initialize()
+{
+ for (const auto &parser : std::as_const(s_parsers))
+ parser->initializeParser();
+}
+
+/*!
+ All the code parsers in the static list are terminated here.
+ */
+void CodeParser::terminate()
+{
+ for (const auto parser : s_parsers)
+ parser->terminateParser();
+}
+
+CodeParser *CodeParser::parserForLanguage(const QString &language)
+{
+ for (const auto parser : std::as_const(s_parsers)) {
+ if (parser->language() == language)
+ return parser;
+ }
+ return nullptr;
+}
+
+CodeParser *CodeParser::parserForSourceFile(const QString &filePath)
+{
+ QString fileName = QFileInfo(filePath).fileName();
+
+ for (const auto &parser : s_parsers) {
+ const QStringList sourcePatterns = parser->sourceFileNameFilter();
+ for (const QString &pattern : sourcePatterns) {
+ auto re = QRegularExpression::fromWildcard(pattern, Qt::CaseInsensitive);
+ if (re.match(fileName).hasMatch())
+ return parser;
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ \internal
+ */
+void CodeParser::extractPageLinkAndDesc(QStringView arg, QString *link, QString *desc)
+{
+ static const QRegularExpression bracedRegExp(
+ QRegularExpression::anchoredPattern(QLatin1String(R"(\{([^{}]*)\}(?:\{([^{}]*)\})?)")));
+ auto match = bracedRegExp.matchView(arg);
+ if (match.hasMatch()) {
+ *link = match.captured(1);
+ *desc = match.captured(2);
+ if (desc->isEmpty())
+ *desc = *link;
+ } else {
+ qsizetype spaceAt = arg.indexOf(QLatin1Char(' '));
+ if (arg.contains(QLatin1String(".html")) && spaceAt != -1) {
+ *link = arg.left(spaceAt).trimmed().toString();
+ *desc = arg.mid(spaceAt).trimmed().toString();
+ } else {
+ *link = arg.toString();
+ *desc = *link;
+ }
+ }
+}
+
+/*!
+ \internal
+ */
+void CodeParser::setLink(Node *node, Node::LinkType linkType, const QString &arg)
+{
+ QString link;
+ QString desc;
+ extractPageLinkAndDesc(arg, &link, &desc);
+ node->setLink(linkType, link, desc);
+}
+
+/*!
+ \brief Test for whether a doc comment warrants warnings.
+
+ Returns true if qdoc should report that it has found something
+ wrong with the qdoc comment in \a doc. Sometimes, qdoc should
+ not report the warning, for example, when the comment contains
+ the \c internal command, which normally means qdoc will not use
+ the comment in the documentation anyway, so there is no point
+ in reporting warnings about it.
+ */
+bool CodeParser::isWorthWarningAbout(const Doc &doc)
+{
+ return (Config::instance().showInternal()
+ || !doc.metaCommandsUsed().contains(QStringLiteral("internal")));
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/codeparser.h b/src/qdoc/qdoc/src/qdoc/codeparser.h
new file mode 100644
index 000000000..51d1ac2a4
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/codeparser.h
@@ -0,0 +1,142 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CODEPARSER_H
+#define CODEPARSER_H
+
+#include "node.h"
+
+#include <QtCore/qset.h>
+
+QT_BEGIN_NAMESPACE
+
+#define COMMAND_ABSTRACT QLatin1String("abstract")
+#define COMMAND_CLASS QLatin1String("class")
+#define COMMAND_COMPARES QLatin1String("compares")
+#define COMMAND_COMPARESWITH QLatin1String("compareswith")
+#define COMMAND_DEFAULT QLatin1String("default")
+#define COMMAND_DEPRECATED QLatin1String("deprecated") // ### don't document
+#define COMMAND_DONTDOCUMENT QLatin1String("dontdocument")
+#define COMMAND_ENUM QLatin1String("enum")
+#define COMMAND_EXAMPLE QLatin1String("example")
+#define COMMAND_EXTERNALPAGE QLatin1String("externalpage")
+#define COMMAND_FN QLatin1String("fn")
+#define COMMAND_GROUP QLatin1String("group")
+#define COMMAND_HEADERFILE QLatin1String("headerfile")
+#define COMMAND_INGROUP QLatin1String("ingroup")
+#define COMMAND_INHEADERFILE QLatin1String("inheaderfile")
+#define COMMAND_INMODULE QLatin1String("inmodule") // ### don't document
+#define COMMAND_INPUBLICGROUP QLatin1String("inpublicgroup")
+#define COMMAND_INQMLMODULE QLatin1String("inqmlmodule")
+#define COMMAND_INTERNAL QLatin1String("internal")
+#define COMMAND_MACRO QLatin1String("macro")
+#define COMMAND_MODULE QLatin1String("module")
+#define COMMAND_MODULESTATE QLatin1String("modulestate")
+#define COMMAND_NAMESPACE QLatin1String("namespace")
+#define COMMAND_NEXTPAGE QLatin1String("nextpage")
+#define COMMAND_NOAUTOLIST QLatin1String("noautolist")
+#define COMMAND_NONREENTRANT QLatin1String("nonreentrant")
+#define COMMAND_OBSOLETE QLatin1String("obsolete")
+#define COMMAND_OVERLOAD QLatin1String("overload")
+#define COMMAND_PAGE QLatin1String("page")
+#define COMMAND_PRELIMINARY QLatin1String("preliminary")
+#define COMMAND_PREVIOUSPAGE QLatin1String("previouspage")
+#define COMMAND_PROPERTY QLatin1String("property")
+#define COMMAND_QMLABSTRACT QLatin1String("qmlabstract")
+#define COMMAND_QMLATTACHEDMETHOD QLatin1String("qmlattachedmethod")
+#define COMMAND_QMLATTACHEDPROPERTY QLatin1String("qmlattachedproperty")
+#define COMMAND_QMLATTACHEDSIGNAL QLatin1String("qmlattachedsignal")
+#define COMMAND_QMLVALUETYPE QLatin1String("qmlvaluetype")
+#define COMMAND_QMLCLASS QLatin1String("qmlclass")
+#define COMMAND_QMLDEFAULT QLatin1String("qmldefault")
+#define COMMAND_QMLENUMERATORSFROM QLatin1String("qmlenumeratorsfrom")
+#define COMMAND_QMLINHERITS QLatin1String("inherits")
+#define COMMAND_QMLINSTANTIATES QLatin1String("instantiates") // TODO Qt 7.0.0 - Remove: Deprecated since 6.8.
+#define COMMAND_QMLMETHOD QLatin1String("qmlmethod")
+#define COMMAND_QMLMODULE QLatin1String("qmlmodule")
+#define COMMAND_QMLNATIVETYPE QLatin1String("nativetype")
+#define COMMAND_QMLPROPERTY QLatin1String("qmlproperty")
+#define COMMAND_QMLPROPERTYGROUP QLatin1String("qmlpropertygroup")
+#define COMMAND_QMLREADONLY QLatin1String("readonly")
+#define COMMAND_QMLREQUIRED QLatin1String("required")
+#define COMMAND_QMLSIGNAL QLatin1String("qmlsignal")
+#define COMMAND_QMLTYPE QLatin1String("qmltype")
+#define COMMAND_QTCMAKEPACKAGE QLatin1String("qtcmakepackage")
+#define COMMAND_QTCMAKETARGETITEM QLatin1String("qtcmaketargetitem")
+#define COMMAND_QTVARIABLE QLatin1String("qtvariable")
+#define COMMAND_REENTRANT QLatin1String("reentrant")
+#define COMMAND_REIMP QLatin1String("reimp")
+#define COMMAND_RELATES QLatin1String("relates")
+#define COMMAND_SINCE QLatin1String("since")
+#define COMMAND_STRUCT QLatin1String("struct")
+#define COMMAND_SUBTITLE QLatin1String("subtitle")
+#define COMMAND_STARTPAGE QLatin1String("startpage")
+#define COMMAND_THREADSAFE QLatin1String("threadsafe")
+#define COMMAND_TITLE QLatin1String("title")
+#define COMMAND_TYPEALIAS QLatin1String("typealias")
+#define COMMAND_TYPEDEF QLatin1String("typedef")
+#define COMMAND_VARIABLE QLatin1String("variable")
+#define COMMAND_VERSION QLatin1String("version")
+#define COMMAND_UNION QLatin1String("union")
+#define COMMAND_WRAPPER QLatin1String("wrapper")
+#define COMMAND_ATTRIBUTION QLatin1String("attribution")
+
+// deprecated alias of qmlvaluetype
+#define COMMAND_QMLBASICTYPE QLatin1String("qmlbasictype")
+
+class Location;
+class QString;
+class QDocDatabase;
+class CppCodeParser;
+
+struct UntiedDocumentation {
+ Doc documentation;
+ QStringList context;
+};
+
+struct TiedDocumentation {
+ Doc documentation;
+ Node* node;
+};
+
+class CodeParser
+{
+public:
+ static inline const QSet<QString> common_meta_commands{
+ COMMAND_ABSTRACT, COMMAND_DEFAULT, COMMAND_DEPRECATED, COMMAND_INGROUP,
+ COMMAND_INMODULE, COMMAND_INPUBLICGROUP, COMMAND_INQMLMODULE, COMMAND_INTERNAL,
+ COMMAND_MODULESTATE, COMMAND_NOAUTOLIST, COMMAND_NONREENTRANT, COMMAND_OBSOLETE,
+ COMMAND_PRELIMINARY, COMMAND_QMLABSTRACT, COMMAND_QMLDEFAULT, COMMAND_QMLENUMERATORSFROM, COMMAND_QMLINHERITS,
+ COMMAND_QMLREADONLY, COMMAND_QMLREQUIRED, COMMAND_QTCMAKEPACKAGE, COMMAND_QTCMAKETARGETITEM,
+ COMMAND_QTVARIABLE, COMMAND_REENTRANT, COMMAND_SINCE, COMMAND_STARTPAGE, COMMAND_SUBTITLE,
+ COMMAND_THREADSAFE, COMMAND_TITLE, COMMAND_WRAPPER, COMMAND_ATTRIBUTION,
+ };
+
+public:
+ CodeParser();
+ virtual ~CodeParser();
+
+ virtual void initializeParser() = 0;
+ virtual void terminateParser();
+ virtual QString language() = 0;
+ virtual QStringList sourceFileNameFilter() = 0;
+ virtual void parseSourceFile(const Location &location, const QString &filePath, CppCodeParser& cpp_code_parser) = 0;
+
+ static void initialize();
+ static void terminate();
+ static CodeParser *parserForLanguage(const QString &language);
+ static CodeParser *parserForSourceFile(const QString &filePath);
+ static void setLink(Node *node, Node::LinkType linkType, const QString &arg);
+ static bool isWorthWarningAbout(const Doc &doc);
+
+protected:
+ static void extractPageLinkAndDesc(QStringView arg, QString *link, QString *desc);
+ QDocDatabase *m_qdb {};
+
+private:
+ static QList<CodeParser *> s_parsers;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/collectionnode.cpp b/src/qdoc/qdoc/src/qdoc/collectionnode.cpp
new file mode 100644
index 000000000..833a9d037
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/collectionnode.cpp
@@ -0,0 +1,104 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "collectionnode.h"
+
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class CollectionNode
+ \brief A class for holding the members of a collection of doc pages.
+ */
+
+/*!
+ Appends \a node to the collection node's member list, if
+ and only if it isn't already in the member list.
+ */
+void CollectionNode::addMember(Node *node)
+{
+ if (!m_members.contains(node))
+ m_members.append(node);
+}
+
+/*!
+ Returns \c true if this collection node contains at least
+ one namespace node.
+ */
+bool CollectionNode::hasNamespaces() const
+{
+ return std::any_of(m_members.cbegin(), m_members.cend(), [](const Node *member) {
+ return member->isClassNode() && member->isInAPI();
+ });
+}
+
+/*!
+ Returns \c true if this collection node contains at least
+ one class node.
+ */
+bool CollectionNode::hasClasses() const
+{
+ return std::any_of(m_members.cbegin(), m_members.cend(), [](const Node *member) {
+ return member->isClassNode() && member->isInAPI();
+ });
+}
+
+/*!
+ \fn template <typename F> NodeMap CollectionNode::getMembers(const F &&predicate) const
+
+ Returns a map containing this collection node's member nodes for which \c
+ predicate(node) returns \c true. The \a predicate is a function or a
+ lambda that takes a const Node pointer as an argument and returns a bool.
+*/
+
+/*!
+ \fn NodeMap CollectionNode::getMembers(Node::NodeType type) const
+
+ Returns a map containing this collection node's member nodes with
+ a specified node \a type.
+*/
+
+/*!
+ Returns the logical module version.
+*/
+QString CollectionNode::logicalModuleVersion() const
+{
+ QStringList version;
+ version << m_logicalModuleVersionMajor << m_logicalModuleVersionMinor;
+ version.removeAll(QString());
+ return version.join(".");
+}
+
+/*!
+ This function accepts the logical module \a info as a string
+ list. If the logical module info contains the version number,
+ it splits the version number on the '.' character to get the
+ major and minor version numbers. Both major and minor version
+ numbers should be provided, but the minor version number is
+ not strictly necessary.
+ */
+void CollectionNode::setLogicalModuleInfo(const QStringList &info)
+{
+ m_logicalModuleName = info[0];
+ if (info.size() > 1) {
+ QStringList dotSplit = info[1].split(QLatin1Char('.'));
+ m_logicalModuleVersionMajor = dotSplit[0];
+ if (dotSplit.size() > 1)
+ m_logicalModuleVersionMinor = dotSplit[1];
+ else
+ m_logicalModuleVersionMinor = "0";
+ }
+}
+
+/*!
+ \fn void CollectionNode::setState(const QString &state)
+ \fn QString CollectionNode::state()
+
+ Sets or gets a description of this module's state. For example,
+ \e {"Technical Preview"}. This string is used when generating the
+ module's documentation page and reference pages of the module's
+ members.
+*/
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/collectionnode.h b/src/qdoc/qdoc/src/qdoc/collectionnode.h
new file mode 100644
index 000000000..3756d3534
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/collectionnode.h
@@ -0,0 +1,126 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef COLLECTIONNODE_H
+#define COLLECTIONNODE_H
+
+#include "pagenode.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class CollectionNode : public PageNode
+{
+public:
+ CollectionNode(NodeType type, Aggregate *parent, const QString &name)
+ : PageNode(type, parent, name)
+ {
+ }
+
+ [[nodiscard]] bool isCollectionNode() const override { return true; }
+ [[nodiscard]] QString qtVariable() const override { return m_qtVariable; }
+ void setQtVariable(const QString &v) override { m_qtVariable = v; }
+ [[nodiscard]] QString qtCMakeComponent() const override { return m_qtCMakeComponent; }
+ [[nodiscard]] QString qtCMakeTargetItem() const override { return m_qtCMakeTargetItem; }
+ void setQtCMakeComponent(const QString &target) override { m_qtCMakeComponent = target; }
+ void setQtCMakeTargetItem(const QString &target) override { m_qtCMakeTargetItem = target; }
+ void addMember(Node *node) override;
+ [[nodiscard]] bool hasNamespaces() const override;
+ [[nodiscard]] bool hasClasses() const override;
+ [[nodiscard]] bool wasSeen() const override { return m_seen; }
+
+ [[nodiscard]] QString fullTitle() const override { return title(); }
+ [[nodiscard]] QString logicalModuleName() const override { return m_logicalModuleName; }
+ [[nodiscard]] QString logicalModuleVersion() const override;
+ [[nodiscard]] QString logicalModuleIdentifier() const override
+ {
+ return m_logicalModuleName + m_logicalModuleVersionMajor;
+ }
+ [[nodiscard]] QString state() const { return m_state; }
+
+ template <typename F>
+ NodeMap getMembers(F &&predicate) const
+ {
+ NodeMap result;
+ for (const auto &member : std::as_const(m_members)) {
+ if (std::invoke(predicate, member) && member->isInAPI())
+ result.insert(member->name(), member);
+ }
+ return result;
+ }
+
+ NodeMap getMembers(Node::NodeType type) const
+ {
+ return getMembers([type](const Node *n) {
+ return n->nodeType() == type;
+ });
+ }
+
+ void setLogicalModuleInfo(const QStringList &info) override;
+ void setState(const QString &state) { m_state = state; }
+
+ // REMARK: Those methods are used by QDocDatabase as a performance
+ // detail to avoid merging a collection node multiple times. They
+ // should not be addressed in any other part of the code nor
+ // should their usage appear more than once in QDocDatabase,
+ // albeit this is not enforced.
+ // More information are provided in the comment for the definition
+ // of m_merged.
+ void markMerged() { m_merged = true; }
+ bool isMerged() { return m_merged; }
+
+ [[nodiscard]] const NodeList &members() const { return m_members; }
+
+ void markSeen() { m_seen = true; }
+ void markNotSeen() { m_seen = false; }
+
+private:
+ bool m_seen { false };
+ // REMARK: This is set by the database when merging the collection
+ // node and is later used to avoid merging the same collection
+ // multiple times.
+ // Currently, collection nodes may come from multiple projects,
+ // such that to have a complete overview of the members of a
+ // collection we need to rejoin all members for all instances of
+ // the "same" collection.
+ // This is done in QDocDatabase, generally through an external
+ // method call that is done ad-hoc when a source-of-truth
+ // collection node is needed.
+ // As each part of the code that need such a source-of-truth will
+ // need to merge the node, to avoid the overhead of a relatively
+ // expensive operation being performed multiple times, we expose
+ // this detail so that QDocDatabase can avoid performing the
+ // operation again.
+ // To avoid the coupling, QDocDatabase could keep track of the
+ // merged nodes itself, this is a bit less trivial that this
+ // implementation and doesn't address the source of the problem
+ // (the multiple merges themselves and the sequencing of the
+ // related operations) and as such was discarded in favor of this
+ // simpler implementation.
+ // Do note that outside the very specific purpose for which this
+ // member was made, no part of the code should refer to it and its
+ // associated methods.
+ // Should this start to be the case, we can switch to the more
+ // complex encapsulation into QDocDatabase without having to touch
+ // the outside user of the merges.
+ // Further down the line, this is expected to go away completely
+ // as other part of the code are streamlined.
+ // KLUDGE: Note that this whole exposure is done as a hackish
+ // solution to QTBUG-104237 and should not be considered final or
+ // dependable.
+ bool m_merged { false };
+ NodeList m_members {};
+ QString m_logicalModuleName {};
+ QString m_logicalModuleVersionMajor {};
+ QString m_logicalModuleVersionMinor {};
+ QString m_qtVariable {};
+ QString m_qtCMakeComponent {};
+ QString m_qtCMakeTargetItem {};
+ QString m_state {};
+};
+
+QT_END_NAMESPACE
+
+#endif // COLLECTIONNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/comparisoncategory.cpp b/src/qdoc/qdoc/src/qdoc/comparisoncategory.cpp
new file mode 100644
index 000000000..98f7aaf66
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/comparisoncategory.cpp
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \enum ComparisonCategory
+ \internal
+
+ \value None No comparison is defined.
+ \value Strong Strong comparison is defined, see std::strong_ordering.
+ \value Weak Weak comparison is defined, see std::weak_ordering.
+ \value Partial A partial ordering is defined, see std::partial_ordering.
+ \value Equality Only (in)equality comparison is defined.
+*/
+
+/*!
+ \fn static inline std::string comparisonCategoryAsString(const ComparisonCategory &category)
+ \internal
+
+ Returns a string representation of the comparison category \a category.
+*/
+
+/*!
+ \fn static ComparisonCategory comparisonCategoryFromString(const std::string &string)
+ \internal
+
+ Returns a matching comparison category for a \a string representation, or
+ ComparisonCategory::None for an unknown category string.
+*/
diff --git a/src/qdoc/qdoc/src/qdoc/comparisoncategory.h b/src/qdoc/qdoc/src/qdoc/comparisoncategory.h
new file mode 100644
index 000000000..d3f36654b
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/comparisoncategory.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef COMPARISONCATEGORY_H
+#define COMPARISONCATEGORY_H
+
+#include <string>
+
+QT_BEGIN_NAMESPACE
+
+enum struct ComparisonCategory : unsigned char {
+ None,
+ Strong,
+ Weak,
+ Partial,
+ Equality
+};
+
+static inline std::string comparisonCategoryAsString(ComparisonCategory category)
+{
+ switch (category) {
+ case ComparisonCategory::Strong:
+ return "strong";
+ case ComparisonCategory::Weak:
+ return "weak";
+ case ComparisonCategory::Partial:
+ return "partial";
+ case ComparisonCategory::Equality:
+ return "equality";
+ case ComparisonCategory::None:
+ [[fallthrough]];
+ default:
+ break;
+ }
+ return {};
+}
+
+static inline ComparisonCategory comparisonCategoryFromString(const std::string &string)
+{
+ if (string == "strong")
+ return ComparisonCategory::Strong;
+ if (string == "weak")
+ return ComparisonCategory::Weak;
+ if (string == "partial")
+ return ComparisonCategory::Partial;
+ if (string == "equality")
+ return ComparisonCategory::Equality;
+
+ return ComparisonCategory::None;
+}
+
+QT_END_NAMESPACE
+
+#endif // COMPARISONCATEGORY_H
diff --git a/src/qdoc/qdoc/src/qdoc/config.cpp b/src/qdoc/qdoc/src/qdoc/config.cpp
new file mode 100644
index 000000000..de987dae8
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/config.cpp
@@ -0,0 +1,1439 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "config.h"
+#include "utilities.h"
+
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qtemporaryfile.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qvariant.h>
+#include <QtCore/qregularexpression.h>
+
+QT_BEGIN_NAMESPACE
+
+QString ConfigStrings::AUTOLINKERRORS = QStringLiteral("autolinkerrors");
+QString ConfigStrings::BUILDVERSION = QStringLiteral("buildversion");
+QString ConfigStrings::CODEINDENT = QStringLiteral("codeindent");
+QString ConfigStrings::CODEPREFIX = QStringLiteral("codeprefix");
+QString ConfigStrings::CODESUFFIX = QStringLiteral("codesuffix");
+QString ConfigStrings::CPPCLASSESPAGE = QStringLiteral("cppclassespage");
+QString ConfigStrings::CPPCLASSESTITLE = QStringLiteral("cppclassestitle");
+QString ConfigStrings::DEFINES = QStringLiteral("defines");
+QString ConfigStrings::DEPENDS = QStringLiteral("depends");
+QString ConfigStrings::DESCRIPTION = QStringLiteral("description");
+QString ConfigStrings::DOCBOOKEXTENSIONS = QStringLiteral("usedocbookextensions");
+QString ConfigStrings::ENDHEADER = QStringLiteral("endheader");
+QString ConfigStrings::EXAMPLEDIRS = QStringLiteral("exampledirs");
+QString ConfigStrings::EXAMPLES = QStringLiteral("examples");
+QString ConfigStrings::EXAMPLESINSTALLPATH = QStringLiteral("examplesinstallpath");
+QString ConfigStrings::EXCLUDEDIRS = QStringLiteral("excludedirs");
+QString ConfigStrings::EXCLUDEFILES = QStringLiteral("excludefiles");
+QString ConfigStrings::EXTRAIMAGES = QStringLiteral("extraimages");
+QString ConfigStrings::FALSEHOODS = QStringLiteral("falsehoods");
+QString ConfigStrings::FORMATTING = QStringLiteral("formatting");
+QString ConfigStrings::HEADERDIRS = QStringLiteral("headerdirs");
+QString ConfigStrings::HEADERS = QStringLiteral("headers");
+QString ConfigStrings::HEADERSCRIPTS = QStringLiteral("headerscripts");
+QString ConfigStrings::HEADERSTYLES = QStringLiteral("headerstyles");
+QString ConfigStrings::HOMEPAGE = QStringLiteral("homepage");
+QString ConfigStrings::HOMETITLE = QStringLiteral("hometitle");
+QString ConfigStrings::IGNOREDIRECTIVES = QStringLiteral("ignoredirectives");
+QString ConfigStrings::IGNORESINCE = QStringLiteral("ignoresince");
+QString ConfigStrings::IGNORETOKENS = QStringLiteral("ignoretokens");
+QString ConfigStrings::IGNOREWORDS = QStringLiteral("ignorewords");
+QString ConfigStrings::IMAGEDIRS = QStringLiteral("imagedirs");
+QString ConfigStrings::INCLUDEPATHS = QStringLiteral("includepaths");
+QString ConfigStrings::INCLUSIVE = QStringLiteral("inclusive");
+QString ConfigStrings::INDEXES = QStringLiteral("indexes");
+QString ConfigStrings::LANDINGPAGE = QStringLiteral("landingpage");
+QString ConfigStrings::LANDINGTITLE = QStringLiteral("landingtitle");
+QString ConfigStrings::LANGUAGE = QStringLiteral("language");
+QString ConfigStrings::LOCATIONINFO = QStringLiteral("locationinfo");
+QString ConfigStrings::LOGPROGRESS = QStringLiteral("logprogress");
+QString ConfigStrings::MACRO = QStringLiteral("macro");
+QString ConfigStrings::MANIFESTMETA = QStringLiteral("manifestmeta");
+QString ConfigStrings::MODULEHEADER = QStringLiteral("moduleheader");
+QString ConfigStrings::NATURALLANGUAGE = QStringLiteral("naturallanguage");
+QString ConfigStrings::NAVIGATION = QStringLiteral("navigation");
+QString ConfigStrings::NOLINKERRORS = QStringLiteral("nolinkerrors");
+QString ConfigStrings::OUTPUTDIR = QStringLiteral("outputdir");
+QString ConfigStrings::OUTPUTFORMATS = QStringLiteral("outputformats");
+QString ConfigStrings::OUTPUTPREFIXES = QStringLiteral("outputprefixes");
+QString ConfigStrings::OUTPUTSUFFIXES = QStringLiteral("outputsuffixes");
+QString ConfigStrings::PROJECT = QStringLiteral("project");
+QString ConfigStrings::REDIRECTDOCUMENTATIONTODEVNULL =
+ QStringLiteral("redirectdocumentationtodevnull");
+QString ConfigStrings::QHP = QStringLiteral("qhp");
+QString ConfigStrings::QUOTINGINFORMATION = QStringLiteral("quotinginformation");
+QString ConfigStrings::SCRIPTS = QStringLiteral("scripts");
+QString ConfigStrings::SHOWINTERNAL = QStringLiteral("showinternal");
+QString ConfigStrings::SINGLEEXEC = QStringLiteral("singleexec");
+QString ConfigStrings::SOURCEDIRS = QStringLiteral("sourcedirs");
+QString ConfigStrings::SOURCEENCODING = QStringLiteral("sourceencoding");
+QString ConfigStrings::SOURCES = QStringLiteral("sources");
+QString ConfigStrings::SPURIOUS = QStringLiteral("spurious");
+QString ConfigStrings::STYLESHEETS = QStringLiteral("stylesheets");
+QString ConfigStrings::SYNTAXHIGHLIGHTING = QStringLiteral("syntaxhighlighting");
+QString ConfigStrings::TABSIZE = QStringLiteral("tabsize");
+QString ConfigStrings::TAGFILE = QStringLiteral("tagfile");
+QString ConfigStrings::TIMESTAMPS = QStringLiteral("timestamps");
+QString ConfigStrings::TOCTITLES = QStringLiteral("toctitles");
+QString ConfigStrings::TRADEMARKSPAGE = QStringLiteral("trademarkspage");
+QString ConfigStrings::URL = QStringLiteral("url");
+QString ConfigStrings::VERSION = QStringLiteral("version");
+QString ConfigStrings::VERSIONSYM = QStringLiteral("versionsym");
+QString ConfigStrings::FILEEXTENSIONS = QStringLiteral("fileextensions");
+QString ConfigStrings::IMAGEEXTENSIONS = QStringLiteral("imageextensions");
+QString ConfigStrings::QMLTYPESPAGE = QStringLiteral("qmltypespage");
+QString ConfigStrings::QMLTYPESTITLE = QStringLiteral("qmltypestitle");
+QString ConfigStrings::WARNINGLIMIT = QStringLiteral("warninglimit");
+
+/*!
+ An entry in a stack, where each entry is a list
+ of string values.
+ */
+class MetaStackEntry
+{
+public:
+ void open();
+ void close();
+
+ QStringList accum;
+ QStringList next;
+};
+Q_DECLARE_TYPEINFO(MetaStackEntry, Q_RELOCATABLE_TYPE);
+
+/*!
+ Start accumulating values in a list by appending an empty
+ string to the list.
+ */
+void MetaStackEntry::open()
+{
+ next.append(QString());
+}
+
+/*!
+ Stop accumulating values and append the list of accumulated
+ values to the complete list of accumulated values.
+
+ */
+void MetaStackEntry::close()
+{
+ accum += next;
+ next.clear();
+}
+
+/*!
+ \class MetaStack
+
+ This class maintains a stack of values of config file variables.
+*/
+class MetaStack : private QStack<MetaStackEntry>
+{
+public:
+ MetaStack();
+
+ void process(QChar ch, const Location &location);
+ QStringList getExpanded(const Location &location);
+};
+
+/*!
+ The default constructor pushes a new stack entry and
+ opens it.
+ */
+MetaStack::MetaStack()
+{
+ push(MetaStackEntry());
+ top().open();
+}
+
+/*!
+ Processes the character \a ch using the \a location.
+ It really just builds up a name by appending \a ch to
+ it.
+ */
+void MetaStack::process(QChar ch, const Location &location)
+{
+ if (ch == QLatin1Char('{')) {
+ push(MetaStackEntry());
+ top().open();
+ } else if (ch == QLatin1Char('}')) {
+ if (size() == 1)
+ location.fatal(QStringLiteral("Unexpected '}'"));
+
+ top().close();
+ const QStringList suffixes = pop().accum;
+ const QStringList prefixes = top().next;
+
+ top().next.clear();
+ for (const auto &prefix : prefixes) {
+ for (const auto &suffix : suffixes)
+ top().next << prefix + suffix;
+ }
+ } else if (ch == QLatin1Char(',') && size() > 1) {
+ top().close();
+ top().open();
+ } else {
+ for (QString &topNext : top().next)
+ topNext += ch;
+ }
+}
+
+/*!
+ Returns the accumulated string values.
+ */
+QStringList MetaStack::getExpanded(const Location &location)
+{
+ if (size() > 1)
+ location.fatal(QStringLiteral("Missing '}'"));
+
+ top().close();
+ return top().accum;
+}
+
+const QString Config::dot = QLatin1String(".");
+bool Config::m_debug = false;
+bool Config::m_atomsDump = false;
+bool Config::generateExamples = true;
+QString Config::overrideOutputDir;
+QString Config::installDir;
+QSet<QString> Config::overrideOutputFormats;
+QMap<QString, QString> Config::m_extractedDirs;
+QStack<QString> Config::m_workingDirs;
+QMap<QString, QStringList> Config::m_includeFilesMap;
+
+/*!
+ \class ConfigVar
+ \brief contains all the information for a single config variable in a
+ .qdocconf file.
+*/
+
+/*!
+ Returns this configuration variable as a string.
+
+ If the variable is not defined, returns \a defaultString.
+
+ \note By default, \a defaultString is a null string.
+ This allows determining whether a configuration variable is
+ undefined (returns a null string) or defined as empty
+ (returns a non-null, empty string).
+*/
+QString ConfigVar::asString(const QString defaultString) const
+{
+ if (m_name.isEmpty())
+ return defaultString;
+
+ QString result(""); // an empty but non-null string
+ for (const auto &value : std::as_const(m_values)) {
+ if (!result.isEmpty() && !result.endsWith(QChar('\n')))
+ result.append(QChar(' '));
+ result.append(value.m_value);
+ }
+ return result;
+}
+
+/*!
+ Returns this config variable as a string list.
+*/
+QStringList ConfigVar::asStringList() const
+{
+ QStringList result;
+ for (const auto &value : std::as_const(m_values))
+ result << value.m_value;
+ return result;
+}
+
+/*!
+ Returns this config variable as a string set.
+*/
+QSet<QString> ConfigVar::asStringSet() const
+{
+ const auto &stringList = asStringList();
+ return QSet<QString>(stringList.cbegin(), stringList.cend());
+}
+
+/*!
+ Returns this config variable as a boolean.
+*/
+bool ConfigVar::asBool() const
+{
+ return QVariant(asString()).toBool();
+}
+
+/*!
+ Returns this configuration variable as an integer; iterates
+ through the string list, interpreting each
+ string in the list as an integer and adding it to a total sum.
+
+ Returns 0 if this variable is defined as empty, and
+ -1 if it's is not defined.
+ */
+int ConfigVar::asInt() const
+{
+ const QStringList strs = asStringList();
+ if (strs.isEmpty())
+ return -1;
+
+ int sum = 0;
+ for (const auto &str : strs)
+ sum += str.toInt();
+ return sum;
+}
+
+/*!
+ Appends values to this ConfigVar, and adjusts the ExpandVar
+ parameters so that they continue to refer to the correct values.
+*/
+void ConfigVar::append(const ConfigVar &other)
+{
+ m_expandVars << other.m_expandVars;
+ QList<ExpandVar>::Iterator it = m_expandVars.end();
+ it -= other.m_expandVars.size();
+ std::for_each(it, m_expandVars.end(), [this](ExpandVar &v) {
+ v.m_valueIndex += m_values.size();
+ });
+ m_values << other.m_values;
+ m_location = other.m_location;
+}
+
+/*!
+ \class Config
+ \brief The Config class contains the configuration variables
+ for controlling how qdoc produces documentation.
+
+ Its load() function reads, parses, and processes a qdocconf file.
+ */
+
+/*!
+ \enum Config::PathFlags
+
+ Flags used for retrieving canonicalized paths from Config.
+
+ \value Validate
+ Issue a warning for paths that do not exist and
+ remove them from the returned list.
+
+ \value IncludePaths
+ Assume the variable contains include paths with
+ prefixes such as \c{-I} that are to be removed
+ before canonicalizing and then re-inserted.
+
+ \omitvalue None
+
+ \sa getCanonicalPathList()
+*/
+
+/*!
+ Initializes the Config with \a programName and sets all
+ internal state variables to either default values or to ones
+ defined in command line arguments \a args.
+ */
+void Config::init(const QString &programName, const QStringList &args)
+{
+ m_prog = programName;
+ processCommandLineOptions(args);
+ reset();
+}
+
+Config::~Config()
+{
+ clear();
+}
+
+/*!
+ Clears the location and internal maps for config variables.
+ */
+void Config::clear()
+{
+ m_location = Location();
+ m_configVars.clear();
+ m_includeFilesMap.clear();
+ m_excludedPaths.reset();
+}
+
+/*!
+ Resets the Config instance - used by load()
+ */
+void Config::reset()
+{
+ clear();
+
+ // Default values
+ setStringList(CONFIG_CODEINDENT, QStringList("0"));
+ setStringList(CONFIG_FALSEHOODS, QStringList("0"));
+ setStringList(CONFIG_HEADERS + dot + CONFIG_FILEEXTENSIONS, QStringList("*.ch *.h *.h++ *.hh *.hpp *.hxx"));
+ setStringList(CONFIG_SOURCES + dot + CONFIG_FILEEXTENSIONS, QStringList("*.c++ *.cc *.cpp *.cxx *.mm *.qml *.qdoc"));
+ setStringList(CONFIG_LANGUAGE, QStringList("Cpp")); // i.e. C++
+ setStringList(CONFIG_OUTPUTFORMATS, QStringList("HTML"));
+ setStringList(CONFIG_TABSIZE, QStringList("8"));
+ setStringList(CONFIG_LOCATIONINFO, QStringList("true"));
+
+ // Publish options from the command line as config variables
+ const auto setListFlag = [this](const QString &key, bool test) {
+ setStringList(key, QStringList(test ? QStringLiteral("true") : QStringLiteral("false")));
+ };
+#define SET(opt, test) setListFlag(opt, m_parser.isSet(m_parser.test))
+ SET(CONFIG_SYNTAXHIGHLIGHTING, highlightingOption);
+ SET(CONFIG_SHOWINTERNAL, showInternalOption);
+ SET(CONFIG_SINGLEEXEC, singleExecOption);
+ SET(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL, redirectDocumentationToDevNullOption);
+ SET(CONFIG_AUTOLINKERRORS, autoLinkErrorsOption);
+#undef SET
+ m_showInternal = m_configVars.value(CONFIG_SHOWINTERNAL).asBool();
+ setListFlag(CONFIG_NOLINKERRORS,
+ m_parser.isSet(m_parser.noLinkErrorsOption)
+ || qEnvironmentVariableIsSet("QDOC_NOLINKERRORS"));
+
+ // CONFIG_DEFINES and CONFIG_INCLUDEPATHS are set in load()
+}
+
+/*!
+ Loads and parses the qdoc configuration file \a fileName.
+ If a previous project was loaded, this function first resets the
+ Config instance. Then it calls the other load() function, which
+ does the loading, parsing, and processing of the configuration file.
+ */
+void Config::load(const QString &fileName)
+{
+ // Reset if a previous project was loaded
+ if (m_configVars.contains(CONFIG_PROJECT))
+ reset();
+
+ load(Location(), fileName);
+ if (m_location.isEmpty())
+ m_location = Location(fileName);
+ else
+ m_location.setEtc(true);
+
+ expandVariables();
+
+ // Add defines and includepaths from command line to their
+ // respective configuration variables. Values set here are
+ // always added to what's defined in configuration file.
+ insertStringList(CONFIG_DEFINES, m_defines);
+ insertStringList(CONFIG_INCLUDEPATHS, m_includePaths);
+
+ // Prefetch values that are used internally
+ m_exampleFiles = getCanonicalPathList(CONFIG_EXAMPLES);
+ m_exampleDirs = getCanonicalPathList(CONFIG_EXAMPLEDIRS);
+}
+
+/*!
+ Expands other config variables referred to in all stored ConfigVars.
+*/
+void Config::expandVariables()
+{
+ for (auto &configVar : m_configVars) {
+ for (auto it = configVar.m_expandVars.crbegin(); it != configVar.m_expandVars.crend(); ++it) {
+ Q_ASSERT(it->m_valueIndex < configVar.m_values.size());
+ const QString &key = it->m_var;
+ const auto &refVar = m_configVars.value(key);
+ if (refVar.m_name.isEmpty()) {
+ configVar.m_location.fatal(
+ QStringLiteral("Environment or configuration variable '%1' undefined")
+ .arg(it->m_var));
+ } else if (!refVar.m_expandVars.empty()) {
+ configVar.m_location.fatal(
+ QStringLiteral("Nested variable expansion not allowed"),
+ QStringLiteral("When expanding '%1' at %2:%3")
+ .arg(refVar.m_name, refVar.m_location.filePath(),
+ QString::number(refVar.m_location.lineNo())));
+ }
+ QString expanded;
+ if (it->m_delim.isNull())
+ expanded = m_configVars.value(key).asStringList().join(QString());
+ else
+ expanded = m_configVars.value(key).asStringList().join(it->m_delim);
+ configVar.m_values[it->m_valueIndex].m_value.insert(it->m_index, expanded);
+ }
+ configVar.m_expandVars.clear();
+ }
+}
+
+/*!
+ Sets the \a values of a configuration variable \a var from a string list.
+ */
+void Config::setStringList(const QString &var, const QStringList &values)
+{
+ m_configVars.insert(var, ConfigVar(var, values, QDir::currentPath()));
+}
+
+/*!
+ Adds the \a values from a string list to the configuration variable \a var.
+ Existing value(s) are kept.
+*/
+void Config::insertStringList(const QString &var, const QStringList &values)
+{
+ m_configVars[var].append(ConfigVar(var, values, QDir::currentPath()));
+}
+
+/*!
+ Process and store variables from the command line.
+ */
+void Config::processCommandLineOptions(const QStringList &args)
+{
+ m_parser.process(args);
+
+ m_defines = m_parser.values(m_parser.defineOption);
+ m_dependModules = m_parser.values(m_parser.dependsOption);
+ setIndexDirs();
+ setIncludePaths();
+
+ generateExamples = !m_parser.isSet(m_parser.noExamplesOption);
+ if (m_parser.isSet(m_parser.installDirOption))
+ installDir = m_parser.value(m_parser.installDirOption);
+ if (m_parser.isSet(m_parser.outputDirOption))
+ overrideOutputDir = QDir(m_parser.value(m_parser.outputDirOption)).absolutePath();
+
+ const auto outputFormats = m_parser.values(m_parser.outputFormatOption);
+ for (const auto &format : outputFormats)
+ overrideOutputFormats.insert(format);
+ m_debug = m_parser.isSet(m_parser.debugOption) || qEnvironmentVariableIsSet("QDOC_DEBUG");
+ m_atomsDump = m_parser.isSet(m_parser.atomsDumpOption);
+ m_showInternal = m_parser.isSet(m_parser.showInternalOption)
+ || qEnvironmentVariableIsSet("QDOC_SHOW_INTERNAL");
+
+ if (m_parser.isSet(m_parser.prepareOption))
+ m_qdocPass = Prepare;
+ if (m_parser.isSet(m_parser.generateOption))
+ m_qdocPass = Generate;
+ if (m_debug || m_parser.isSet(m_parser.logProgressOption))
+ setStringList(CONFIG_LOGPROGRESS, QStringList("true"));
+ if (m_parser.isSet(m_parser.timestampsOption))
+ setStringList(CONFIG_TIMESTAMPS, QStringList("true"));
+ if (m_parser.isSet(m_parser.useDocBookExtensions))
+ setStringList(CONFIG_DOCBOOKEXTENSIONS, QStringList("true"));
+}
+
+void Config::setIncludePaths()
+{
+ QDir currentDir = QDir::current();
+ const auto addIncludePaths = [this, currentDir](const char *flag, const QStringList &paths) {
+ for (const auto &path : paths)
+ m_includePaths << currentDir.absoluteFilePath(path).insert(0, flag);
+ };
+
+ addIncludePaths("-I", m_parser.values(m_parser.includePathOption));
+#ifdef QDOC_PASS_ISYSTEM
+ addIncludePaths("-isystem", m_parser.values(m_parser.includePathSystemOption));
+#endif
+ addIncludePaths("-F", m_parser.values(m_parser.frameworkOption));
+}
+
+/*!
+ Stores paths from -indexdir command line option(s).
+ */
+void Config::setIndexDirs()
+{
+ m_indexDirs = m_parser.values(m_parser.indexDirOption);
+ auto it = std::remove_if(m_indexDirs.begin(), m_indexDirs.end(),
+ [](const QString &s) { return !QFile::exists(s); });
+
+ std::for_each(it, m_indexDirs.end(), [](const QString &s) {
+ qCWarning(lcQdoc) << "Cannot find index directory: " << s;
+ });
+ m_indexDirs.erase(it, m_indexDirs.end());
+}
+
+/*!
+ Function to return the correct outputdir for the output \a format.
+ If \a format is not specified, defaults to 'HTML'.
+ outputdir can be set using the qdocconf or the command-line
+ variable -outputdir.
+ */
+QString Config::getOutputDir(const QString &format) const
+{
+ QString t;
+ if (overrideOutputDir.isNull())
+ t = m_configVars.value(CONFIG_OUTPUTDIR).asString();
+ else
+ t = overrideOutputDir;
+ if (m_configVars.value(CONFIG_SINGLEEXEC).asBool()) {
+ QString project = m_configVars.value(CONFIG_PROJECT).asString();
+ t += QLatin1Char('/') + project.toLower();
+ }
+ if (m_configVars.value(format + Config::dot + "nosubdirs").asBool()) {
+ QString singleOutputSubdir = m_configVars.value(format + Config::dot + "outputsubdir").asString();
+ if (singleOutputSubdir.isEmpty())
+ singleOutputSubdir = "html";
+ t += QLatin1Char('/') + singleOutputSubdir;
+ }
+ return QDir::cleanPath(t);
+}
+
+/*!
+ Function to return the correct outputformats.
+ outputformats can be set using the qdocconf or the command-line
+ variable -outputformat.
+ */
+QSet<QString> Config::getOutputFormats() const
+{
+ if (overrideOutputFormats.isEmpty())
+ return m_configVars.value(CONFIG_OUTPUTFORMATS).asStringSet();
+ else
+ return overrideOutputFormats;
+}
+
+// TODO: [late-canonicalization][pod-configuration]
+// The canonicalization for paths is done at the time where they are
+// required, and done each time they are requested.
+// Instead, config should be parsed to an intermediate format that is
+// a POD type that already contains canonicalized representations for
+// each element.
+// Those representations should provide specific guarantees about
+// their format and be representable at the API boundaries.
+//
+// This would ensure that the correct canonicalization is always
+// applied, is applied only once and that dependent sub-logics can be
+// written in a way that doesn't require branching or futher
+// canonicalization.
+
+/*!
+ Returns a path list where all paths from the config variable \a var
+ are canonicalized. If \a flags contains \c Validate, outputs a warning
+ for invalid paths. The \c IncludePaths flag is used as a hint to strip
+ away potential prefixes found in include paths before attempting to
+ canonicalize.
+ */
+QStringList Config::getCanonicalPathList(const QString &var, PathFlags flags) const
+{
+ QStringList result;
+ const auto &configVar = m_configVars.value(var);
+
+ for (const auto &value : configVar.m_values) {
+ const QString &currentPath = value.m_path;
+ QString rawValue = value.m_value.simplified();
+ QString prefix;
+
+ if (flags & IncludePaths) {
+ const QStringList prefixes = QStringList()
+ << QLatin1String("-I")
+ << QLatin1String("-F")
+ << QLatin1String("-isystem");
+ const auto end = std::end(prefixes);
+ const auto it =
+ std::find_if(std::begin(prefixes), end,
+ [&rawValue](const QString &p) {
+ return rawValue.startsWith(p);
+ });
+ if (it != end) {
+ prefix = *it;
+ rawValue.remove(0, it->size());
+ if (rawValue.isEmpty())
+ continue;
+ } else {
+ prefix = prefixes[0]; // -I as default
+ }
+ }
+
+ QDir dir(rawValue.trimmed());
+ const QString path = dir.path();
+
+ if (dir.isRelative())
+ dir.setPath(currentPath + QLatin1Char('/') + path);
+ if ((flags & Validate) && !QFileInfo::exists(dir.path()))
+ configVar.m_location.warning(QStringLiteral("Cannot find file or directory: %1").arg(path));
+ else {
+ const QString canonicalPath = dir.canonicalPath();
+ if (!canonicalPath.isEmpty())
+ result.append(prefix + canonicalPath);
+ else if (path.contains(QLatin1Char('*')) || path.contains(QLatin1Char('?')))
+ result.append(path);
+ else
+ qCDebug(lcQdoc) <<
+ qUtf8Printable(QStringLiteral("%1: Ignored nonexistent path \'%2\'")
+ .arg(configVar.m_location.toString(), rawValue));
+ }
+ }
+ return result;
+}
+
+/*!
+ Calls getRegExpList() with the control variable \a var and
+ iterates through the resulting list of regular expressions,
+ concatenating them with extra characters to form a single
+ QRegularExpression, which is then returned.
+
+ \sa getRegExpList()
+ */
+QRegularExpression Config::getRegExp(const QString &var) const
+{
+ QString pattern;
+ const auto subRegExps = getRegExpList(var);
+
+ for (const auto &regExp : subRegExps) {
+ if (!regExp.isValid())
+ return regExp;
+ if (!pattern.isEmpty())
+ pattern += QLatin1Char('|');
+ pattern += QLatin1String("(?:") + regExp.pattern() + QLatin1Char(')');
+ }
+ if (pattern.isEmpty())
+ pattern = QLatin1String("$x"); // cannot match
+ return QRegularExpression(pattern);
+}
+
+/*!
+ Looks up the configuration variable \a var in the string list
+ map, converts the string list to a list of regular expressions,
+ and returns it.
+ */
+QList<QRegularExpression> Config::getRegExpList(const QString &var) const
+{
+ const QStringList strs = m_configVars.value(var).asStringList();
+ QList<QRegularExpression> regExps;
+ for (const auto &str : strs)
+ regExps += QRegularExpression(str);
+ return regExps;
+}
+
+/*!
+ This function is slower than it could be. What it does is
+ find all the keys that begin with \a var + dot and return
+ the matching keys in a set, stripped of the matching prefix
+ and dot.
+ */
+QSet<QString> Config::subVars(const QString &var) const
+{
+ QSet<QString> result;
+ QString varDot = var + QLatin1Char('.');
+ for (auto it = m_configVars.constBegin(); it != m_configVars.constEnd(); ++it) {
+ if (it.key().startsWith(varDot)) {
+ QString subVar = it.key().mid(varDot.size());
+ int dot = subVar.indexOf(QLatin1Char('.'));
+ if (dot != -1)
+ subVar.truncate(dot);
+ result.insert(subVar);
+ }
+ }
+ return result;
+}
+
+/*!
+ Searches for a path to \a fileName in 'sources', 'sourcedirs', and
+ 'exampledirs' config variables and returns a full path to the first
+ match found. If the file is not found, returns an empty string.
+ */
+QString Config::getIncludeFilePath(const QString &fileName) const
+{
+ QString ext = QFileInfo(fileName).suffix();
+
+ if (!m_includeFilesMap.contains(ext)) {
+ QStringList result = getCanonicalPathList(CONFIG_SOURCES);
+ result.erase(std::remove_if(result.begin(), result.end(),
+ [&](const QString &s) { return !s.endsWith(ext); }),
+ result.end());
+ const QStringList dirs =
+ getCanonicalPathList(CONFIG_SOURCEDIRS) +
+ getCanonicalPathList(CONFIG_EXAMPLEDIRS);
+
+ for (const auto &dir : dirs)
+ result += getFilesHere(dir, "*." + ext, location());
+ result.removeDuplicates();
+ m_includeFilesMap.insert(ext, result);
+ }
+ const QStringList &paths = (*m_includeFilesMap.find(ext));
+ QString match = fileName;
+ if (!match.startsWith('/'))
+ match.prepend('/');
+ for (const auto &path : paths) {
+ if (path.endsWith(match))
+ return path;
+ }
+ return QString();
+}
+
+/*!
+ Builds and returns a list of file pathnames for the file
+ type specified by \a filesVar (e.g. "headers" or "sources").
+ The files are found in the directories specified by
+ \a dirsVar, and they are filtered by \a defaultNameFilter
+ if a better filter can't be constructed from \a filesVar.
+ The directories in \a excludedDirs are avoided. The files
+ in \a excludedFiles are not included in the return list.
+ */
+QStringList Config::getAllFiles(const QString &filesVar, const QString &dirsVar,
+ const QSet<QString> &excludedDirs,
+ const QSet<QString> &excludedFiles)
+{
+ QStringList result = getCanonicalPathList(filesVar, Validate);
+ const QStringList dirs = getCanonicalPathList(dirsVar, Validate);
+
+ const QString nameFilter = m_configVars.value(filesVar + dot + CONFIG_FILEEXTENSIONS).asString();
+
+ for (const auto &dir : dirs)
+ result += getFilesHere(dir, nameFilter, location(), excludedDirs, excludedFiles);
+ return result;
+}
+
+QStringList Config::getExampleQdocFiles(const QSet<QString> &excludedDirs,
+ const QSet<QString> &excludedFiles)
+{
+ QStringList result;
+ const QStringList dirs = getCanonicalPathList("exampledirs");
+ const QString nameFilter = " *.qdoc";
+
+ for (const auto &dir : dirs)
+ result += getFilesHere(dir, nameFilter, location(), excludedDirs, excludedFiles);
+ return result;
+}
+
+QStringList Config::getExampleImageFiles(const QSet<QString> &excludedDirs,
+ const QSet<QString> &excludedFiles)
+{
+ QStringList result;
+ const QStringList dirs = getCanonicalPathList("exampledirs");
+ const QString nameFilter = m_configVars.value(CONFIG_EXAMPLES + dot + CONFIG_IMAGEEXTENSIONS).asString();
+
+ for (const auto &dir : dirs)
+ result += getFilesHere(dir, nameFilter, location(), excludedDirs, excludedFiles);
+ return result;
+}
+
+// TODO: [misplaced-logic][examples][pod-configuration]
+// The definition of how an example is structured and how to find its
+// components should not be part of Config or, for that matter,
+// CppCodeParser, which is the actual caller of this method.
+// Move this method to a more appropriate place as soon as a suitable
+// place is available for it.
+
+/*!
+ Returns the path to the project file for \a examplePath, or an empty string
+ if no project file was found.
+ */
+QString Config::getExampleProjectFile(const QString &examplePath)
+{
+ QFileInfo fileInfo(examplePath);
+ QStringList validNames;
+ validNames << QLatin1String("CMakeLists.txt")
+ << fileInfo.fileName() + QLatin1String(".pro")
+ << fileInfo.fileName() + QLatin1String(".qmlproject")
+ << fileInfo.fileName() + QLatin1String(".pyproject")
+ << QLatin1String("qbuild.pro"); // legacy
+
+ QString projectFile;
+
+ for (const auto &name : std::as_const(validNames)) {
+ projectFile = Config::findFile(Location(), m_exampleFiles, m_exampleDirs,
+ examplePath + QLatin1Char('/') + name);
+ if (!projectFile.isEmpty())
+ return projectFile;
+ }
+
+ return projectFile;
+}
+
+// TODO: [pod-configuration]
+// Remove findFile completely from the configuration.
+// External usages of findFile were already removed but a last caller
+// of this method exists internally to Config in
+// `getExampleProjectFile`.
+// That method has to be removed at some point and this method should
+// go with it.
+// Do notice that FileResolver is the replacement for findFile but it
+// is designed, for now, with a scope that does only care about the
+// usages of findFile that are outside the Config class.
+// More specifically, it was designed to replace only the uses of
+// findFile that deal with user provided queries or queries related to
+// that.
+// The logic that is used internally in Config is the same, but has a
+// different conceptual meaning.
+// When findFile is permanently removed, it must be considered whether
+// FileResolver itself should be used for the same logic or not.
+
+/*!
+ \a fileName is the path of the file to find.
+
+ \a files and \a dirs are the lists where we must find the
+ components of \a fileName.
+
+ \a location is used for obtaining the file and line numbers
+ for report qdoc errors.
+ */
+QString Config::findFile(const Location &location, const QStringList &files,
+ const QStringList &dirs, const QString &fileName,
+ QString *userFriendlyFilePath)
+{
+ if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
+ if (userFriendlyFilePath)
+ *userFriendlyFilePath = fileName;
+ return fileName;
+ }
+
+ QFileInfo fileInfo;
+ QStringList components = fileName.split(QLatin1Char('?'));
+ QString firstComponent = components.first();
+
+ for (const auto &file : files) {
+ if (file == firstComponent || file.endsWith(QLatin1Char('/') + firstComponent)) {
+ fileInfo.setFile(file);
+ if (!fileInfo.exists())
+ location.fatal(QStringLiteral("File '%1' does not exist").arg(file));
+ break;
+ }
+ }
+
+ if (fileInfo.fileName().isEmpty()) {
+ for (const auto &dir : dirs) {
+ fileInfo.setFile(QDir(dir), firstComponent);
+ if (fileInfo.exists())
+ break;
+ }
+ }
+
+ if (userFriendlyFilePath)
+ userFriendlyFilePath->clear();
+ if (!fileInfo.exists())
+ return QString();
+
+ // <<REMARK: This is actually dead code. It is unclear what it tries
+ // to do and why but its usage is unnecessary in the current
+ // codebase.
+ // Indeed, the whole concept of the "userFriendlyFilePath" is
+ // removed for file searching.
+ // It will be removed directly with the whole of findFile, but it
+ // should not be considered anymore until then.
+ if (userFriendlyFilePath) {
+ for (auto c = components.constBegin();;) {
+ bool isArchive = (c != components.constEnd() - 1);
+ userFriendlyFilePath->append(*c);
+
+ if (isArchive) {
+ QString extracted = m_extractedDirs[fileInfo.filePath()];
+
+ ++c;
+ fileInfo.setFile(QDir(extracted), *c);
+ } else {
+ break;
+ }
+
+ userFriendlyFilePath->append(QLatin1Char('?'));
+ }
+ }
+ // REMARK>>
+
+ return fileInfo.filePath();
+}
+
+// TODO: [pod-configuration]
+// An intermediate representation for the configuration should only
+// contain data that will later be destructured into subsystem that
+// care about specific subsets of the configuration and can carry that
+// information with them, uniquely.
+// Remove copyFile, moving it into whatever will have the unique
+// resposability of knowing how to build an output directory for a
+// QDoc execution.
+// Should copy file being used for not only copying file to the build
+// output directory, split its responsabilities into smaller elements
+// instead of forcing the logic together.
+
+/*!
+ Copies the \a sourceFilePath to the file name constructed by
+ concatenating \a targetDirPath and the file name from the
+ \a userFriendlySourceFilePath. \a location is for identifying
+ the file and line number where a qdoc error occurred. The
+ constructed output file name is returned.
+ */
+QString Config::copyFile(const Location &location, const QString &sourceFilePath,
+ const QString &userFriendlySourceFilePath, const QString &targetDirPath)
+{
+ // TODO: A copying operation should only be performed on files
+ // that we assume to be available. Ensure that this is true at the
+ // API boundary and bubble up the error checking and reporting to
+ // call-site users. Possibly this will be as simple as
+ // ResolvedFile, but could not be done at the time of the introduction of
+ // that type as we first need to encapsulate the logic for
+ // copying files into an appropriate subsystem and have a better
+ // understanding of call-site usages.
+
+ QFile inFile(sourceFilePath);
+ if (!inFile.open(QFile::ReadOnly)) {
+ location.warning(QStringLiteral("Cannot open input file for copy: '%1': %2")
+ .arg(sourceFilePath, inFile.errorString()));
+ return QString();
+ }
+
+ // TODO: [non-canonical-representation]
+ // Similar to other part of QDoc, we do a series of non-intuitive
+ // checks to canonicalize some multi-format parameter into
+ // something we can use.
+ // Understand which of those formats are actually in use and
+ // provide a canonicalized version that can be requested at the
+ // API boundary to ensure that correct formatting is used.
+ // If possible, gradually bubble up the canonicalization until a
+ // single entry-point in the program exists where the
+ // canonicalization can be processed to avoid complicating
+ // intermediate steps.
+ // ADDENDUM 1: At least one usage of this seems to depend on the
+ // processing done for files coming from
+ // Generator::copyTemplateFile, which are expressed as absolute
+ // paths. This seems to be the only usage that is currently
+ // needed, hence a temporary new implementation is provided that
+ // only takes this case into account.
+ // Do notice that we assume that in this case we always want a
+ // flat structure, that is, we are copying the file as a direct
+ // child of the target directory.
+ // Nonetheless, it is possible that this case will not be needed,
+ // such that it can be removed later on, or that it will be nedeed
+ // in multiple places such that an higher level interface for it
+ // should be provided.
+ // Furthermoe, it might be possible that there is an edge case
+ // that is now not considered, as it is unknown, that was
+ // considered before.
+ // As it is now unclear what kind of paths are used here, what
+ // format they have, why they are used and why they have some
+ // specific format, further processing is avoided but a more
+ // torough overview of what should is needed must be done when
+ // more information are gathered and this function is extracted
+ // away from config.
+
+ QString outFileName{userFriendlySourceFilePath};
+ QFileInfo outFileNameInfo{userFriendlySourceFilePath};
+ if (outFileNameInfo.isAbsolute())
+ outFileName = outFileNameInfo.fileName();
+
+ outFileName = targetDirPath + "/" + outFileName;
+ QDir targetDir(targetDirPath);
+ if (!targetDir.exists())
+ targetDir.mkpath(".");
+
+ QFile outFile(outFileName);
+ if (!outFile.open(QFile::WriteOnly)) {
+ // TODO: [uncrentralized-warning]
+ location.warning(QStringLiteral("Cannot open output file for copy: '%1': %2")
+ .arg(outFileName, outFile.errorString()));
+ return QString();
+ }
+
+ // TODO: There shouldn't be any particular advantage to copying
+ // the file by readying its content and writing it compared to
+ // asking the underlying system to do the copy for us.
+ // Consider simplifying this part by avoiding doing the manual
+ // work ourselves.
+
+ char buffer[1024];
+ qsizetype len;
+ while ((len = inFile.read(buffer, sizeof(buffer))) > 0)
+ outFile.write(buffer, len);
+ return outFileName;
+}
+
+/*!
+ Finds the largest unicode digit in \a value in the range
+ 1..7 and returns it.
+ */
+int Config::numParams(const QString &value)
+{
+ int max = 0;
+ for (int i = 0; i != value.size(); ++i) {
+ uint c = value[i].unicode();
+ if (c > 0 && c < 8)
+ max = qMax(max, static_cast<int>(c));
+ }
+ return max;
+}
+
+/*!
+ Returns \c true if \a ch is a letter, number, '_', '.',
+ '{', '}', or ','.
+ */
+bool Config::isMetaKeyChar(QChar ch)
+{
+ return ch.isLetterOrNumber() || ch == QLatin1Char('_') || ch == QLatin1Char('.')
+ || ch == QLatin1Char('{') || ch == QLatin1Char('}') || ch == QLatin1Char(',');
+}
+
+/*!
+ \a fileName is a master qdocconf file. It contains a list of
+ qdocconf files and nothing else. Read the list and return it.
+ */
+QStringList Config::loadMaster(const QString &fileName)
+{
+ Location location;
+ QFile fin(fileName);
+ if (!fin.open(QFile::ReadOnly | QFile::Text)) {
+ if (!Config::installDir.isEmpty()) {
+ qsizetype prefix = location.filePath().size() - location.fileName().size();
+ fin.setFileName(Config::installDir + QLatin1Char('/')
+ + fileName.right(fileName.size() - prefix));
+ }
+ if (!fin.open(QFile::ReadOnly | QFile::Text))
+ location.fatal(QStringLiteral("Cannot open master qdocconf file '%1': %2")
+ .arg(fileName, fin.errorString()));
+ }
+ QTextStream stream(&fin);
+ QStringList qdocFiles;
+ QDir configDir(QFileInfo(fileName).canonicalPath());
+ QString line = stream.readLine();
+ while (!line.isNull()) {
+ if (!line.isEmpty())
+ qdocFiles.append(QFileInfo(configDir, line).filePath());
+ line = stream.readLine();
+ }
+ fin.close();
+ return qdocFiles;
+}
+
+/*!
+ Load, parse, and process a qdoc configuration file. This
+ function is only called by the other load() function, but
+ this one is recursive, i.e., it calls itself when it sees
+ an \c{include} statement in the qdoc configuration file.
+ */
+void Config::load(Location location, const QString &fileName)
+{
+ QFileInfo fileInfo(fileName);
+ pushWorkingDir(fileInfo.canonicalPath());
+ static const QRegularExpression keySyntax(QRegularExpression::anchoredPattern(QLatin1String("\\w+(?:\\.\\w+)*")));
+
+#define SKIP_CHAR() \
+ do { \
+ location.advance(c); \
+ ++i; \
+ c = text.at(i); \
+ cc = c.unicode(); \
+ } while (0)
+
+#define SKIP_SPACES() \
+ while (c.isSpace() && cc != '\n') \
+ SKIP_CHAR()
+
+#define PUT_CHAR() \
+ word += c; \
+ SKIP_CHAR();
+
+ if (location.depth() > 16)
+ location.fatal(QStringLiteral("Too many nested includes"));
+
+ QFile fin(fileInfo.fileName());
+ if (!fin.open(QFile::ReadOnly | QFile::Text)) {
+ if (!Config::installDir.isEmpty()) {
+ qsizetype prefix = location.filePath().size() - location.fileName().size();
+ fin.setFileName(Config::installDir + QLatin1Char('/')
+ + fileName.right(fileName.size() - prefix));
+ }
+ if (!fin.open(QFile::ReadOnly | QFile::Text))
+ location.fatal(
+ QStringLiteral("Cannot open file '%1': %2").arg(fileName, fin.errorString()));
+ }
+
+ QTextStream stream(&fin);
+ QString text = stream.readAll();
+ text += QLatin1String("\n\n");
+ text += QLatin1Char('\0');
+ fin.close();
+
+ location.push(fileName);
+ location.start();
+
+ int i = 0;
+ QChar c = text.at(0);
+ uint cc = c.unicode();
+ while (i < text.size()) {
+ if (cc == 0) {
+ ++i;
+ } else if (c.isSpace()) {
+ SKIP_CHAR();
+ } else if (cc == '#') {
+ do {
+ SKIP_CHAR();
+ } while (cc != '\n');
+ } else if (isMetaKeyChar(c)) {
+ Location keyLoc = location;
+ bool plus = false;
+ QStringList rhsValues;
+ QList<ExpandVar> expandVars;
+ QString word;
+ bool inQuote = false;
+ bool needsExpansion = false;
+
+ MetaStack stack;
+ do {
+ stack.process(c, location);
+ SKIP_CHAR();
+ } while (isMetaKeyChar(c));
+
+ const QStringList keys = stack.getExpanded(location);
+ SKIP_SPACES();
+
+ if (keys.size() == 1 && keys.first() == QLatin1String("include")) {
+ QString includeFile;
+
+ if (cc != '(')
+ location.fatal(QStringLiteral("Bad include syntax"));
+ SKIP_CHAR();
+ SKIP_SPACES();
+
+ while (!c.isSpace() && cc != '#' && cc != ')') {
+
+ if (cc == '$') {
+ QString var;
+ SKIP_CHAR();
+ while (c.isLetterOrNumber() || cc == '_') {
+ var += c;
+ SKIP_CHAR();
+ }
+ if (!var.isEmpty()) {
+ const QByteArray val = qgetenv(var.toLatin1().data());
+ if (val.isNull()) {
+ location.fatal(QStringLiteral("Environment variable '%1' undefined")
+ .arg(var));
+ } else {
+ includeFile += QString::fromLatin1(val);
+ }
+ }
+ } else {
+ includeFile += c;
+ SKIP_CHAR();
+ }
+ }
+ SKIP_SPACES();
+ if (cc != ')')
+ location.fatal(QStringLiteral("Bad include syntax"));
+ SKIP_CHAR();
+ SKIP_SPACES();
+ if (cc != '#' && cc != '\n')
+ location.fatal(QStringLiteral("Trailing garbage"));
+
+ /*
+ Here is the recursive call.
+ */
+ load(location, QFileInfo(QDir(m_workingDirs.top()), includeFile).filePath());
+ } else {
+ /*
+ It wasn't an include statement, so it's something else.
+ We must see either '=' or '+=' next. If not, fatal error.
+ */
+ if (cc == '+') {
+ plus = true;
+ SKIP_CHAR();
+ }
+ if (cc != '=')
+ location.fatal(QStringLiteral("Expected '=' or '+=' after key"));
+ SKIP_CHAR();
+ SKIP_SPACES();
+
+ for (;;) {
+ if (cc == '\\') {
+ qsizetype metaCharPos;
+
+ SKIP_CHAR();
+ if (cc == '\n') {
+ SKIP_CHAR();
+ } else if (cc > '0' && cc < '8') {
+ word += QChar(c.digitValue());
+ SKIP_CHAR();
+ } else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c))
+ != -1) {
+ word += QLatin1Char("\a\b\f\n\r\t\v"[metaCharPos]);
+ SKIP_CHAR();
+ } else {
+ PUT_CHAR();
+ }
+ } else if (c.isSpace() || cc == '#') {
+ if (inQuote) {
+ if (cc == '\n')
+ location.fatal(QStringLiteral("Unterminated string"));
+ PUT_CHAR();
+ } else {
+ if (!word.isEmpty() || needsExpansion) {
+ rhsValues << word;
+ word.clear();
+ needsExpansion = false;
+ }
+ if (cc == '\n' || cc == '#')
+ break;
+ SKIP_SPACES();
+ }
+ } else if (cc == '"') {
+ if (inQuote) {
+ if (!word.isEmpty() || needsExpansion)
+ rhsValues << word;
+ word.clear();
+ needsExpansion = false;
+ }
+ inQuote = !inQuote;
+ SKIP_CHAR();
+ } else if (cc == '$') {
+ QString var;
+ QChar delim(' ');
+ bool braces = false;
+ SKIP_CHAR();
+ if (cc == '{') {
+ SKIP_CHAR();
+ braces = true;
+ }
+ while (c.isLetterOrNumber() || cc == '_') {
+ var += c;
+ SKIP_CHAR();
+ }
+ if (braces) {
+ if (cc == ',') {
+ SKIP_CHAR();
+ delim = c;
+ SKIP_CHAR();
+ }
+ if (cc == '}')
+ SKIP_CHAR();
+ else if (delim == '}')
+ delim = QChar(); // null delimiter
+ else
+ location.fatal(QStringLiteral("Missing '}'"));
+ }
+ if (!var.isEmpty()) {
+ const QByteArray val = qgetenv(var.toLatin1().constData());
+ if (val.isNull()) {
+ expandVars << ExpandVar(rhsValues.size(), word.size(), var, delim);
+ needsExpansion = true;
+ } else if (braces) { // ${VAR} inserts content from an env. variable for processing
+ text.insert(i, QString::fromLatin1(val));
+ c = text.at(i);
+ cc = c.unicode();
+ } else { // while $VAR simply reads the value and stores it to a config variable.
+ word += QString::fromLatin1(val);
+ }
+ }
+ } else {
+ if (!inQuote && cc == '=')
+ location.fatal(QStringLiteral("Unexpected '='"));
+ PUT_CHAR();
+ }
+ }
+ for (const auto &key : keys) {
+ if (!keySyntax.match(key).hasMatch())
+ keyLoc.fatal(QStringLiteral("Invalid key '%1'").arg(key));
+
+ ConfigVar configVar(key, rhsValues, QDir::currentPath(), keyLoc, expandVars);
+ if (plus && m_configVars.contains(key)) {
+ m_configVars[key].append(configVar);
+ } else {
+ m_configVars.insert(key, configVar);
+ }
+ }
+ }
+ } else {
+ location.fatal(QStringLiteral("Unexpected character '%1' at beginning of line").arg(c));
+ }
+ }
+ popWorkingDir();
+
+#undef SKIP_CHAR
+#undef SKIP_SPACES
+#undef PUT_CHAR
+}
+
+bool Config::isFileExcluded(const QString &fileName, const QSet<QString> &excludedFiles)
+{
+ for (const QString &entry : excludedFiles) {
+ if (entry.contains(QLatin1Char('*')) || entry.contains(QLatin1Char('?'))) {
+ QRegularExpression re(QRegularExpression::wildcardToRegularExpression(entry));
+ if (re.match(fileName).hasMatch())
+ return true;
+ }
+ }
+ return excludedFiles.contains(fileName);
+}
+
+QStringList Config::getFilesHere(const QString &uncleanDir, const QString &nameFilter,
+ const Location &location, const QSet<QString> &excludedDirs,
+ const QSet<QString> &excludedFiles)
+{
+ // TODO: Understand why location is used to branch the
+ // canonicalization and why the two different methods are used.
+ QString dir =
+ location.isEmpty() ? QDir::cleanPath(uncleanDir) : QDir(uncleanDir).canonicalPath();
+ QStringList result;
+ if (excludedDirs.contains(dir))
+ return result;
+
+ QDir dirInfo(dir);
+
+ dirInfo.setNameFilters(nameFilter.split(QLatin1Char(' ')));
+ dirInfo.setSorting(QDir::Name);
+ dirInfo.setFilter(QDir::Files);
+ QStringList fileNames = dirInfo.entryList();
+ for (const auto &file : std::as_const(fileNames)) {
+ // TODO: Understand if this is needed and, should it be, if it
+ // is indeed the only case that should be considered.
+ if (!file.startsWith(QLatin1Char('~'))) {
+ QString s = dirInfo.filePath(file);
+ QString c = QDir::cleanPath(s);
+ if (!isFileExcluded(c, excludedFiles))
+ result.append(c);
+ }
+ }
+
+ dirInfo.setNameFilters(QStringList(QLatin1String("*")));
+ dirInfo.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
+ fileNames = dirInfo.entryList();
+ for (const auto &file : fileNames)
+ result += getFilesHere(dirInfo.filePath(file), nameFilter, location, excludedDirs,
+ excludedFiles);
+ return result;
+}
+
+/*!
+ Set \a dir as the working directory and push it onto the
+ stack of working directories.
+ */
+void Config::pushWorkingDir(const QString &dir)
+{
+ m_workingDirs.push(dir);
+ QDir::setCurrent(dir);
+}
+
+/*!
+ Pop the top entry from the stack of working directories.
+ Set the working directory to the next one on the stack,
+ if one exists.
+ */
+void Config::popWorkingDir()
+{
+ Q_ASSERT(!m_workingDirs.isEmpty());
+ m_workingDirs.pop();
+ if (!m_workingDirs.isEmpty())
+ QDir::setCurrent(m_workingDirs.top());
+}
+
+const Config::ExcludedPaths& Config::getExcludedPaths() {
+ if (m_excludedPaths)
+ return *m_excludedPaths;
+
+ const auto &excludedDirList = getCanonicalPathList(CONFIG_EXCLUDEDIRS);
+ const auto &excludedFilesList = getCanonicalPathList(CONFIG_EXCLUDEFILES);
+
+ QSet<QString> excludedDirs = QSet<QString>(excludedDirList.cbegin(), excludedDirList.cend());
+ QSet<QString> excludedFiles = QSet<QString>(excludedFilesList.cbegin(), excludedFilesList.cend());
+
+ m_excludedPaths.emplace(ExcludedPaths{excludedDirs, excludedFiles});
+
+ return *m_excludedPaths;
+}
+
+std::set<Config::HeaderFilePath> Config::getHeaderFiles() {
+ static QStringList accepted_header_file_extensions{
+ "ch", "h", "h++", "hh", "hpp", "hxx"
+ };
+
+ const auto& [excludedDirs, excludedFiles] = getExcludedPaths();
+
+ QStringList headerList =
+ getAllFiles(CONFIG_HEADERS, CONFIG_HEADERDIRS, excludedDirs, excludedFiles);
+
+ std::set<HeaderFilePath> headers{};
+
+ for (const auto& header : headerList) {
+ if (header.contains("doc/snippets")) continue;
+
+ if (!accepted_header_file_extensions.contains(QFileInfo{header}.suffix()))
+ continue;
+
+ headers.insert(HeaderFilePath{QFileInfo{header}.canonicalPath(), QFileInfo{header}.fileName()});
+ }
+
+ return headers;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/config.h b/src/qdoc/qdoc/src/qdoc/config.h
new file mode 100644
index 000000000..30dad4746
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/config.h
@@ -0,0 +1,409 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include "location.h"
+#include "qdoccommandlineparser.h"
+#include "singleton.h"
+
+#include <QtCore/qmap.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qstringlist.h>
+
+#include <set>
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+class Config;
+
+/*
+ Contains information about a location
+ where a ConfigVar string needs to be expanded
+ from another config variable.
+*/
+struct ExpandVar
+{
+ int m_valueIndex {};
+ int m_index {};
+ QString m_var {};
+ QChar m_delim {};
+
+ ExpandVar(int valueIndex, int index, QString var, const QChar &delim)
+ : m_valueIndex(valueIndex), m_index(index), m_var(std::move(var)), m_delim(delim)
+ {
+ }
+};
+
+class ConfigVar
+{
+public:
+ struct ConfigValue {
+ QString m_value;
+ QString m_path;
+ };
+
+ [[nodiscard]] QString asString(const QString defaultString = QString()) const;
+ [[nodiscard]] QStringList asStringList() const;
+ [[nodiscard]] QSet<QString> asStringSet() const;
+ [[nodiscard]] bool asBool() const;
+ [[nodiscard]] int asInt() const;
+ [[nodiscard]] const Location &location() const { return m_location; }
+
+ ConfigVar() = default;
+ ConfigVar(QString name, const QStringList &values, const QString &dir,
+ const Location &loc = Location(),
+ const QList<ExpandVar> &expandVars = QList<ExpandVar>())
+ : m_name(std::move(name)), m_location(loc), m_expandVars(expandVars)
+ {
+ for (const auto &v : values)
+ m_values << ConfigValue {v, dir};
+ }
+
+private:
+ void append(const ConfigVar &other);
+
+private:
+ QString m_name {};
+ QList<ConfigValue> m_values {};
+ Location m_location {};
+ QList<ExpandVar> m_expandVars {};
+
+ friend class Config;
+};
+
+/*
+ In this multimap, the key is a config variable name.
+ */
+typedef QMap<QString, ConfigVar> ConfigVarMap;
+
+class Config : public Singleton<Config>
+{
+public:
+ ~Config();
+
+ enum QDocPass { Neither, Prepare, Generate };
+
+ enum PathFlags : unsigned char {
+ None = 0x0,
+ // TODO: [unenforced-unclear-validation]
+ // The Validate flag is used, for example, during the retrival
+ // of paths in getCanonicalPathList.
+ // It is unclear what kind of validation it performs, if any,
+ // and when this validation is required.
+ // Instead, remove this kind of flag and ensure that any
+ // amount of required validation is performed during the
+ // parsing step, if possilbe, and only once.
+ // Furthemore, ensure any such validation removes some
+ // uncertainty on dependent subsystems, moving constraints to
+ // preconditions and expressing them at the API boundaries.
+ Validate = 0x1,
+ IncludePaths = 0x2
+ };
+
+ void init(const QString &programName, const QStringList &args);
+ [[nodiscard]] bool getDebug() const { return m_debug; }
+ [[nodiscard]] bool getAtomsDump() const { return m_atomsDump; }
+ [[nodiscard]] bool showInternal() const { return m_showInternal; }
+
+ void clear();
+ void reset();
+ void load(const QString &fileName);
+ void setStringList(const QString &var, const QStringList &values);
+ void insertStringList(const QString &var, const QStringList &values);
+
+ void showHelp(int exitCode = 0) { m_parser.showHelp(exitCode); }
+ [[nodiscard]] QStringList qdocFiles() const { return m_parser.positionalArguments(); }
+ [[nodiscard]] const QString &programName() const { return m_prog; }
+ [[nodiscard]] const Location &location() const { return m_location; }
+ [[nodiscard]] const ConfigVar &get(const QString &var) const
+ {
+ // Avoid injecting default-constructed values to map if var doesn't exist
+ static ConfigVar empty;
+ auto it = m_configVars.constFind(var);
+ return (it != m_configVars.constEnd()) ? *it : empty;
+ }
+ [[nodiscard]] QString getOutputDir(const QString &format = QString("HTML")) const;
+ [[nodiscard]] QSet<QString> getOutputFormats() const;
+ [[nodiscard]] QStringList getCanonicalPathList(const QString &var,
+ PathFlags flags = None) const;
+ [[nodiscard]] QRegularExpression getRegExp(const QString &var) const;
+ [[nodiscard]] QList<QRegularExpression> getRegExpList(const QString &var) const;
+ [[nodiscard]] QSet<QString> subVars(const QString &var) const;
+ QStringList getAllFiles(const QString &filesVar, const QString &dirsVar,
+ const QSet<QString> &excludedDirs = QSet<QString>(),
+ const QSet<QString> &excludedFiles = QSet<QString>());
+ [[nodiscard]] QString getIncludeFilePath(const QString &fileName) const;
+ QStringList getExampleQdocFiles(const QSet<QString> &excludedDirs,
+ const QSet<QString> &excludedFiles);
+ QStringList getExampleImageFiles(const QSet<QString> &excludedDirs,
+ const QSet<QString> &excludedFiles);
+ QString getExampleProjectFile(const QString &examplePath);
+
+ static QStringList loadMaster(const QString &fileName);
+ static bool isFileExcluded(const QString &fileName, const QSet<QString> &excludedFiles);
+ static QStringList getFilesHere(const QString &dir, const QString &nameFilter,
+ const Location &location = Location(),
+ const QSet<QString> &excludedDirs = QSet<QString>(),
+ const QSet<QString> &excludedFiles = QSet<QString>());
+ static QString findFile(const Location &location, const QStringList &files,
+ const QStringList &dirs, const QString &fileName,
+ QString *userFriendlyFilePath = nullptr);
+ static QString copyFile(const Location &location, const QString &sourceFilePath,
+ const QString &userFriendlySourceFilePath,
+ const QString &targetDirPath);
+ static int numParams(const QString &value);
+ static void pushWorkingDir(const QString &dir);
+ static void popWorkingDir();
+
+ static const QString dot;
+
+ static bool generateExamples;
+ static QString installDir;
+ static QString overrideOutputDir;
+ static QSet<QString> overrideOutputFormats;
+
+ [[nodiscard]] inline bool singleExec() const;
+ [[nodiscard]] inline bool dualExec() const;
+ QStringList &defines() { return m_defines; }
+ QStringList &dependModules() { return m_dependModules; }
+ QStringList &includePaths() { return m_includePaths; }
+ QStringList &indexDirs() { return m_indexDirs; }
+ [[nodiscard]] QString currentDir() const { return m_currentDir; }
+ void setCurrentDir(const QString &path) { m_currentDir = path; }
+ [[nodiscard]] QString previousCurrentDir() const { return m_previousCurrentDir; }
+ void setPreviousCurrentDir(const QString &path) { m_previousCurrentDir = path; }
+
+ void setQDocPass(const QDocPass &pass) { m_qdocPass = pass; };
+ [[nodiscard]] bool preparing() const { return (m_qdocPass == Prepare); }
+ [[nodiscard]] bool generating() const { return (m_qdocPass == Generate); }
+
+ struct ExcludedPaths {
+ QSet<QString> excluded_directories;
+ QSet<QString> excluded_files;
+ };
+ const ExcludedPaths& getExcludedPaths();
+
+ struct HeaderFilePath {
+ QString path;
+ QString filename;
+
+ friend bool operator<(const HeaderFilePath& lhs, const HeaderFilePath& rhs) {
+ return std::tie(lhs.path, lhs.filename) < std::tie(rhs.path, rhs.filename);
+ }
+ };
+ std::set<HeaderFilePath> getHeaderFiles();
+
+private:
+ void processCommandLineOptions(const QStringList &args);
+ void setIncludePaths();
+ void setIndexDirs();
+ void expandVariables();
+
+ QStringList m_dependModules {};
+ QStringList m_defines {};
+ QStringList m_includePaths {};
+ QStringList m_indexDirs {};
+ QStringList m_exampleFiles {};
+ QStringList m_exampleDirs {};
+ QString m_currentDir {};
+ QString m_previousCurrentDir {};
+ std::optional<ExcludedPaths> m_excludedPaths{};
+
+ bool m_showInternal { false };
+ static bool m_debug;
+
+ // An option that can be set trough a similarly named command-line option.
+ // When this is set, every time QDoc parses a block-comment, a
+ // human-readable presentation of the `Atom`s structure for that
+ // block will shown to the user.
+ static bool m_atomsDump;
+
+ static bool isMetaKeyChar(QChar ch);
+ void load(Location location, const QString &fileName);
+
+ QString m_prog {};
+ Location m_location {};
+ ConfigVarMap m_configVars {};
+
+ static QMap<QString, QString> m_extractedDirs;
+ static QStack<QString> m_workingDirs;
+ static QMap<QString, QStringList> m_includeFilesMap;
+ QDocCommandLineParser m_parser {};
+
+ QDocPass m_qdocPass { Neither };
+};
+
+struct ConfigStrings
+{
+ static QString ALIAS;
+ static QString AUTOLINKERRORS;
+ static QString BUILDVERSION;
+ static QString CODEINDENT;
+ static QString CODEPREFIX;
+ static QString CODESUFFIX;
+ static QString CPPCLASSESPAGE;
+ static QString CPPCLASSESTITLE;
+ static QString DEFINES;
+ static QString DEPENDS;
+ static QString DESCRIPTION;
+ static QString DOCBOOKEXTENSIONS;
+ static QString ENDHEADER;
+ static QString EXAMPLEDIRS;
+ static QString EXAMPLES;
+ static QString EXAMPLESINSTALLPATH;
+ static QString EXCLUDEDIRS;
+ static QString EXCLUDEFILES;
+ static QString EXTRAIMAGES;
+ static QString FALSEHOODS;
+ static QString FORMATTING;
+ static QString HEADERDIRS;
+ static QString HEADERS;
+ static QString HEADERSCRIPTS;
+ static QString HEADERSTYLES;
+ static QString HOMEPAGE;
+ static QString HOMETITLE;
+ static QString IGNOREDIRECTIVES;
+ static QString IGNORETOKENS;
+ static QString IGNORESINCE;
+ static QString IGNOREWORDS;
+ static QString IMAGEDIRS;
+ static QString IMAGES;
+ static QString INCLUDEPATHS;
+ static QString INCLUSIVE;
+ static QString INDEXES;
+ static QString LANDINGPAGE;
+ static QString LANDINGTITLE;
+ static QString LANGUAGE;
+ static QString LOCATIONINFO;
+ static QString LOGPROGRESS;
+ static QString MACRO;
+ static QString MANIFESTMETA;
+ static QString MODULEHEADER;
+ static QString NATURALLANGUAGE;
+ static QString NAVIGATION;
+ static QString NOLINKERRORS;
+ static QString OUTPUTDIR;
+ static QString OUTPUTFORMATS;
+ static QString OUTPUTPREFIXES;
+ static QString OUTPUTSUFFIXES;
+ static QString PROJECT;
+ static QString REDIRECTDOCUMENTATIONTODEVNULL;
+ static QString QHP;
+ static QString QUOTINGINFORMATION;
+ static QString SCRIPTS;
+ static QString SHOWINTERNAL;
+ static QString SINGLEEXEC;
+ static QString SOURCEDIRS;
+ static QString SOURCEENCODING;
+ static QString SOURCES;
+ static QString SPURIOUS;
+ static QString STYLESHEETS;
+ static QString SYNTAXHIGHLIGHTING;
+ static QString TABSIZE;
+ static QString TAGFILE;
+ static QString TIMESTAMPS;
+ static QString TOCTITLES;
+ static QString TRADEMARKSPAGE;
+ static QString URL;
+ static QString VERSION;
+ static QString VERSIONSYM;
+ static QString FILEEXTENSIONS;
+ static QString IMAGEEXTENSIONS;
+ static QString QMLTYPESPAGE;
+ static QString QMLTYPESTITLE;
+ static QString WARNINGLIMIT;
+};
+
+#define CONFIG_AUTOLINKERRORS ConfigStrings::AUTOLINKERRORS
+#define CONFIG_BUILDVERSION ConfigStrings::BUILDVERSION
+#define CONFIG_CODEINDENT ConfigStrings::CODEINDENT
+#define CONFIG_CODEPREFIX ConfigStrings::CODEPREFIX
+#define CONFIG_CODESUFFIX ConfigStrings::CODESUFFIX
+#define CONFIG_CPPCLASSESPAGE ConfigStrings::CPPCLASSESPAGE
+#define CONFIG_CPPCLASSESTITLE ConfigStrings::CPPCLASSESTITLE
+#define CONFIG_DEFINES ConfigStrings::DEFINES
+#define CONFIG_DEPENDS ConfigStrings::DEPENDS
+#define CONFIG_DESCRIPTION ConfigStrings::DESCRIPTION
+#define CONFIG_DOCBOOKEXTENSIONS ConfigStrings::DOCBOOKEXTENSIONS
+#define CONFIG_ENDHEADER ConfigStrings::ENDHEADER
+#define CONFIG_EXAMPLEDIRS ConfigStrings::EXAMPLEDIRS
+#define CONFIG_EXAMPLES ConfigStrings::EXAMPLES
+#define CONFIG_EXAMPLESINSTALLPATH ConfigStrings::EXAMPLESINSTALLPATH
+#define CONFIG_EXCLUDEDIRS ConfigStrings::EXCLUDEDIRS
+#define CONFIG_EXCLUDEFILES ConfigStrings::EXCLUDEFILES
+#define CONFIG_EXTRAIMAGES ConfigStrings::EXTRAIMAGES
+#define CONFIG_FALSEHOODS ConfigStrings::FALSEHOODS
+#define CONFIG_FORMATTING ConfigStrings::FORMATTING
+#define CONFIG_HEADERDIRS ConfigStrings::HEADERDIRS
+#define CONFIG_HEADERS ConfigStrings::HEADERS
+#define CONFIG_HEADERSCRIPTS ConfigStrings::HEADERSCRIPTS
+#define CONFIG_HEADERSTYLES ConfigStrings::HEADERSTYLES
+#define CONFIG_HOMEPAGE ConfigStrings::HOMEPAGE
+#define CONFIG_HOMETITLE ConfigStrings::HOMETITLE
+#define CONFIG_IGNOREDIRECTIVES ConfigStrings::IGNOREDIRECTIVES
+#define CONFIG_IGNORESINCE ConfigStrings::IGNORESINCE
+#define CONFIG_IGNORETOKENS ConfigStrings::IGNORETOKENS
+#define CONFIG_IGNOREWORDS ConfigStrings::IGNOREWORDS
+#define CONFIG_IMAGEDIRS ConfigStrings::IMAGEDIRS
+#define CONFIG_INCLUDEPATHS ConfigStrings::INCLUDEPATHS
+#define CONFIG_INCLUSIVE ConfigStrings::INCLUSIVE
+#define CONFIG_INDEXES ConfigStrings::INDEXES
+#define CONFIG_LANDINGPAGE ConfigStrings::LANDINGPAGE
+#define CONFIG_LANDINGTITLE ConfigStrings::LANDINGTITLE
+#define CONFIG_LANGUAGE ConfigStrings::LANGUAGE
+#define CONFIG_LOCATIONINFO ConfigStrings::LOCATIONINFO
+#define CONFIG_LOGPROGRESS ConfigStrings::LOGPROGRESS
+#define CONFIG_MACRO ConfigStrings::MACRO
+#define CONFIG_MANIFESTMETA ConfigStrings::MANIFESTMETA
+#define CONFIG_MODULEHEADER ConfigStrings::MODULEHEADER
+#define CONFIG_NATURALLANGUAGE ConfigStrings::NATURALLANGUAGE
+#define CONFIG_NAVIGATION ConfigStrings::NAVIGATION
+#define CONFIG_NOLINKERRORS ConfigStrings::NOLINKERRORS
+#define CONFIG_OUTPUTDIR ConfigStrings::OUTPUTDIR
+#define CONFIG_OUTPUTFORMATS ConfigStrings::OUTPUTFORMATS
+#define CONFIG_OUTPUTPREFIXES ConfigStrings::OUTPUTPREFIXES
+#define CONFIG_OUTPUTSUFFIXES ConfigStrings::OUTPUTSUFFIXES
+#define CONFIG_PROJECT ConfigStrings::PROJECT
+#define CONFIG_REDIRECTDOCUMENTATIONTODEVNULL ConfigStrings::REDIRECTDOCUMENTATIONTODEVNULL
+#define CONFIG_QHP ConfigStrings::QHP
+#define CONFIG_QUOTINGINFORMATION ConfigStrings::QUOTINGINFORMATION
+#define CONFIG_SCRIPTS ConfigStrings::SCRIPTS
+#define CONFIG_SHOWINTERNAL ConfigStrings::SHOWINTERNAL
+#define CONFIG_SINGLEEXEC ConfigStrings::SINGLEEXEC
+#define CONFIG_SOURCEDIRS ConfigStrings::SOURCEDIRS
+#define CONFIG_SOURCEENCODING ConfigStrings::SOURCEENCODING
+#define CONFIG_SOURCES ConfigStrings::SOURCES
+#define CONFIG_SPURIOUS ConfigStrings::SPURIOUS
+#define CONFIG_STYLESHEETS ConfigStrings::STYLESHEETS
+#define CONFIG_SYNTAXHIGHLIGHTING ConfigStrings::SYNTAXHIGHLIGHTING
+#define CONFIG_TABSIZE ConfigStrings::TABSIZE
+#define CONFIG_TAGFILE ConfigStrings::TAGFILE
+#define CONFIG_TIMESTAMPS ConfigStrings::TIMESTAMPS
+#define CONFIG_TOCTITLES ConfigStrings::TOCTITLES
+#define CONFIG_TRADEMARKSPAGE ConfigStrings::TRADEMARKSPAGE
+#define CONFIG_URL ConfigStrings::URL
+#define CONFIG_VERSION ConfigStrings::VERSION
+#define CONFIG_VERSIONSYM ConfigStrings::VERSIONSYM
+#define CONFIG_FILEEXTENSIONS ConfigStrings::FILEEXTENSIONS
+#define CONFIG_IMAGEEXTENSIONS ConfigStrings::IMAGEEXTENSIONS
+#define CONFIG_QMLTYPESPAGE ConfigStrings::QMLTYPESPAGE
+#define CONFIG_QMLTYPESTITLE ConfigStrings::QMLTYPESTITLE
+#define CONFIG_WARNINGLIMIT ConfigStrings::WARNINGLIMIT
+
+inline bool Config::singleExec() const
+{
+ return m_configVars.value(CONFIG_SINGLEEXEC).asBool();
+}
+
+inline bool Config::dualExec() const
+{
+ return !m_configVars.value(CONFIG_SINGLEEXEC).asBool();
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/cppcodemarker.cpp b/src/qdoc/qdoc/src/qdoc/cppcodemarker.cpp
new file mode 100644
index 000000000..7fb26db0c
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/cppcodemarker.cpp
@@ -0,0 +1,594 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cppcodemarker.h"
+
+#include "access.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "namespacenode.h"
+#include "propertynode.h"
+#include "qmlpropertynode.h"
+#include "text.h"
+#include "tree.h"
+#include "typedefnode.h"
+#include "variablenode.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qregularexpression.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+/*!
+ Returns \c true.
+ */
+bool CppCodeMarker::recognizeCode(const QString & /* code */)
+{
+ return true;
+}
+
+/*!
+ Returns \c true if \a ext is any of a list of file extensions
+ for the C++ language.
+ */
+bool CppCodeMarker::recognizeExtension(const QString &extension)
+{
+ QByteArray ext = extension.toLatin1();
+ return ext == "c" || ext == "c++" || ext == "qdoc" || ext == "qtt" || ext == "qtx"
+ || ext == "cc" || ext == "cpp" || ext == "cxx" || ext == "ch" || ext == "h"
+ || ext == "h++" || ext == "hh" || ext == "hpp" || ext == "hxx";
+}
+
+/*!
+ Returns \c true if \a lang is either "C" or "Cpp".
+ */
+bool CppCodeMarker::recognizeLanguage(const QString &lang)
+{
+ return lang == QLatin1String("C") || lang == QLatin1String("Cpp");
+}
+
+/*!
+ Returns the type of atom used to represent C++ code in the documentation.
+*/
+Atom::AtomType CppCodeMarker::atomType() const
+{
+ return Atom::Code;
+}
+
+QString CppCodeMarker::markedUpCode(const QString &code, const Node *relative,
+ const Location &location)
+{
+ return addMarkUp(code, relative, location);
+}
+
+QString CppCodeMarker::markedUpSynopsis(const Node *node, const Node * /* relative */,
+ Section::Style style)
+{
+ const int MaxEnumValues = 6;
+ const FunctionNode *func;
+ const VariableNode *variable;
+ const EnumNode *enume;
+ QString synopsis;
+ QString name;
+
+ name = taggedNode(node);
+ if (style != Section::Details)
+ name = linkTag(node, name);
+ name = "<@name>" + name + "</@name>";
+
+ if (style == Section::Details) {
+ if (!node->isRelatedNonmember() && !node->isProxyNode() && !node->parent()->name().isEmpty()
+ && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()) {
+ name.prepend(taggedNode(node->parent()) + "::");
+ }
+ }
+
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ synopsis = Node::nodeTypeString(node->nodeType());
+ synopsis += QLatin1Char(' ') + name;
+ break;
+ case Node::Function:
+ func = (const FunctionNode *)node;
+ if (style == Section::Details) {
+ auto templateDecl = node->templateDecl();
+ if (templateDecl)
+ synopsis = protect((*templateDecl).to_qstring()) + QLatin1Char(' ');
+ }
+ if (style != Section::AllMembers && !func->returnType().isEmpty())
+ synopsis += typified(func->returnType(), true);
+ synopsis += name;
+ if (!func->isMacroWithoutParams()) {
+ synopsis += QLatin1Char('(');
+ if (!func->parameters().isEmpty()) {
+ const Parameters &parameters = func->parameters();
+ for (int i = 0; i < parameters.count(); ++i) {
+ if (i > 0)
+ synopsis += ", ";
+ QString name = parameters.at(i).name();
+ QString type = parameters.at(i).type();
+ QString value = parameters.at(i).defaultValue();
+ bool trailingSpace = style != Section::AllMembers && !name.isEmpty();
+ synopsis += typified(type, trailingSpace);
+ if (style != Section::AllMembers && !name.isEmpty())
+ synopsis += "<@param>" + protect(name) + "</@param>";
+ if (style != Section::AllMembers && !value.isEmpty())
+ synopsis += " = " + protect(value);
+ }
+ }
+ synopsis += QLatin1Char(')');
+ }
+ if (func->isConst())
+ synopsis += " const";
+
+ if (style == Section::Summary || style == Section::Accessors) {
+ if (!func->isNonvirtual())
+ synopsis.prepend("virtual ");
+ if (func->isFinal())
+ synopsis.append(" final");
+ if (func->isOverride())
+ synopsis.append(" override");
+ if (func->isPureVirtual())
+ synopsis.append(" = 0");
+ if (func->isRef())
+ synopsis.append(" &");
+ else if (func->isRefRef())
+ synopsis.append(" &&");
+ } else if (style == Section::AllMembers) {
+ if (!func->returnType().isEmpty() && func->returnType() != "void")
+ synopsis += " : " + typified(func->returnType());
+ } else {
+ if (func->isRef())
+ synopsis.append(" &");
+ else if (func->isRefRef())
+ synopsis.append(" &&");
+ }
+ break;
+ case Node::Enum:
+ enume = static_cast<const EnumNode *>(node);
+ synopsis = "enum ";
+ if (enume->isScoped())
+ synopsis += "class ";
+ synopsis += name;
+ if (style == Section::Summary) {
+ synopsis += " { ";
+
+ QStringList documentedItems = enume->doc().enumItemNames();
+ if (documentedItems.isEmpty()) {
+ const auto &enumItems = enume->items();
+ for (const auto &item : enumItems)
+ documentedItems << item.name();
+ }
+ const QStringList omitItems = enume->doc().omitEnumItemNames();
+ for (const auto &item : omitItems)
+ documentedItems.removeAll(item);
+
+ if (documentedItems.size() > MaxEnumValues) {
+ // Take the last element and keep it safe, then elide the surplus.
+ const QString last = documentedItems.last();
+ documentedItems = documentedItems.mid(0, MaxEnumValues - 1);
+ documentedItems += "&hellip;";
+ documentedItems += last;
+ }
+ synopsis += documentedItems.join(QLatin1String(", "));
+
+ if (!documentedItems.isEmpty())
+ synopsis += QLatin1Char(' ');
+ synopsis += QLatin1Char('}');
+ }
+ break;
+ case Node::TypeAlias:
+ if (style == Section::Details) {
+ auto templateDecl = node->templateDecl();
+ if (templateDecl)
+ synopsis += protect((*templateDecl).to_qstring()) + QLatin1Char(' ');
+ }
+ synopsis += name;
+ break;
+ case Node::Typedef:
+ if (static_cast<const TypedefNode *>(node)->associatedEnum())
+ synopsis = "flags ";
+ synopsis += name;
+ break;
+ case Node::Property: {
+ auto property = static_cast<const PropertyNode *>(node);
+ synopsis = name + " : " + typified(property->qualifiedDataType());
+ break;
+ }
+ case Node::QmlProperty: {
+ auto property = static_cast<const QmlPropertyNode *>(node);
+ synopsis = name + " : " + typified(property->dataType());
+ break;
+ }
+ case Node::Variable:
+ variable = static_cast<const VariableNode *>(node);
+ if (style == Section::AllMembers) {
+ synopsis = name + " : " + typified(variable->dataType());
+ } else {
+ synopsis = typified(variable->leftType(), true) + name + protect(variable->rightType());
+ }
+ break;
+ default:
+ synopsis = name;
+ }
+
+ QString extra = CodeMarker::extraSynopsis(node, style);
+ if (!extra.isEmpty()) {
+ extra.prepend(u"<@extra>"_s);
+ extra.append(u"</@extra> "_s);
+ }
+
+ return extra + synopsis;
+}
+
+/*!
+ */
+QString CppCodeMarker::markedUpQmlItem(const Node *node, bool summary)
+{
+ QString name = taggedQmlNode(node);
+ QString synopsis;
+
+ if (summary) {
+ name = linkTag(node, name);
+ } else if (node->isQmlProperty()) {
+ const auto *pn = static_cast<const QmlPropertyNode *>(node);
+ if (pn->isAttached())
+ name.prepend(pn->element() + QLatin1Char('.'));
+ }
+ name = "<@name>" + name + "</@name>";
+ if (node->isQmlProperty()) {
+ const auto *pn = static_cast<const QmlPropertyNode *>(node);
+ synopsis = name + " : " + typified(pn->dataType());
+ } else if (node->isFunction(Node::QML)) {
+ const auto *func = static_cast<const FunctionNode *>(node);
+ if (!func->returnType().isEmpty())
+ synopsis = typified(func->returnType(), true) + name;
+ else
+ synopsis = name;
+ synopsis += QLatin1Char('(');
+ if (!func->parameters().isEmpty()) {
+ const Parameters &parameters = func->parameters();
+ for (int i = 0; i < parameters.count(); ++i) {
+ if (i > 0)
+ synopsis += ", ";
+ QString name = parameters.at(i).name();
+ QString type = parameters.at(i).type();
+ QString paramName;
+ if (!name.isEmpty()) {
+ synopsis += typified(type, true);
+ paramName = name;
+ } else {
+ paramName = type;
+ }
+ synopsis += "<@param>" + protect(paramName) + "</@param>";
+ }
+ }
+ synopsis += QLatin1Char(')');
+ } else {
+ synopsis = name;
+ }
+
+ QString extra = CodeMarker::extraSynopsis(node, summary ? Section::Summary : Section::Details);
+ if (!extra.isEmpty()) {
+ extra.prepend(u" <@extra>"_s);
+ extra.append(u"</@extra>"_s);
+ }
+
+ return synopsis + extra;
+}
+
+QString CppCodeMarker::markedUpName(const Node *node)
+{
+ QString name = linkTag(node, taggedNode(node));
+ if (node->isFunction() && !node->isMacro())
+ name += "()";
+ return name;
+}
+
+QString CppCodeMarker::markedUpEnumValue(const QString &enumValue, const Node *relative)
+{
+ const auto *node = relative->parent();
+
+ if (relative->isQmlProperty()) {
+ const auto *qpn = static_cast<const QmlPropertyNode*>(relative);
+ if (qpn->enumNode() && !enumValue.startsWith("%1."_L1.arg(qpn->enumPrefix())))
+ return "%1<@op>.</@op>%2"_L1.arg(qpn->enumPrefix(), enumValue);
+ }
+
+ if (!relative->isEnumType()) {
+ return enumValue;
+ }
+
+ QStringList parts;
+ while (!node->isHeader() && node->parent()) {
+ parts.prepend(markedUpName(node));
+ if (node->parent() == relative || node->parent()->name().isEmpty())
+ break;
+ node = node->parent();
+ }
+ if (static_cast<const EnumNode *>(relative)->isScoped())
+ parts.append(relative->name());
+
+ parts.append(enumValue);
+ return parts.join(QLatin1String("<@op>::</@op>"));
+}
+
+QString CppCodeMarker::markedUpInclude(const QString &include)
+{
+ return "<@preprocessor>#include &lt;<@headerfile>" + include + "</@headerfile>&gt;</@preprocessor>";
+}
+
+/*
+ @char
+ @class
+ @comment
+ @function
+ @keyword
+ @number
+ @op
+ @preprocessor
+ @string
+ @type
+*/
+
+QString CppCodeMarker::addMarkUp(const QString &in, const Node * /* relative */,
+ const Location & /* location */)
+{
+ static QSet<QString> types{
+ QLatin1String("bool"), QLatin1String("char"), QLatin1String("double"),
+ QLatin1String("float"), QLatin1String("int"), QLatin1String("long"),
+ QLatin1String("short"), QLatin1String("signed"), QLatin1String("unsigned"),
+ QLatin1String("uint"), QLatin1String("ulong"), QLatin1String("ushort"),
+ QLatin1String("uchar"), QLatin1String("void"), QLatin1String("qlonglong"),
+ QLatin1String("qulonglong"), QLatin1String("qint"), QLatin1String("qint8"),
+ QLatin1String("qint16"), QLatin1String("qint32"), QLatin1String("qint64"),
+ QLatin1String("quint"), QLatin1String("quint8"), QLatin1String("quint16"),
+ QLatin1String("quint32"), QLatin1String("quint64"), QLatin1String("qreal"),
+ QLatin1String("cond")
+ };
+
+ static QSet<QString> keywords{
+ QLatin1String("and"), QLatin1String("and_eq"), QLatin1String("asm"), QLatin1String("auto"),
+ QLatin1String("bitand"), QLatin1String("bitor"), QLatin1String("break"),
+ QLatin1String("case"), QLatin1String("catch"), QLatin1String("class"),
+ QLatin1String("compl"), QLatin1String("const"), QLatin1String("const_cast"),
+ QLatin1String("continue"), QLatin1String("default"), QLatin1String("delete"),
+ QLatin1String("do"), QLatin1String("dynamic_cast"), QLatin1String("else"),
+ QLatin1String("enum"), QLatin1String("explicit"), QLatin1String("export"),
+ QLatin1String("extern"), QLatin1String("false"), QLatin1String("for"),
+ QLatin1String("friend"), QLatin1String("goto"), QLatin1String("if"),
+ QLatin1String("include"), QLatin1String("inline"), QLatin1String("monitor"),
+ QLatin1String("mutable"), QLatin1String("namespace"), QLatin1String("new"),
+ QLatin1String("not"), QLatin1String("not_eq"), QLatin1String("operator"),
+ QLatin1String("or"), QLatin1String("or_eq"), QLatin1String("private"),
+ QLatin1String("protected"), QLatin1String("public"), QLatin1String("register"),
+ QLatin1String("reinterpret_cast"), QLatin1String("return"), QLatin1String("sizeof"),
+ QLatin1String("static"), QLatin1String("static_cast"), QLatin1String("struct"),
+ QLatin1String("switch"), QLatin1String("template"), QLatin1String("this"),
+ QLatin1String("throw"), QLatin1String("true"), QLatin1String("try"),
+ QLatin1String("typedef"), QLatin1String("typeid"), QLatin1String("typename"),
+ QLatin1String("union"), QLatin1String("using"), QLatin1String("virtual"),
+ QLatin1String("volatile"), QLatin1String("wchar_t"), QLatin1String("while"),
+ QLatin1String("xor"), QLatin1String("xor_eq"), QLatin1String("synchronized"),
+ // Qt specific
+ QLatin1String("signals"), QLatin1String("slots"), QLatin1String("emit")
+ };
+
+ QString code = in;
+ QString out;
+ QStringView text;
+ int braceDepth = 0;
+ int parenDepth = 0;
+ int i = 0;
+ int start = 0;
+ int finish = 0;
+ QChar ch;
+ static const QRegularExpression classRegExp(QRegularExpression::anchoredPattern("Qt?(?:[A-Z3]+[a-z][A-Za-z]*|t)"));
+ static const QRegularExpression functionRegExp(QRegularExpression::anchoredPattern("q([A-Z][a-z]+)+"));
+ static const QRegularExpression findFunctionRegExp(QStringLiteral("^\\s*\\("));
+ bool atEOF = false;
+
+ auto readChar = [&]() {
+ if (i < code.size())
+ ch = code[i++];
+ else
+ atEOF = true;
+ };
+
+ readChar();
+ while (!atEOF) {
+ QString tag;
+ bool target = false;
+
+ if (ch.isLetter() || ch == '_') {
+ QString ident;
+ do {
+ ident += ch;
+ finish = i;
+ readChar();
+ } while (!atEOF && (ch.isLetterOrNumber() || ch == '_'));
+
+ if (classRegExp.match(ident).hasMatch()) {
+ tag = QStringLiteral("type");
+ } else if (functionRegExp.match(ident).hasMatch()) {
+ tag = QStringLiteral("func");
+ target = true;
+ } else if (types.contains(ident)) {
+ tag = QStringLiteral("type");
+ } else if (keywords.contains(ident)) {
+ tag = QStringLiteral("keyword");
+ } else if (braceDepth == 0 && parenDepth == 0) {
+ if (code.indexOf(findFunctionRegExp, i - 1) == i - 1)
+ tag = QStringLiteral("func");
+ target = true;
+ }
+ } else if (ch.isDigit()) {
+ do {
+ finish = i;
+ readChar();
+ } while (!atEOF && (ch.isLetterOrNumber() || ch == '.' || ch == '\''));
+ tag = QStringLiteral("number");
+ } else {
+ switch (ch.unicode()) {
+ case '+':
+ case '-':
+ case '!':
+ case '%':
+ case '^':
+ case '&':
+ case '*':
+ case ',':
+ case '.':
+ case '<':
+ case '=':
+ case '>':
+ case '?':
+ case '[':
+ case ']':
+ case '|':
+ case '~':
+ finish = i;
+ readChar();
+ tag = QStringLiteral("op");
+ break;
+ case '"':
+ finish = i;
+ readChar();
+
+ while (!atEOF && ch != '"') {
+ if (ch == '\\')
+ readChar();
+ readChar();
+ }
+ finish = i;
+ readChar();
+ tag = QStringLiteral("string");
+ break;
+ case '#':
+ finish = i;
+ readChar();
+ while (!atEOF && ch != '\n') {
+ if (ch == '\\')
+ readChar();
+ finish = i;
+ readChar();
+ }
+ tag = QStringLiteral("preprocessor");
+ break;
+ case '\'':
+ finish = i;
+ readChar();
+
+ while (!atEOF && ch != '\'') {
+ if (ch == '\\')
+ readChar();
+ readChar();
+ }
+ finish = i;
+ readChar();
+ tag = QStringLiteral("char");
+ break;
+ case '(':
+ finish = i;
+ readChar();
+ ++parenDepth;
+ break;
+ case ')':
+ finish = i;
+ readChar();
+ --parenDepth;
+ break;
+ case ':':
+ finish = i;
+ readChar();
+ if (!atEOF && ch == ':') {
+ finish = i;
+ readChar();
+ tag = QStringLiteral("op");
+ }
+ break;
+ case '/':
+ finish = i;
+ readChar();
+ if (!atEOF && ch == '/') {
+ do {
+ finish = i;
+ readChar();
+ } while (!atEOF && ch != '\n');
+ tag = QStringLiteral("comment");
+ } else if (ch == '*') {
+ bool metAster = false;
+ bool metAsterSlash = false;
+
+ finish = i;
+ readChar();
+
+ while (!metAsterSlash) {
+ if (atEOF)
+ break;
+ if (ch == '*')
+ metAster = true;
+ else if (metAster && ch == '/')
+ metAsterSlash = true;
+ else
+ metAster = false;
+ finish = i;
+ readChar();
+ }
+ tag = QStringLiteral("comment");
+ } else {
+ tag = QStringLiteral("op");
+ }
+ break;
+ case '{':
+ finish = i;
+ readChar();
+ braceDepth++;
+ break;
+ case '}':
+ finish = i;
+ readChar();
+ braceDepth--;
+ break;
+ default:
+ finish = i;
+ readChar();
+ }
+ }
+
+ text = QStringView{code}.mid(start, finish - start);
+ start = finish;
+
+ if (!tag.isEmpty()) {
+ out += QStringLiteral("<@");
+ out += tag;
+ if (target) {
+ out += QStringLiteral(" target=\"");
+ out += text;
+ out += QStringLiteral("()\"");
+ }
+ out += QStringLiteral(">");
+ }
+
+ appendProtectedString(&out, text);
+
+ if (!tag.isEmpty()) {
+ out += QStringLiteral("</@");
+ out += tag;
+ out += QStringLiteral(">");
+ }
+ }
+
+ if (start < code.size()) {
+ appendProtectedString(&out, QStringView{code}.mid(start));
+ }
+
+ return out;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/cppcodemarker.h b/src/qdoc/qdoc/src/qdoc/cppcodemarker.h
new file mode 100644
index 000000000..b0c5f3615
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/cppcodemarker.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CPPCODEMARKER_H
+#define CPPCODEMARKER_H
+
+#include "codemarker.h"
+
+QT_BEGIN_NAMESPACE
+
+class CppCodeMarker : public CodeMarker
+{
+public:
+ CppCodeMarker() = default;
+ ~CppCodeMarker() override = default;
+
+ bool recognizeCode(const QString &code) override;
+ bool recognizeExtension(const QString &ext) override;
+ bool recognizeLanguage(const QString &lang) override;
+ [[nodiscard]] Atom::AtomType atomType() const override;
+ QString markedUpCode(const QString &code, const Node *relative,
+ const Location &location) override;
+ QString markedUpSynopsis(const Node *node, const Node *relative, Section::Style style) override;
+ QString markedUpQmlItem(const Node *node, bool summary) override;
+ QString markedUpName(const Node *node) override;
+ QString markedUpEnumValue(const QString &enumValue, const Node *relative) override;
+ QString markedUpInclude(const QString &include) override;
+
+private:
+ QString addMarkUp(const QString &protectedCode, const Node *relative, const Location &location);
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp b/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp
new file mode 100644
index 000000000..d2e8d7c63
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp
@@ -0,0 +1,1021 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cppcodeparser.h"
+
+#include "access.h"
+#include "classnode.h"
+#include "clangcodeparser.h"
+#include "collectionnode.h"
+#include "comparisoncategory.h"
+#include "config.h"
+#include "examplenode.h"
+#include "externalpagenode.h"
+#include "functionnode.h"
+#include "generator.h"
+#include "headernode.h"
+#include "namespacenode.h"
+#include "qdocdatabase.h"
+#include "qmltypenode.h"
+#include "qmlpropertynode.h"
+#include "sharedcommentnode.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qmap.h>
+
+#include <algorithm>
+
+using namespace Qt::Literals::StringLiterals;
+
+QT_BEGIN_NAMESPACE
+
+/*
+ All these can appear in a C++ namespace. Don't add
+ anything that can't be in a C++ namespace.
+ */
+static const QMap<QString, Node::NodeType> s_nodeTypeMap{
+ { COMMAND_NAMESPACE, Node::Namespace }, { COMMAND_NAMESPACE, Node::Namespace },
+ { COMMAND_CLASS, Node::Class }, { COMMAND_STRUCT, Node::Struct },
+ { COMMAND_UNION, Node::Union }, { COMMAND_ENUM, Node::Enum },
+ { COMMAND_TYPEALIAS, Node::TypeAlias }, { COMMAND_TYPEDEF, Node::Typedef },
+ { COMMAND_PROPERTY, Node::Property }, { COMMAND_VARIABLE, Node::Variable }
+};
+
+typedef bool (Node::*NodeTypeTestFunc)() const;
+static const QMap<QString, NodeTypeTestFunc> s_nodeTypeTestFuncMap{
+ { COMMAND_NAMESPACE, &Node::isNamespace }, { COMMAND_CLASS, &Node::isClassNode },
+ { COMMAND_STRUCT, &Node::isStruct }, { COMMAND_UNION, &Node::isUnion },
+ { COMMAND_ENUM, &Node::isEnumType }, { COMMAND_TYPEALIAS, &Node::isTypeAlias },
+ { COMMAND_TYPEDEF, &Node::isTypedef }, { COMMAND_PROPERTY, &Node::isProperty },
+ { COMMAND_VARIABLE, &Node::isVariable },
+};
+
+CppCodeParser::CppCodeParser(FnCommandParser&& parser)
+ : fn_parser{parser}
+{
+ Config &config = Config::instance();
+ QStringList exampleFilePatterns{config.get(CONFIG_EXAMPLES
+ + Config::dot
+ + CONFIG_FILEEXTENSIONS).asStringList()};
+
+ if (!exampleFilePatterns.isEmpty())
+ m_exampleNameFilter = exampleFilePatterns.join(' ');
+ else
+ m_exampleNameFilter = "*.cpp *.h *.js *.xq *.svg *.xml *.ui";
+
+ QStringList exampleImagePatterns{config.get(CONFIG_EXAMPLES
+ + Config::dot
+ + CONFIG_IMAGEEXTENSIONS).asStringList()};
+
+ if (!exampleImagePatterns.isEmpty())
+ m_exampleImageFilter = exampleImagePatterns.join(' ');
+ else
+ m_exampleImageFilter = "*.png";
+
+ m_showLinkErrors = !config.get(CONFIG_NOLINKERRORS).asBool();
+}
+
+/*!
+ Process the topic \a command found in the \a doc with argument \a arg.
+ */
+Node *CppCodeParser::processTopicCommand(const Doc &doc, const QString &command,
+ const ArgPair &arg)
+{
+ QDocDatabase* database = QDocDatabase::qdocDB();
+
+ if (command == COMMAND_FN) {
+ Q_UNREACHABLE();
+ } else if (s_nodeTypeMap.contains(command)) {
+ /*
+ We should only get in here if the command refers to
+ something that can appear in a C++ namespace,
+ i.e. a class, another namespace, an enum, a typedef,
+ a property or a variable. I think these are handled
+ this way to allow the writer to refer to the entity
+ without including the namespace qualifier.
+ */
+ Node::NodeType type = s_nodeTypeMap[command];
+ QStringList words = arg.first.split(QLatin1Char(' '));
+ QStringList path;
+ qsizetype idx = 0;
+ Node *node = nullptr;
+
+ if (type == Node::Variable && words.size() > 1)
+ idx = words.size() - 1;
+ path = words[idx].split("::");
+
+ node = database->findNodeByNameAndType(path, s_nodeTypeTestFuncMap[command]);
+ // Allow representing a type alias as a class
+ if (node == nullptr && command == COMMAND_CLASS) {
+ node = database->findNodeByNameAndType(path, &Node::isTypeAlias);
+ if (node) {
+ auto access = node->access();
+ auto loc = node->location();
+ auto templateDecl = node->templateDecl();
+ node = new ClassNode(Node::Class, node->parent(), node->name());
+ node->setAccess(access);
+ node->setLocation(loc);
+ node->setTemplateDecl(templateDecl);
+ }
+ }
+ if (node == nullptr) {
+ if (CodeParser::isWorthWarningAbout(doc)) {
+ doc.location().warning(
+ QStringLiteral("Cannot find '%1' specified with '\\%2' in any header file")
+ .arg(arg.first, command));
+ }
+ } else if (node->isAggregate()) {
+ if (type == Node::Namespace) {
+ auto *ns = static_cast<NamespaceNode *>(node);
+ ns->markSeen();
+ ns->setWhereDocumented(ns->tree()->camelCaseModuleName());
+ }
+ }
+ return node;
+ } else if (command == COMMAND_EXAMPLE) {
+ if (Config::generateExamples) {
+ auto *en = new ExampleNode(database->primaryTreeRoot(), arg.first);
+ en->setLocation(doc.startLocation());
+ setExampleFileLists(en);
+ return en;
+ }
+ } else if (command == COMMAND_EXTERNALPAGE) {
+ auto *epn = new ExternalPageNode(database->primaryTreeRoot(), arg.first);
+ epn->setLocation(doc.startLocation());
+ return epn;
+ } else if (command == COMMAND_HEADERFILE) {
+ auto *hn = new HeaderNode(database->primaryTreeRoot(), arg.first);
+ hn->setLocation(doc.startLocation());
+ return hn;
+ } else if (command == COMMAND_GROUP) {
+ CollectionNode *cn = database->addGroup(arg.first);
+ cn->setLocation(doc.startLocation());
+ cn->markSeen();
+ return cn;
+ } else if (command == COMMAND_MODULE) {
+ CollectionNode *cn = database->addModule(arg.first);
+ cn->setLocation(doc.startLocation());
+ cn->markSeen();
+ return cn;
+ } else if (command == COMMAND_QMLMODULE) {
+ QStringList blankSplit = arg.first.split(QLatin1Char(' '));
+ CollectionNode *cn = database->addQmlModule(blankSplit[0]);
+ cn->setLogicalModuleInfo(blankSplit);
+ cn->setLocation(doc.startLocation());
+ cn->markSeen();
+ return cn;
+ } else if (command == COMMAND_PAGE) {
+ auto *pn = new PageNode(database->primaryTreeRoot(), arg.first.split(' ').front());
+ pn->setLocation(doc.startLocation());
+ return pn;
+ } else if (command == COMMAND_QMLTYPE ||
+ command == COMMAND_QMLVALUETYPE ||
+ command == COMMAND_QMLBASICTYPE) {
+ auto nodeType = (command == COMMAND_QMLTYPE) ? Node::QmlType : Node::QmlValueType;
+ QString qmid;
+ if (auto args = doc.metaCommandArgs(COMMAND_INQMLMODULE); !args.isEmpty())
+ qmid = args.first().first;
+ auto *qcn = database->findQmlTypeInPrimaryTree(qmid, arg.first);
+ // A \qmlproperty may have already constructed a placeholder type
+ // without providing a module identifier; allow such cases
+ if (!qcn && !qmid.isEmpty())
+ qcn = database->findQmlTypeInPrimaryTree(QString(), arg.first);
+ if (!qcn || qcn->nodeType() != nodeType)
+ qcn = new QmlTypeNode(database->primaryTreeRoot(), arg.first, nodeType);
+ if (!qmid.isEmpty())
+ database->addToQmlModule(qmid, qcn);
+ qcn->setLocation(doc.startLocation());
+ return qcn;
+ } else if ((command == COMMAND_QMLSIGNAL) || (command == COMMAND_QMLMETHOD)
+ || (command == COMMAND_QMLATTACHEDSIGNAL) || (command == COMMAND_QMLATTACHEDMETHOD)) {
+ Q_UNREACHABLE();
+ }
+ return nullptr;
+}
+
+/*!
+ A QML property argument has the form...
+
+ <type> <QML-type>::<name>
+ <type> <QML-module>::<QML-type>::<name>
+
+ This function splits the argument into one of those
+ two forms. The three part form is the old form, which
+ was used before the creation of Qt Quick 2 and Qt
+ Components. A <QML-module> is the QML equivalent of a
+ C++ namespace. So this function splits \a arg on "::"
+ and stores the parts in \a type, \a module, \a qmlTypeName,
+ and \a name, and returns \c true. If any part other than
+ \a module is not found, a qdoc warning is emitted and
+ false is returned.
+
+ \note The two QML types \e{Component} and \e{QtObject}
+ never have a module qualifier.
+ */
+bool CppCodeParser::splitQmlPropertyArg(const QString &arg, QString &type, QString &module,
+ QString &qmlTypeName, QString &name,
+ const Location &location)
+{
+ QStringList blankSplit = arg.split(QLatin1Char(' '));
+ if (blankSplit.size() > 1) {
+ type = blankSplit[0];
+ QStringList colonSplit(blankSplit[1].split("::"));
+ if (colonSplit.size() == 3) {
+ module = colonSplit[0];
+ qmlTypeName = colonSplit[1];
+ name = colonSplit[2];
+ return true;
+ }
+ if (colonSplit.size() == 2) {
+ module.clear();
+ qmlTypeName = colonSplit[0];
+ name = colonSplit[1];
+ return true;
+ }
+ location.warning(
+ QStringLiteral("Unrecognizable QML module/component qualifier for %1").arg(arg));
+ } else {
+ location.warning(QStringLiteral("Missing property type for %1").arg(arg));
+ }
+ return false;
+}
+
+std::vector<TiedDocumentation> CppCodeParser::processQmlProperties(const UntiedDocumentation &untied)
+{
+ const Doc &doc = untied.documentation;
+ const TopicList &topics = doc.topicsUsed();
+ if (topics.isEmpty())
+ return {};
+
+ QString arg;
+ QString type;
+ QString group;
+ QString qmlModule;
+ QString property;
+ QString qmlTypeName;
+
+ std::vector<TiedDocumentation> tied{};
+
+ Topic topic = topics.at(0);
+ arg = topic.m_args;
+ if (splitQmlPropertyArg(arg, type, qmlModule, qmlTypeName, property, doc.location())) {
+ qsizetype i = property.indexOf('.');
+ if (i != -1)
+ group = property.left(i);
+ }
+
+ QDocDatabase *database = QDocDatabase::qdocDB();
+
+ NodeList sharedNodes;
+ QmlTypeNode *qmlType = database->findQmlTypeInPrimaryTree(qmlModule, qmlTypeName);
+ // Note: Constructing a QmlType node by default, as opposed to QmlValueType.
+ // This may lead to unexpected behavior if documenting \qmlvaluetype's properties
+ // before the type itself.
+ if (qmlType == nullptr) {
+ qmlType = new QmlTypeNode(database->primaryTreeRoot(), qmlTypeName, Node::QmlType);
+ qmlType->setLocation(doc.startLocation());
+ if (!qmlModule.isEmpty())
+ database->addToQmlModule(qmlModule, qmlType);
+ }
+
+ for (const auto &topicCommand : topics) {
+ QString cmd = topicCommand.m_topic;
+ arg = topicCommand.m_args;
+ if ((cmd == COMMAND_QMLPROPERTY) || (cmd == COMMAND_QMLATTACHEDPROPERTY)) {
+ bool attached = cmd.contains(QLatin1String("attached"));
+ if (splitQmlPropertyArg(arg, type, qmlModule, qmlTypeName, property, doc.location())) {
+ if (qmlType != database->findQmlTypeInPrimaryTree(qmlModule, qmlTypeName)) {
+ doc.startLocation().warning(
+ QStringLiteral(
+ "All properties in a group must belong to the same type: '%1'")
+ .arg(arg));
+ continue;
+ }
+ QmlPropertyNode *existingProperty = qmlType->hasQmlProperty(property, attached);
+ if (existingProperty) {
+ processMetaCommands(doc, existingProperty);
+ if (!doc.body().isEmpty()) {
+ doc.startLocation().warning(
+ QStringLiteral("QML property documented multiple times: '%1'")
+ .arg(arg), QStringLiteral("also seen here: %1")
+ .arg(existingProperty->location().toString()));
+ }
+ continue;
+ }
+ auto *qpn = new QmlPropertyNode(qmlType, property, type, attached);
+ qpn->setLocation(doc.startLocation());
+ qpn->setGenus(Node::QML);
+
+ tied.emplace_back(TiedDocumentation{doc, qpn});
+
+ sharedNodes << qpn;
+ }
+ } else {
+ doc.startLocation().warning(
+ QStringLiteral("Command '\\%1'; not allowed with QML property commands")
+ .arg(cmd));
+ }
+ }
+
+ // Construct a SharedCommentNode (scn) if multiple topics generated
+ // valid nodes. Note that it's important to do this *after* constructing
+ // the topic nodes - which need to be written to index before the related
+ // scn.
+ if (sharedNodes.size() > 1) {
+ auto *scn = new SharedCommentNode(qmlType, sharedNodes.size(), group);
+ scn->setLocation(doc.startLocation());
+
+ tied.emplace_back(TiedDocumentation{doc, scn});
+
+ for (const auto n : sharedNodes)
+ scn->append(n);
+ scn->sort();
+ }
+
+ return tied;
+}
+
+/*!
+ Process the metacommand \a command in the context of the
+ \a node associated with the topic command and the \a doc.
+ \a arg is the argument to the metacommand.
+
+ \a node is guaranteed to be non-null.
+ */
+void CppCodeParser::processMetaCommand(const Doc &doc, const QString &command,
+ const ArgPair &argPair, Node *node)
+{
+ QDocDatabase* database = QDocDatabase::qdocDB();
+
+ QString arg = argPair.first;
+ if (command == COMMAND_INHEADERFILE) {
+ // TODO: [incorrect-constructs][header-arg]
+ // The emptiness check for arg is required as,
+ // currently, DocParser fancies passing (without any warning)
+ // incorrect constructs doen the chain, such as an
+ // "\inheaderfile" command with no argument.
+ //
+ // As it is the case here, we require further sanity checks to
+ // preserve some of the semantic for the later phases.
+ // This generally has a ripple effect on the whole codebase,
+ // making it more complex and increasesing the surface of bugs.
+ //
+ // The following emptiness check should be removed as soon as
+ // DocParser is enhanced with correct semantics.
+ if (node->isAggregate() && !arg.isEmpty())
+ static_cast<Aggregate *>(node)->setIncludeFile(arg);
+ else
+ doc.location().warning(QStringLiteral("Ignored '\\%1'").arg(COMMAND_INHEADERFILE));
+ } else if (command == COMMAND_COMPARES) {
+ processComparesCommand(node, arg, doc.location());
+ } else if (command == COMMAND_COMPARESWITH) {
+ if (!node->isClassNode())
+ doc.location().warning(
+ u"Found \\%1 command outside of \\%2 context."_s
+ .arg(COMMAND_COMPARESWITH, COMMAND_CLASS));
+ } else if (command == COMMAND_OVERLOAD) {
+ /*
+ Note that this might set the overload flag of the
+ primary function. This is ok because the overload
+ flags and overload numbers will be resolved later
+ in Aggregate::normalizeOverloads().
+ */
+ if (node->isFunction())
+ static_cast<FunctionNode *>(node)->setOverloadFlag();
+ else if (node->isSharedCommentNode())
+ static_cast<SharedCommentNode *>(node)->setOverloadFlags();
+ else
+ doc.location().warning(QStringLiteral("Ignored '\\%1'").arg(COMMAND_OVERLOAD));
+ } else if (command == COMMAND_REIMP) {
+ if (node->parent() && !node->parent()->isInternal()) {
+ if (node->isFunction()) {
+ auto *fn = static_cast<FunctionNode *>(node);
+ // The clang visitor class will have set the
+ // qualified name of the overridden function.
+ // If the name of the overridden function isn't
+ // set, issue a warning.
+ if (fn->overridesThis().isEmpty() && CodeParser::isWorthWarningAbout(doc)) {
+ doc.location().warning(
+ QStringLiteral("Cannot find base function for '\\%1' in %2()")
+ .arg(COMMAND_REIMP, node->name()),
+ QStringLiteral("The function either doesn't exist in any "
+ "base class with the same signature or it "
+ "exists but isn't virtual."));
+ }
+ fn->setReimpFlag();
+ } else {
+ doc.location().warning(
+ QStringLiteral("Ignored '\\%1' in %2").arg(COMMAND_REIMP, node->name()));
+ }
+ }
+ } else if (command == COMMAND_RELATES) {
+ // REMARK: Generates warnings only; Node instances are
+ // adopted from the root namespace to other Aggregates
+ // in a post-processing step, Aggregate::resolveRelates(),
+ // after all topic commands are processed.
+ if (node->isAggregate()) {
+ doc.location().warning("Invalid '\\%1' not allowed in '\\%2'"_L1
+ .arg(COMMAND_RELATES, node->nodeTypeString()));
+ } else if (!node->isRelatedNonmember() && node->parent()->isClassNode()) {
+ if (!doc.isInternal()) {
+ doc.location().warning("Invalid '\\%1' ('%2' must be global)"_L1
+ .arg(COMMAND_RELATES, node->name()));
+ }
+ }
+ } else if (command == COMMAND_NEXTPAGE) {
+ CodeParser::setLink(node, Node::NextLink, arg);
+ } else if (command == COMMAND_PREVIOUSPAGE) {
+ CodeParser::setLink(node, Node::PreviousLink, arg);
+ } else if (command == COMMAND_STARTPAGE) {
+ CodeParser::setLink(node, Node::StartLink, arg);
+ } else if (command == COMMAND_QMLINHERITS) {
+ if (node->name() == arg)
+ doc.location().warning(QStringLiteral("%1 tries to inherit itself").arg(arg));
+ else if (node->isQmlType()) {
+ auto *qmlType = static_cast<QmlTypeNode *>(node);
+ qmlType->setQmlBaseName(arg);
+ }
+ } else if (command == COMMAND_QMLNATIVETYPE || command == COMMAND_QMLINSTANTIATES) {
+ if (command == COMMAND_QMLINSTANTIATES)
+ doc.location().report(u"\\instantiates is deprected and will be removed in a future version. Use \\nativetype instead."_s);
+ // TODO: COMMAND_QMLINSTANTIATES is deprecated since 6.8. Its remains should be removed no later than Qt 7.0.0.
+ processQmlNativeTypeCommand(node, arg, doc.location());
+ } else if (command == COMMAND_DEFAULT) {
+ if (!node->isQmlProperty()) {
+ doc.location().warning(QStringLiteral("Ignored '\\%1', applies only to '\\%2'")
+ .arg(command, COMMAND_QMLPROPERTY));
+ } else if (arg.isEmpty()) {
+ doc.location().warning(QStringLiteral("Expected an argument for '\\%1' (maybe you meant '\\%2'?)")
+ .arg(command, COMMAND_QMLDEFAULT));
+ } else {
+ static_cast<QmlPropertyNode *>(node)->setDefaultValue(arg);
+ }
+ } else if (command == COMMAND_QMLDEFAULT) {
+ node->markDefault();
+ } else if (command == COMMAND_QMLENUMERATORSFROM) {
+ if (!node->isQmlProperty()) {
+ doc.location().warning("Ignored '\\%1', applies only to '\\%2'"_L1
+ .arg(command, COMMAND_QMLPROPERTY));
+ } else if (!static_cast<QmlPropertyNode*>(node)->setEnumNode(argPair.first, argPair.second)) {
+ doc.location().warning("Failed to find C++ enumeration '%2' passed to \\%1"_L1
+ .arg(command, arg), "Use \\value commands instead"_L1);
+ }
+ } else if (command == COMMAND_QMLREADONLY) {
+ node->markReadOnly(true);
+ } else if (command == COMMAND_QMLREQUIRED) {
+ if (!node->isQmlProperty())
+ doc.location().warning(QStringLiteral("Ignored '\\%1'").arg(COMMAND_QMLREQUIRED));
+ else
+ static_cast<QmlPropertyNode *>(node)->setRequired();
+ } else if ((command == COMMAND_QMLABSTRACT) || (command == COMMAND_ABSTRACT)) {
+ if (node->isQmlType())
+ node->setAbstract(true);
+ } else if (command == COMMAND_DEPRECATED) {
+ node->setDeprecated(argPair.second);
+ } else if (command == COMMAND_INGROUP || command == COMMAND_INPUBLICGROUP) {
+ // Note: \ingroup and \inpublicgroup are the same (and now recognized as such).
+ database->addToGroup(arg, node);
+ } else if (command == COMMAND_INMODULE) {
+ database->addToModule(arg, node);
+ } else if (command == COMMAND_INQMLMODULE) {
+ // Handled when parsing topic commands
+ } else if (command == COMMAND_OBSOLETE) {
+ node->setStatus(Node::Deprecated);
+ } else if (command == COMMAND_NONREENTRANT) {
+ node->setThreadSafeness(Node::NonReentrant);
+ } else if (command == COMMAND_PRELIMINARY) {
+ // \internal wins.
+ if (!node->isInternal())
+ node->setStatus(Node::Preliminary);
+ } else if (command == COMMAND_INTERNAL) {
+ if (!Config::instance().showInternal())
+ node->markInternal();
+ } else if (command == COMMAND_REENTRANT) {
+ node->setThreadSafeness(Node::Reentrant);
+ } else if (command == COMMAND_SINCE) {
+ node->setSince(arg);
+ } else if (command == COMMAND_WRAPPER) {
+ node->setWrapper();
+ } else if (command == COMMAND_THREADSAFE) {
+ node->setThreadSafeness(Node::ThreadSafe);
+ } else if (command == COMMAND_TITLE) {
+ if (!node->setTitle(arg))
+ doc.location().warning(QStringLiteral("Ignored '\\%1'").arg(COMMAND_TITLE));
+ else if (node->isExample())
+ database->addExampleNode(static_cast<ExampleNode *>(node));
+ } else if (command == COMMAND_SUBTITLE) {
+ if (!node->setSubtitle(arg))
+ doc.location().warning(QStringLiteral("Ignored '\\%1'").arg(COMMAND_SUBTITLE));
+ } else if (command == COMMAND_QTVARIABLE) {
+ node->setQtVariable(arg);
+ if (!node->isModule() && !node->isQmlModule())
+ doc.location().warning(
+ QStringLiteral(
+ "Command '\\%1' is only meaningful in '\\module' and '\\qmlmodule'.")
+ .arg(COMMAND_QTVARIABLE));
+ } else if (command == COMMAND_QTCMAKEPACKAGE) {
+ if (node->isModule())
+ node->setQtCMakeComponent(arg);
+ else
+ doc.location().warning(
+ QStringLiteral("Command '\\%1' is only meaningful in '\\module'.")
+ .arg(COMMAND_QTCMAKEPACKAGE));
+ } else if (command == COMMAND_QTCMAKETARGETITEM) {
+ if (node->isModule())
+ node->setQtCMakeTargetItem(arg);
+ else
+ doc.location().warning(
+ QStringLiteral("Command '\\%1' is only meaningful in '\\module'.")
+ .arg(COMMAND_QTCMAKETARGETITEM));
+ } else if (command == COMMAND_MODULESTATE ) {
+ if (!node->isModule() && !node->isQmlModule()) {
+ doc.location().warning(
+ QStringLiteral(
+ "Command '\\%1' is only meaningful in '\\module' and '\\qmlmodule'.")
+ .arg(COMMAND_MODULESTATE));
+ } else {
+ static_cast<CollectionNode*>(node)->setState(arg);
+ }
+ } else if (command == COMMAND_NOAUTOLIST) {
+ if (!node->isCollectionNode() && !node->isExample()) {
+ doc.location().warning(
+ QStringLiteral(
+ "Command '\\%1' is only meaningful in '\\module', '\\qmlmodule', `\\group` and `\\example`.")
+ .arg(COMMAND_NOAUTOLIST));
+ } else {
+ static_cast<PageNode*>(node)->setNoAutoList(true);
+ }
+ } else if (command == COMMAND_ATTRIBUTION) {
+ // TODO: This condition is not currently exact enough, as it
+ // will allow any non-aggregate `PageNode` to use the command,
+ // For example, an `ExampleNode`.
+ //
+ // The command is intended only for internal usage by
+ // "qattributionscanner" and should only work on `PageNode`s
+ // that are generated from a "\page" command.
+ //
+ // It is already possible to provide a more restricted check,
+ // albeit in a somewhat dirty way. It is not expected that
+ // this warning will have any particular use.
+ // If it so happens that a case where the too-broad scope of
+ // the warning is a problem or hides a bug, modify the
+ // condition to be restrictive enough.
+ // Otherwise, wait until a more torough look at QDoc's
+ // internal representations an way to enable "Attribution
+ // Pages" is performed before looking at the issue again.
+ if (!node->isTextPageNode()) {
+ doc.location().warning(u"Command '\\%1' is only meaningful in '\\%2'"_s.arg(COMMAND_ATTRIBUTION, COMMAND_PAGE));
+ } else { static_cast<PageNode*>(node)->markAttribution(); }
+ }
+}
+
+/*!
+ \internal
+ Processes the argument \a arg that's passed to the \\compares command,
+ and sets the comparison category of the \a node accordingly.
+
+ If the argument is invalid, issue a warning at the location the command
+ appears through \a loc.
+*/
+void CppCodeParser::processComparesCommand(Node *node, const QString &arg, const Location &loc)
+{
+ if (!node->isClassNode()) {
+ loc.warning(u"Found \\%1 command outside of \\%2 context."_s.arg(COMMAND_COMPARES,
+ COMMAND_CLASS));
+ return;
+ }
+
+ if (auto category = comparisonCategoryFromString(arg.toStdString());
+ category != ComparisonCategory::None) {
+ node->setComparisonCategory(category);
+ } else {
+ loc.warning(u"Invalid argument to \\%1 command: `%2`"_s.arg(COMMAND_COMPARES, arg),
+ u"Valid arguments are `strong`, `weak`, `partial`, or `equality`."_s);
+ }
+}
+
+/*!
+ The topic command has been processed, and now \a doc and
+ \a node are passed to this function to get the metacommands
+ from \a doc and process them one at a time. \a node is the
+ node where \a doc resides.
+ */
+void CppCodeParser::processMetaCommands(const Doc &doc, Node *node)
+{
+ std::vector<Node*> nodes_to_process{};
+ if (node->isSharedCommentNode()) {
+ auto scn = static_cast<SharedCommentNode*>(node);
+
+ nodes_to_process.reserve(scn->count() + 1);
+ std::copy(scn->collective().cbegin(), scn->collective().cend(), std::back_inserter(nodes_to_process));
+ }
+
+ // REMARK: Ordering is important here. If node is a
+ // SharedCommentNode it MUST be processed after all its child
+ // nodes.
+ // Failure to do so can incur in incorrect warnings.
+ // For example, if a shared documentation has a "\relates" command.
+ // When the command is processed for the SharedCommentNode it will
+ // apply to all its child nodes.
+ // If a child node is processed after the SharedCommentNode that
+ // contains it, that "\relates" command will be considered applied
+ // already, resulting in a warning.
+ nodes_to_process.push_back(node);
+
+ const QStringList metaCommandsUsed = doc.metaCommandsUsed().values();
+ for (const auto &command : metaCommandsUsed) {
+ const ArgList args = doc.metaCommandArgs(command);
+ for (const auto &arg : args) {
+ std::for_each(nodes_to_process.cbegin(), nodes_to_process.cend(), [this, doc, command, arg](auto node){
+ processMetaCommand(doc, command, arg, node);
+ });
+ }
+ }
+}
+
+/*!
+ Parse QML signal/method topic commands.
+ */
+FunctionNode *CppCodeParser::parseOtherFuncArg(const QString &topic, const Location &location,
+ const QString &funcArg)
+{
+ QString funcName;
+ QString returnType;
+
+ qsizetype leftParen = funcArg.indexOf(QChar('('));
+ if (leftParen > 0)
+ funcName = funcArg.left(leftParen);
+ else
+ funcName = funcArg;
+ qsizetype firstBlank = funcName.indexOf(QChar(' '));
+ if (firstBlank > 0) {
+ returnType = funcName.left(firstBlank);
+ funcName = funcName.right(funcName.size() - firstBlank - 1);
+ }
+
+ QStringList colonSplit(funcName.split("::"));
+ if (colonSplit.size() < 2) {
+ QString msg = "Unrecognizable QML module/component qualifier for " + funcArg;
+ location.warning(msg.toLatin1().data());
+ return nullptr;
+ }
+ QString moduleName;
+ QString elementName;
+ if (colonSplit.size() > 2) {
+ moduleName = colonSplit[0];
+ elementName = colonSplit[1];
+ } else {
+ elementName = colonSplit[0];
+ }
+ funcName = colonSplit.last();
+
+ QDocDatabase* database = QDocDatabase::qdocDB();
+
+ auto *aggregate = database->findQmlTypeInPrimaryTree(moduleName, elementName);
+ // Note: Constructing a QmlType node by default, as opposed to QmlValueType.
+ // This may lead to unexpected behavior if documenting \qmlvaluetype's methods
+ // before the type itself.
+ if (!aggregate) {
+ aggregate = new QmlTypeNode(database->primaryTreeRoot(), elementName, Node::QmlType);
+ aggregate->setLocation(location);
+ if (!moduleName.isEmpty())
+ database->addToQmlModule(moduleName, aggregate);
+ }
+
+ QString params;
+ QStringList leftParenSplit = funcArg.split('(');
+ if (leftParenSplit.size() > 1) {
+ QStringList rightParenSplit = leftParenSplit[1].split(')');
+ if (!rightParenSplit.empty())
+ params = rightParenSplit[0];
+ }
+
+ FunctionNode::Metaness metaness = FunctionNode::getMetanessFromTopic(topic);
+ bool attached = topic.contains(QLatin1String("attached"));
+ auto *fn = new FunctionNode(metaness, aggregate, funcName, attached);
+ fn->setAccess(Access::Public);
+ fn->setLocation(location);
+ fn->setReturnType(returnType);
+ fn->setParameters(params);
+ return fn;
+}
+
+/*!
+ Parse the macro arguments in \a macroArg ad hoc, without using
+ any actual parser. If successful, return a pointer to the new
+ FunctionNode for the macro. Otherwise return null. \a location
+ is used for reporting errors.
+ */
+FunctionNode *CppCodeParser::parseMacroArg(const Location &location, const QString &macroArg)
+{
+ QDocDatabase* database = QDocDatabase::qdocDB();
+
+ QStringList leftParenSplit = macroArg.split('(');
+ if (leftParenSplit.isEmpty())
+ return nullptr;
+ QString macroName;
+ FunctionNode *oldMacroNode = nullptr;
+ QStringList blankSplit = leftParenSplit[0].split(' ');
+ if (!blankSplit.empty()) {
+ macroName = blankSplit.last();
+ oldMacroNode = database->findMacroNode(macroName);
+ }
+ QString returnType;
+ if (blankSplit.size() > 1) {
+ blankSplit.removeLast();
+ returnType = blankSplit.join(' ');
+ }
+ QString params;
+ if (leftParenSplit.size() > 1) {
+ const QString &afterParen = leftParenSplit.at(1);
+ qsizetype rightParen = afterParen.indexOf(')');
+ if (rightParen >= 0)
+ params = afterParen.left(rightParen);
+ }
+ int i = 0;
+ while (i < macroName.size() && !macroName.at(i).isLetter())
+ i++;
+ if (i > 0) {
+ returnType += QChar(' ') + macroName.left(i);
+ macroName = macroName.mid(i);
+ }
+ FunctionNode::Metaness metaness = FunctionNode::MacroWithParams;
+ if (params.isEmpty())
+ metaness = FunctionNode::MacroWithoutParams;
+ auto *macro = new FunctionNode(metaness, database->primaryTreeRoot(), macroName);
+ macro->setAccess(Access::Public);
+ macro->setLocation(location);
+ macro->setReturnType(returnType);
+ macro->setParameters(params);
+ if (oldMacroNode && macro->parent() == oldMacroNode->parent()
+ && compare(macro, oldMacroNode) == 0) {
+ location.warning(QStringLiteral("\\macro %1 documented more than once")
+ .arg(macroArg), QStringLiteral("also seen here: %1")
+ .arg(oldMacroNode->doc().location().toString()));
+ }
+ return macro;
+}
+
+void CppCodeParser::setExampleFileLists(ExampleNode *en)
+{
+ Config &config = Config::instance();
+ QString fullPath = config.getExampleProjectFile(en->name());
+ if (fullPath.isEmpty()) {
+ QString details = QLatin1String("Example directories: ")
+ + config.getCanonicalPathList(CONFIG_EXAMPLEDIRS).join(QLatin1Char(' '));
+ en->location().warning(
+ QStringLiteral("Cannot find project file for example '%1'").arg(en->name()),
+ details);
+ return;
+ }
+
+ QDir exampleDir(QFileInfo(fullPath).dir());
+
+ const auto& [excludeDirs, excludeFiles] = config.getExcludedPaths();
+
+ QStringList exampleFiles = Config::getFilesHere(exampleDir.path(), m_exampleNameFilter,
+ Location(), excludeDirs, excludeFiles);
+ // Search for all image files under the example project, excluding doc/images directory.
+ QSet<QString> excludeDocDirs(excludeDirs);
+ excludeDocDirs.insert(exampleDir.path() + QLatin1String("/doc/images"));
+ QStringList imageFiles = Config::getFilesHere(exampleDir.path(), m_exampleImageFilter,
+ Location(), excludeDocDirs, excludeFiles);
+ if (!exampleFiles.isEmpty()) {
+ // move main.cpp to the end, if it exists
+ QString mainCpp;
+
+ const auto isGeneratedOrMainCpp = [&mainCpp](const QString &fileName) {
+ if (fileName.endsWith("/main.cpp")) {
+ if (mainCpp.isEmpty())
+ mainCpp = fileName;
+ return true;
+ }
+ return fileName.contains("/qrc_") || fileName.contains("/moc_")
+ || fileName.contains("/ui_");
+ };
+
+ exampleFiles.erase(
+ std::remove_if(exampleFiles.begin(), exampleFiles.end(), isGeneratedOrMainCpp),
+ exampleFiles.end());
+
+ if (!mainCpp.isEmpty())
+ exampleFiles.append(mainCpp);
+
+ // Add any resource and project files
+ exampleFiles += Config::getFilesHere(exampleDir.path(),
+ QLatin1String("*.qrc *.pro *.qmlproject *.pyproject CMakeLists.txt qmldir"),
+ Location(), excludeDirs, excludeFiles);
+ }
+
+ const qsizetype pathLen = exampleDir.path().size() - en->name().size();
+ for (auto &file : exampleFiles)
+ file = file.mid(pathLen);
+ for (auto &file : imageFiles)
+ file = file.mid(pathLen);
+
+ en->setFiles(exampleFiles, fullPath.mid(pathLen));
+ en->setImages(imageFiles);
+}
+
+/*!
+ returns true if \a t is \e {qmlsignal}, \e {qmlmethod},
+ \e {qmlattachedsignal}, or \e {qmlattachedmethod}.
+ */
+bool CppCodeParser::isQMLMethodTopic(const QString &t)
+{
+ return (t == COMMAND_QMLSIGNAL || t == COMMAND_QMLMETHOD || t == COMMAND_QMLATTACHEDSIGNAL
+ || t == COMMAND_QMLATTACHEDMETHOD);
+}
+
+/*!
+ Returns true if \a t is \e {qmlproperty}, \e {qmlpropertygroup},
+ or \e {qmlattachedproperty}.
+ */
+bool CppCodeParser::isQMLPropertyTopic(const QString &t)
+{
+ return (t == COMMAND_QMLPROPERTY || t == COMMAND_QMLATTACHEDPROPERTY);
+}
+
+std::pair<std::vector<TiedDocumentation>, std::vector<FnMatchError>>
+CppCodeParser::processTopicArgs(const UntiedDocumentation &untied)
+{
+ const Doc &doc = untied.documentation;
+
+ if (doc.topicsUsed().isEmpty())
+ return {};
+
+ QDocDatabase *database = QDocDatabase::qdocDB();
+
+ const QString topic = doc.topicsUsed().first().m_topic;
+
+ std::vector<TiedDocumentation> tied{};
+ std::vector<FnMatchError> errors{};
+
+ if (isQMLPropertyTopic(topic)) {
+ auto tied_qml = processQmlProperties(untied);
+ tied.insert(tied.end(), tied_qml.begin(), tied_qml.end());
+ } else {
+ ArgList args = doc.metaCommandArgs(topic);
+ Node *node = nullptr;
+ if (args.size() == 1) {
+ if (topic == COMMAND_FN) {
+ if (Config::instance().showInternal() || !doc.isInternal()) {
+ auto result = fn_parser(doc.location(), args[0].first, args[0].second, untied.context);
+ if (auto *error = std::get_if<FnMatchError>(&result))
+ errors.emplace_back(*error);
+ else
+ node = std::get<Node*>(result);
+ }
+ } else if (topic == COMMAND_MACRO) {
+ node = parseMacroArg(doc.location(), args[0].first);
+ } else if (isQMLMethodTopic(topic)) {
+ node = parseOtherFuncArg(topic, doc.location(), args[0].first);
+ } else if (topic == COMMAND_DONTDOCUMENT) {
+ database->primaryTree()->addToDontDocumentMap(args[0].first);
+ } else {
+ node = processTopicCommand(doc, topic, args[0]);
+ }
+ if (node != nullptr) {
+ tied.emplace_back(TiedDocumentation{doc, node});
+ }
+ } else if (args.size() > 1) {
+ QList<SharedCommentNode *> sharedCommentNodes;
+ for (const auto &arg : std::as_const(args)) {
+ node = nullptr;
+ if (topic == COMMAND_FN) {
+ if (Config::instance().showInternal() || !doc.isInternal()) {
+ auto result = fn_parser(doc.location(), arg.first, arg.second, untied.context);
+ if (auto *error = std::get_if<FnMatchError>(&result))
+ errors.emplace_back(*error);
+ else
+ node = std::get<Node*>(result);
+ }
+ } else if (topic == COMMAND_MACRO) {
+ node = parseMacroArg(doc.location(), arg.first);
+ } else if (isQMLMethodTopic(topic)) {
+ node = parseOtherFuncArg(topic, doc.location(), arg.first);
+ } else {
+ node = processTopicCommand(doc, topic, arg);
+ }
+ if (node != nullptr) {
+ bool found = false;
+ for (SharedCommentNode *scn : sharedCommentNodes) {
+ if (scn->parent() == node->parent()) {
+ scn->append(node);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ auto *scn = new SharedCommentNode(node);
+ sharedCommentNodes.append(scn);
+ tied.emplace_back(TiedDocumentation{doc, scn});
+ }
+ }
+ }
+ for (auto *scn : sharedCommentNodes)
+ scn->sort();
+ }
+ }
+ return std::make_pair(tied, errors);
+}
+
+/*!
+ For each node that is part of C++ API and produces a documentation
+ page, this function ensures that the node belongs to a module.
+ */
+static void checkModuleInclusion(Node *n)
+{
+ if (n->physicalModuleName().isEmpty()) {
+ if (n->isInAPI() && !n->name().isEmpty()) {
+ switch (n->nodeType()) {
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ case Node::Namespace:
+ case Node::HeaderFile:
+ break;
+ default:
+ return;
+ }
+ n->setPhysicalModuleName(Generator::defaultModuleName());
+ QDocDatabase::qdocDB()->addToModule(Generator::defaultModuleName(), n);
+ n->doc().location().warning(
+ QStringLiteral("Documentation for %1 '%2' has no \\inmodule command; "
+ "using project name by default: %3")
+ .arg(Node::nodeTypeString(n->nodeType()), n->name(),
+ n->physicalModuleName()));
+ }
+ }
+}
+
+void CppCodeParser::processMetaCommands(const std::vector<TiedDocumentation> &tied)
+{
+ for (auto [doc, node] : tied) {
+ processMetaCommands(doc, node);
+ node->setDoc(doc);
+ checkModuleInclusion(node);
+ if (node->isAggregate()) {
+ auto *aggregate = static_cast<Aggregate *>(node);
+
+ if (!aggregate->includeFile()) {
+ Aggregate *parent = aggregate;
+ while (parent->physicalModuleName().isEmpty() && (parent->parent() != nullptr))
+ parent = parent->parent();
+
+ if (parent == aggregate)
+ // TODO: Understand if the name can be empty.
+ // In theory it should not be possible as
+ // there would be no aggregate to refer to
+ // such that this code is never reached.
+ //
+ // If the name can be empty, this would
+ // endanger users of the include file down the
+ // line, forcing them to ensure that, further
+ // to there being an actual include file, that
+ // include file is not an empty string, such
+ // that we would require a different way to
+ // generate the include file here.
+ aggregate->setIncludeFile(aggregate->name());
+ else if (aggregate->includeFile())
+ aggregate->setIncludeFile(*parent->includeFile());
+ }
+ }
+ }
+}
+
+void CppCodeParser::processQmlNativeTypeCommand(Node *node, const QString &arg, const Location &location)
+{
+ Q_ASSERT(node);
+ if (!node->isQmlNode()) {
+ location.warning(
+ QStringLiteral("Command '\\%1' is only meaningful in '\\%2'")
+ .arg(COMMAND_QMLNATIVETYPE, COMMAND_QMLTYPE));
+ return;
+ }
+
+ auto qmlNode = static_cast<QmlTypeNode *>(node);
+
+ QDocDatabase *database = QDocDatabase::qdocDB();
+ auto classNode = database->findClassNode(arg.split(u"::"_s));
+
+ if (!classNode) {
+ if (m_showLinkErrors) {
+ location.warning(
+ QStringLiteral("C++ class %2 not found: \\%1 %2")
+ .arg(COMMAND_QMLNATIVETYPE, arg));
+ }
+ return;
+ }
+
+ if (qmlNode->classNode()) {
+ location.warning(
+ QStringLiteral("QML type %1 documented with %2 as its native type. Replacing %2 with %3")
+ .arg(qmlNode->name(), qmlNode->classNode()->name(), arg));
+ }
+
+ qmlNode->setClassNode(classNode);
+ classNode->insertQmlNativeType(qmlNode);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/cppcodeparser.h b/src/qdoc/qdoc/src/qdoc/cppcodeparser.h
new file mode 100644
index 000000000..32e11d05b
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/cppcodeparser.h
@@ -0,0 +1,111 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CPPCODEPARSER_H
+#define CPPCODEPARSER_H
+
+#include "clangcodeparser.h"
+#include "codeparser.h"
+#include "parsererror.h"
+#include "utilities.h"
+
+QT_BEGIN_NAMESPACE
+
+class ClassNode;
+class ExampleNode;
+class FunctionNode;
+class Aggregate;
+
+class CppCodeParser
+{
+public:
+ static inline const QSet<QString> topic_commands{
+ COMMAND_CLASS, COMMAND_DONTDOCUMENT, COMMAND_ENUM, COMMAND_EXAMPLE,
+ COMMAND_EXTERNALPAGE, COMMAND_FN, COMMAND_GROUP, COMMAND_HEADERFILE,
+ COMMAND_MACRO, COMMAND_MODULE, COMMAND_NAMESPACE, COMMAND_PAGE,
+ COMMAND_PROPERTY, COMMAND_TYPEALIAS, COMMAND_TYPEDEF, COMMAND_VARIABLE,
+ COMMAND_QMLTYPE, COMMAND_QMLPROPERTY, COMMAND_QMLPROPERTYGROUP,
+ COMMAND_QMLATTACHEDPROPERTY, COMMAND_QMLSIGNAL, COMMAND_QMLATTACHEDSIGNAL,
+ COMMAND_QMLMETHOD, COMMAND_QMLATTACHEDMETHOD, COMMAND_QMLVALUETYPE, COMMAND_QMLBASICTYPE,
+ COMMAND_QMLMODULE, COMMAND_STRUCT, COMMAND_UNION,
+ };
+
+ static inline const QSet<QString> meta_commands = QSet<QString>(CodeParser::common_meta_commands)
+ << COMMAND_COMPARES << COMMAND_COMPARESWITH << COMMAND_INHEADERFILE
+ << COMMAND_NEXTPAGE << COMMAND_OVERLOAD << COMMAND_PREVIOUSPAGE
+ << COMMAND_QMLINSTANTIATES << COMMAND_QMLNATIVETYPE << COMMAND_REIMP << COMMAND_RELATES;
+
+public:
+ explicit CppCodeParser(FnCommandParser&& parser);
+
+ FunctionNode *parseMacroArg(const Location &location, const QString &macroArg);
+ FunctionNode *parseOtherFuncArg(const QString &topic, const Location &location,
+ const QString &funcArg);
+ static bool isQMLMethodTopic(const QString &t);
+ static bool isQMLPropertyTopic(const QString &t);
+
+ std::pair<std::vector<TiedDocumentation>, std::vector<FnMatchError>>
+ processTopicArgs(const UntiedDocumentation &untied);
+
+ void processMetaCommand(const Doc &doc, const QString &command, const ArgPair &argLocPair,
+ Node *node);
+ void processMetaCommands(const Doc &doc, Node *node);
+ void processMetaCommands(const std::vector<TiedDocumentation> &tied);
+
+protected:
+ virtual Node *processTopicCommand(const Doc &doc, const QString &command,
+ const ArgPair &arg);
+ std::vector<TiedDocumentation> processQmlProperties(const UntiedDocumentation& untied);
+ bool splitQmlPropertyArg(const QString &arg, QString &type, QString &module, QString &element,
+ QString &name, const Location &location);
+
+private:
+ void setExampleFileLists(ExampleNode *en);
+ static void processComparesCommand(Node *node, const QString &arg, const Location &loc);
+ void processQmlNativeTypeCommand(Node *node, const QString &arg, const Location &loc);
+
+private:
+ FnCommandParser fn_parser;
+ QString m_exampleNameFilter;
+ QString m_exampleImageFilter;
+ bool m_showLinkErrors { false };
+};
+
+/*!
+ * \internal
+ * \brief Checks if there are too many topic commands in \a doc.
+ *
+ * This method compares the commands used in \a doc with the set of topic
+ * commands. If zero or one topic command is found, or if all found topic
+ * commands are {\\qml*}-commands, the method returns \c false.
+ *
+ * If more than one topic command is found, QDoc issues a warning and the list
+ * of topic commands used in \a doc, and the method returns \c true.
+ */
+[[nodiscard]] inline bool hasTooManyTopics(const Doc &doc)
+{
+ const QSet<QString> topicCommandsUsed = CppCodeParser::topic_commands & doc.metaCommandsUsed();
+
+ if (topicCommandsUsed.empty() || topicCommandsUsed.size() == 1)
+ return false;
+ if (std::all_of(topicCommandsUsed.cbegin(), topicCommandsUsed.cend(),
+ [](const auto &cmd) { return cmd.startsWith(QLatin1String("qml")); }))
+ return false;
+
+ const QStringList commands = topicCommandsUsed.values();
+ const QString topicCommands{ std::accumulate(
+ commands.cbegin(), commands.cend(), QString{},
+ [index = qsizetype{ 0 }, numberOfCommands = commands.size()](
+ const QString &accumulator, const QString &topic) mutable -> QString {
+ return accumulator + QLatin1String("\\") + topic
+ + Utilities::separator(index++, numberOfCommands);
+ }) };
+
+ doc.location().warning(
+ QStringLiteral("Multiple topic commands found in comment: %1").arg(topicCommands));
+ return true;
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/doc.cpp b/src/qdoc/qdoc/src/qdoc/doc.cpp
new file mode 100644
index 000000000..4a2aa72fd
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/doc.cpp
@@ -0,0 +1,426 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "doc.h"
+
+#include "atom.h"
+#include "config.h"
+#include "codemarker.h"
+#include "docparser.h"
+#include "docprivate.h"
+#include "generator.h"
+#include "qmltypenode.h"
+#include "quoter.h"
+#include "text.h"
+#include "utilities.h"
+
+#include <qcryptographichash.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+DocUtilities &Doc::m_utilities = DocUtilities::instance();
+
+/*!
+ \typedef ArgList
+ \relates Doc
+
+ A list of metacommand arguments that appear in a Doc. Each entry
+ in the list is a <QString, QString> pair (ArgPair):
+
+ \list
+ \li \c {ArgPair.first} - arguments passed to the command.
+ \li \c {ArgPair.second} - optional argument string passed
+ within brackets immediately following the command.
+ \endlist
+*/
+
+/*!
+ Parse the qdoc comment \a source. Build up a list of all the topic
+ commands found including their arguments. This constructor is used
+ when there can be more than one topic command in theqdoc comment.
+ Normally, there is only one topic command in a qdoc comment, but in
+ QML documentation, there is the case where the qdoc \e{qmlproperty}
+ command can appear multiple times in a qdoc comment.
+ */
+Doc::Doc(const Location &start_loc, const Location &end_loc, const QString &source,
+ const QSet<QString> &metaCommandSet, const QSet<QString> &topics)
+{
+ m_priv = new DocPrivate(start_loc, end_loc, source);
+ DocParser parser;
+ parser.parse(source, m_priv, metaCommandSet, topics);
+
+ if (Config::instance().getAtomsDump()) {
+ start_loc.information(u"==== Atoms Structure for block comment starting at %1 ===="_s.arg(
+ start_loc.toString()));
+ body().dump();
+ end_loc.information(
+ u"==== Ending atoms Structure for block comment ending at %1 ===="_s.arg(
+ end_loc.toString()));
+ }
+}
+
+Doc::Doc(const Doc &doc) : m_priv(nullptr)
+{
+ operator=(doc);
+}
+
+Doc::~Doc()
+{
+ if (m_priv && m_priv->deref())
+ delete m_priv;
+}
+
+Doc &Doc::operator=(const Doc &doc)
+{
+ if (&doc == this)
+ return *this;
+ if (doc.m_priv)
+ doc.m_priv->ref();
+ if (m_priv && m_priv->deref())
+ delete m_priv;
+ m_priv = doc.m_priv;
+ return *this;
+}
+
+/*!
+ Returns the starting location of a qdoc comment.
+ */
+const Location &Doc::location() const
+{
+ static const Location dummy;
+ return m_priv == nullptr ? dummy : m_priv->m_start_loc;
+}
+
+/*!
+ Returns the starting location of a qdoc comment.
+ */
+const Location &Doc::startLocation() const
+{
+ return location();
+}
+
+const QString &Doc::source() const
+{
+ static QString null;
+ return m_priv == nullptr ? null : m_priv->m_src;
+}
+
+bool Doc::isEmpty() const
+{
+ return m_priv == nullptr || m_priv->m_src.isEmpty();
+}
+
+const Text &Doc::body() const
+{
+ static const Text dummy;
+ return m_priv == nullptr ? dummy : m_priv->m_text;
+}
+
+Text Doc::briefText(bool inclusive) const
+{
+ return body().subText(Atom::BriefLeft, Atom::BriefRight, nullptr, inclusive);
+}
+
+Text Doc::trimmedBriefText(const QString &className) const
+{
+ QString classNameOnly = className;
+ if (className.contains("::"))
+ classNameOnly = className.split("::").last();
+
+ Text originalText = briefText();
+ Text resultText;
+ const Atom *atom = originalText.firstAtom();
+ if (atom) {
+ QString briefStr;
+ QString whats;
+ /*
+ This code is really ugly. The entire \brief business
+ should be rethought.
+ */
+ while (atom) {
+ if (atom->type() == Atom::AutoLink || atom->type() == Atom::String) {
+ briefStr += atom->string();
+ } else if (atom->type() == Atom::C) {
+ briefStr += Generator::plainCode(atom->string());
+ }
+ atom = atom->next();
+ }
+
+ QStringList w = briefStr.split(QLatin1Char(' '));
+ if (!w.isEmpty() && w.first() == "Returns") {
+ } else {
+ if (!w.isEmpty() && w.first() == "The")
+ w.removeFirst();
+
+ if (!w.isEmpty() && (w.first() == className || w.first() == classNameOnly))
+ w.removeFirst();
+
+ if (!w.isEmpty()
+ && ((w.first() == "class") || (w.first() == "function") || (w.first() == "macro")
+ || (w.first() == "widget") || (w.first() == "namespace")
+ || (w.first() == "header")))
+ w.removeFirst();
+
+ if (!w.isEmpty() && (w.first() == "is" || w.first() == "provides"))
+ w.removeFirst();
+
+ if (!w.isEmpty() && (w.first() == "a" || w.first() == "an"))
+ w.removeFirst();
+ }
+
+ whats = w.join(' ');
+
+ if (whats.endsWith(QLatin1Char('.')))
+ whats.truncate(whats.size() - 1);
+
+ if (!whats.isEmpty())
+ whats[0] = whats[0].toUpper();
+
+ // ### move this once \brief is abolished for properties
+ resultText << whats;
+ }
+ return resultText;
+}
+
+Text Doc::legaleseText() const
+{
+ if (m_priv == nullptr || !m_priv->m_hasLegalese)
+ return Text();
+ else
+ return body().subText(Atom::LegaleseLeft, Atom::LegaleseRight);
+}
+
+QSet<QString> Doc::parameterNames() const
+{
+ return m_priv == nullptr ? QSet<QString>() : m_priv->m_params;
+}
+
+QStringList Doc::enumItemNames() const
+{
+ return m_priv == nullptr ? QStringList() : m_priv->m_enumItemList;
+}
+
+QStringList Doc::omitEnumItemNames() const
+{
+ return m_priv == nullptr ? QStringList() : m_priv->m_omitEnumItemList;
+}
+
+QSet<QString> Doc::metaCommandsUsed() const
+{
+ return m_priv == nullptr ? QSet<QString>() : m_priv->m_metacommandsUsed;
+}
+
+/*!
+ Returns true if the set of metacommands used in the doc
+ comment contains \e {internal}.
+ */
+bool Doc::isInternal() const
+{
+ return metaCommandsUsed().contains(QLatin1String("internal"));
+}
+
+/*!
+ Returns true if the set of metacommands used in the doc
+ comment contains \e {reimp}.
+ */
+bool Doc::isMarkedReimp() const
+{
+ return metaCommandsUsed().contains(QLatin1String("reimp"));
+}
+
+/*!
+ Returns a reference to the list of topic commands used in the
+ current qdoc comment. Normally there is only one, but there
+ can be multiple \e{qmlproperty} commands, for example.
+ */
+TopicList Doc::topicsUsed() const
+{
+ return m_priv == nullptr ? TopicList() : m_priv->m_topics;
+}
+
+ArgList Doc::metaCommandArgs(const QString &metacommand) const
+{
+ return m_priv == nullptr ? ArgList() : m_priv->m_metaCommandMap.value(metacommand);
+}
+
+QList<Text> Doc::alsoList() const
+{
+ return m_priv == nullptr ? QList<Text>() : m_priv->m_alsoList;
+}
+
+bool Doc::hasTableOfContents() const
+{
+ return m_priv && m_priv->extra && !m_priv->extra->m_tableOfContents.isEmpty();
+}
+
+bool Doc::hasKeywords() const
+{
+ return m_priv && m_priv->extra && !m_priv->extra->m_keywords.isEmpty();
+}
+
+bool Doc::hasTargets() const
+{
+ return m_priv && m_priv->extra && !m_priv->extra->m_targets.isEmpty();
+}
+
+const QList<Atom *> &Doc::tableOfContents() const
+{
+ m_priv->constructExtra();
+ return m_priv->extra->m_tableOfContents;
+}
+
+const QList<int> &Doc::tableOfContentsLevels() const
+{
+ m_priv->constructExtra();
+ return m_priv->extra->m_tableOfContentsLevels;
+}
+
+const QList<Atom *> &Doc::keywords() const
+{
+ m_priv->constructExtra();
+ return m_priv->extra->m_keywords;
+}
+
+const QList<Atom *> &Doc::targets() const
+{
+ m_priv->constructExtra();
+ return m_priv->extra->m_targets;
+}
+
+QStringMultiMap *Doc::metaTagMap() const
+{
+ return m_priv && m_priv->extra ? &m_priv->extra->m_metaMap : nullptr;
+}
+
+QMultiMap<ComparisonCategory, Text> *Doc::comparesWithMap() const
+{
+ return m_priv && m_priv->extra ? &m_priv->extra->m_comparesWithMap : nullptr;
+}
+
+void Doc::constructExtra() const
+{
+ if (m_priv)
+ m_priv->constructExtra();
+}
+
+void Doc::initialize(FileResolver& file_resolver)
+{
+ Config &config = Config::instance();
+ DocParser::initialize(config, file_resolver);
+
+ const auto &configMacros = config.subVars(CONFIG_MACRO);
+ for (const auto &macroName : configMacros) {
+ QString macroDotName = CONFIG_MACRO + Config::dot + macroName;
+ Macro macro;
+ macro.numParams = -1;
+ const auto &macroConfigVar = config.get(macroDotName);
+ macro.m_defaultDef = macroConfigVar.asString();
+ if (!macro.m_defaultDef.isEmpty()) {
+ macro.m_defaultDefLocation = macroConfigVar.location();
+ macro.numParams = Config::numParams(macro.m_defaultDef);
+ }
+ bool silent = false;
+
+ const auto &macroDotNames = config.subVars(macroDotName);
+ for (const auto &f : macroDotNames) {
+ const auto &macroSubVar = config.get(macroDotName + Config::dot + f);
+ QString def{macroSubVar.asString()};
+ if (!def.isEmpty()) {
+ macro.m_otherDefs.insert(f, def);
+ int m = Config::numParams(def);
+ if (macro.numParams == -1)
+ macro.numParams = m;
+ // .match definition is a regular expression that contains no params
+ else if (macro.numParams != m && f != QLatin1String("match")) {
+ if (!silent) {
+ QString other = QStringLiteral("default");
+ if (macro.m_defaultDef.isEmpty())
+ other = macro.m_otherDefs.constBegin().key();
+ macroSubVar.location().warning(
+ QStringLiteral("Macro '\\%1' takes inconsistent number of "
+ "arguments (%2 %3, %4 %5)")
+ .arg(macroName, f, QString::number(m), other,
+ QString::number(macro.numParams)));
+ silent = true;
+ }
+ if (macro.numParams < m)
+ macro.numParams = m;
+ }
+ }
+ }
+ if (macro.numParams != -1)
+ m_utilities.macroHash.insert(macroName, macro);
+ }
+}
+
+/*!
+ All the heap allocated variables are deleted.
+ */
+void Doc::terminate()
+{
+ m_utilities.cmdHash.clear();
+ m_utilities.macroHash.clear();
+}
+
+/*!
+ Trims the deadwood out of \a str. i.e., this function
+ cleans up \a str.
+ */
+void Doc::trimCStyleComment(Location &location, QString &str)
+{
+ QString cleaned;
+ Location m = location;
+ bool metAsterColumn = true;
+ int asterColumn = location.columnNo() + 1;
+ int i;
+
+ for (i = 0; i < str.size(); ++i) {
+ if (m.columnNo() == asterColumn) {
+ if (str[i] != '*')
+ break;
+ cleaned += ' ';
+ metAsterColumn = true;
+ } else {
+ if (str[i] == '\n') {
+ if (!metAsterColumn)
+ break;
+ metAsterColumn = false;
+ }
+ cleaned += str[i];
+ }
+ m.advance(str[i]);
+ }
+ if (cleaned.size() == str.size())
+ str = cleaned;
+
+ for (int i = 0; i < 3; ++i)
+ location.advance(str[i]);
+ str = str.mid(3, str.size() - 5);
+}
+
+void Doc::quoteFromFile(const Location &location, Quoter &quoter, ResolvedFile resolved_file)
+{
+ // TODO: quoteFromFile should not care about modifying a stateful
+ // quoter from the outside, instead, it should produce a quoter
+ // that allows the caller to retrieve the required information
+ // about the quoted file.
+ //
+ // When changing the way in which quoting works, this kind of
+ // spread resposability should be removed, together with quoteFromFile.
+ quoter.reset();
+
+ QString code;
+ {
+ QFile input_file{resolved_file.get_path()};
+ if (!input_file.open(QFile::ReadOnly))
+ return;
+ code = DocParser::untabifyEtc(QTextStream{&input_file}.readAll());
+ }
+
+ CodeMarker *marker = CodeMarker::markerForFileName(resolved_file.get_path());
+ quoter.quoteFromFile(resolved_file.get_path(), code, marker->markedUpCode(code, nullptr, location));
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/doc.h b/src/qdoc/qdoc/src/qdoc/doc.h
new file mode 100644
index 000000000..49a9f7947
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/doc.h
@@ -0,0 +1,93 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef DOC_H
+#define DOC_H
+
+#include "location.h"
+#include "comparisoncategory.h"
+#include "docutilities.h"
+#include "topic.h"
+
+#include "filesystem/fileresolver.h"
+#include "boundaries/filesystem/resolvedfile.h"
+
+#include <QtCore/qmap.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class Atom;
+class DocPrivate;
+class Quoter;
+class Text;
+
+typedef std::pair<QString, QString> ArgPair;
+typedef QList<ArgPair> ArgList;
+typedef QMultiMap<QString, QString> QStringMultiMap;
+
+class Doc
+{
+public:
+ // the order is important
+ enum Sections {
+ NoSection = -1,
+ Section1 = 1,
+ Section2 = 2,
+ Section3 = 3,
+ Section4 = 4
+ };
+
+ Doc() = default;
+ Doc(const Location &start_loc, const Location &end_loc, const QString &source,
+ const QSet<QString> &metaCommandSet, const QSet<QString> &topics);
+ Doc(const Doc &doc);
+ ~Doc();
+
+ Doc &operator=(const Doc &doc);
+
+ [[nodiscard]] const Location &location() const;
+ [[nodiscard]] const Location &startLocation() const;
+ [[nodiscard]] bool isEmpty() const;
+ [[nodiscard]] const QString &source() const;
+ [[nodiscard]] const Text &body() const;
+ [[nodiscard]] Text briefText(bool inclusive = false) const;
+ [[nodiscard]] Text trimmedBriefText(const QString &className) const;
+ [[nodiscard]] Text legaleseText() const;
+ [[nodiscard]] QSet<QString> parameterNames() const;
+ [[nodiscard]] QStringList enumItemNames() const;
+ [[nodiscard]] QStringList omitEnumItemNames() const;
+ [[nodiscard]] QSet<QString> metaCommandsUsed() const;
+ [[nodiscard]] TopicList topicsUsed() const;
+ [[nodiscard]] ArgList metaCommandArgs(const QString &metaCommand) const;
+ [[nodiscard]] QList<Text> alsoList() const;
+ [[nodiscard]] bool hasTableOfContents() const;
+ [[nodiscard]] bool hasKeywords() const;
+ [[nodiscard]] bool hasTargets() const;
+ [[nodiscard]] bool isInternal() const;
+ [[nodiscard]] bool isMarkedReimp() const;
+ [[nodiscard]] const QList<Atom *> &tableOfContents() const;
+ [[nodiscard]] const QList<int> &tableOfContentsLevels() const;
+ [[nodiscard]] const QList<Atom *> &keywords() const;
+ [[nodiscard]] const QList<Atom *> &targets() const;
+ [[nodiscard]] QStringMultiMap *metaTagMap() const;
+ [[nodiscard]] QMultiMap<ComparisonCategory, Text> *comparesWithMap() const;
+ void constructExtra() const;
+
+ static void initialize(FileResolver& file_resolver);
+ static void terminate();
+ static void trimCStyleComment(Location &location, QString &str);
+ static void quoteFromFile(const Location &location, Quoter &quoter,
+ ResolvedFile resolved_file);
+
+private:
+ DocPrivate *m_priv { nullptr };
+ static DocUtilities &m_utilities;
+};
+Q_DECLARE_TYPEINFO(Doc, Q_RELOCATABLE_TYPE);
+typedef QList<Doc> DocList;
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/docbookgenerator.cpp b/src/qdoc/qdoc/src/qdoc/docbookgenerator.cpp
new file mode 100644
index 000000000..6ac83ff13
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docbookgenerator.cpp
@@ -0,0 +1,4771 @@
+// Copyright (C) 2019 Thibaut Cuvelier
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "docbookgenerator.h"
+
+#include "access.h"
+#include "aggregate.h"
+#include "classnode.h"
+#include "codemarker.h"
+#include "collectionnode.h"
+#include "comparisoncategory.h"
+#include "config.h"
+#include "enumnode.h"
+#include "examplenode.h"
+#include "functionnode.h"
+#include "generator.h"
+#include "node.h"
+#include "propertynode.h"
+#include "quoter.h"
+#include "qdocdatabase.h"
+#include "qmlpropertynode.h"
+#include "sharedcommentnode.h"
+#include "typedefnode.h"
+#include "variablenode.h"
+
+#include <QtCore/qlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/quuid.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qversionnumber.h>
+
+#include <cctype>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+static const char dbNamespace[] = "http://docbook.org/ns/docbook";
+static const char xlinkNamespace[] = "http://www.w3.org/1999/xlink";
+static const char itsNamespace[] = "http://www.w3.org/2005/11/its";
+
+DocBookGenerator::DocBookGenerator(FileResolver& file_resolver) : XmlGenerator(file_resolver) {}
+
+inline void DocBookGenerator::newLine()
+{
+ m_writer->writeCharacters("\n");
+}
+
+void DocBookGenerator::writeXmlId(const QString &id)
+{
+ if (id.isEmpty())
+ return;
+
+ m_writer->writeAttribute("xml:id", registerRef(id, true));
+}
+
+void DocBookGenerator::writeXmlId(const Node *node)
+{
+ if (!node)
+ return;
+
+ // Specifically for nodes, do not use the same code path as for QString
+ // inputs, as refForNode calls registerRef in all cases. Calling
+ // registerRef a second time adds a character to "disambiguate" the two IDs
+ // (the one returned by refForNode, then the one that is written as
+ // xml:id).
+ QString id = Generator::cleanRef(refForNode(node), true);
+ if (!id.isEmpty())
+ m_writer->writeAttribute("xml:id", id);
+}
+
+void DocBookGenerator::startSectionBegin(const QString &id)
+{
+ m_hasSection = true;
+
+ m_writer->writeStartElement(dbNamespace, "section");
+ writeXmlId(id);
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "title");
+}
+
+void DocBookGenerator::startSectionBegin(const Node *node)
+{
+ m_writer->writeStartElement(dbNamespace, "section");
+ writeXmlId(node);
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "title");
+}
+
+void DocBookGenerator::startSectionEnd()
+{
+ m_writer->writeEndElement(); // title
+ newLine();
+}
+
+void DocBookGenerator::startSection(const QString &id, const QString &title)
+{
+ startSectionBegin(id);
+ m_writer->writeCharacters(title);
+ startSectionEnd();
+}
+
+void DocBookGenerator::startSection(const Node *node, const QString &title)
+{
+ startSectionBegin(node);
+ m_writer->writeCharacters(title);
+ startSectionEnd();
+}
+
+void DocBookGenerator::startSection(const QString &title)
+{
+ // No xml:id given: down the calls, "" is interpreted as "no ID".
+ startSection("", title);
+}
+
+void DocBookGenerator::endSection()
+{
+ m_writer->writeEndElement(); // section
+ newLine();
+}
+
+void DocBookGenerator::writeAnchor(const QString &id)
+{
+ if (id.isEmpty())
+ return;
+
+ m_writer->writeEmptyElement(dbNamespace, "anchor");
+ writeXmlId(id);
+ newLine();
+}
+
+/*!
+ Initializes the DocBook output generator's data structures
+ from the configuration (Config).
+ */
+void DocBookGenerator::initializeGenerator()
+{
+ // Excerpts from HtmlGenerator::initializeGenerator.
+ Generator::initializeGenerator();
+ m_config = &Config::instance();
+
+ m_project = m_config->get(CONFIG_PROJECT).asString();
+
+ m_projectDescription = m_config->get(CONFIG_DESCRIPTION).asString();
+ if (m_projectDescription.isEmpty() && !m_project.isEmpty())
+ m_projectDescription = m_project + QLatin1String(" Reference Documentation");
+
+ m_naturalLanguage = m_config->get(CONFIG_NATURALLANGUAGE).asString();
+ if (m_naturalLanguage.isEmpty())
+ m_naturalLanguage = QLatin1String("en");
+
+ m_buildVersion = m_config->get(CONFIG_BUILDVERSION).asString();
+ m_useDocBook52 = m_config->get(CONFIG_DOCBOOKEXTENSIONS).asBool() ||
+ m_config->get(format() + Config::dot + "usedocbookextensions").asBool();
+ m_useITS = m_config->get(format() + Config::dot + "its").asBool();
+}
+
+QString DocBookGenerator::format()
+{
+ return "DocBook";
+}
+
+/*!
+ Returns "xml" for this subclass of Generator.
+ */
+QString DocBookGenerator::fileExtension() const
+{
+ return "xml";
+}
+
+/*!
+ 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 DocBookGenerator::generateText(const Text &text, const Node *relative)
+{
+ // From Generator::generateText.
+ if (!text.firstAtom())
+ return false;
+
+ int numAtoms = 0;
+ initializeTextOutput();
+ generateAtomList(text.firstAtom(), relative, nullptr, true, numAtoms);
+ closeTextSections();
+ return true;
+}
+
+QString removeCodeMarkers(const QString& code) {
+ QString rewritten = code;
+ static const QRegularExpression re("(<@[^>&]*>)|(<\\/@[^&>]*>)");
+ rewritten.replace(re, "");
+ return rewritten;
+}
+
+/*!
+ Generate DocBook from an instance of Atom.
+ */
+qsizetype DocBookGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker*)
+{
+ Q_ASSERT(m_writer);
+ // From HtmlGenerator::generateAtom, without warning generation.
+ int idx = 0;
+ int skipAhead = 0;
+ Node::Genus genus = Node::DontCare;
+
+ switch (atom->type()) {
+ case Atom::AutoLink:
+ // Allow auto-linking to nodes in API reference
+ genus = Node::API;
+ Q_FALLTHROUGH();
+ case Atom::NavAutoLink:
+ if (!m_inLink && !m_inContents && !m_inSectionHeading) {
+ const Node *node = nullptr;
+ QString link = getAutoLink(atom, relative, &node, genus);
+ if (!link.isEmpty() && node && node->isDeprecated()
+ && relative->parent() != node && !relative->isDeprecated()) {
+ link.clear();
+ }
+ if (link.isEmpty()) {
+ m_writer->writeCharacters(atom->string());
+ } else {
+ beginLink(link, node, relative);
+ generateLink(atom);
+ endLink();
+ }
+ } else {
+ m_writer->writeCharacters(atom->string());
+ }
+ break;
+ case Atom::BaseName:
+ break;
+ case Atom::BriefLeft:
+ if (!hasBrief(relative)) {
+ skipAhead = skipAtoms(atom, Atom::BriefRight);
+ break;
+ }
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_inPara = true;
+ rewritePropertyBrief(atom, relative);
+ break;
+ case Atom::BriefRight:
+ if (hasBrief(relative)) {
+ m_writer->writeEndElement(); // para
+ m_inPara = false;
+ newLine();
+ }
+ break;
+ case Atom::C:
+ // This may at one time have been used to mark up C++ code but it is
+ // now widely used to write teletype text. As a result, text marked
+ // with the \c command is not passed to a code marker.
+ if (m_inTeletype)
+ m_writer->writeCharacters(plainCode(atom->string()));
+ else
+ m_writer->writeTextElement(dbNamespace, "code", plainCode(atom->string()));
+ break;
+ case Atom::CaptionLeft:
+ m_writer->writeStartElement(dbNamespace, "title");
+ break;
+ case Atom::CaptionRight:
+ endLink();
+ m_writer->writeEndElement(); // title
+ newLine();
+ break;
+ case Atom::Qml:
+ m_writer->writeStartElement(dbNamespace, "programlisting");
+ m_writer->writeAttribute("language", "qml");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters(removeCodeMarkers(atom->string()));
+ m_writer->writeEndElement(); // programlisting
+ newLine();
+ break;
+ case Atom::Code:
+ m_writer->writeStartElement(dbNamespace, "programlisting");
+ m_writer->writeAttribute("language", "cpp");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters(removeCodeMarkers(atom->string()));
+ m_writer->writeEndElement(); // programlisting
+ newLine();
+ break;
+ case Atom::CodeBad:
+ m_writer->writeStartElement(dbNamespace, "programlisting");
+ m_writer->writeAttribute("language", "cpp");
+ m_writer->writeAttribute("role", "bad");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters(removeCodeMarkers(atom->string()));
+ m_writer->writeEndElement(); // programlisting
+ newLine();
+ break;
+ case Atom::DetailsLeft:
+ case Atom::DetailsRight:
+ break;
+ case Atom::DivLeft:
+ case Atom::DivRight:
+ break;
+ case Atom::FootnoteLeft:
+ m_writer->writeStartElement(dbNamespace, "footnote");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_inPara = true;
+ break;
+ case Atom::FootnoteRight:
+ m_writer->writeEndElement(); // para
+ m_inPara = false;
+ newLine();
+ m_writer->writeEndElement(); // footnote
+ break;
+ case Atom::FormatElse:
+ case Atom::FormatEndif:
+ case Atom::FormatIf:
+ break;
+ case Atom::FormattingLeft:
+ if (atom->string() == ATOM_FORMATTING_BOLD) {
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ } else if (atom->string() == ATOM_FORMATTING_ITALIC) {
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ } else if (atom->string() == ATOM_FORMATTING_UNDERLINE) {
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "underline");
+ } else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT) {
+ m_writer->writeStartElement(dbNamespace, "subscript");
+ } else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT) {
+ m_writer->writeStartElement(dbNamespace, "superscript");
+ } else if (atom->string() == ATOM_FORMATTING_TELETYPE
+ || atom->string() == ATOM_FORMATTING_PARAMETER) {
+ m_writer->writeStartElement(dbNamespace, "code");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+
+ if (atom->string() == ATOM_FORMATTING_PARAMETER)
+ m_writer->writeAttribute("role", "parameter");
+ else // atom->string() == ATOM_FORMATTING_TELETYPE
+ m_inTeletype = true;
+ } else if (atom->string() == ATOM_FORMATTING_UICONTROL) {
+ m_writer->writeStartElement(dbNamespace, "guilabel");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ } else if (atom->string() == ATOM_FORMATTING_TRADEMARK) {
+ m_writer->writeStartElement(dbNamespace,
+ appendTrademark(atom->find(Atom::FormattingRight)) ?
+ "trademark" : "phrase");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ } else {
+ relative->location().warning(QStringLiteral("Unsupported formatting: %1").arg(atom->string()));
+ }
+ break;
+ case Atom::FormattingRight:
+ if (atom->string() == ATOM_FORMATTING_BOLD || atom->string() == ATOM_FORMATTING_ITALIC
+ || atom->string() == ATOM_FORMATTING_UNDERLINE
+ || atom->string() == ATOM_FORMATTING_SUBSCRIPT
+ || atom->string() == ATOM_FORMATTING_SUPERSCRIPT
+ || atom->string() == ATOM_FORMATTING_TELETYPE
+ || atom->string() == ATOM_FORMATTING_PARAMETER
+ || atom->string() == ATOM_FORMATTING_UICONTROL
+ || atom->string() == ATOM_FORMATTING_TRADEMARK) {
+ m_writer->writeEndElement();
+ } else if (atom->string() == ATOM_FORMATTING_LINK) {
+ if (atom->string() == ATOM_FORMATTING_TELETYPE)
+ m_inTeletype = false;
+ endLink();
+ } else {
+ relative->location().warning(QStringLiteral("Unsupported formatting: %1").arg(atom->string()));
+ }
+ break;
+ case Atom::AnnotatedList: {
+ if (const CollectionNode *cn = m_qdb->getCollectionNode(atom->string(), Node::Group))
+ generateList(cn, atom->string(), Generator::sortOrder(atom->strings().last()));
+ } break;
+ case Atom::GeneratedList: {
+ const auto sortOrder{Generator::sortOrder(atom->strings().last())};
+ bool hasGeneratedSomething = false;
+ if (atom->string() == QLatin1String("annotatedclasses")
+ || atom->string() == QLatin1String("attributions")
+ || atom->string() == QLatin1String("namespaces")) {
+ const NodeMultiMap things = atom->string() == QLatin1String("annotatedclasses")
+ ? m_qdb->getCppClasses()
+ : atom->string() == QLatin1String("attributions") ? m_qdb->getAttributions()
+ : m_qdb->getNamespaces();
+ generateAnnotatedList(relative, things.values(), atom->string(), Auto, sortOrder);
+ hasGeneratedSomething = !things.isEmpty();
+ } else if (atom->string() == QLatin1String("annotatedexamples")
+ || atom->string() == QLatin1String("annotatedattributions")) {
+ const NodeMultiMap things = atom->string() == QLatin1String("annotatedexamples")
+ ? m_qdb->getAttributions()
+ : m_qdb->getExamples();
+ generateAnnotatedLists(relative, things, atom->string());
+ hasGeneratedSomething = !things.isEmpty();
+ } else if (atom->string() == QLatin1String("classes")
+ || atom->string() == QLatin1String("qmlbasictypes") // deprecated!
+ || atom->string() == QLatin1String("qmlvaluetypes")
+ || atom->string() == QLatin1String("qmltypes")) {
+ const NodeMultiMap things = atom->string() == QLatin1String("classes")
+ ? m_qdb->getCppClasses()
+ : (atom->string() == QLatin1String("qmlvaluetypes")
+ || atom->string() == QLatin1String("qmlbasictypes"))
+ ? m_qdb->getQmlValueTypes()
+ : m_qdb->getQmlTypes();
+ generateCompactList(relative, things, true, QString(), atom->string());
+ hasGeneratedSomething = !things.isEmpty();
+ } else if (atom->string().contains("classes ")) {
+ QString rootName = atom->string().mid(atom->string().indexOf("classes") + 7).trimmed();
+ NodeMultiMap things = m_qdb->getCppClasses();
+
+ hasGeneratedSomething = !things.isEmpty();
+ generateCompactList(relative, things, true, rootName, atom->string());
+ } else if ((idx = atom->string().indexOf(QStringLiteral("bymodule"))) != -1) {
+ QString moduleName = atom->string().mid(idx + 8).trimmed();
+ Node::NodeType moduleType = typeFromString(atom);
+ QDocDatabase *qdb = QDocDatabase::qdocDB();
+ if (const CollectionNode *cn = qdb->getCollectionNode(moduleName, moduleType)) {
+ NodeMap map;
+ switch (moduleType) {
+ case Node::Module:
+ // classesbymodule <module_name>
+ map = cn->getMembers([](const Node *n){ return n->isClassNode(); });
+ break;
+ case Node::QmlModule:
+ if (atom->string().contains(QLatin1String("qmlvaluetypes")))
+ map = cn->getMembers(Node::QmlValueType); // qmlvaluetypesbymodule <module_name>
+ else
+ map = cn->getMembers(Node::QmlType); // qmltypesbymodule <module_name>
+ break;
+ default: // fall back to generating all members
+ generateAnnotatedList(relative, cn->members(), atom->string(), Auto, sortOrder);
+ hasGeneratedSomething = !cn->members().isEmpty();
+ break;
+ }
+ if (!map.isEmpty()) {
+ generateAnnotatedList(relative, map.values(), atom->string(), Auto, sortOrder);
+ hasGeneratedSomething = true;
+ }
+ }
+ } else if (atom->string() == QLatin1String("classhierarchy")) {
+ generateClassHierarchy(relative, m_qdb->getCppClasses());
+ hasGeneratedSomething = !m_qdb->getCppClasses().isEmpty();
+ } else if (atom->string().startsWith("obsolete")) {
+ QString prefix = atom->string().contains("cpp") ? QStringLiteral("Q") : QString();
+ const NodeMultiMap &things = atom->string() == QLatin1String("obsoleteclasses")
+ ? m_qdb->getObsoleteClasses()
+ : atom->string() == QLatin1String("obsoleteqmltypes")
+ ? m_qdb->getObsoleteQmlTypes()
+ : atom->string() == QLatin1String("obsoletecppmembers")
+ ? m_qdb->getClassesWithObsoleteMembers()
+ : m_qdb->getQmlTypesWithObsoleteMembers();
+ generateCompactList(relative, things, false, prefix, atom->string());
+ hasGeneratedSomething = !things.isEmpty();
+ } else if (atom->string() == QLatin1String("functionindex")) {
+ generateFunctionIndex(relative);
+ hasGeneratedSomething = !m_qdb->getFunctionIndex().isEmpty();
+ } else if (atom->string() == QLatin1String("legalese")) {
+ generateLegaleseList(relative);
+ hasGeneratedSomething = !m_qdb->getLegaleseTexts().isEmpty();
+ } else if (atom->string() == QLatin1String("overviews")
+ || atom->string() == QLatin1String("cpp-modules")
+ || atom->string() == QLatin1String("qml-modules")
+ || atom->string() == QLatin1String("related")) {
+ generateList(relative, atom->string());
+ hasGeneratedSomething = true; // Approximation, because there is
+ // some nontrivial logic in generateList.
+ } else if (const auto *cn = m_qdb->getCollectionNode(atom->string(), Node::Group); cn) {
+ generateAnnotatedList(cn, cn->members(), atom->string(), ItemizedList, sortOrder);
+ hasGeneratedSomething = true; // Approximation
+ }
+
+ // There must still be some content generated for the DocBook document
+ // to be valid (except if already in a paragraph).
+ if (!hasGeneratedSomething && !m_inPara) {
+ m_writer->writeEmptyElement(dbNamespace, "para");
+ newLine();
+ }
+ }
+ break;
+ case Atom::SinceList:
+ // Table of contents, should automatically be generated by the DocBook processor.
+ Q_FALLTHROUGH();
+ case Atom::LineBreak:
+ case Atom::BR:
+ case Atom::HR:
+ // Not supported in DocBook.
+ break;
+ case Atom::Image: // mediaobject
+ // An Image atom is always followed by an ImageText atom,
+ // containing the alternative text.
+ // If no caption is present, we just output a <db:mediaobject>,
+ // avoiding the wrapper as it is not required.
+ // For bordered images, there is another atom before the
+ // caption, DivRight (the corresponding DivLeft being just
+ // before the image).
+
+ if (atom->next() && matchAhead(atom->next(), Atom::DivRight) && atom->next()->next()
+ && matchAhead(atom->next()->next(), Atom::CaptionLeft)) {
+ // If there is a caption, there must be a <db:figure>
+ // wrapper starting with the caption.
+ Q_ASSERT(atom->next());
+ Q_ASSERT(atom->next()->next());
+ Q_ASSERT(atom->next()->next()->next());
+ Q_ASSERT(atom->next()->next()->next()->next());
+ Q_ASSERT(atom->next()->next()->next()->next()->next());
+
+ m_writer->writeStartElement(dbNamespace, "figure");
+ newLine();
+
+ const Atom *current = atom->next()->next()->next();
+ skipAhead += 2;
+
+ Q_ASSERT(current->type() == Atom::CaptionLeft);
+ generateAtom(current, relative, nullptr);
+ current = current->next();
+ ++skipAhead;
+
+ while (current->type() != Atom::CaptionRight) { // The actual caption.
+ generateAtom(current, relative, nullptr);
+ current = current->next();
+ ++skipAhead;
+ }
+
+ Q_ASSERT(current->type() == Atom::CaptionRight);
+ generateAtom(current, relative, nullptr);
+ current = current->next();
+ ++skipAhead;
+
+ m_closeFigureWrapper = true;
+ }
+
+ if (atom->next() && matchAhead(atom->next(), Atom::CaptionLeft)) {
+ // If there is a caption, there must be a <db:figure>
+ // wrapper starting with the caption.
+ Q_ASSERT(atom->next());
+ Q_ASSERT(atom->next()->next());
+ Q_ASSERT(atom->next()->next()->next());
+ Q_ASSERT(atom->next()->next()->next()->next());
+
+ m_writer->writeStartElement(dbNamespace, "figure");
+ newLine();
+
+ const Atom *current = atom->next()->next();
+ ++skipAhead;
+
+ Q_ASSERT(current->type() == Atom::CaptionLeft);
+ generateAtom(current, relative, nullptr);
+ current = current->next();
+ ++skipAhead;
+
+ while (current->type() != Atom::CaptionRight) { // The actual caption.
+ generateAtom(current, relative, nullptr);
+ current = current->next();
+ ++skipAhead;
+ }
+
+ Q_ASSERT(current->type() == Atom::CaptionRight);
+ generateAtom(current, relative, nullptr);
+ current = current->next();
+ ++skipAhead;
+
+ m_closeFigureWrapper = true;
+ }
+
+ Q_FALLTHROUGH();
+ case Atom::InlineImage: { // inlinemediaobject
+ // TODO: [generator-insufficient-structural-abstraction]
+ // The structure of the computations for this part of the
+ // docbook generation and the same parts in other format
+ // generators is the same.
+ //
+ // The difference, instead, lies in what the generated output
+ // is like. A correct abstraction for a generator would take
+ // this structural equivalence into account and encapsulate it
+ // into a driver for the format generators.
+ //
+ // This would avoid the replication of content, and the
+ // subsequent friction for changes and desynchronization
+ // between generators.
+ //
+ // Review all the generators routines and find the actual
+ // skeleton that is shared between them, then consider it when
+ // extracting the logic for the generation phase.
+ QString tag = atom->type() == Atom::Image ? "mediaobject" : "inlinemediaobject";
+ m_writer->writeStartElement(dbNamespace, tag);
+ newLine();
+
+ auto maybe_resolved_file{file_resolver.resolve(atom->string())};
+ if (!maybe_resolved_file) {
+ // TODO: [uncetnralized-admonition][failed-resolve-file]
+ relative->location().warning(QStringLiteral("Missing image: %1").arg(atom->string()));
+
+ m_writer->writeStartElement(dbNamespace, "textobject");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeTextElement(dbNamespace, "emphasis",
+ "[Missing image " + atom->string() + "]");
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // textobject
+ newLine();
+ } else {
+ ResolvedFile file{*maybe_resolved_file};
+ QString file_name{QFileInfo{file.get_path()}.fileName()};
+
+ // TODO: [uncentralized-output-directory-structure]
+ Config::copyFile(relative->doc().location(), file.get_path(), file_name, outputDir() + QLatin1String("/images"));
+
+ if (atom->next() && !atom->next()->string().isEmpty()
+ && atom->next()->type() == Atom::ImageText) {
+ m_writer->writeTextElement(dbNamespace, "alt", atom->next()->string());
+ newLine();
+ }
+
+ m_writer->writeStartElement(dbNamespace, "imageobject");
+ newLine();
+ m_writer->writeEmptyElement(dbNamespace, "imagedata");
+ // TODO: [uncentralized-output-directory-structure]
+ m_writer->writeAttribute("fileref", "images/" + file_name);
+ newLine();
+ m_writer->writeEndElement(); // imageobject
+ newLine();
+
+ // TODO: [uncentralized-output-directory-structure]
+ setImageFileName(relative, "images/" + file_name);
+ }
+
+ m_writer->writeEndElement(); // [inline]mediaobject
+ if (atom->type() == Atom::Image)
+ newLine();
+
+ if (m_closeFigureWrapper) {
+ m_writer->writeEndElement(); // figure
+ newLine();
+ m_closeFigureWrapper = false;
+ }
+ } break;
+ case Atom::ImageText:
+ break;
+ case Atom::ImportantLeft:
+ case Atom::NoteLeft:
+ case Atom::WarningLeft: {
+ QString admonType = atom->typeString().toLower();
+ // Remove 'Left' to get the admonition type
+ admonType.chop(4);
+ m_writer->writeStartElement(dbNamespace, admonType);
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_inPara = true;
+ } break;
+ case Atom::ImportantRight:
+ case Atom::NoteRight:
+ case Atom::WarningRight:
+ m_writer->writeEndElement(); // para
+ m_inPara = false;
+ newLine();
+ m_writer->writeEndElement(); // note/important
+ newLine();
+ break;
+ case Atom::LegaleseLeft:
+ case Atom::LegaleseRight:
+ break;
+ case Atom::Link:
+ case Atom::NavLink: {
+ const Node *node = nullptr;
+ QString link = getLink(atom, relative, &node);
+ beginLink(link, node, relative); // Ended at Atom::FormattingRight
+ skipAhead = 1;
+ } break;
+ case Atom::LinkNode: {
+ const Node *node = CodeMarker::nodeForString(atom->string());
+ beginLink(linkForNode(node, relative), node, relative);
+ skipAhead = 1;
+ } break;
+ case Atom::ListLeft:
+ if (m_inPara) {
+ // The variable m_inPara is not set in a very smart way, because
+ // it ignores nesting. This might in theory create false positives
+ // here. A better solution would be to track the depth of
+ // paragraphs the generator is in, but determining the right check
+ // for this condition is far from trivial (think of nested lists).
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_inPara = false;
+ }
+
+ if (atom->string() == ATOM_LIST_BULLET) {
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+ } else if (atom->string() == ATOM_LIST_TAG) {
+ m_writer->writeStartElement(dbNamespace, "variablelist");
+ newLine();
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ m_writer->writeStartElement(dbNamespace, "informaltable");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "thead");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "tr");
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "th", "Constant");
+ newLine();
+
+ m_threeColumnEnumValueTable = isThreeColumnEnumValueTable(atom);
+ if (m_threeColumnEnumValueTable && relative->nodeType() == Node::Enum) {
+ // With three columns, if not in \enum topic, skip the value column
+ m_writer->writeTextElement(dbNamespace, "th", "Value");
+ newLine();
+ }
+
+ if (!isOneColumnValueTable(atom)) {
+ m_writer->writeTextElement(dbNamespace, "th", "Description");
+ newLine();
+ }
+
+ m_writer->writeEndElement(); // tr
+ newLine();
+ m_writer->writeEndElement(); // thead
+ newLine();
+ } else { // No recognized list type.
+ m_writer->writeStartElement(dbNamespace, "orderedlist");
+
+ if (atom->next() != nullptr && atom->next()->string().toInt() > 1)
+ m_writer->writeAttribute("startingnumber", atom->next()->string());
+
+ if (atom->string() == ATOM_LIST_UPPERALPHA)
+ m_writer->writeAttribute("numeration", "upperalpha");
+ else if (atom->string() == ATOM_LIST_LOWERALPHA)
+ m_writer->writeAttribute("numeration", "loweralpha");
+ else if (atom->string() == ATOM_LIST_UPPERROMAN)
+ m_writer->writeAttribute("numeration", "upperroman");
+ else if (atom->string() == ATOM_LIST_LOWERROMAN)
+ m_writer->writeAttribute("numeration", "lowerroman");
+ else // (atom->string() == ATOM_LIST_NUMERIC)
+ m_writer->writeAttribute("numeration", "arabic");
+
+ newLine();
+ }
+ m_inList++;
+ break;
+ case Atom::ListItemNumber:
+ break;
+ case Atom::ListTagLeft:
+ if (atom->string() == ATOM_LIST_TAG) {
+ m_writer->writeStartElement(dbNamespace, "varlistentry");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "item");
+ } else { // (atom->string() == ATOM_LIST_VALUE)
+ std::pair<QString, int> pair = getAtomListValue(atom);
+ skipAhead = pair.second;
+
+ m_writer->writeStartElement(dbNamespace, "tr");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "td");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ generateEnumValue(pair.first, relative);
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // td
+ newLine();
+
+ if (relative->nodeType() == Node::Enum) {
+ const auto enume = static_cast<const EnumNode *>(relative);
+ QString itemValue = enume->itemValue(atom->next()->string());
+
+ m_writer->writeStartElement(dbNamespace, "td");
+ if (itemValue.isEmpty())
+ m_writer->writeCharacters("?");
+ else {
+ m_writer->writeStartElement(dbNamespace, "code");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters(itemValue);
+ m_writer->writeEndElement(); // code
+ }
+ m_writer->writeEndElement(); // td
+ newLine();
+ }
+ }
+ m_inList++;
+ break;
+ case Atom::SinceTagRight:
+ if (atom->string() == ATOM_LIST_TAG) {
+ m_writer->writeEndElement(); // item
+ newLine();
+ }
+ break;
+ case Atom::ListTagRight:
+ if (m_inList > 0 && atom->string() == ATOM_LIST_TAG) {
+ m_writer->writeEndElement(); // item
+ newLine();
+ m_inList = false;
+ }
+ break;
+ case Atom::ListItemLeft:
+ if (m_inList > 0) {
+ m_inListItemLineOpen = false;
+ if (atom->string() == ATOM_LIST_TAG) {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_inPara = true;
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ if (m_threeColumnEnumValueTable) {
+ if (matchAhead(atom, Atom::ListItemRight)) {
+ m_writer->writeEmptyElement(dbNamespace, "td");
+ newLine();
+ m_inListItemLineOpen = false;
+ } else {
+ m_writer->writeStartElement(dbNamespace, "td");
+ newLine();
+ m_inListItemLineOpen = true;
+ }
+ }
+ } else {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ }
+ // Don't skip a paragraph, DocBook requires them within list items.
+ }
+ break;
+ case Atom::ListItemRight:
+ if (m_inList > 0) {
+ if (atom->string() == ATOM_LIST_TAG) {
+ m_writer->writeEndElement(); // para
+ m_inPara = false;
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ m_writer->writeEndElement(); // varlistentry
+ newLine();
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ if (m_inListItemLineOpen) {
+ m_writer->writeEndElement(); // td
+ newLine();
+ m_inListItemLineOpen = false;
+ }
+ m_writer->writeEndElement(); // tr
+ newLine();
+ } else {
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ }
+ }
+ break;
+ case Atom::ListRight:
+ // Depending on atom->string(), closing a different item:
+ // - ATOM_LIST_BULLET: itemizedlist
+ // - ATOM_LIST_TAG: variablelist
+ // - ATOM_LIST_VALUE: informaltable
+ // - ATOM_LIST_NUMERIC: orderedlist
+ m_writer->writeEndElement();
+ newLine();
+ m_inList--;
+ break;
+ case Atom::Nop:
+ break;
+ case Atom::ParaLeft:
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_inPara = true;
+ break;
+ case Atom::ParaRight:
+ endLink();
+ if (m_inPara) {
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_inPara = false;
+ }
+ break;
+ case Atom::QuotationLeft:
+ m_writer->writeStartElement(dbNamespace, "blockquote");
+ m_inBlockquote = true;
+ break;
+ case Atom::QuotationRight:
+ m_writer->writeEndElement(); // blockquote
+ newLine();
+ m_inBlockquote = false;
+ break;
+ case Atom::RawString: {
+ m_writer->device()->write(atom->string().toUtf8());
+ }
+ break;
+ case Atom::SectionLeft:
+ m_hasSection = true;
+
+ currentSectionLevel = atom->string().toInt() + hOffset(relative);
+ // Level 1 is dealt with at the header level (info tag).
+ if (currentSectionLevel > 1) {
+ // Unfortunately, SectionRight corresponds to the end of any section,
+ // i.e. going to a new section, even deeper.
+ while (!sectionLevels.empty() && sectionLevels.top() >= currentSectionLevel) {
+ sectionLevels.pop();
+ m_writer->writeEndElement(); // section
+ newLine();
+ }
+
+ sectionLevels.push(currentSectionLevel);
+
+ m_writer->writeStartElement(dbNamespace, "section");
+ writeXmlId(Tree::refForAtom(atom));
+ newLine();
+ // Unlike startSectionBegin, don't start a title here.
+ }
+
+ if (matchAhead(atom, Atom::SectionHeadingLeft) &&
+ matchAhead(atom->next(), Atom::String) &&
+ matchAhead(atom->next()->next(), Atom::SectionHeadingRight) &&
+ matchAhead(atom->next()->next()->next(), Atom::SectionRight) &&
+ !atom->next()->next()->next()->next()->next()) {
+ // A lonely section at the end of the document indicates that a
+ // generated list of some sort should be within this section.
+ // Close this section later on, in generateFooter().
+ generateAtom(atom->next(), relative, nullptr);
+ generateAtom(atom->next()->next(), relative, nullptr);
+ generateAtom(atom->next()->next()->next(), relative, nullptr);
+
+ m_closeSectionAfterGeneratedList = true;
+ skipAhead += 4;
+ sectionLevels.pop();
+ }
+
+ if (!matchAhead(atom, Atom::SectionHeadingLeft)) {
+ // No section title afterwards, make one up. This likely indicates a problem in the original documentation.
+ m_writer->writeTextElement(dbNamespace, "title", "");
+ }
+ break;
+ case Atom::SectionRight:
+ // All the logic about closing sections is done in the SectionLeft case
+ // and generateFooter() for the end of the page.
+ break;
+ case Atom::SectionHeadingLeft:
+ // Level 1 is dealt with at the header level (info tag).
+ if (currentSectionLevel > 1) {
+ m_writer->writeStartElement(dbNamespace, "title");
+ m_inSectionHeading = true;
+ }
+ break;
+ case Atom::SectionHeadingRight:
+ // Level 1 is dealt with at the header level (info tag).
+ if (currentSectionLevel > 1) {
+ m_writer->writeEndElement(); // title
+ newLine();
+ m_inSectionHeading = false;
+ }
+ break;
+ case Atom::SidebarLeft:
+ m_writer->writeStartElement(dbNamespace, "sidebar");
+ break;
+ case Atom::SidebarRight:
+ m_writer->writeEndElement(); // sidebar
+ newLine();
+ break;
+ case Atom::String:
+ if (m_inLink && !m_inContents && !m_inSectionHeading)
+ generateLink(atom);
+ else
+ m_writer->writeCharacters(atom->string());
+ break;
+ case Atom::TableLeft: {
+ std::pair<QString, QString> pair = getTableWidthAttr(atom);
+ QString attr = pair.second;
+ QString width = pair.first;
+
+ if (m_inPara) {
+ m_writer->writeEndElement(); // para or blockquote
+ newLine();
+ m_inPara = false;
+ }
+
+ m_tableHeaderAlreadyOutput = false;
+
+ m_writer->writeStartElement(dbNamespace, "informaltable");
+ m_writer->writeAttribute("style", attr);
+ if (!width.isEmpty())
+ m_writer->writeAttribute("width", width);
+ newLine();
+ } break;
+ case Atom::TableRight:
+ m_tableWidthAttr = {"", ""};
+ m_writer->writeEndElement(); // table
+ newLine();
+ break;
+ case Atom::TableHeaderLeft: {
+ if (matchAhead(atom, Atom::TableHeaderRight)) {
+ ++skipAhead;
+ break;
+ }
+
+ if (m_tableHeaderAlreadyOutput) {
+ // Headers are only allowed at the beginning of the table: close
+ // the table and reopen one.
+ m_writer->writeEndElement(); // table
+ newLine();
+
+ const QString &attr = m_tableWidthAttr.second;
+ const QString &width = m_tableWidthAttr.first;
+
+ m_writer->writeStartElement(dbNamespace, "informaltable");
+ m_writer->writeAttribute("style", attr);
+ if (!width.isEmpty())
+ m_writer->writeAttribute("width", width);
+ newLine();
+ } else {
+ m_tableHeaderAlreadyOutput = true;
+ }
+
+ const Atom *next = atom->next();
+ QString id{""};
+ if (matchAhead(atom, Atom::Target)) {
+ id = Utilities::asAsciiPrintable(next->string());
+ next = next->next();
+ ++skipAhead;
+ }
+
+ m_writer->writeStartElement(dbNamespace, "thead");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "tr");
+ writeXmlId(id);
+ newLine();
+ m_inTableHeader = true;
+
+ if (!matchAhead(atom, Atom::TableItemLeft)) {
+ m_closeTableCell = true;
+ m_writer->writeStartElement(dbNamespace, "td");
+ newLine();
+ }
+ }
+ break;
+ case Atom::TableHeaderRight:
+ if (m_closeTableCell) {
+ m_closeTableCell = false;
+ m_writer->writeEndElement(); // td
+ newLine();
+ }
+
+ m_writer->writeEndElement(); // tr
+ newLine();
+ if (matchAhead(atom, Atom::TableHeaderLeft)) {
+ skipAhead = 1;
+ m_writer->writeStartElement(dbNamespace, "tr");
+ newLine();
+ } else {
+ m_writer->writeEndElement(); // thead
+ newLine();
+ m_inTableHeader = false;
+ }
+ break;
+ case Atom::TableRowLeft: {
+ if (matchAhead(atom, Atom::TableRowRight)) {
+ skipAhead = 1;
+ break;
+ }
+
+ QString id{""};
+ bool hasTarget {false};
+ if (matchAhead(atom, Atom::Target)) {
+ id = Utilities::asAsciiPrintable(atom->next()->string());
+ ++skipAhead;
+ hasTarget = true;
+ }
+
+ m_writer->writeStartElement(dbNamespace, "tr");
+ writeXmlId(id);
+
+ if (atom->string().isEmpty()) {
+ m_writer->writeAttribute("valign", "top");
+ } else {
+ // Basic parsing of attributes, should be enough. The input string (atom->string())
+ // looks like:
+ // arg1="val1" arg2="val2"
+ QStringList args = atom->string().split("\"", Qt::SkipEmptyParts);
+ // arg1=, val1, arg2=, val2,
+ // \-- 1st --/ \-- 2nd --/ \-- remainder
+ const int nArgs = args.size();
+
+ if (nArgs % 2) {
+ // Problem...
+ relative->doc().location().warning(
+ QStringLiteral("Error when parsing attributes for the table: got \"%1\"")
+ .arg(atom->string()));
+ }
+ for (int i = 0; i + 1 < nArgs; i += 2) {
+ // args.at(i): name of the attribute being set.
+ // args.at(i + 1): value of the said attribute.
+ const QString &attr = args.at(i).chopped(1);
+ if (attr == "id") { // Too bad if there is an anchor later on
+ // (currently never happens).
+ writeXmlId(args.at(i + 1));
+ } else {
+ m_writer->writeAttribute(attr, args.at(i + 1));
+ }
+ }
+ }
+ newLine();
+
+ // If there is nothing in this row, close it right now. There might be keywords before the row contents.
+ bool isRowEmpty = hasTarget ? !matchAhead(atom->next(), Atom::TableItemLeft) : !matchAhead(atom, Atom::TableItemLeft);
+ if (isRowEmpty && matchAhead(atom, Atom::Keyword)) {
+ const Atom* next = atom->next();
+ while (matchAhead(next, Atom::Keyword))
+ next = next->next();
+ isRowEmpty = !matchAhead(next, Atom::TableItemLeft);
+ }
+
+ if (isRowEmpty) {
+ m_closeTableRow = true;
+ m_writer->writeEndElement(); // td
+ newLine();
+ }
+ }
+ break;
+ case Atom::TableRowRight:
+ if (m_closeTableRow) {
+ m_closeTableRow = false;
+ m_writer->writeEndElement(); // td
+ newLine();
+ }
+
+ m_writer->writeEndElement(); // tr
+ newLine();
+ break;
+ case Atom::TableItemLeft:
+ m_writer->writeStartElement(dbNamespace, m_inTableHeader ? "th" : "td");
+
+ for (int i = 0; i < atom->count(); ++i) {
+ const QString &p = atom->string(i);
+ if (p.contains('=')) {
+ QStringList lp = p.split(QLatin1Char('='));
+ m_writer->writeAttribute(lp.at(0), lp.at(1));
+ } else {
+ QStringList spans = p.split(QLatin1Char(','));
+ if (spans.size() == 2) {
+ if (spans.at(0) != "1")
+ m_writer->writeAttribute("colspan", spans.at(0).trimmed());
+ if (spans.at(1) != "1")
+ m_writer->writeAttribute("rowspan", spans.at(1).trimmed());
+ }
+ }
+ }
+ newLine();
+ // No skipahead, as opposed to HTML: in DocBook, the text must be wrapped in paragraphs.
+ break;
+ case Atom::TableItemRight:
+ m_writer->writeEndElement(); // th if m_inTableHeader, otherwise td
+ newLine();
+ break;
+ case Atom::TableOfContents:
+ Q_FALLTHROUGH();
+ case Atom::Keyword:
+ break;
+ case Atom::Target:
+ // Sometimes, there is a \target just before a section title with the same ID. Only output one xml:id.
+ if (matchAhead(atom, Atom::SectionRight) && matchAhead(atom->next(), Atom::SectionLeft)) {
+ QString nextId = Utilities::asAsciiPrintable(
+ Text::sectionHeading(atom->next()->next()).toString());
+ QString ownId = Utilities::asAsciiPrintable(atom->string());
+ if (nextId == ownId)
+ break;
+ }
+
+ writeAnchor(Utilities::asAsciiPrintable(atom->string()));
+ break;
+ case Atom::UnhandledFormat:
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters("<Missing DocBook>");
+ m_writer->writeEndElement(); // emphasis
+ break;
+ case Atom::UnknownCommand:
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters("<Unknown command>");
+ m_writer->writeStartElement(dbNamespace, "code");
+ m_writer->writeCharacters(atom->string());
+ m_writer->writeEndElement(); // code
+ m_writer->writeEndElement(); // emphasis
+ break;
+ case Atom::CodeQuoteArgument:
+ case Atom::CodeQuoteCommand:
+ case Atom::ComparesLeft:
+ case Atom::ComparesRight:
+ case Atom::SnippetCommand:
+ case Atom::SnippetIdentifier:
+ case Atom::SnippetLocation:
+ // No output (ignore).
+ break;
+ default:
+ unknownAtom(atom);
+ }
+ return skipAhead;
+}
+
+void DocBookGenerator::generateClassHierarchy(const Node *relative, NodeMultiMap &classMap)
+{
+ // From HtmlGenerator::generateClassHierarchy.
+ if (classMap.isEmpty())
+ return;
+
+ std::function<void(ClassNode *)> generateClassAndChildren
+ = [this, &relative, &generateClassAndChildren](ClassNode * classe) {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+
+ // This class.
+ m_writer->writeStartElement(dbNamespace, "para");
+ generateFullName(classe, relative);
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ // Children, if any.
+ bool hasChild = false;
+ for (const RelatedClass &relatedClass : classe->derivedClasses()) {
+ if (relatedClass.m_node && relatedClass.m_node->isInAPI()) {
+ hasChild = true;
+ break;
+ }
+ }
+
+ if (hasChild) {
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ for (const RelatedClass &relatedClass: classe->derivedClasses()) {
+ if (relatedClass.m_node && relatedClass.m_node->isInAPI()) {
+ generateClassAndChildren(relatedClass.m_node);
+ }
+ }
+
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+ }
+
+ // End this class.
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ };
+
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ for (const auto &it : classMap) {
+ auto *classe = static_cast<ClassNode *>(it);
+ if (classe->baseClasses().isEmpty())
+ generateClassAndChildren(classe);
+ }
+
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+}
+
+void DocBookGenerator::generateLink(const Atom *atom)
+{
+ Q_ASSERT(m_inLink);
+
+ // From HtmlGenerator::generateLink.
+ if (m_linkNode && m_linkNode->isFunction()) {
+ auto match = XmlGenerator::m_funcLeftParen.match(atom->string());
+ if (match.hasMatch()) {
+ // C++: move () outside of link
+ qsizetype leftParenLoc = match.capturedStart(1);
+ m_writer->writeCharacters(atom->string().left(leftParenLoc));
+ endLink();
+ m_writer->writeCharacters(atom->string().mid(leftParenLoc));
+ return;
+ }
+ }
+ m_writer->writeCharacters(atom->string());
+}
+
+/*!
+ This version of the function is called when the \a link is known
+ to be correct.
+ */
+void DocBookGenerator::beginLink(const QString &link, const Node *node, const Node *relative)
+{
+ // From HtmlGenerator::beginLink.
+ m_writer->writeStartElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "href", link);
+ if (node && !(relative && node->status() == relative->status())
+ && node->isDeprecated())
+ m_writer->writeAttribute("role", "deprecated");
+ m_inLink = true;
+ m_linkNode = node;
+}
+
+void DocBookGenerator::endLink()
+{
+ // From HtmlGenerator::endLink.
+ if (m_inLink)
+ m_writer->writeEndElement(); // link
+ m_inLink = false;
+ m_linkNode = nullptr;
+}
+
+void DocBookGenerator::generateList(const Node *relative, const QString &selector,
+ Qt::SortOrder sortOrder)
+{
+ // From HtmlGenerator::generateList, without warnings, changing prototype.
+ CNMap cnm;
+ Node::NodeType type = Node::NoType;
+ if (selector == QLatin1String("overviews"))
+ type = Node::Group;
+ else if (selector == QLatin1String("cpp-modules"))
+ type = Node::Module;
+ else if (selector == QLatin1String("qml-modules"))
+ type = Node::QmlModule;
+
+ if (type != Node::NoType) {
+ NodeList nodeList;
+ m_qdb->mergeCollections(type, cnm, relative);
+ const QList<CollectionNode *> collectionList = cnm.values();
+ nodeList.reserve(collectionList.size());
+ for (auto *collectionNode : collectionList)
+ nodeList.append(collectionNode);
+ generateAnnotatedList(relative, nodeList, selector, Auto, sortOrder);
+ } else {
+ /*
+ \generatelist {selector} is only allowed in a comment where
+ the topic is \group, \module, or \qmlmodule.
+ */
+ Node *n = const_cast<Node *>(relative);
+ auto *cn = static_cast<CollectionNode *>(n);
+ m_qdb->mergeCollections(cn);
+ generateAnnotatedList(cn, cn->members(), selector, Auto, sortOrder);
+ }
+}
+
+/*!
+ Outputs an annotated list of the nodes in \a nodeList.
+ A two-column table is output.
+ */
+void DocBookGenerator::generateAnnotatedList(const Node *relative, const NodeList &nodeList,
+ const QString &selector, GeneratedListType type,
+ Qt::SortOrder sortOrder)
+{
+ if (nodeList.isEmpty())
+ return;
+
+ // Do nothing if all items are internal or obsolete.
+ if (std::all_of(nodeList.cbegin(), nodeList.cend(), [](const Node *n) {
+ return n->isInternal() || n->isDeprecated(); })) {
+ return;
+ }
+
+ // Detect if there is a need for a variablelist (i.e. titles mapped to
+ // descriptions) or a regular itemizedlist (only titles).
+ bool noItemsHaveTitle =
+ type == ItemizedList || std::all_of(nodeList.begin(), nodeList.end(),
+ [](const Node* node) {
+ return node->doc().briefText().toString().isEmpty();
+ });
+
+ // Wrap the list in a section if needed.
+ if (type == AutoSection && m_hasSection)
+ startSection("", "Contents");
+
+ // From WebXMLGenerator::generateAnnotatedList.
+ if (!nodeList.isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, noItemsHaveTitle ? "itemizedlist" : "variablelist");
+ m_writer->writeAttribute("role", selector);
+ newLine();
+
+ NodeList members{nodeList};
+ if (sortOrder == Qt::DescendingOrder)
+ std::sort(members.rbegin(), members.rend(), Node::nodeSortKeyOrNameLessThan);
+ else
+ std::sort(members.begin(), members.end(), Node::nodeSortKeyOrNameLessThan);
+ for (const auto &node : std::as_const(members)) {
+ if (node->isInternal() || node->isDeprecated())
+ continue;
+
+ if (noItemsHaveTitle) {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ } else {
+ m_writer->writeStartElement(dbNamespace, "varlistentry");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "term");
+ }
+ generateFullName(node, relative);
+ if (noItemsHaveTitle) {
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ } else {
+ m_writer->writeEndElement(); // term
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters(node->doc().briefText().toString());
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ m_writer->writeEndElement(); // varlistentry
+ }
+ newLine();
+ }
+
+ m_writer->writeEndElement(); // itemizedlist or variablelist
+ newLine();
+ }
+
+ if (type == AutoSection && m_hasSection)
+ endSection();
+}
+
+/*!
+ Outputs a series of annotated lists from the nodes in \a nmm,
+ divided into sections based by the key names in the multimap.
+ */
+void DocBookGenerator::generateAnnotatedLists(const Node *relative, const NodeMultiMap &nmm,
+ const QString &selector)
+{
+ // From HtmlGenerator::generateAnnotatedLists.
+ for (const QString &name : nmm.uniqueKeys()) {
+ if (!name.isEmpty())
+ startSection(name.toLower(), name);
+ generateAnnotatedList(relative, nmm.values(name), selector);
+ if (!name.isEmpty())
+ endSection();
+ }
+}
+
+/*!
+ This function finds the common prefix of the names of all
+ the classes in the class map \a nmm and then generates a
+ compact list of the class names alphabetized on the part
+ of the name not including the common prefix. You can tell
+ the function to use \a comonPrefix as the common prefix,
+ but normally you let it figure it out itself by looking at
+ the name of the first and last classes in the class map
+ \a nmm.
+ */
+void DocBookGenerator::generateCompactList(const Node *relative, const NodeMultiMap &nmm,
+ bool includeAlphabet, const QString &commonPrefix,
+ const QString &selector)
+{
+ // From HtmlGenerator::generateCompactList. No more "includeAlphabet", this should be handled by
+ // the DocBook toolchain afterwards.
+ // TODO: In DocBook, probably no need for this method: this is purely presentational, i.e. to be
+ // fully handled by the DocBook toolchain.
+
+ if (nmm.isEmpty())
+ return;
+
+ const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_'
+ qsizetype commonPrefixLen = commonPrefix.size();
+
+ /*
+ Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z,
+ underscore (_). QAccel will fall in paragraph 10 (A) and
+ QXtWidget in paragraph 33 (X). This is the only place where we
+ assume that NumParagraphs is 37. Each paragraph is a NodeMultiMap.
+ */
+ NodeMultiMap paragraph[NumParagraphs + 1];
+ QString paragraphName[NumParagraphs + 1];
+ QSet<char> usedParagraphNames;
+
+ for (auto c = nmm.constBegin(); c != nmm.constEnd(); ++c) {
+ QStringList pieces = c.key().split("::");
+ int idx = commonPrefixLen;
+ if (idx > 0 && !pieces.last().startsWith(commonPrefix, Qt::CaseInsensitive))
+ idx = 0;
+ QString last = pieces.last().toLower();
+ QString key = last.mid(idx);
+
+ int paragraphNr = NumParagraphs - 1;
+
+ if (key[0].digitValue() != -1) {
+ paragraphNr = key[0].digitValue();
+ } else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) {
+ paragraphNr = 10 + key[0].unicode() - 'a';
+ }
+
+ paragraphName[paragraphNr] = key[0].toUpper();
+ usedParagraphNames.insert(key[0].toLower().cell());
+ paragraph[paragraphNr].insert(last, c.value());
+ }
+
+ /*
+ Each paragraph j has a size: paragraph[j].count(). In the
+ discussion, we will assume paragraphs 0 to 5 will have sizes
+ 3, 1, 4, 1, 5, 9.
+
+ We now want to compute the paragraph offset. Paragraphs 0 to 6
+ start at offsets 0, 3, 4, 8, 9, 14, 23.
+ */
+ int paragraphOffset[NumParagraphs + 1]; // 37 + 1
+ paragraphOffset[0] = 0;
+ for (int i = 0; i < NumParagraphs; i++) // i = 0..36
+ paragraphOffset[i + 1] = paragraphOffset[i] + paragraph[i].size();
+
+ // Output the alphabet as a row of links.
+ if (includeAlphabet && !usedParagraphNames.isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "simplelist");
+ newLine();
+
+ for (int i = 0; i < 26; i++) {
+ QChar ch('a' + i);
+ if (usedParagraphNames.contains(char('a' + i))) {
+ m_writer->writeStartElement(dbNamespace, "member");
+ generateSimpleLink(ch, ch.toUpper());
+ m_writer->writeEndElement(); // member
+ newLine();
+ }
+ }
+
+ m_writer->writeEndElement(); // simplelist
+ newLine();
+ }
+
+ // Actual output.
+ int curParNr = 0;
+ int curParOffset = 0;
+ QString previousName;
+ bool multipleOccurrences = false;
+
+ m_writer->writeStartElement(dbNamespace, "variablelist");
+ m_writer->writeAttribute("role", selector);
+ newLine();
+
+ for (int i = 0; i < nmm.size(); i++) {
+ while ((curParNr < NumParagraphs) && (curParOffset == paragraph[curParNr].size())) {
+
+ ++curParNr;
+ curParOffset = 0;
+ }
+
+ // Starting a new paragraph means starting a new varlistentry.
+ if (curParOffset == 0) {
+ if (i > 0) {
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ m_writer->writeEndElement(); // varlistentry
+ newLine();
+ }
+
+ m_writer->writeStartElement(dbNamespace, "varlistentry");
+ if (includeAlphabet)
+ writeXmlId(paragraphName[curParNr][0].toLower());
+ newLine();
+
+ m_writer->writeStartElement(dbNamespace, "term");
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters(paragraphName[curParNr]);
+ m_writer->writeEndElement(); // emphasis
+ m_writer->writeEndElement(); // term
+ newLine();
+
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+ }
+
+ // Output a listitem for the current offset in the current paragraph.
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+
+ if ((curParNr < NumParagraphs) && !paragraphName[curParNr].isEmpty()) {
+ NodeMultiMap::Iterator it;
+ NodeMultiMap::Iterator next;
+ it = paragraph[curParNr].begin();
+ for (int j = 0; j < curParOffset; j++)
+ ++it;
+
+ // Cut the name into pieces to determine whether it is simple (one piece) or complex
+ // (more than one piece).
+ QStringList pieces{it.value()->fullName(relative).split("::"_L1)};
+ const auto &name{pieces.last()};
+ next = it;
+ ++next;
+ if (name != previousName)
+ multipleOccurrences = false;
+ if ((next != paragraph[curParNr].end()) && (name == next.value()->name())) {
+ multipleOccurrences = true;
+ previousName = name;
+ }
+ if (multipleOccurrences && pieces.size() == 1)
+ pieces.last().append(": "_L1.arg(it.value()->tree()->camelCaseModuleName()));
+
+ // Write the link to the element, which is identical if the element is obsolete or not.
+ m_writer->writeStartElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "href", linkForNode(*it, relative));
+ if (const QString type = targetType(it.value()); !type.isEmpty())
+ m_writer->writeAttribute("role", type);
+ m_writer->writeCharacters(pieces.last());
+ m_writer->writeEndElement(); // link
+
+ // Outside the link, give the full name of the node if it is complex.
+ if (pieces.size() > 1) {
+ m_writer->writeCharacters(" (");
+ generateFullName(it.value()->parent(), relative);
+ m_writer->writeCharacters(")");
+ }
+ }
+
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+
+ curParOffset++;
+ }
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ m_writer->writeEndElement(); // varlistentry
+ newLine();
+
+ m_writer->writeEndElement(); // variablelist
+ newLine();
+}
+
+void DocBookGenerator::generateFunctionIndex(const Node *relative)
+{
+ // From HtmlGenerator::generateFunctionIndex.
+
+ // First list: links to parts of the second list, one item per letter.
+ m_writer->writeStartElement(dbNamespace, "simplelist");
+ m_writer->writeAttribute("role", "functionIndex");
+ newLine();
+ for (int i = 0; i < 26; i++) {
+ QChar ch('a' + i);
+ m_writer->writeStartElement(dbNamespace, "member");
+ m_writer->writeAttribute(xlinkNamespace, "href", QString("#") + ch);
+ m_writer->writeCharacters(ch.toUpper());
+ m_writer->writeEndElement(); // member
+ newLine();
+ }
+ m_writer->writeEndElement(); // simplelist
+ newLine();
+
+ // Second list: the actual list of functions, sorted by alphabetical
+ // order. One entry of the list per letter.
+ if (m_qdb->getFunctionIndex().isEmpty())
+ return;
+ char nextLetter = 'a';
+ char currentLetter;
+
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ NodeMapMap &funcIndex = m_qdb->getFunctionIndex();
+ QMap<QString, NodeMap>::ConstIterator f = funcIndex.constBegin();
+ while (f != funcIndex.constEnd()) {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters(f.key() + ": ");
+
+ currentLetter = f.key()[0].unicode();
+ while (islower(currentLetter) && currentLetter >= nextLetter) {
+ writeAnchor(QString(nextLetter));
+ nextLetter++;
+ }
+
+ NodeMap::ConstIterator s = (*f).constBegin();
+ while (s != (*f).constEnd()) {
+ m_writer->writeCharacters(" ");
+ generateFullName((*s)->parent(), relative);
+ ++s;
+ }
+
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ ++f;
+ }
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+}
+
+void DocBookGenerator::generateLegaleseList(const Node *relative)
+{
+ // From HtmlGenerator::generateLegaleseList.
+ TextToNodeMap &legaleseTexts = m_qdb->getLegaleseTexts();
+ for (auto it = legaleseTexts.cbegin(), end = legaleseTexts.cend(); it != end; ++it) {
+ Text text = it.key();
+ generateText(text, relative);
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+ do {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ generateFullName(it.value(), relative);
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ ++it;
+ } while (it != legaleseTexts.constEnd() && it.key() == text);
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+ }
+}
+
+void DocBookGenerator::generateBrief(const Node *node)
+{
+ // From HtmlGenerator::generateBrief. Also see generateHeader, which is specifically dealing
+ // with the DocBook header (and thus wraps the brief in an abstract).
+ Text brief = node->doc().briefText();
+
+ if (!brief.isEmpty()) {
+ if (!brief.lastAtom()->string().endsWith('.'))
+ brief << Atom(Atom::String, ".");
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ generateText(brief, node);
+ m_writer->writeEndElement(); // para
+ newLine();
+ }
+}
+
+bool DocBookGenerator::generateSince(const Node *node)
+{
+ // From Generator::generateSince.
+ if (!node->since().isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("This " + typeString(node) + " was introduced in ");
+ m_writer->writeCharacters(formatSince(node) + ".");
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ return true;
+ }
+
+ return false;
+}
+
+/*!
+ Generate the DocBook header for the file, including the abstract.
+ Equivalent to calling generateTitle and generateBrief in HTML.
+*/
+void DocBookGenerator::generateHeader(const QString &title, const QString &subTitle,
+ const Node *node)
+{
+ refMap.clear();
+
+ // Output the DocBook header.
+ m_writer->writeStartElement(dbNamespace, "info");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "title");
+ if (node->genus() & Node::API && m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters(title);
+ m_writer->writeEndElement(); // title
+ newLine();
+
+ if (!subTitle.isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "subtitle");
+ if (node->genus() & Node::API && m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters(subTitle);
+ m_writer->writeEndElement(); // subtitle
+ newLine();
+ }
+
+ if (!m_project.isEmpty()) {
+ m_writer->writeTextElement(dbNamespace, "productname", m_project);
+ newLine();
+ }
+
+ if (!m_buildVersion.isEmpty()) {
+ m_writer->writeTextElement(dbNamespace, "edition", m_buildVersion);
+ newLine();
+ }
+
+ if (!m_projectDescription.isEmpty()) {
+ m_writer->writeTextElement(dbNamespace, "titleabbrev", m_projectDescription);
+ newLine();
+ }
+
+ // Deal with links.
+ // Adapted from HtmlGenerator::generateHeader (output part: no need to update a navigationLinks
+ // or useSeparator field, as this content is only output in the info tag, not in the main
+ // content).
+ if (node && !node->links().empty()) {
+ std::pair<QString, QString> linkPair;
+ std::pair<QString, QString> anchorPair;
+ const Node *linkNode;
+
+ if (node->links().contains(Node::PreviousLink)) {
+ linkPair = node->links()[Node::PreviousLink];
+ linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
+ if (!linkNode || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ m_writer->writeStartElement(dbNamespace, "extendedlink");
+ m_writer->writeAttribute(xlinkNamespace, "type", "extended");
+ m_writer->writeEmptyElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
+ m_writer->writeAttribute(xlinkNamespace, "type", "arc");
+ m_writer->writeAttribute(xlinkNamespace, "arcrole", "prev");
+ if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
+ m_writer->writeAttribute(xlinkNamespace, "title", anchorPair.second);
+ else
+ m_writer->writeAttribute(xlinkNamespace, "title", linkPair.second);
+ m_writer->writeEndElement(); // extendedlink
+ newLine();
+ }
+ if (node->links().contains(Node::NextLink)) {
+ linkPair = node->links()[Node::NextLink];
+ linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
+ if (!linkNode || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ m_writer->writeStartElement(dbNamespace, "extendedlink");
+ m_writer->writeAttribute(xlinkNamespace, "type", "extended");
+ m_writer->writeEmptyElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
+ m_writer->writeAttribute(xlinkNamespace, "type", "arc");
+ m_writer->writeAttribute(xlinkNamespace, "arcrole", "next");
+ if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
+ m_writer->writeAttribute(xlinkNamespace, "title", anchorPair.second);
+ else
+ m_writer->writeAttribute(xlinkNamespace, "title", linkPair.second);
+ m_writer->writeEndElement(); // extendedlink
+ newLine();
+ }
+ if (node->links().contains(Node::StartLink)) {
+ linkPair = node->links()[Node::StartLink];
+ linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
+ if (!linkNode || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ m_writer->writeStartElement(dbNamespace, "extendedlink");
+ m_writer->writeAttribute(xlinkNamespace, "type", "extended");
+ m_writer->writeEmptyElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
+ m_writer->writeAttribute(xlinkNamespace, "type", "arc");
+ m_writer->writeAttribute(xlinkNamespace, "arcrole", "start");
+ if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
+ m_writer->writeAttribute(xlinkNamespace, "title", anchorPair.second);
+ else
+ m_writer->writeAttribute(xlinkNamespace, "title", linkPair.second);
+ m_writer->writeEndElement(); // extendedlink
+ newLine();
+ }
+ }
+
+ // Deal with the abstract (what qdoc calls brief).
+ if (node) {
+ // Adapted from HtmlGenerator::generateBrief, without extraction marks. The parameter
+ // addLink is always false. Factoring this function out is not as easy as in HtmlGenerator:
+ // abstracts only happen in the header (info tag), slightly different tags must be used at
+ // other places. Also includes code from HtmlGenerator::generateCppReferencePage to handle
+ // the name spaces.
+ m_writer->writeStartElement(dbNamespace, "abstract");
+ newLine();
+
+ bool generatedSomething = false;
+
+ Text brief;
+ const NamespaceNode *ns =
+ node->isNamespace() ? static_cast<const NamespaceNode *>(node) : nullptr;
+ if (ns && !ns->hasDoc() && ns->docNode()) {
+ NamespaceNode *NS = ns->docNode();
+ brief << "The " << ns->name()
+ << " namespace includes the following elements from module "
+ << ns->tree()->camelCaseModuleName() << ". The full namespace is "
+ << "documented in module " << NS->tree()->camelCaseModuleName()
+ << Atom(Atom::LinkNode, fullDocumentLocation(NS))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, " here.")
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ } else {
+ brief = node->doc().briefText();
+ }
+
+ if (!brief.isEmpty()) {
+ if (!brief.lastAtom()->string().endsWith('.'))
+ brief << Atom(Atom::String, ".");
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ generateText(brief, node);
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ generatedSomething = true;
+ }
+
+ // Generate other paragraphs that should go into the abstract.
+ generatedSomething |= generateStatus(node);
+ generatedSomething |= generateSince(node);
+ generatedSomething |= generateThreadSafeness(node);
+ generatedSomething |= generateComparisonCategory(node);
+ generatedSomething |= generateComparisonList(node);
+
+ // An abstract cannot be empty, hence use the project description.
+ if (!generatedSomething)
+ m_writer->writeTextElement(dbNamespace, "para", m_projectDescription + ".");
+
+ m_writer->writeEndElement(); // abstract
+ newLine();
+ }
+
+ // End of the DocBook header.
+ m_writer->writeEndElement(); // info
+ newLine();
+}
+
+void DocBookGenerator::closeTextSections()
+{
+ while (!sectionLevels.isEmpty()) {
+ sectionLevels.pop();
+ endSection();
+ }
+}
+
+void DocBookGenerator::generateFooter()
+{
+ if (m_closeSectionAfterGeneratedList) {
+ m_closeSectionAfterGeneratedList = false;
+ endSection();
+ }
+ if (m_closeSectionAfterRawTitle) {
+ m_closeSectionAfterRawTitle = false;
+ endSection();
+ }
+
+ closeTextSections();
+ m_writer->writeEndElement(); // article
+}
+
+void DocBookGenerator::generateSimpleLink(const QString &href, const QString &text)
+{
+ m_writer->writeStartElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "href", href);
+ m_writer->writeCharacters(text);
+ m_writer->writeEndElement(); // link
+}
+
+void DocBookGenerator::generateObsoleteMembers(const Sections &sections)
+{
+ // From HtmlGenerator::generateObsoleteMembersFile.
+ SectionPtrVector summary_spv; // Summaries are ignored in DocBook (table of contents).
+ SectionPtrVector details_spv;
+ if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
+ return;
+
+ Aggregate *aggregate = sections.aggregate();
+ startSection("obsolete", "Obsolete Members for " + aggregate->name());
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters("The following members of class ");
+ generateSimpleLink(linkForNode(aggregate, nullptr), aggregate->name());
+ m_writer->writeCharacters(" are deprecated.");
+ m_writer->writeEndElement(); // emphasis bold
+ m_writer->writeCharacters(" We strongly advise against using them in new code.");
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ for (const Section *section : details_spv) {
+ const QString &title = "Obsolete " + section->title();
+ startSection(title.toLower(), title);
+
+ const NodeVector &members = section->obsoleteMembers();
+ NodeVector::ConstIterator m = members.constBegin();
+ while (m != members.constEnd()) {
+ if ((*m)->access() != Access::Private)
+ generateDetailedMember(*m, aggregate);
+ ++m;
+ }
+
+ endSection();
+ }
+
+ endSection();
+}
+
+/*!
+ Generates a separate section where obsolete members of the QML
+ type \a qcn are listed. The \a marker is used to generate
+ the section lists, which are then traversed and output here.
+
+ Note that this function currently only handles correctly the
+ case where \a status is \c {Section::Deprecated}.
+ */
+void DocBookGenerator::generateObsoleteQmlMembers(const Sections &sections)
+{
+ // From HtmlGenerator::generateObsoleteQmlMembersFile.
+ SectionPtrVector summary_spv; // Summaries are not useful in DocBook.
+ SectionPtrVector details_spv;
+ if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
+ return;
+
+ Aggregate *aggregate = sections.aggregate();
+ startSection("obsolete", "Obsolete Members for " + aggregate->name());
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters("The following members of QML type ");
+ generateSimpleLink(linkForNode(aggregate, nullptr), aggregate->name());
+ m_writer->writeCharacters(" are deprecated.");
+ m_writer->writeEndElement(); // emphasis bold
+ m_writer->writeCharacters(" We strongly advise against using them in new code.");
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ for (const auto *section : details_spv) {
+ const QString &title = "Obsolete " + section->title();
+ startSection(title.toLower(), title);
+
+ const NodeVector &members = section->obsoleteMembers();
+ NodeVector::ConstIterator m = members.constBegin();
+ while (m != members.constEnd()) {
+ if ((*m)->access() != Access::Private)
+ generateDetailedQmlMember(*m, aggregate);
+ ++m;
+ }
+
+ endSection();
+ }
+
+ endSection();
+}
+
+static QString nodeToSynopsisTag(const Node *node)
+{
+ // Order from Node::nodeTypeString.
+ if (node->isClass() || node->isQmlType())
+ return QStringLiteral("classsynopsis");
+ if (node->isNamespace())
+ return QStringLiteral("packagesynopsis");
+ if (node->isPageNode()) {
+ node->doc().location().warning("Unexpected document node in nodeToSynopsisTag");
+ return QString();
+ }
+ if (node->isEnumType())
+ return QStringLiteral("enumsynopsis");
+ if (node->isTypedef())
+ return QStringLiteral("typedefsynopsis");
+ if (node->isFunction()) {
+ // Signals are also encoded as functions (including QML ones).
+ const auto fn = static_cast<const FunctionNode *>(node);
+ if (fn->isCtor() || fn->isCCtor() || fn->isMCtor())
+ return QStringLiteral("constructorsynopsis");
+ if (fn->isDtor())
+ return QStringLiteral("destructorsynopsis");
+ return QStringLiteral("methodsynopsis");
+ }
+ if (node->isProperty() || node->isVariable() || node->isQmlProperty())
+ return QStringLiteral("fieldsynopsis");
+
+ node->doc().location().warning(QString("Unknown node tag %1").arg(node->nodeTypeString()));
+ return QStringLiteral("synopsis");
+}
+
+void DocBookGenerator::generateStartRequisite(const QString &description)
+{
+ m_writer->writeStartElement(dbNamespace, "varlistentry");
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "term", description);
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_inPara = true;
+}
+
+void DocBookGenerator::generateEndRequisite()
+{
+ m_writer->writeEndElement(); // para
+ m_inPara = false;
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ m_writer->writeEndElement(); // varlistentry
+ newLine();
+}
+
+void DocBookGenerator::generateRequisite(const QString &description, const QString &value)
+{
+ generateStartRequisite(description);
+ m_writer->writeCharacters(value);
+ generateEndRequisite();
+}
+
+/*!
+ * \internal
+ * Generates the CMake (\a description) requisites
+ */
+void DocBookGenerator::generateCMakeRequisite(const QStringList &values)
+{
+ const QString description("CMake");
+ generateStartRequisite(description);
+ m_writer->writeCharacters(values.first());
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters(values.last());
+ generateEndRequisite();
+}
+
+void DocBookGenerator::generateSortedNames(const ClassNode *cn, const QList<RelatedClass> &rc)
+{
+ // From Generator::appendSortedNames.
+ QMap<QString, ClassNode *> classMap;
+ QList<RelatedClass>::ConstIterator r = rc.constBegin();
+ while (r != rc.constEnd()) {
+ ClassNode *rcn = (*r).m_node;
+ if (rcn && rcn->access() == Access::Public && rcn->status() != Node::Internal
+ && !rcn->doc().isEmpty()) {
+ classMap[rcn->plainFullName(cn).toLower()] = rcn;
+ }
+ ++r;
+ }
+
+ QStringList classNames = classMap.keys();
+ classNames.sort();
+
+ int index = 0;
+ for (const QString &className : classNames) {
+ generateFullName(classMap.value(className), cn);
+ m_writer->writeCharacters(Utilities::comma(index++, classNames.size()));
+ }
+}
+
+void DocBookGenerator::generateSortedQmlNames(const Node *base, const NodeList &subs)
+{
+ // From Generator::appendSortedQmlNames.
+ QMap<QString, Node *> classMap;
+
+ for (auto sub : subs)
+ classMap[sub->plainFullName(base).toLower()] = sub;
+
+ QStringList names = classMap.keys();
+ names.sort();
+
+ int index = 0;
+ for (const QString &name : names) {
+ generateFullName(classMap.value(name), base);
+ m_writer->writeCharacters(Utilities::comma(index++, names.size()));
+ }
+}
+
+/*!
+ Lists the required imports and includes.
+*/
+void DocBookGenerator::generateRequisites(const Aggregate *aggregate)
+{
+ // Adapted from HtmlGenerator::generateRequisites, but simplified: no need to store all the
+ // elements, they can be produced one by one.
+
+ // Generate the requisites first separately: if some of them are generated, output them in a wrapper.
+ // This complexity is required to ensure the DocBook file is valid: an empty list is not valid. It is not easy
+ // to write a truly comprehensive condition.
+ QXmlStreamWriter* oldWriter = m_writer;
+ QString output;
+ m_writer = new QXmlStreamWriter(&output);
+
+ // Includes.
+ if (aggregate->includeFile()) generateRequisite("Header", *aggregate->includeFile());
+
+ // Since and project.
+ if (!aggregate->since().isEmpty())
+ generateRequisite("Since", formatSince(aggregate));
+
+ if (aggregate->isClassNode() || aggregate->isNamespace()) {
+ // CMake and QT variable.
+ const CollectionNode *cn =
+ m_qdb->getCollectionNode(aggregate->physicalModuleName(), Node::Module);
+ if (cn && !cn->qtCMakeComponent().isEmpty()) {
+ const QString qtComponent = "Qt" + QString::number(QT_VERSION_MAJOR);
+ const QString findpackageText = "find_package(" + qtComponent
+ + " REQUIRED COMPONENTS " + cn->qtCMakeComponent() + ")";
+ const QString targetItem =
+ cn->qtCMakeTargetItem().isEmpty() ? cn->qtCMakeComponent() : cn->qtCMakeTargetItem();
+ const QString targetLinkLibrariesText = "target_link_libraries(mytarget PRIVATE "
+ + qtComponent + "::" + targetItem + ")";
+ const QStringList cmakeInfo { findpackageText, targetLinkLibrariesText };
+ generateCMakeRequisite(cmakeInfo);
+ }
+ if (cn && !cn->qtVariable().isEmpty())
+ generateRequisite("qmake", "QT += " + cn->qtVariable());
+ }
+
+ if (aggregate->nodeType() == Node::Class) {
+ // Native type information.
+ auto *classe = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate));
+ if (classe && classe->isQmlNativeType() && classe->status() != Node::Internal) {
+ generateStartRequisite("In QML");
+
+ qsizetype idx{0};
+ QList<QmlTypeNode *> nativeTypes { classe->qmlNativeTypes().cbegin(), classe->qmlNativeTypes().cend()};
+ std::sort(nativeTypes.begin(), nativeTypes.end(), Node::nodeNameLessThan);
+
+ for (const auto &item : std::as_const(nativeTypes)) {
+ generateFullName(item, classe);
+ m_writer->writeCharacters(
+ Utilities::comma(idx++, nativeTypes.size()));
+ }
+ generateEndRequisite();
+ }
+
+ // Inherits.
+ QList<RelatedClass>::ConstIterator r;
+ if (!classe->baseClasses().isEmpty()) {
+ generateStartRequisite("Inherits");
+
+ r = classe->baseClasses().constBegin();
+ int index = 0;
+ while (r != classe->baseClasses().constEnd()) {
+ if ((*r).m_node) {
+ generateFullName((*r).m_node, classe);
+
+ if ((*r).m_access == Access::Protected)
+ m_writer->writeCharacters(" (protected)");
+ else if ((*r).m_access == Access::Private)
+ m_writer->writeCharacters(" (private)");
+ m_writer->writeCharacters(
+ Utilities::comma(index++, classe->baseClasses().size()));
+ }
+ ++r;
+ }
+
+ generateEndRequisite();
+ }
+
+ // Inherited by.
+ if (!classe->derivedClasses().isEmpty()) {
+ generateStartRequisite("Inherited By");
+ generateSortedNames(classe, classe->derivedClasses());
+ generateEndRequisite();
+ }
+ }
+
+ // Group.
+ if (!aggregate->groupNames().empty()) {
+ generateStartRequisite("Group");
+ generateGroupReferenceText(aggregate);
+ generateEndRequisite();
+ }
+
+ // Status.
+ if (auto status = formatStatus(aggregate, m_qdb); status)
+ generateRequisite("Status", status.value());
+
+ // Write the elements as a list if not empty.
+ delete m_writer;
+ m_writer = oldWriter;
+
+ if (!output.isEmpty()) {
+ // Namespaces are mangled in this output, because QXmlStreamWriter doesn't know about them. (Letting it know
+ // would imply generating the xmlns declaration one more time.)
+ static const QRegularExpression xmlTag(R"(<(/?)n\d+:)"); // Only for DocBook tags.
+ static const QRegularExpression xmlnsDocBookDefinition(R"( xmlns:n\d+=")" + QString{dbNamespace} + "\"");
+ static const QRegularExpression xmlnsXLinkDefinition(R"( xmlns:n\d+=")" + QString{xlinkNamespace} + "\"");
+ static const QRegularExpression xmlAttr(R"( n\d+:)"); // Only for XLink attributes.
+ // Space at the beginning!
+ const QString cleanOutput = output.replace(xmlTag, R"(<\1db:)")
+ .replace(xmlnsDocBookDefinition, "")
+ .replace(xmlnsXLinkDefinition, "")
+ .replace(xmlAttr, " xlink:");
+
+ m_writer->writeStartElement(dbNamespace, "variablelist");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ newLine();
+
+ m_writer->device()->write(cleanOutput.toUtf8());
+
+ m_writer->writeEndElement(); // variablelist
+ newLine();
+ }
+}
+
+/*!
+ Lists the required imports and includes.
+*/
+void DocBookGenerator::generateQmlRequisites(const QmlTypeNode *qcn)
+{
+ // From HtmlGenerator::generateQmlRequisites, but simplified: no need to store all the elements,
+ // they can be produced one by one.
+ if (!qcn)
+ return;
+
+ const CollectionNode *collection = qcn->logicalModule();
+
+ NodeList subs;
+ QmlTypeNode::subclasses(qcn, subs);
+
+ QmlTypeNode *base = qcn->qmlBaseNode();
+ while (base && base->isInternal()) {
+ base = base->qmlBaseNode();
+ }
+
+ // Skip import statement for \internal collections
+ const bool generate_import_statement = !qcn->logicalModuleName().isEmpty() && (!collection || !collection->isInternal() || m_showInternal);
+ // Detect if anything is generated in this method. If not, exit early to avoid having an empty list.
+ const bool generates_something = generate_import_statement || !qcn->since().isEmpty() || !subs.isEmpty() || base;
+
+ if (!generates_something)
+ return;
+
+ // Start writing the elements as a list.
+ m_writer->writeStartElement(dbNamespace, "variablelist");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ newLine();
+
+ if (generate_import_statement) {
+ QStringList parts = QStringList() << "import" << qcn->logicalModuleName() << qcn->logicalModuleVersion();
+ generateRequisite("Import Statement", parts.join(' ').trimmed());
+ }
+
+ // Since and project.
+ if (!qcn->since().isEmpty())
+ generateRequisite("Since:", formatSince(qcn));
+
+ // Inherited by.
+ if (!subs.isEmpty()) {
+ generateStartRequisite("Inherited By:");
+ generateSortedQmlNames(qcn, subs);
+ generateEndRequisite();
+ }
+
+ // Inherits.
+ if (base) {
+ const Node *otherNode = nullptr;
+ Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(base));
+ QString link = getAutoLink(&a, qcn, &otherNode);
+
+ generateStartRequisite("Inherits:");
+ generateSimpleLink(link, base->name());
+ generateEndRequisite();
+ }
+
+ // Native type information.
+ ClassNode *cn = (const_cast<QmlTypeNode *>(qcn))->classNode();
+ if (cn && cn->isQmlNativeType() && cn->status() != Node::Internal) {
+ generateStartRequisite("In C++:");
+ generateSimpleLink(fullDocumentLocation(cn), cn->name());
+ generateEndRequisite();
+ }
+
+ // Group.
+ if (!qcn->groupNames().empty()) {
+ generateStartRequisite("Group");
+ generateGroupReferenceText(qcn);
+ generateEndRequisite();
+ }
+
+ // Status.
+ if (auto status = formatStatus(qcn, m_qdb); status)
+ generateRequisite("Status:", status.value());
+
+ m_writer->writeEndElement(); // variablelist
+ newLine();
+}
+
+bool DocBookGenerator::generateStatus(const Node *node)
+{
+ // From Generator::generateStatus.
+ switch (node->status()) {
+ case Node::Active:
+ // Output the module 'state' description if set.
+ if (node->isModule() || node->isQmlModule()) {
+ const QString &state = static_cast<const CollectionNode*>(node)->state();
+ if (!state.isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("This " + typeString(node) + " is in ");
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeCharacters(state);
+ m_writer->writeEndElement(); // emphasis
+ m_writer->writeCharacters(" state.");
+ m_writer->writeEndElement(); // para
+ newLine();
+ return true;
+ }
+ }
+ if (const auto version = node->deprecatedSince(); !version.isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("This " + typeString(node)
+ + " is scheduled for deprecation in version "
+ + version + ".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ return true;
+ }
+ return false;
+ case Node::Preliminary:
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters("This " + typeString(node)
+ + " is under development and is subject to change.");
+ m_writer->writeEndElement(); // emphasis
+ m_writer->writeEndElement(); // para
+ newLine();
+ return true;
+ case Node::Deprecated:
+ m_writer->writeStartElement(dbNamespace, "para");
+ if (node->isAggregate()) {
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ }
+ m_writer->writeCharacters("This " + typeString(node) + " is deprecated");
+ if (const QString &version = node->deprecatedSince(); !version.isEmpty()) {
+ m_writer->writeCharacters(" since ");
+ if (node->isQmlNode() && !node->logicalModuleName().isEmpty())
+ m_writer->writeCharacters(node->logicalModuleName() + " ");
+ m_writer->writeCharacters(version);
+ }
+ m_writer->writeCharacters(". We strongly advise against using it in new code.");
+ if (node->isAggregate())
+ m_writer->writeEndElement(); // emphasis
+ m_writer->writeEndElement(); // para
+ newLine();
+ return true;
+ case Node::Internal:
+ default:
+ return false;
+ }
+}
+
+/*!
+ Generate a list of function signatures. The function nodes
+ are in \a nodes.
+ */
+void DocBookGenerator::generateSignatureList(const NodeList &nodes)
+{
+ // From Generator::signatureList and Generator::appendSignature.
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ NodeList::ConstIterator n = nodes.constBegin();
+ while (n != nodes.constEnd()) {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+
+ generateSimpleLink(currentGenerator()->fullDocumentLocation(*n),
+ (*n)->signature(Node::SignaturePlain));
+
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+ ++n;
+ }
+
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+}
+
+/*!
+ * Return a string representing a text that exposes information about
+ * the groups that the \a node is part of.
+ */
+void DocBookGenerator::generateGroupReferenceText(const Node* node)
+{
+ // From HtmlGenerator::groupReferenceText
+
+ if (!node->isAggregate())
+ return;
+ const auto aggregate = static_cast<const Aggregate *>(node);
+
+ const QStringList &groups_names{aggregate->groupNames()};
+ if (!groups_names.empty()) {
+ m_writer->writeCharacters(aggregate->name() + " is part of ");
+ m_writer->writeStartElement(dbNamespace, "simplelist");
+
+ for (qsizetype index{0}; index < groups_names.size(); ++index) {
+ CollectionNode* group{m_qdb->groups()[groups_names[index]]};
+ m_qdb->mergeCollections(group);
+
+ m_writer->writeStartElement(dbNamespace, "member");
+ if (QString target{linkForNode(group, nullptr)}; !target.isEmpty())
+ generateSimpleLink(target, group->fullTitle());
+ else
+ m_writer->writeCharacters(group->name());
+ m_writer->writeEndElement(); // member
+ }
+
+ m_writer->writeEndElement(); // simplelist
+ newLine();
+ }
+}
+
+/*!
+ Generates text that explains how threadsafe and/or reentrant
+ \a node is.
+ */
+bool DocBookGenerator::generateThreadSafeness(const Node *node)
+{
+ // From Generator::generateThreadSafeness
+ Node::ThreadSafeness ts = node->threadSafeness();
+
+ const Node *reentrantNode;
+ Atom reentrantAtom = Atom(Atom::Link, "reentrant");
+ QString linkReentrant = getAutoLink(&reentrantAtom, node, &reentrantNode);
+ const Node *threadSafeNode;
+ Atom threadSafeAtom = Atom(Atom::Link, "thread-safe");
+ QString linkThreadSafe = getAutoLink(&threadSafeAtom, node, &threadSafeNode);
+
+ if (ts == Node::NonReentrant) {
+ m_writer->writeStartElement(dbNamespace, "warning");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("This " + typeString(node) + " is not ");
+ generateSimpleLink(linkReentrant, "reentrant");
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // warning
+
+ return true;
+ } else if (ts == Node::Reentrant || ts == Node::ThreadSafe) {
+ m_writer->writeStartElement(dbNamespace, "note");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+
+ if (node->isAggregate()) {
+ m_writer->writeCharacters("All functions in this " + typeString(node) + " are ");
+ if (ts == Node::ThreadSafe)
+ generateSimpleLink(linkThreadSafe, "thread-safe");
+ else
+ generateSimpleLink(linkReentrant, "reentrant");
+
+ NodeList reentrant;
+ NodeList threadsafe;
+ NodeList nonreentrant;
+ bool exceptions = hasExceptions(node, reentrant, threadsafe, nonreentrant);
+ if (!exceptions || (ts == Node::Reentrant && !threadsafe.isEmpty())) {
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ } else {
+ m_writer->writeCharacters(" with the following exceptions:");
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+
+ if (ts == Node::Reentrant) {
+ if (!nonreentrant.isEmpty()) {
+ m_writer->writeCharacters("These functions are not ");
+ generateSimpleLink(linkReentrant, "reentrant");
+ m_writer->writeCharacters(":");
+ m_writer->writeEndElement(); // para
+ newLine();
+ generateSignatureList(nonreentrant);
+ }
+ if (!threadsafe.isEmpty()) {
+ m_writer->writeCharacters("These functions are also ");
+ generateSimpleLink(linkThreadSafe, "thread-safe");
+ m_writer->writeCharacters(":");
+ m_writer->writeEndElement(); // para
+ newLine();
+ generateSignatureList(threadsafe);
+ }
+ } else { // thread-safe
+ if (!reentrant.isEmpty()) {
+ m_writer->writeCharacters("These functions are only ");
+ generateSimpleLink(linkReentrant, "reentrant");
+ m_writer->writeCharacters(":");
+ m_writer->writeEndElement(); // para
+ newLine();
+ generateSignatureList(reentrant);
+ }
+ if (!nonreentrant.isEmpty()) {
+ m_writer->writeCharacters("These functions are not ");
+ generateSimpleLink(linkReentrant, "reentrant");
+ m_writer->writeCharacters(":");
+ m_writer->writeEndElement(); // para
+ newLine();
+ generateSignatureList(nonreentrant);
+ }
+ }
+ }
+ } else {
+ m_writer->writeCharacters("This " + typeString(node) + " is ");
+ if (ts == Node::ThreadSafe)
+ generateSimpleLink(linkThreadSafe, "thread-safe");
+ else
+ generateSimpleLink(linkReentrant, "reentrant");
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ }
+ m_writer->writeEndElement(); // note
+ newLine();
+
+ return true;
+ }
+
+ return false;
+}
+
+/*!
+ Generate the body of the documentation from the qdoc comment
+ found with the entity represented by the \a node.
+ */
+void DocBookGenerator::generateBody(const Node *node)
+{
+ // From Generator::generateBody, without warnings.
+ const FunctionNode *fn = node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
+
+ if (!node->hasDoc()) {
+ /*
+ Test for special function, like a destructor or copy constructor,
+ that has no documentation.
+ */
+ if (fn) {
+ QString t;
+ if (fn->isDtor()) {
+ t = "Destroys the instance of " + fn->parent()->name() + ".";
+ if (fn->isVirtual())
+ t += " The destructor is virtual.";
+ } else if (fn->isCtor()) {
+ t = "Default constructs an instance of " + fn->parent()->name() + ".";
+ } else if (fn->isCCtor()) {
+ t = "Copy constructor.";
+ } else if (fn->isMCtor()) {
+ t = "Move-copy constructor.";
+ } else if (fn->isCAssign()) {
+ t = "Copy-assignment constructor.";
+ } else if (fn->isMAssign()) {
+ t = "Move-assignment constructor.";
+ }
+
+ if (!t.isEmpty())
+ m_writer->writeTextElement(dbNamespace, "para", t);
+ }
+ } else if (!node->isSharingComment()) {
+ // Reimplements clause and type alias info precede body text
+ if (fn && !fn->overridesThis().isEmpty())
+ generateReimplementsClause(fn);
+ else if (node->isProperty()) {
+ if (static_cast<const PropertyNode *>(node)->propertyType() != PropertyNode::PropertyType::StandardProperty)
+ generateAddendum(node, BindableProperty, nullptr, false);
+ }
+
+ // Generate the body.
+ if (!generateText(node->doc().body(), node)) {
+ if (node->isMarkedReimp())
+ return;
+ }
+
+ // Output what is after the main body.
+ if (fn) {
+ if (fn->isQmlSignal())
+ generateAddendum(node, QmlSignalHandler, nullptr, true);
+ if (fn->isPrivateSignal())
+ generateAddendum(node, PrivateSignal, nullptr, true);
+ if (fn->isInvokable())
+ generateAddendum(node, Invokable, nullptr, true);
+ if (fn->hasAssociatedProperties())
+ generateAddendum(node, AssociatedProperties, nullptr, true);
+ }
+
+ // Warning generation skipped with respect to Generator::generateBody.
+ }
+
+ generateEnumValuesForQmlProperty(node, nullptr);
+ generateRequiredLinks(node);
+}
+
+/*!
+ 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 DocBookGenerator::generateRequiredLinks(const Node *node)
+{
+ // From Generator::generateRequiredLinks.
+ if (!node->isExample())
+ return;
+
+ const auto en = static_cast<const ExampleNode *>(node);
+ QString exampleUrl{Config::instance().get(CONFIG_URL + Config::dot + CONFIG_EXAMPLES).asString()};
+
+ if (exampleUrl.isEmpty()) {
+ if (!en->noAutoList()) {
+ generateFileList(en, false); // files
+ generateFileList(en, true); // images
+ }
+ } else {
+ generateLinkToExample(en, exampleUrl);
+ }
+}
+
+/*!
+ 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 DocBookGenerator::generateLinkToExample(const ExampleNode *en, const QString &baseUrl)
+{
+ // From Generator::generateLinkToExample.
+ 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>
+ QStringList path = QStringList()
+ << Config::instance().get(CONFIG_EXAMPLESINSTALLPATH).asString() << en->name();
+ path.removeAll(QString());
+
+ // Write the link to the example. Typically, this link comes after sections, hence
+ // wrap it in a section too.
+ startSection("Example project");
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ generateSimpleLink(exampleUrl.replace(placeholder, path.join(separator)), link);
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ endSection();
+}
+
+// TODO: [multi-purpose-function-with-flag][generate-file-list]
+
+/*!
+ 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 DocBookGenerator::generateFileList(const ExampleNode *en, bool images)
+{
+ // TODO: [possibly-stale-duplicate-code][generator-insufficient-structural-abstraction]
+ // Review and compare this code with
+ // Generator::generateFileList.
+ // Some subtle changes that might be semantically equivalent are
+ // present between the two.
+ // Supposedly, this version is to be considered stale compared to
+ // Generator's one and it might be possible to remove it in favor
+ // of that as long as the difference in output are taken into consideration.
+
+ // From Generator::generateFileList
+ QString tag;
+ QStringList paths;
+ if (images) {
+ paths = en->images();
+ tag = "Images:";
+ } else { // files
+ paths = en->files();
+ tag = "Files:";
+ }
+ std::sort(paths.begin(), paths.end(), Generator::comparePaths);
+
+ if (paths.isEmpty())
+ return;
+
+ startSection("", "List of Files");
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters(tag);
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ startSection("List of Files");
+
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ for (const auto &path : std::as_const(paths)) {
+ auto maybe_resolved_file{file_resolver.resolve(path)};
+ if (!maybe_resolved_file) {
+ // TODO: [uncentralized-admonition][failed-resolve-file]
+ QString details = std::transform_reduce(
+ file_resolver.get_search_directories().cbegin(),
+ file_resolver.get_search_directories().cend(),
+ u"Searched directories:"_s,
+ std::plus(),
+ [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
+ );
+
+ en->location().warning(u"Cannot find file to quote from: %1"_s.arg(path), details);
+
+ continue;
+ }
+
+ auto file{*maybe_resolved_file};
+ if (images) addImageToCopy(en, file);
+ else generateExampleFilePage(en, file);
+
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ generateSimpleLink(file.get_query(), file.get_query());
+ m_writer->writeEndElement(); // para
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ }
+
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+
+ endSection();
+}
+
+/*!
+ Generate a file with the contents of a C++ or QML source file.
+ */
+void DocBookGenerator::generateExampleFilePage(const Node *node, ResolvedFile resolved_file, CodeMarker*)
+{
+ // TODO: [generator-insufficient-structural-abstraction]
+
+ // From HtmlGenerator::generateExampleFilePage.
+ if (!node->isExample())
+ return;
+
+ // TODO: Understand if this is safe.
+ const auto en = static_cast<const ExampleNode *>(node);
+
+ // Store current (active) writer
+ QXmlStreamWriter *currentWriter = m_writer;
+ m_writer = startDocument(en, resolved_file.get_query());
+ generateHeader(en->fullTitle(), en->subtitle(), en);
+
+ Text text;
+ Quoter quoter;
+ Doc::quoteFromFile(en->doc().location(), quoter, resolved_file);
+ QString code = quoter.quoteTo(en->location(), QString(), QString());
+ CodeMarker *codeMarker = CodeMarker::markerForFileName(resolved_file.get_path());
+ text << Atom(codeMarker->atomType(), code);
+ Atom a(codeMarker->atomType(), code);
+ generateText(text, en);
+
+ endDocument(); // Delete m_writer.
+ m_writer = currentWriter; // Restore writer.
+}
+
+void DocBookGenerator::generateReimplementsClause(const FunctionNode *fn)
+{
+ // From Generator::generateReimplementsClause, without warning generation.
+ if (fn->overridesThis().isEmpty() || !fn->parent()->isClassNode())
+ return;
+
+ auto cn = static_cast<ClassNode *>(fn->parent());
+
+ if (const FunctionNode *overrides = cn->findOverriddenFunction(fn);
+ overrides && !overrides->isPrivate() && !overrides->parent()->isPrivate()) {
+ if (overrides->hasDoc()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("Reimplements: ");
+ QString fullName =
+ overrides->parent()->name() + "::" + overrides->signature(Node::SignaturePlain);
+ generateFullName(overrides->parent(), fullName, overrides);
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ return;
+ }
+ }
+
+ if (const PropertyNode *sameName = cn->findOverriddenProperty(fn); sameName && sameName->hasDoc()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("Reimplements an access function for property: ");
+ QString fullName = sameName->parent()->name() + "::" + sameName->name();
+ generateFullName(sameName->parent(), fullName, sameName);
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ return;
+ }
+}
+
+void DocBookGenerator::generateAlsoList(const Node *node)
+{
+ // From Generator::generateAlsoList.
+ QList<Text> alsoList = node->doc().alsoList();
+ supplementAlsoList(node, alsoList);
+
+ if (!alsoList.isEmpty()) {
+ startSection("See Also");
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeCharacters("See also ");
+ m_writer->writeEndElement(); // emphasis
+ newLine();
+
+ m_writer->writeStartElement(dbNamespace, "simplelist");
+ m_writer->writeAttribute("type", "vert");
+ m_writer->writeAttribute("role", "see-also");
+ newLine();
+
+ for (const Text &text : alsoList) {
+ m_writer->writeStartElement(dbNamespace, "member");
+ generateText(text, node);
+ m_writer->writeEndElement(); // member
+ newLine();
+ }
+
+ m_writer->writeEndElement(); // simplelist
+ newLine();
+
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ endSection();
+ }
+}
+
+/*!
+ Open a new file to write XML contents, including the DocBook
+ opening tag.
+ */
+QXmlStreamWriter *DocBookGenerator::startGenericDocument(const Node *node, const QString &fileName)
+{
+ QFile *outFile = openSubPageFile(node, fileName);
+ m_writer = new QXmlStreamWriter(outFile);
+ m_writer->setAutoFormatting(false); // We need a precise handling of line feeds.
+
+ m_writer->writeStartDocument();
+ newLine();
+ m_writer->writeNamespace(dbNamespace, "db");
+ m_writer->writeNamespace(xlinkNamespace, "xlink");
+ if (m_useITS)
+ m_writer->writeNamespace(itsNamespace, "its");
+ m_writer->writeStartElement(dbNamespace, "article");
+ m_writer->writeAttribute("version", "5.2");
+ if (!m_naturalLanguage.isEmpty())
+ m_writer->writeAttribute("xml:lang", m_naturalLanguage);
+ newLine();
+
+ // Reset the state for the new document.
+ sectionLevels.resize(0);
+ m_inPara = false;
+ m_inList = 0;
+
+ return m_writer;
+}
+
+QXmlStreamWriter *DocBookGenerator::startDocument(const Node *node)
+{
+ m_hasSection = false;
+ refMap.clear();
+
+ QString fileName = Generator::fileName(node, fileExtension());
+ return startGenericDocument(node, fileName);
+}
+
+QXmlStreamWriter *DocBookGenerator::startDocument(const ExampleNode *en, const QString &file)
+{
+ m_hasSection = false;
+
+ QString fileName = linkForExampleFile(file);
+ return startGenericDocument(en, fileName);
+}
+
+void DocBookGenerator::endDocument()
+{
+ m_writer->writeEndElement(); // article
+ m_writer->writeEndDocument();
+
+ m_writer->device()->close();
+ delete m_writer->device();
+ delete m_writer;
+ m_writer = nullptr;
+}
+
+/*!
+ Generate a reference page for the C++ class, namespace, or
+ header file documented in \a node.
+ */
+void DocBookGenerator::generateCppReferencePage(Node *node)
+{
+ // Based on HtmlGenerator::generateCppReferencePage.
+ Q_ASSERT(node->isAggregate());
+ const auto aggregate = static_cast<const Aggregate *>(node);
+
+ QString title;
+ QString rawTitle;
+ QString fullTitle;
+ if (aggregate->isNamespace()) {
+ rawTitle = aggregate->plainName();
+ fullTitle = aggregate->plainFullName();
+ title = rawTitle + " Namespace";
+ } else if (aggregate->isClass()) {
+ rawTitle = aggregate->plainName();
+
+ auto templateDecl = node->templateDecl();
+ if (templateDecl)
+ fullTitle = QString("%1 %2 ").arg((*templateDecl).to_qstring(), aggregate->typeWord(false));
+
+ fullTitle += aggregate->plainFullName();
+ title = rawTitle + QLatin1Char(' ') + aggregate->typeWord(true);
+ } else if (aggregate->isHeader()) {
+ title = fullTitle = rawTitle = aggregate->fullTitle();
+ }
+
+ QString subtitleText;
+ if (rawTitle != fullTitle)
+ subtitleText = fullTitle;
+
+ // Start producing the DocBook file.
+ m_writer = startDocument(node);
+
+ // Info container.
+ generateHeader(title, subtitleText, aggregate);
+
+ generateRequisites(aggregate);
+ generateStatus(aggregate);
+
+ // Element synopsis.
+ generateDocBookSynopsis(node);
+
+ // Actual content.
+ if (!aggregate->doc().isEmpty()) {
+ startSection("details", "Detailed Description");
+
+ generateBody(aggregate);
+ generateAlsoList(aggregate);
+
+ endSection();
+ }
+
+ Sections sections(const_cast<Aggregate *>(aggregate));
+ SectionVector sectionVector =
+ (aggregate->isNamespace() || aggregate->isHeader()) ?
+ sections.stdDetailsSections() :
+ sections.stdCppClassDetailsSections();
+ for (const Section &section : sectionVector) {
+ if (section.members().isEmpty())
+ continue;
+
+ startSection(section.title().toLower(), section.title());
+
+ for (const Node *member : section.members()) {
+ if (member->access() == Access::Private) // ### check necessary?
+ continue;
+
+ if (member->nodeType() != Node::Class) {
+ // This function starts its own section.
+ generateDetailedMember(member, aggregate);
+ } else {
+ startSectionBegin();
+ m_writer->writeCharacters("class ");
+ generateFullName(member, aggregate);
+ startSectionEnd();
+
+ generateBrief(member);
+
+ endSection();
+ }
+ }
+
+ endSection();
+ }
+
+ generateObsoleteMembers(sections);
+
+ endDocument();
+}
+
+void DocBookGenerator::generateSynopsisInfo(const QString &key, const QString &value)
+{
+ m_writer->writeStartElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", key);
+ m_writer->writeCharacters(value);
+ m_writer->writeEndElement(); // synopsisinfo
+ newLine();
+}
+
+void DocBookGenerator::generateModifier(const QString &value)
+{
+ m_writer->writeTextElement(dbNamespace, "modifier", value);
+ newLine();
+}
+
+/*!
+ Generate the metadata for the given \a node in DocBook.
+ */
+void DocBookGenerator::generateDocBookSynopsis(const Node *node)
+{
+ if (!node)
+ return;
+
+ // From Generator::generateStatus, HtmlGenerator::generateRequisites,
+ // Generator::generateThreadSafeness, QDocIndexFiles::generateIndexSection.
+
+ // This function is the major place where DocBook extensions are used.
+ if (!m_useDocBook52)
+ return;
+
+ // Nothing to export in some cases. Note that isSharedCommentNode() returns
+ // true also for QML property groups.
+ if (node->isGroup() || node->isSharedCommentNode() || node->isModule() || node->isQmlModule() || node->isPageNode())
+ return;
+
+ // Cast the node to several subtypes (null pointer if the node is not of the required type).
+ const Aggregate *aggregate =
+ node->isAggregate() ? static_cast<const Aggregate *>(node) : nullptr;
+ const ClassNode *classNode = node->isClass() ? static_cast<const ClassNode *>(node) : nullptr;
+ const FunctionNode *functionNode =
+ node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
+ const PropertyNode *propertyNode =
+ node->isProperty() ? static_cast<const PropertyNode *>(node) : nullptr;
+ const VariableNode *variableNode =
+ node->isVariable() ? static_cast<const VariableNode *>(node) : nullptr;
+ const EnumNode *enumNode = node->isEnumType() ? static_cast<const EnumNode *>(node) : nullptr;
+ const QmlPropertyNode *qpn =
+ node->isQmlProperty() ? static_cast<const QmlPropertyNode *>(node) : nullptr;
+ const QmlTypeNode *qcn = node->isQmlType() ? static_cast<const QmlTypeNode *>(node) : nullptr;
+ // Typedefs are ignored, as they correspond to enums.
+ // Groups and modules are ignored.
+ // Documents are ignored, they have no interesting metadata.
+
+ // Start the synopsis tag.
+ QString synopsisTag = nodeToSynopsisTag(node);
+ m_writer->writeStartElement(dbNamespace, synopsisTag);
+ newLine();
+
+ // Name and basic properties of each tag (like types and parameters).
+ if (node->isClass()) {
+ m_writer->writeStartElement(dbNamespace, "ooclass");
+ m_writer->writeTextElement(dbNamespace, "classname", node->plainName());
+ m_writer->writeEndElement(); // ooclass
+ newLine();
+ } else if (node->isNamespace()) {
+ m_writer->writeTextElement(dbNamespace, "namespacename", node->plainName());
+ newLine();
+ } else if (node->isQmlType()) {
+ m_writer->writeStartElement(dbNamespace, "ooclass");
+ m_writer->writeTextElement(dbNamespace, "classname", node->plainName());
+ m_writer->writeEndElement(); // ooclass
+ newLine();
+ if (!qcn->groupNames().isEmpty())
+ m_writer->writeAttribute("groups", qcn->groupNames().join(QLatin1Char(',')));
+ } else if (node->isProperty()) {
+ m_writer->writeTextElement(dbNamespace, "modifier", "(Qt property)");
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "type", propertyNode->dataType());
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "varname", node->plainName());
+ newLine();
+ } else if (node->isVariable()) {
+ if (variableNode->isStatic()) {
+ m_writer->writeTextElement(dbNamespace, "modifier", "static");
+ newLine();
+ }
+ m_writer->writeTextElement(dbNamespace, "type", variableNode->dataType());
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "varname", node->plainName());
+ newLine();
+ } else if (node->isEnumType()) {
+ m_writer->writeTextElement(dbNamespace, "enumname", node->plainName());
+ newLine();
+ } else if (node->isQmlProperty()) {
+ QString name = node->name();
+ if (qpn->isAttached())
+ name.prepend(qpn->element() + QLatin1Char('.'));
+
+ m_writer->writeTextElement(dbNamespace, "type", qpn->dataType());
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "varname", name);
+ newLine();
+
+ if (qpn->isAttached()) {
+ m_writer->writeTextElement(dbNamespace, "modifier", "attached");
+ newLine();
+ }
+ if (!(const_cast<QmlPropertyNode *>(qpn))->isReadOnly()) {
+ m_writer->writeTextElement(dbNamespace, "modifier", "writable");
+ newLine();
+ }
+ if ((const_cast<QmlPropertyNode *>(qpn))->isRequired()) {
+ m_writer->writeTextElement(dbNamespace, "modifier", "required");
+ newLine();
+ }
+ if (qpn->isReadOnly()) {
+ generateModifier("[read-only]");
+ newLine();
+ }
+ if (qpn->isDefault()) {
+ generateModifier("[default]");
+ newLine();
+ }
+ } else if (node->isFunction()) {
+ if (functionNode->virtualness() != "non")
+ generateModifier("virtual");
+ if (functionNode->isConst())
+ generateModifier("const");
+ if (functionNode->isStatic())
+ generateModifier("static");
+
+ if (!functionNode->isMacro() && !functionNode->isCtor() &&
+ !functionNode->isCCtor() && !functionNode->isMCtor()
+ && !functionNode->isDtor()) {
+ if (functionNode->returnType() == "void")
+ m_writer->writeEmptyElement(dbNamespace, "void");
+ else
+ m_writer->writeTextElement(dbNamespace, "type", functionNode->returnType());
+ newLine();
+ }
+ // Remove two characters from the plain name to only get the name
+ // of the method without parentheses (only for functions, not macros).
+ QString name = node->plainName();
+ if (name.endsWith("()"))
+ name.chop(2);
+ m_writer->writeTextElement(dbNamespace, "methodname", name);
+ newLine();
+
+ if (functionNode->parameters().isEmpty()) {
+ m_writer->writeEmptyElement(dbNamespace, "void");
+ newLine();
+ }
+
+ const Parameters &lp = functionNode->parameters();
+ for (int i = 0; i < lp.count(); ++i) {
+ const Parameter &parameter = lp.at(i);
+ m_writer->writeStartElement(dbNamespace, "methodparam");
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "type", parameter.type());
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "parameter", parameter.name());
+ newLine();
+ if (!parameter.defaultValue().isEmpty()) {
+ m_writer->writeTextElement(dbNamespace, "initializer", parameter.defaultValue());
+ newLine();
+ }
+ m_writer->writeEndElement(); // methodparam
+ newLine();
+ }
+
+ if (functionNode->isDefault())
+ generateModifier("default");
+ if (functionNode->isFinal())
+ generateModifier("final");
+ if (functionNode->isOverride())
+ generateModifier("override");
+ } else if (node->isTypedef()) {
+ m_writer->writeTextElement(dbNamespace, "typedefname", node->plainName());
+ newLine();
+ } else {
+ node->doc().location().warning(
+ QStringLiteral("Unexpected node type in generateDocBookSynopsis: %1")
+ .arg(node->nodeTypeString()));
+ newLine();
+ }
+
+ // Enums and typedefs.
+ if (enumNode) {
+ for (const EnumItem &item : enumNode->items()) {
+ m_writer->writeStartElement(dbNamespace, "enumitem");
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "enumidentifier", item.name());
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "enumvalue", item.value());
+ newLine();
+ m_writer->writeEndElement(); // enumitem
+ newLine();
+ }
+
+ if (enumNode->items().isEmpty()) {
+ // If the enumeration is empty (really rare case), still produce
+ // something for the DocBook document to be valid.
+ m_writer->writeStartElement(dbNamespace, "enumitem");
+ newLine();
+ m_writer->writeEmptyElement(dbNamespace, "enumidentifier");
+ newLine();
+ m_writer->writeEndElement(); // enumitem
+ newLine();
+ }
+ }
+
+ // Below: only synopsisinfo within synopsisTag. These elements must be at
+ // the end of the tag, as per DocBook grammar.
+
+ // Information for functions that could not be output previously
+ // (synopsisinfo).
+ if (node->isFunction()) {
+ generateSynopsisInfo("meta", functionNode->metanessString());
+
+ if (functionNode->isOverload()) {
+ generateSynopsisInfo("overload", "overload");
+ generateSynopsisInfo("overload-number",
+ QString::number(functionNode->overloadNumber()));
+ }
+
+ if (functionNode->isRef())
+ generateSynopsisInfo("refness", QString::number(1));
+ else if (functionNode->isRefRef())
+ generateSynopsisInfo("refness", QString::number(2));
+
+ if (functionNode->hasAssociatedProperties()) {
+ QStringList associatedProperties;
+ const auto &nodes = functionNode->associatedProperties();
+ for (const Node *n : nodes) {
+ const auto pn = static_cast<const PropertyNode *>(n);
+ associatedProperties << pn->name();
+ }
+ associatedProperties.sort();
+ generateSynopsisInfo("associated-property",
+ associatedProperties.join(QLatin1Char(',')));
+ }
+
+ QString signature = functionNode->signature(Node::SignatureReturnType);
+ // 'const' is already part of FunctionNode::signature()
+ if (functionNode->isFinal())
+ signature += " final";
+ if (functionNode->isOverride())
+ signature += " override";
+ if (functionNode->isPureVirtual())
+ signature += " = 0";
+ else if (functionNode->isDefault())
+ signature += " = default";
+ generateSynopsisInfo("signature", signature);
+ }
+
+ // Accessibility status.
+ if (!node->isPageNode() && !node->isCollectionNode()) {
+ switch (node->access()) {
+ case Access::Public:
+ generateSynopsisInfo("access", "public");
+ break;
+ case Access::Protected:
+ generateSynopsisInfo("access", "protected");
+ break;
+ case Access::Private:
+ generateSynopsisInfo("access", "private");
+ break;
+ default:
+ break;
+ }
+ if (node->isAbstract())
+ generateSynopsisInfo("abstract", "true");
+ }
+
+ // Status.
+ switch (node->status()) {
+ case Node::Active:
+ generateSynopsisInfo("status", "active");
+ break;
+ case Node::Preliminary:
+ generateSynopsisInfo("status", "preliminary");
+ break;
+ case Node::Deprecated:
+ generateSynopsisInfo("status", "deprecated");
+ break;
+ case Node::Internal:
+ generateSynopsisInfo("status", "internal");
+ break;
+ default:
+ generateSynopsisInfo("status", "main");
+ break;
+ }
+
+ // C++ classes and name spaces.
+ if (aggregate) {
+ // Includes.
+ if (aggregate->includeFile()) generateSynopsisInfo("headers", *aggregate->includeFile());
+
+ // Since and project.
+ if (!aggregate->since().isEmpty())
+ generateSynopsisInfo("since", formatSince(aggregate));
+
+ if (aggregate->nodeType() == Node::Class || aggregate->nodeType() == Node::Namespace) {
+ // CMake and QT variable.
+ if (!aggregate->physicalModuleName().isEmpty()) {
+ const CollectionNode *cn =
+ m_qdb->getCollectionNode(aggregate->physicalModuleName(), Node::Module);
+ if (cn && !cn->qtCMakeComponent().isEmpty()) {
+ const QString qtComponent = "Qt" + QString::number(QT_VERSION_MAJOR);
+ const QString findpackageText = "find_package(" + qtComponent
+ + " REQUIRED COMPONENTS " + cn->qtCMakeComponent() + ")";
+ const QString targetLinkLibrariesText =
+ "target_link_libraries(mytarget PRIVATE " + qtComponent + "::" + cn->qtCMakeComponent()
+ + ")";
+ generateSynopsisInfo("cmake-find-package", findpackageText);
+ generateSynopsisInfo("cmake-target-link-libraries", targetLinkLibrariesText);
+ }
+ if (cn && !cn->qtVariable().isEmpty())
+ generateSynopsisInfo("qmake", "QT += " + cn->qtVariable());
+ }
+ }
+
+ if (aggregate->nodeType() == Node::Class) {
+ // Native type
+ auto *classe = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate));
+ if (classe && classe->isQmlNativeType() && classe->status() != Node::Internal) {
+ m_writer->writeStartElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", "nativeTypeFor");
+
+ QList<QmlTypeNode *> nativeTypes { classe->qmlNativeTypes().cbegin(), classe->qmlNativeTypes().cend()};
+ std::sort(nativeTypes.begin(), nativeTypes.end(), Node::nodeNameLessThan);
+
+ for (auto item : std::as_const(nativeTypes)) {
+ const Node *otherNode{nullptr};
+ Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(item));
+ const QString &link = getAutoLink(&a, aggregate, &otherNode);
+ generateSimpleLink(link, item->name());
+ }
+
+ m_writer->writeEndElement(); // synopsisinfo
+ }
+
+ // Inherits.
+ QList<RelatedClass>::ConstIterator r;
+ if (!classe->baseClasses().isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", "inherits");
+
+ r = classe->baseClasses().constBegin();
+ int index = 0;
+ while (r != classe->baseClasses().constEnd()) {
+ if ((*r).m_node) {
+ generateFullName((*r).m_node, classe);
+
+ if ((*r).m_access == Access::Protected) {
+ m_writer->writeCharacters(" (protected)");
+ } else if ((*r).m_access == Access::Private) {
+ m_writer->writeCharacters(" (private)");
+ }
+ m_writer->writeCharacters(
+ Utilities::comma(index++, classe->baseClasses().size()));
+ }
+ ++r;
+ }
+
+ m_writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+
+ // Inherited by.
+ if (!classe->derivedClasses().isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", "inheritedBy");
+ generateSortedNames(classe, classe->derivedClasses());
+ m_writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+ }
+ }
+
+ // QML types.
+ if (qcn) {
+ // Module name and version (i.e. import).
+ QString logicalModuleVersion;
+ const CollectionNode *collection =
+ m_qdb->getCollectionNode(qcn->logicalModuleName(), qcn->nodeType());
+ if (collection)
+ logicalModuleVersion = collection->logicalModuleVersion();
+ else
+ logicalModuleVersion = qcn->logicalModuleVersion();
+
+ QStringList importText;
+ importText << "import " + qcn->logicalModuleName();
+ if (!logicalModuleVersion.isEmpty())
+ importText << logicalModuleVersion;
+ generateSynopsisInfo("import", importText.join(' '));
+
+ // Since and project.
+ if (!qcn->since().isEmpty())
+ generateSynopsisInfo("since", formatSince(qcn));
+
+ // Inherited by.
+ NodeList subs;
+ QmlTypeNode::subclasses(qcn, subs);
+ if (!subs.isEmpty()) {
+ m_writer->writeTextElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", "inheritedBy");
+ generateSortedQmlNames(qcn, subs);
+ m_writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+
+ // Inherits.
+ QmlTypeNode *base = qcn->qmlBaseNode();
+ while (base && base->isInternal())
+ base = base->qmlBaseNode();
+ if (base) {
+ const Node *otherNode = nullptr;
+ Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(base));
+ QString link = getAutoLink(&a, base, &otherNode);
+
+ m_writer->writeTextElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", "inherits");
+ generateSimpleLink(link, base->name());
+ m_writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+
+ // Native type
+ ClassNode *cn = (const_cast<QmlTypeNode *>(qcn))->classNode();
+
+ if (cn && cn->isQmlNativeType() && (cn->status() != Node::Internal)) {
+ const Node *otherNode = nullptr;
+ Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(qcn));
+ QString link = getAutoLink(&a, cn, &otherNode);
+
+ m_writer->writeTextElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", "nativeType");
+ generateSimpleLink(link, cn->name());
+ m_writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+ }
+
+ // Thread safeness.
+ switch (node->threadSafeness()) {
+ case Node::UnspecifiedSafeness:
+ generateSynopsisInfo("threadsafeness", "unspecified");
+ break;
+ case Node::NonReentrant:
+ generateSynopsisInfo("threadsafeness", "non-reentrant");
+ break;
+ case Node::Reentrant:
+ generateSynopsisInfo("threadsafeness", "reentrant");
+ break;
+ case Node::ThreadSafe:
+ generateSynopsisInfo("threadsafeness", "thread safe");
+ break;
+ default:
+ generateSynopsisInfo("threadsafeness", "unspecified");
+ break;
+ }
+
+ // Module.
+ if (!node->physicalModuleName().isEmpty())
+ generateSynopsisInfo("module", node->physicalModuleName());
+
+ // Group.
+ if (classNode && !classNode->groupNames().isEmpty()) {
+ generateSynopsisInfo("groups", classNode->groupNames().join(QLatin1Char(',')));
+ } else if (qcn && !qcn->groupNames().isEmpty()) {
+ generateSynopsisInfo("groups", qcn->groupNames().join(QLatin1Char(',')));
+ }
+
+ // Properties.
+ if (propertyNode) {
+ for (const Node *fnNode : propertyNode->getters()) {
+ if (fnNode) {
+ const auto funcNode = static_cast<const FunctionNode *>(fnNode);
+ generateSynopsisInfo("getter", funcNode->name());
+ }
+ }
+ for (const Node *fnNode : propertyNode->setters()) {
+ if (fnNode) {
+ const auto funcNode = static_cast<const FunctionNode *>(fnNode);
+ generateSynopsisInfo("setter", funcNode->name());
+ }
+ }
+ for (const Node *fnNode : propertyNode->resetters()) {
+ if (fnNode) {
+ const auto funcNode = static_cast<const FunctionNode *>(fnNode);
+ generateSynopsisInfo("resetter", funcNode->name());
+ }
+ }
+ for (const Node *fnNode : propertyNode->notifiers()) {
+ if (fnNode) {
+ const auto funcNode = static_cast<const FunctionNode *>(fnNode);
+ generateSynopsisInfo("notifier", funcNode->name());
+ }
+ }
+ }
+
+ m_writer->writeEndElement(); // nodeToSynopsisTag (like classsynopsis)
+ newLine();
+
+ // The typedef associated to this enum. It is output *after* the main tag,
+ // i.e. it must be after the synopsisinfo.
+ if (enumNode && enumNode->flagsType()) {
+ m_writer->writeStartElement(dbNamespace, "typedefsynopsis");
+ newLine();
+
+ m_writer->writeTextElement(dbNamespace, "typedefname",
+ enumNode->flagsType()->fullDocumentName());
+ newLine();
+
+ m_writer->writeEndElement(); // typedefsynopsis
+ newLine();
+ }
+}
+
+QString taggedNode(const Node *node)
+{
+ // From CodeMarker::taggedNode, but without the tag part (i.e. only the QML specific case
+ // remaining).
+ // TODO: find a better name for this.
+ if (node->nodeType() == Node::QmlType && node->name().startsWith(QLatin1String("QML:")))
+ return node->name().mid(4);
+ return node->name();
+}
+
+/*!
+ Parses a string with method/variable name and (return) type
+ to include type tags.
+ */
+void DocBookGenerator::typified(const QString &string, const Node *relative, bool trailingSpace,
+ bool generateType)
+{
+ // Adapted from CodeMarker::typified and HtmlGenerator::highlightedCode.
+ // Note: CppCodeMarker::markedUpIncludes is not needed for DocBook, as this part is natively
+ // generated as DocBook. Hence, there is no need to reimplement <@headerfile> from
+ // HtmlGenerator::highlightedCode.
+ QString result;
+ QString pendingWord;
+
+ for (int i = 0; i <= string.size(); ++i) {
+ QChar ch;
+ if (i != string.size())
+ ch = string.at(i);
+
+ QChar lower = ch.toLower();
+ if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) || ch.digitValue() >= 0
+ || ch == QLatin1Char('_') || ch == QLatin1Char(':')) {
+ pendingWord += ch;
+ } else {
+ if (!pendingWord.isEmpty()) {
+ bool isProbablyType = (pendingWord != QLatin1String("const"));
+ if (generateType && isProbablyType) {
+ // Flush the current buffer.
+ m_writer->writeCharacters(result);
+ result.truncate(0);
+
+ // Add the link, logic from HtmlGenerator::highlightedCode.
+ const Node *n = m_qdb->findTypeNode(pendingWord, relative, Node::DontCare);
+ QString href;
+ if (!(n && n->isQmlBasicType())
+ || (relative
+ && (relative->genus() == n->genus() || Node::DontCare == n->genus()))) {
+ href = linkForNode(n, relative);
+ }
+
+ m_writer->writeStartElement(dbNamespace, "type");
+ if (href.isEmpty())
+ m_writer->writeCharacters(pendingWord);
+ else
+ generateSimpleLink(href, pendingWord);
+ m_writer->writeEndElement(); // type
+ } else {
+ result += pendingWord;
+ }
+ }
+ pendingWord.clear();
+
+ if (ch.unicode() != '\0')
+ result += ch;
+ }
+ }
+
+ if (trailingSpace && string.size()) {
+ if (!string.endsWith(QLatin1Char('*')) && !string.endsWith(QLatin1Char('&')))
+ result += QLatin1Char(' ');
+ }
+
+ m_writer->writeCharacters(result);
+}
+
+void DocBookGenerator::generateSynopsisName(const Node *node, const Node *relative,
+ bool generateNameLink)
+{
+ // Implements the rewriting of <@link> from HtmlGenerator::highlightedCode, only due to calls to
+ // CodeMarker::linkTag in CppCodeMarker::markedUpSynopsis.
+ QString name = taggedNode(node);
+
+ if (!generateNameLink) {
+ m_writer->writeCharacters(name);
+ return;
+ }
+
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ generateSimpleLink(linkForNode(node, relative), name);
+ m_writer->writeEndElement(); // emphasis
+}
+
+void DocBookGenerator::generateParameter(const Parameter &parameter, const Node *relative,
+ bool generateExtra, bool generateType)
+{
+ const QString &pname = parameter.name();
+ const QString &ptype = parameter.type();
+ QString paramName;
+ if (!pname.isEmpty()) {
+ typified(ptype, relative, true, generateType);
+ paramName = pname;
+ } else {
+ paramName = ptype;
+ }
+
+ if (generateExtra || pname.isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeCharacters(paramName);
+ m_writer->writeEndElement(); // emphasis
+ }
+
+ const QString &pvalue = parameter.defaultValue();
+ if (generateExtra && !pvalue.isEmpty())
+ m_writer->writeCharacters(" = " + pvalue);
+}
+
+void DocBookGenerator::generateSynopsis(const Node *node, const Node *relative,
+ Section::Style style)
+{
+ // From HtmlGenerator::generateSynopsis (conditions written as booleans).
+ const bool generateExtra = style != Section::AllMembers;
+ const bool generateType = style != Section::Details;
+ const bool generateNameLink = style != Section::Details;
+
+ // From CppCodeMarker::markedUpSynopsis, reversed the generation of "extra" and "synopsis".
+ const int MaxEnumValues = 6;
+
+ if (generateExtra) {
+ if (auto extra = CodeMarker::extraSynopsis(node, style); !extra.isEmpty())
+ m_writer->writeCharacters(extra + " ");
+ }
+
+ // Then generate the synopsis.
+ QString namePrefix {};
+ if (style == Section::Details) {
+ if (!node->isRelatedNonmember() && !node->isProxyNode() && !node->parent()->name().isEmpty()
+ && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()) {
+ namePrefix = taggedNode(node->parent()) + "::";
+ }
+ }
+
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ m_writer->writeCharacters("namespace ");
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ break;
+ case Node::Class:
+ m_writer->writeCharacters("class ");
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ break;
+ case Node::Function: {
+ const auto func = (const FunctionNode *)node;
+
+ // First, the part coming before the name.
+ if (style == Section::Summary || style == Section::Accessors) {
+ if (!func->isNonvirtual())
+ m_writer->writeCharacters(QStringLiteral("virtual "));
+ }
+
+ // Name and parameters.
+ if (style != Section::AllMembers && !func->returnType().isEmpty())
+ typified(func->returnType(), relative, true, generateType);
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+
+ if (!func->isMacroWithoutParams()) {
+ m_writer->writeCharacters(QStringLiteral("("));
+ if (!func->parameters().isEmpty()) {
+ const Parameters &parameters = func->parameters();
+ for (int i = 0; i < parameters.count(); i++) {
+ if (i > 0)
+ m_writer->writeCharacters(QStringLiteral(", "));
+ generateParameter(parameters.at(i), relative, generateExtra, generateType);
+ }
+ }
+ m_writer->writeCharacters(QStringLiteral(")"));
+ }
+
+ if (func->isConst())
+ m_writer->writeCharacters(QStringLiteral(" const"));
+
+ if (style == Section::Summary || style == Section::Accessors) {
+ // virtual is prepended, if needed.
+ QString synopsis;
+ if (func->isFinal())
+ synopsis += QStringLiteral(" final");
+ if (func->isOverride())
+ synopsis += QStringLiteral(" override");
+ if (func->isPureVirtual())
+ synopsis += QStringLiteral(" = 0");
+ if (func->isRef())
+ synopsis += QStringLiteral(" &");
+ else if (func->isRefRef())
+ synopsis += QStringLiteral(" &&");
+ m_writer->writeCharacters(synopsis);
+ } else if (style == Section::AllMembers) {
+ if (!func->returnType().isEmpty() && func->returnType() != "void") {
+ m_writer->writeCharacters(QStringLiteral(" : "));
+ typified(func->returnType(), relative, false, generateType);
+ }
+ } else {
+ QString synopsis;
+ if (func->isRef())
+ synopsis += QStringLiteral(" &");
+ else if (func->isRefRef())
+ synopsis += QStringLiteral(" &&");
+ m_writer->writeCharacters(synopsis);
+ }
+ } break;
+ case Node::Enum: {
+ const auto enume = static_cast<const EnumNode *>(node);
+ m_writer->writeCharacters(QStringLiteral("enum "));
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+
+ QString synopsis;
+ if (style == Section::Summary) {
+ synopsis += " { ";
+
+ QStringList documentedItems = enume->doc().enumItemNames();
+ if (documentedItems.isEmpty()) {
+ const auto &enumItems = enume->items();
+ for (const auto &item : enumItems)
+ documentedItems << item.name();
+ }
+ const QStringList omitItems = enume->doc().omitEnumItemNames();
+ for (const auto &item : omitItems)
+ documentedItems.removeAll(item);
+
+ if (documentedItems.size() > MaxEnumValues) {
+ // Take the last element and keep it safe, then elide the surplus.
+ const QString last = documentedItems.last();
+ documentedItems = documentedItems.mid(0, MaxEnumValues - 1);
+ documentedItems += "&#x2026;"; // Ellipsis: in HTML, &hellip;.
+ documentedItems += last;
+ }
+ synopsis += documentedItems.join(QLatin1String(", "));
+
+ if (!documentedItems.isEmpty())
+ synopsis += QLatin1Char(' ');
+ synopsis += QLatin1Char('}');
+ }
+ m_writer->writeCharacters(synopsis);
+ } break;
+ case Node::TypeAlias: {
+ if (style == Section::Details) {
+ auto templateDecl = node->templateDecl();
+ if (templateDecl)
+ m_writer->writeCharacters((*templateDecl).to_qstring() + QLatin1Char(' '));
+ }
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ } break;
+ case Node::Typedef: {
+ if (static_cast<const TypedefNode *>(node)->associatedEnum())
+ m_writer->writeCharacters("flags ");
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ } break;
+ case Node::Property: {
+ const auto property = static_cast<const PropertyNode *>(node);
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ m_writer->writeCharacters(" : ");
+ typified(property->qualifiedDataType(), relative, false, generateType);
+ } break;
+ case Node::Variable: {
+ const auto variable = static_cast<const VariableNode *>(node);
+ if (style == Section::AllMembers) {
+ generateSynopsisName(node, relative, generateNameLink);
+ m_writer->writeCharacters(" : ");
+ typified(variable->dataType(), relative, false, generateType);
+ } else {
+ typified(variable->leftType(), relative, false, generateType);
+ m_writer->writeCharacters(" ");
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ m_writer->writeCharacters(variable->rightType());
+ }
+ } break;
+ default:
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ }
+}
+
+void DocBookGenerator::generateEnumValue(const QString &enumValue, const Node *relative)
+{
+ // From CppCodeMarker::markedUpEnumValue, simplifications from Generator::plainCode (removing
+ // <@op>). With respect to CppCodeMarker::markedUpEnumValue, the order of generation of parents
+ // must be reversed so that they are processed in the order
+ const auto *node = relative->parent();
+
+ if (relative->isQmlProperty()) {
+ const auto *qpn = static_cast<const QmlPropertyNode*>(relative);
+ if (qpn->enumNode() && !enumValue.startsWith("%1."_L1.arg(qpn->enumPrefix()))) {
+ m_writer->writeCharacters("%1.%2"_L1.arg(qpn->enumPrefix(), enumValue));
+ return;
+ }
+ }
+
+ if (!relative->isEnumType()) {
+ m_writer->writeCharacters(enumValue);
+ return;
+ }
+
+ QList<const Node *> parents;
+ while (!node->isHeader() && node->parent()) {
+ parents.prepend(node);
+ if (node->parent() == relative || node->parent()->name().isEmpty())
+ break;
+ node = node->parent();
+ }
+ if (static_cast<const EnumNode *>(relative)->isScoped())
+ parents << relative;
+
+ m_writer->writeStartElement(dbNamespace, "code");
+ for (auto parent : parents) {
+ generateSynopsisName(parent, relative, true);
+ m_writer->writeCharacters("::");
+ }
+
+ m_writer->writeCharacters(enumValue);
+ m_writer->writeEndElement(); // code
+}
+
+/*!
+ If the node is an overloaded signal, and a node with an
+ example on how to connect to it
+
+ Someone didn't finish writing this comment, and I don't know what this
+ function is supposed to do, so I have not tried to complete the comment
+ yet.
+ */
+void DocBookGenerator::generateOverloadedSignal(const Node *node)
+{
+ // From Generator::generateOverloadedSignal.
+ QString code = getOverloadedSignalCode(node);
+ if (code.isEmpty())
+ return;
+
+ m_writer->writeStartElement(dbNamespace, "note");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("Signal ");
+ m_writer->writeTextElement(dbNamespace, "emphasis", node->name());
+ m_writer->writeCharacters(" 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:");
+ m_writer->writeTextElement(dbNamespace, "code", code);
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // note
+ newLine();
+}
+
+/*!
+ Generates an addendum note of type \a type for \a node. \a marker
+ is unused in this generator.
+*/
+void DocBookGenerator::generateAddendum(const Node *node, Addendum type, CodeMarker *marker,
+ bool generateNote)
+{
+ Q_UNUSED(marker)
+ Q_ASSERT(node && !node->name().isEmpty());
+ if (generateNote) {
+ m_writer->writeStartElement(dbNamespace, "note");
+ newLine();
+ }
+ switch (type) {
+ case Invokable:
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters(
+ "This function can be invoked via the meta-object system and from QML. See ");
+ generateSimpleLink(node->url(), "Q_INVOKABLE");
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ break;
+ case PrivateSignal:
+ m_writer->writeTextElement(
+ dbNamespace, "para",
+ "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());
+ int prefixLocation = handler.lastIndexOf('.', -2) + 1;
+ handler[prefixLocation] = handler[prefixLocation].toTitleCase();
+ handler.insert(prefixLocation, QLatin1String("on"));
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("The corresponding handler is ");
+ m_writer->writeTextElement(dbNamespace, "code", handler);
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ break;
+ }
+ case AssociatedProperties:
+ {
+ if (!node->isFunction())
+ return;
+ const auto *fn = static_cast<const FunctionNode *>(node);
+ auto propertyNodes = fn->associatedProperties();
+ if (propertyNodes.isEmpty())
+ return;
+ std::sort(propertyNodes.begin(), propertyNodes.end(), Node::nodeNameLessThan);
+ for (const auto propertyNode : std::as_const(propertyNodes)) {
+ QString msg;
+ const auto pn = static_cast<const PropertyNode *>(propertyNode);
+ switch (pn->role(fn)) {
+ case PropertyNode::FunctionRole::Getter:
+ msg = QStringLiteral("Getter function");
+ break;
+ case PropertyNode::FunctionRole::Setter:
+ msg = QStringLiteral("Setter function");
+ break;
+ case PropertyNode::FunctionRole::Resetter:
+ msg = QStringLiteral("Resetter function");
+ break;
+ case PropertyNode::FunctionRole::Notifier:
+ msg = QStringLiteral("Notifier signal");
+ break;
+ default:
+ continue;
+ }
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters(msg + " for property ");
+ generateSimpleLink(linkForNode(pn, nullptr), pn->name());
+ m_writer->writeCharacters(". ");
+ m_writer->writeEndElement(); // para
+ newLine();
+ }
+ break;
+ }
+ case BindableProperty:
+ {
+ const Node *linkNode;
+ Atom linkAtom = Atom(Atom::Link, "QProperty");
+ QString link = getAutoLink(&linkAtom, node, &linkNode);
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("This property supports ");
+ generateSimpleLink(link, "QProperty");
+ m_writer->writeCharacters(" bindings.");
+ m_writer->writeEndElement(); // para
+ newLine();
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (generateNote) {
+ m_writer->writeEndElement(); // note
+ newLine();
+ }
+}
+
+void DocBookGenerator::generateDetailedMember(const Node *node, const PageNode *relative)
+{
+ // From HtmlGenerator::generateDetailedMember.
+ bool closeSupplementarySection = false;
+
+ if (node->isSharedCommentNode()) {
+ const auto *scn = reinterpret_cast<const SharedCommentNode *>(node);
+ const QList<Node *> &collective = scn->collective();
+
+ bool firstFunction = true;
+ for (const auto *sharedNode : collective) {
+ if (firstFunction) {
+ startSectionBegin(sharedNode);
+ } else {
+ m_writer->writeStartElement(dbNamespace, "bridgehead");
+ m_writer->writeAttribute("renderas", "sect2");
+ writeXmlId(sharedNode);
+ }
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+
+ generateSynopsis(sharedNode, relative, Section::Details);
+
+ if (firstFunction) {
+ startSectionEnd();
+ firstFunction = false;
+ } else {
+ m_writer->writeEndElement(); // bridgehead
+ newLine();
+ }
+ }
+ } else {
+ const EnumNode *etn;
+ if (node->isEnumType() && (etn = static_cast<const EnumNode *>(node))->flagsType()) {
+ startSectionBegin(node);
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ generateSynopsis(etn, relative, Section::Details);
+ startSectionEnd();
+
+ m_writer->writeStartElement(dbNamespace, "bridgehead");
+ m_writer->writeAttribute("renderas", "sect2");
+ generateSynopsis(etn->flagsType(), relative, Section::Details);
+ m_writer->writeEndElement(); // bridgehead
+ newLine();
+ } else {
+ startSectionBegin(node);
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ generateSynopsis(node, relative, Section::Details);
+ startSectionEnd();
+ }
+ }
+ Q_ASSERT(m_hasSection);
+
+ generateDocBookSynopsis(node);
+
+ generateStatus(node);
+ generateBody(node);
+
+ // If the body ends with a section, the rest of the description must be wrapped in a section too.
+ if (node->hasDoc() && node->doc().body().firstAtom() && node->doc().body().lastAtom()->type() == Atom::SectionRight) {
+ closeSupplementarySection = true;
+ startSection("", "Notes");
+ }
+
+ generateOverloadedSignal(node);
+ generateComparisonCategory(node);
+ generateThreadSafeness(node);
+ generateSince(node);
+
+ if (node->isProperty()) {
+ const auto property = static_cast<const PropertyNode *>(node);
+ if (property->propertyType() == PropertyNode::PropertyType::StandardProperty) {
+ Section section("", "", "", "", Section::Accessors);
+
+ section.appendMembers(property->getters().toVector());
+ section.appendMembers(property->setters().toVector());
+ section.appendMembers(property->resetters().toVector());
+
+ if (!section.members().isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters("Access functions:");
+ newLine();
+ m_writer->writeEndElement(); // emphasis
+ newLine();
+ m_writer->writeEndElement(); // para
+ newLine();
+ generateSectionList(section, node);
+ }
+
+ Section notifiers("", "", "", "", Section::Accessors);
+ notifiers.appendMembers(property->notifiers().toVector());
+
+ if (!notifiers.members().isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters("Notifier signal:");
+ newLine();
+ m_writer->writeEndElement(); // emphasis
+ newLine();
+ m_writer->writeEndElement(); // para
+ newLine();
+ generateSectionList(notifiers, node);
+ }
+ }
+ } else if (node->isEnumType()) {
+ const auto en = static_cast<const EnumNode *>(node);
+
+ if (m_qflagsHref.isEmpty()) {
+ Node *qflags = m_qdb->findClassNode(QStringList("QFlags"));
+ if (qflags)
+ m_qflagsHref = linkForNode(qflags, nullptr);
+ }
+
+ if (en->flagsType()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("The ");
+ m_writer->writeStartElement(dbNamespace, "code");
+ m_writer->writeCharacters(en->flagsType()->name());
+ m_writer->writeEndElement(); // code
+ m_writer->writeCharacters(" type is a typedef for ");
+ m_writer->writeStartElement(dbNamespace, "code");
+ generateSimpleLink(m_qflagsHref, "QFlags");
+ m_writer->writeCharacters("<" + en->name() + ">. ");
+ m_writer->writeEndElement(); // code
+ m_writer->writeCharacters("It stores an OR combination of ");
+ m_writer->writeStartElement(dbNamespace, "code");
+ m_writer->writeCharacters(en->name());
+ m_writer->writeEndElement(); // code
+ m_writer->writeCharacters(" values.");
+ m_writer->writeEndElement(); // para
+ newLine();
+ }
+ }
+
+ if (closeSupplementarySection)
+ endSection();
+
+ // The list of linked pages is always in its own section.
+ generateAlsoList(node);
+
+ // Close the section for this member.
+ endSection(); // section
+}
+
+void DocBookGenerator::generateSectionList(const Section &section, const Node *relative,
+ bool useObsoleteMembers)
+{
+ // From HtmlGenerator::generateSectionList, just generating a list (not tables).
+ const NodeVector &members =
+ (useObsoleteMembers ? section.obsoleteMembers() : section.members());
+ if (!members.isEmpty()) {
+ bool hasPrivateSignals = false;
+ bool isInvokable = false;
+
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ newLine();
+
+ NodeVector::ConstIterator m = members.constBegin();
+ while (m != members.constEnd()) {
+ if ((*m)->access() == Access::Private) {
+ ++m;
+ continue;
+ }
+
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+
+ // prefix no more needed.
+ generateSynopsis(*m, relative, section.style());
+ if ((*m)->isFunction()) {
+ const auto fn = static_cast<const FunctionNode *>(*m);
+ if (fn->isPrivateSignal())
+ hasPrivateSignals = true;
+ else if (fn->isInvokable())
+ isInvokable = true;
+ }
+
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+
+ ++m;
+ }
+
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+
+ if (hasPrivateSignals)
+ generateAddendum(relative, Generator::PrivateSignal, nullptr, true);
+ if (isInvokable)
+ generateAddendum(relative, Generator::Invokable, nullptr, true);
+ }
+
+ if (!useObsoleteMembers && section.style() == Section::Summary
+ && !section.inheritedMembers().isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ newLine();
+
+ generateSectionInheritedList(section, relative);
+
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+ }
+}
+
+void DocBookGenerator::generateSectionInheritedList(const Section &section, const Node *relative)
+{
+ // From HtmlGenerator::generateSectionInheritedList.
+ QList<std::pair<Aggregate *, int>>::ConstIterator p = section.inheritedMembers().constBegin();
+ while (p != section.inheritedMembers().constEnd()) {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ m_writer->writeCharacters(QString::number((*p).second) + u' ');
+ if ((*p).second == 1)
+ m_writer->writeCharacters(section.singular());
+ else
+ m_writer->writeCharacters(section.plural());
+ m_writer->writeCharacters(" inherited from ");
+ generateSimpleLink(fileName((*p).first) + '#'
+ + Generator::cleanRef(section.title().toLower()),
+ (*p).first->plainFullName(relative));
+ ++p;
+ }
+}
+
+/*!
+ Generate the DocBook page for an entity that doesn't map
+ to any underlying parsable C++ or QML element.
+ */
+void DocBookGenerator::generatePageNode(PageNode *pn)
+{
+ // From HtmlGenerator::generatePageNode, remove anything related to TOCs.
+ Q_ASSERT(m_writer == nullptr);
+ m_writer = startDocument(pn);
+
+ generateHeader(pn->fullTitle(), pn->subtitle(), pn);
+ generateBody(pn);
+ generateAlsoList(pn);
+ generateFooter();
+
+ endDocument();
+}
+
+/*!
+ Generate the DocBook page for a QML type. \qcn is the QML type.
+ */
+void DocBookGenerator::generateQmlTypePage(QmlTypeNode *qcn)
+{
+ // From HtmlGenerator::generateQmlTypePage.
+ // Start producing the DocBook file.
+ Q_ASSERT(m_writer == nullptr);
+ m_writer = startDocument(qcn);
+
+ Generator::setQmlTypeContext(qcn);
+ QString title = qcn->fullTitle();
+ if (qcn->isQmlBasicType())
+ title.append(" QML Value Type");
+ else
+ title.append(" QML Type");
+ // TODO: for ITS attribute, only apply translate="no" on qcn->fullTitle(),
+ // not its suffix (which should be translated). generateHeader doesn't
+ // allow this kind of input, the title isn't supposed to be structured.
+ // Ideally, do the same in HTML.
+
+ generateHeader(title, qcn->subtitle(), qcn);
+ generateQmlRequisites(qcn);
+ generateStatus(qcn);
+
+ startSection("details", "Detailed Description");
+ generateBody(qcn);
+
+ generateAlsoList(qcn);
+
+ endSection();
+
+ Sections sections(qcn);
+ for (const auto &section : sections.stdQmlTypeDetailsSections()) {
+ if (!section.isEmpty()) {
+ startSection(section.title().toLower(), section.title());
+
+ for (const auto &member : section.members())
+ generateDetailedQmlMember(member, qcn);
+
+ endSection();
+ }
+ }
+
+ generateObsoleteQmlMembers(sections);
+
+ generateFooter();
+ Generator::setQmlTypeContext(nullptr);
+
+ endDocument();
+}
+
+/*!
+ Outputs the DocBook detailed documentation for a section
+ on a QML element reference page.
+ */
+void DocBookGenerator::generateDetailedQmlMember(Node *node, const Aggregate *relative)
+{
+ // From HtmlGenerator::generateDetailedQmlMember, with elements from
+ // CppCodeMarker::markedUpQmlItem and HtmlGenerator::generateQmlItem.
+ auto getQmlPropertyTitle = [&](QmlPropertyNode *n) {
+ QString title{CodeMarker::extraSynopsis(n, Section::Details)};
+ if (!title.isEmpty())
+ title += ' '_L1;
+ // Finalise generation of name, as per CppCodeMarker::markedUpQmlItem.
+ if (n->isAttached())
+ title += n->element() + QLatin1Char('.');
+ title += n->name() + " : " + n->dataType();
+
+ return title;
+ };
+
+ auto generateQmlMethodTitle = [&](Node *node) {
+ generateSynopsis(node, relative, Section::Details);
+ };
+
+ if (node->isPropertyGroup()) {
+ const auto *scn = static_cast<const SharedCommentNode *>(node);
+
+ QString heading;
+ if (!scn->name().isEmpty())
+ heading = scn->name() + " group";
+ else
+ heading = node->name();
+ startSection(scn, heading);
+ // This last call creates a title for this section. In other words,
+ // titles are forbidden for the rest of the section, hence the use of
+ // bridgehead.
+
+ const QList<Node *> sharedNodes = scn->collective();
+ for (const auto &sharedNode : sharedNodes) {
+ if (sharedNode->isQmlProperty()) {
+ auto *qpn = static_cast<QmlPropertyNode *>(sharedNode);
+
+ m_writer->writeStartElement(dbNamespace, "bridgehead");
+ m_writer->writeAttribute("renderas", "sect2");
+ writeXmlId(qpn);
+ m_writer->writeCharacters(getQmlPropertyTitle(qpn));
+ m_writer->writeEndElement(); // bridgehead
+ newLine();
+
+ generateDocBookSynopsis(qpn);
+ }
+ }
+ } else if (node->isQmlProperty()) {
+ auto qpn = static_cast<QmlPropertyNode *>(node);
+ startSection(qpn, getQmlPropertyTitle(qpn));
+ generateDocBookSynopsis(qpn);
+ } else if (node->isSharedCommentNode()) {
+ const auto scn = reinterpret_cast<const SharedCommentNode *>(node);
+ const QList<Node *> &sharedNodes = scn->collective();
+
+ // In the section, generate a title for the first node, then bridgeheads for
+ // the next ones.
+ int i = 0;
+ for (const auto &sharedNode : sharedNodes) {
+ // Ignore this element if there is nothing to generate.
+ if (!sharedNode->isFunction(Node::QML) && !sharedNode->isQmlProperty()) {
+ continue;
+ }
+
+ // Write the tag containing the title.
+ if (i == 0) {
+ startSectionBegin(sharedNode);
+ } else {
+ m_writer->writeStartElement(dbNamespace, "bridgehead");
+ m_writer->writeAttribute("renderas", "sect2");
+ }
+
+ // Write the title.
+ if (sharedNode->isFunction(Node::QML))
+ generateQmlMethodTitle(sharedNode);
+ else if (sharedNode->isQmlProperty())
+ m_writer->writeCharacters(
+ getQmlPropertyTitle(static_cast<QmlPropertyNode *>(sharedNode)));
+
+ // Complete the title and the synopsis.
+ if (i == 0)
+ startSectionEnd();
+ else
+ m_writer->writeEndElement(); // bridgehead
+ generateDocBookSynopsis(sharedNode);
+ ++i;
+ }
+
+ // If the list is empty, still generate a section.
+ if (i == 0) {
+ startSectionBegin(refForNode(node));
+
+ if (node->isFunction(Node::QML))
+ generateQmlMethodTitle(node);
+ else if (node->isQmlProperty())
+ m_writer->writeCharacters(
+ getQmlPropertyTitle(static_cast<QmlPropertyNode *>(node)));
+
+ startSectionEnd();
+ }
+ } else { // assume the node is a method/signal handler
+ startSectionBegin(node);
+ generateQmlMethodTitle(node);
+ startSectionEnd();
+ }
+
+ generateStatus(node);
+ generateBody(node);
+ generateThreadSafeness(node);
+ generateSince(node);
+ generateAlsoList(node);
+
+ endSection();
+}
+
+/*!
+ Recursive writing of DocBook files from the root \a node.
+ */
+void DocBookGenerator::generateDocumentation(Node *node)
+{
+ // Mainly from Generator::generateDocumentation, with parts from
+ // Generator::generateDocumentation and WebXMLGenerator::generateDocumentation.
+ // Don't generate nodes that are already processed, or if they're not
+ // supposed to generate output, ie. external, index or images nodes.
+ if (!node->url().isNull())
+ return;
+ if (node->isIndexNode())
+ return;
+ if (node->isInternal() && !m_showInternal)
+ return;
+ if (node->isExternalPage())
+ return;
+
+ if (node->parent()) {
+ if (node->isCollectionNode()) {
+ /*
+ A collection node collects: groups, C++ modules, or QML
+ 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, or \qmlmodule 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, or QML 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.
+ */
+ auto cn = static_cast<CollectionNode *>(node);
+ if (cn->wasSeen()) {
+ m_qdb->mergeCollections(cn);
+ generateCollectionNode(cn);
+ } 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.
+ generateGenericCollectionPage(cn);
+ }
+ } else if (node->isTextPageNode()) { // Pages.
+ generatePageNode(static_cast<PageNode *>(node));
+ } else if (node->isAggregate()) { // Aggregates.
+ if ((node->isClassNode() || node->isHeader() || node->isNamespace())
+ && node->docMustBeGenerated()) {
+ generateCppReferencePage(static_cast<Aggregate *>(node));
+ } else if (node->isQmlType()) { // Includes QML value types
+ generateQmlTypePage(static_cast<QmlTypeNode *>(node));
+ } else if (node->isProxyNode()) {
+ generateProxyPage(static_cast<Aggregate *>(node));
+ }
+ }
+ }
+
+ if (node->isAggregate()) {
+ auto *aggregate = static_cast<Aggregate *>(node);
+ for (auto c : aggregate->childNodes()) {
+ if (node->isPageNode() && !node->isPrivate())
+ generateDocumentation(c);
+ }
+ }
+}
+
+void DocBookGenerator::generateProxyPage(Aggregate *aggregate)
+{
+ // Adapted from HtmlGenerator::generateProxyPage.
+ Q_ASSERT(aggregate->isProxyNode());
+
+ // Start producing the DocBook file.
+ Q_ASSERT(m_writer == nullptr);
+ m_writer = startDocument(aggregate);
+
+ // Info container.
+ generateHeader(aggregate->plainFullName(), "", aggregate);
+
+ // No element synopsis.
+
+ // Actual content.
+ if (!aggregate->doc().isEmpty()) {
+ startSection("details", "Detailed Description");
+
+ generateBody(aggregate);
+ generateAlsoList(aggregate);
+
+ endSection();
+ }
+
+ Sections sections(aggregate);
+ SectionVector *detailsSections = &sections.stdDetailsSections();
+
+ for (const auto &section : std::as_const(*detailsSections)) {
+ if (section.isEmpty())
+ continue;
+
+ startSection(section.title().toLower(), section.title());
+
+ const QList<Node *> &members = section.members();
+ for (const auto &member : members) {
+ if (!member->isPrivate()) { // ### check necessary?
+ if (!member->isClassNode()) {
+ generateDetailedMember(member, aggregate);
+ } else {
+ startSectionBegin();
+ generateFullName(member, aggregate);
+ startSectionEnd();
+
+ generateBrief(member);
+ endSection();
+ }
+ }
+ }
+
+ endSection();
+ }
+
+ generateFooter();
+
+ endDocument();
+}
+
+/*!
+ Generate the HTML page for a group, module, or QML module.
+ */
+void DocBookGenerator::generateCollectionNode(CollectionNode *cn)
+{
+ // Adapted from HtmlGenerator::generateCollectionNode.
+ // Start producing the DocBook file.
+ Q_ASSERT(m_writer == nullptr);
+ m_writer = startDocument(cn);
+
+ // Info container.
+ generateHeader(cn->fullTitle(), cn->subtitle(), cn);
+
+ // Element synopsis.
+ generateDocBookSynopsis(cn);
+
+ // Generate brief for C++ modules, status for all modules.
+ if (cn->genus() != Node::DOC && cn->genus() != Node::DontCare) {
+ if (cn->isModule())
+ generateBrief(cn);
+ generateStatus(cn);
+ generateSince(cn);
+ }
+
+ // Actual content.
+ if (cn->isModule()) {
+ if (!cn->noAutoList()) {
+ NodeMap nmm{cn->getMembers(Node::Namespace)};
+ if (!nmm.isEmpty()) {
+ startSection("namespaces", "Namespaces");
+ generateAnnotatedList(cn, nmm.values(), "namespaces");
+ endSection();
+ }
+ nmm = cn->getMembers([](const Node *n){ return n->isClassNode(); });
+ if (!nmm.isEmpty()) {
+ startSection("classes", "Classes");
+ generateAnnotatedList(cn, nmm.values(), "classes");
+ endSection();
+ }
+ }
+ }
+
+ bool generatedTitle = false;
+ if (cn->isModule() && !cn->doc().briefText().isEmpty()) {
+ startSection("details", "Detailed Description");
+ generatedTitle = true;
+ }
+ // The anchor is only needed if the node has a body.
+ else if (
+ // generateBody generates something.
+ !cn->doc().body().isEmpty() ||
+ // generateAlsoList generates something.
+ !cn->doc().alsoList().empty() ||
+ // generateAnnotatedList generates something.
+ (!cn->noAutoList() && (cn->isGroup() || cn->isQmlModule()))) {
+ writeAnchor("details");
+ }
+
+ generateBody(cn);
+ generateAlsoList(cn);
+
+ if (!cn->noAutoList() && (cn->isGroup() || cn->isQmlModule()))
+ generateAnnotatedList(cn, cn->members(), "members", AutoSection);
+
+ if (generatedTitle)
+ endSection();
+
+ generateFooter();
+
+ endDocument();
+}
+
+/*!
+ Generate the HTML page for a generic collection. This is usually
+ a collection of C++ elements that are related to an element in
+ a different module.
+ */
+void DocBookGenerator::generateGenericCollectionPage(CollectionNode *cn)
+{
+ // Adapted from HtmlGenerator::generateGenericCollectionPage.
+ // TODO: factor out this code to generate a file name.
+ QString name = cn->name().toLower();
+ name.replace(QChar(' '), QString("-"));
+ QString filename = cn->tree()->physicalModuleName() + "-" + name + "." + fileExtension();
+
+ // Start producing the DocBook file.
+ Q_ASSERT(m_writer == nullptr);
+ m_writer = startGenericDocument(cn, filename);
+
+ // Info container.
+ generateHeader(cn->fullTitle(), cn->subtitle(), cn);
+
+ // Element synopsis.
+ generateDocBookSynopsis(cn);
+
+ // Actual content.
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("Each function or type documented here is related to a class or "
+ "namespace that is documented in a different module. The reference "
+ "page for that class or namespace will link to the function or type "
+ "on this page.");
+ m_writer->writeEndElement(); // para
+
+ const CollectionNode *cnc = cn;
+ const QList<Node *> members = cn->members();
+ for (const auto &member : members)
+ generateDetailedMember(member, cnc);
+
+ generateFooter();
+
+ endDocument();
+}
+
+void DocBookGenerator::generateFullName(const Node *node, const Node *relative)
+{
+ Q_ASSERT(node);
+ Q_ASSERT(relative);
+
+ // From Generator::appendFullName.
+ m_writer->writeStartElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(node));
+ m_writer->writeAttribute(xlinkNamespace, "role", targetType(node));
+ m_writer->writeCharacters(node->fullName(relative));
+ m_writer->writeEndElement(); // link
+}
+
+void DocBookGenerator::generateFullName(const Node *apparentNode, const QString &fullName,
+ const Node *actualNode)
+{
+ Q_ASSERT(apparentNode);
+ Q_ASSERT(actualNode);
+
+ // From Generator::appendFullName.
+ m_writer->writeStartElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(actualNode));
+ m_writer->writeAttribute("role", targetType(actualNode));
+ m_writer->writeCharacters(fullName);
+ m_writer->writeEndElement(); // link
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/docbookgenerator.h b/src/qdoc/qdoc/src/qdoc/docbookgenerator.h
new file mode 100644
index 000000000..6083fc34a
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docbookgenerator.h
@@ -0,0 +1,170 @@
+// Copyright (C) 2019 Thibaut Cuvelier
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef DOCBOOKGENERATOR_H
+#define DOCBOOKGENERATOR_H
+
+#include "codemarker.h"
+#include "config.h"
+#include "xmlgenerator.h"
+#include "filesystem/fileresolver.h"
+
+#include <QtCore/qhash.h>
+#include <QtCore/qxmlstream.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+class ExampleNode;
+class FunctionNode;
+
+class DocBookGenerator : public XmlGenerator
+{
+public:
+ explicit DocBookGenerator(FileResolver& file_resolver);
+
+ void initializeGenerator() override;
+ QString format() override;
+
+protected:
+ [[nodiscard]] QString fileExtension() const override;
+ void generateDocumentation(Node *node) override;
+ using Generator::generateCppReferencePage;
+ void generateCppReferencePage(Node *node);
+ using Generator::generatePageNode;
+ void generatePageNode(PageNode *pn);
+ using Generator::generateQmlTypePage;
+ void generateQmlTypePage(QmlTypeNode *qcn);
+ using Generator::generateCollectionNode;
+ void generateCollectionNode(CollectionNode *cn);
+ using Generator::generateGenericCollectionPage;
+ void generateGenericCollectionPage(CollectionNode *cn);
+ using Generator::generateProxyPage;
+ void generateProxyPage(Aggregate *aggregate);
+
+ void generateList(const Node *relative, const QString &selector,
+ Qt::SortOrder sortOrder = Qt::AscendingOrder);
+ void generateHeader(const QString &title, const QString &subtitle, const Node *node);
+ void closeTextSections();
+ void generateFooter();
+ void generateDocBookSynopsis(const Node *node);
+ void generateRequisites(const Aggregate *inner);
+ void generateQmlRequisites(const QmlTypeNode *qcn);
+ void generateSortedNames(const ClassNode *cn, const QList<RelatedClass> &rc);
+ void generateSortedQmlNames(const Node *base, const NodeList &subs);
+ bool generateStatus(const Node *node);
+ void generateGroupReferenceText(const Node *node);
+ bool generateThreadSafeness(const Node *node);
+ bool generateSince(const Node *node);
+ void generateAddendum(const Node *node, Generator::Addendum type, CodeMarker *marker,
+ bool generateNote) override;
+ using Generator::generateBody;
+ void generateBody(const Node *node);
+
+ bool generateText(const Text &text, const Node *relative) override;
+ qsizetype generateAtom(const Atom *atom, const Node *relative, CodeMarker*) override;
+
+private:
+
+ enum GeneratedListType { Auto, AutoSection, ItemizedList };
+
+ QXmlStreamWriter *startDocument(const Node *node);
+ QXmlStreamWriter *startDocument(const ExampleNode *en, const QString &file);
+ QXmlStreamWriter *startGenericDocument(const Node *node, const QString &fileName);
+ void endDocument();
+
+ void generateAnnotatedList(const Node *relative, const NodeList &nodeList,
+ const QString &selector, GeneratedListType type = Auto,
+ Qt::SortOrder sortOrder = Qt::AscendingOrder);
+ void generateAnnotatedLists(const Node *relative, const NodeMultiMap &nmm,
+ const QString &selector);
+ void generateCompactList(const Node *relative, const NodeMultiMap &nmm, bool includeAlphabet,
+ const QString &commonPrefix, const QString &selector);
+ using Generator::generateFileList;
+ void generateFileList(const ExampleNode *en, bool images);
+ void generateObsoleteMembers(const Sections &sections);
+ void generateObsoleteQmlMembers(const Sections &sections);
+ void generateSectionList(const Section &section, const Node *relative,
+ bool useObsoleteMembers = false);
+ void generateSectionInheritedList(const Section &section, const Node *relative);
+ void generateSynopsisName(const Node *node, const Node *relative, bool generateNameLink);
+ void generateParameter(const Parameter &parameter, const Node *relative, bool generateExtra,
+ bool generateType);
+ void generateSynopsis(const Node *node, const Node *relative, Section::Style style);
+ void generateEnumValue(const QString &enumValue, const Node *relative);
+ void generateDetailedMember(const Node *node, const PageNode *relative);
+ void generateDetailedQmlMember(Node *node, const Aggregate *relative);
+
+ void generateFullName(const Node *node, const Node *relative);
+ void generateFullName(const Node *apparentNode, const QString &fullName,
+ const Node *actualNode);
+ void generateBrief(const Node *node);
+ void generateAlsoList(const Node *node) override;
+ void generateSignatureList(const NodeList &nodes);
+ void generateReimplementsClause(const FunctionNode *fn);
+ void generateClassHierarchy(const Node *relative, NodeMultiMap &classMap);
+ void generateFunctionIndex(const Node *relative);
+ void generateLegaleseList(const Node *relative);
+ void generateExampleFilePage(const Node *en, ResolvedFile resolved_file, CodeMarker* = nullptr) override;
+ void generateOverloadedSignal(const Node *node);
+ void generateRequiredLinks(const Node *node);
+ void generateLinkToExample(const ExampleNode *en, const QString &baseUrl);
+
+ void typified(const QString &string, const Node *relative, bool trailingSpace = false,
+ bool generateType = true);
+ void generateLink(const Atom *atom);
+ void beginLink(const QString &link, const Node *node, const Node *relative);
+ void endLink();
+ void writeXmlId(const QString &id);
+ void writeXmlId(const Node *node);
+ inline void newLine();
+ void startSectionBegin(const QString &id = "");
+ void startSectionBegin(const Node *node);
+ void startSectionEnd();
+ void startSection(const QString &id, const QString &title);
+ void startSection(const Node *node, const QString &title);
+ void startSection(const QString &title);
+ void endSection();
+ void writeAnchor(const QString &id);
+ void generateSimpleLink(const QString &href, const QString &text);
+ void generateStartRequisite(const QString &description);
+ void generateEndRequisite();
+ void generateRequisite(const QString &description, const QString &value);
+ void generateCMakeRequisite(const QStringList &values);
+ void generateSynopsisInfo(const QString &key, const QString &value);
+ void generateModifier(const QString &value);
+
+ // Generator state when outputting the documentation.
+ bool m_inListItemLineOpen { false };
+ int currentSectionLevel { 0 };
+ QStack<int> sectionLevels {};
+ QString m_qflagsHref {};
+ bool m_inTeletype { false };
+ bool m_hasSection { false };
+ bool m_closeSectionAfterGeneratedList { false };
+ bool m_closeSectionAfterRawTitle { false };
+ bool m_closeFigureWrapper { false };
+ bool m_tableHeaderAlreadyOutput { false };
+ bool m_closeTableRow { false };
+ bool m_closeTableCell { false };
+ std::pair<QString, QString> m_tableWidthAttr {};
+ bool m_inPara { false }; // Ignores nesting of paragraphs (like list items).
+ bool m_inBlockquote { false };
+ unsigned m_inList { 0 }; // Depth in number of nested lists.
+
+ // Generator configuration, set before starting the generation.
+ QString m_project {};
+ QString m_projectDescription {};
+ QString m_naturalLanguage {};
+ QString m_buildVersion {};
+ QXmlStreamWriter *m_writer { nullptr };
+ bool m_useDocBook52 { false }; // Enable tags from DocBook 5.2. Also called "extensions".
+ bool m_useITS { false }; // Enable ITS attributes for parts that should not be translated.
+
+ Config *m_config { nullptr };
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/docparser.cpp b/src/qdoc/qdoc/src/qdoc/docparser.cpp
new file mode 100644
index 000000000..3d78fe38a
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docparser.cpp
@@ -0,0 +1,2846 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "docparser.h"
+
+#include "codemarker.h"
+#include "doc.h"
+#include "docprivate.h"
+#include "editdistance.h"
+#include "macro.h"
+#include "openedlist.h"
+#include "tokenizer.h"
+
+#include <QtCore/qfile.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qtextstream.h>
+
+#include <cctype>
+#include <climits>
+#include <functional>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+DocUtilities &DocParser::s_utilities = DocUtilities::instance();
+
+enum {
+ CMD_A,
+ CMD_ANNOTATEDLIST,
+ CMD_B,
+ CMD_BADCODE,
+ CMD_BOLD,
+ CMD_BR,
+ CMD_BRIEF,
+ CMD_C,
+ CMD_CAPTION,
+ CMD_CODE,
+ CMD_CODELINE,
+ CMD_COMPARESWITH,
+ CMD_DETAILS,
+ CMD_DIV,
+ CMD_DOTS,
+ CMD_E,
+ CMD_ELSE,
+ CMD_ENDCODE,
+ CMD_ENDCOMPARESWITH,
+ CMD_ENDDETAILS,
+ CMD_ENDDIV,
+ CMD_ENDFOOTNOTE,
+ CMD_ENDIF,
+ CMD_ENDLEGALESE,
+ CMD_ENDLINK,
+ CMD_ENDLIST,
+ CMD_ENDMAPREF,
+ CMD_ENDOMIT,
+ CMD_ENDQUOTATION,
+ CMD_ENDRAW,
+ CMD_ENDSECTION1,
+ CMD_ENDSECTION2,
+ CMD_ENDSECTION3,
+ CMD_ENDSECTION4,
+ CMD_ENDSIDEBAR,
+ CMD_ENDTABLE,
+ CMD_FOOTNOTE,
+ CMD_GENERATELIST,
+ CMD_HEADER,
+ CMD_HR,
+ CMD_I,
+ CMD_IF,
+ CMD_IMAGE,
+ CMD_IMPORTANT,
+ CMD_INCLUDE,
+ CMD_INLINEIMAGE,
+ CMD_INDEX,
+ CMD_INPUT,
+ CMD_KEYWORD,
+ CMD_L,
+ CMD_LEGALESE,
+ CMD_LI,
+ CMD_LINK,
+ CMD_LIST,
+ CMD_META,
+ CMD_NOTE,
+ CMD_O,
+ CMD_OMIT,
+ CMD_OMITVALUE,
+ CMD_OVERLOAD,
+ CMD_PRINTLINE,
+ CMD_PRINTTO,
+ CMD_PRINTUNTIL,
+ CMD_QUOTATION,
+ CMD_QUOTEFILE,
+ CMD_QUOTEFROMFILE,
+ CMD_RAW,
+ CMD_ROW,
+ CMD_SA,
+ CMD_SECTION1,
+ CMD_SECTION2,
+ CMD_SECTION3,
+ CMD_SECTION4,
+ CMD_SIDEBAR,
+ CMD_SINCELIST,
+ CMD_SKIPLINE,
+ CMD_SKIPTO,
+ CMD_SKIPUNTIL,
+ CMD_SNIPPET,
+ CMD_SPAN,
+ CMD_SUB,
+ CMD_SUP,
+ CMD_TABLE,
+ CMD_TABLEOFCONTENTS,
+ CMD_TARGET,
+ CMD_TM,
+ CMD_TT,
+ CMD_UICONTROL,
+ CMD_UNDERLINE,
+ CMD_UNICODE,
+ CMD_VALUE,
+ CMD_WARNING,
+ CMD_QML,
+ CMD_ENDQML,
+ CMD_CPP,
+ CMD_ENDCPP,
+ CMD_CPPTEXT,
+ CMD_ENDCPPTEXT,
+ NOT_A_CMD
+};
+
+static struct
+{
+ const char *name;
+ int no;
+ bool is_formatting_command { false };
+} cmds[] = { { "a", CMD_A, true },
+ { "annotatedlist", CMD_ANNOTATEDLIST },
+ { "b", CMD_B, true },
+ { "badcode", CMD_BADCODE },
+ { "bold", CMD_BOLD, true },
+ { "br", CMD_BR },
+ { "brief", CMD_BRIEF },
+ { "c", CMD_C, true },
+ { "caption", CMD_CAPTION },
+ { "code", CMD_CODE },
+ { "codeline", CMD_CODELINE },
+ { "compareswith", CMD_COMPARESWITH },
+ { "details", CMD_DETAILS },
+ { "div", CMD_DIV },
+ { "dots", CMD_DOTS },
+ { "e", CMD_E, true },
+ { "else", CMD_ELSE },
+ { "endcode", CMD_ENDCODE },
+ { "endcompareswith", CMD_ENDCOMPARESWITH },
+ { "enddetails", CMD_ENDDETAILS },
+ { "enddiv", CMD_ENDDIV },
+ { "endfootnote", CMD_ENDFOOTNOTE },
+ { "endif", CMD_ENDIF },
+ { "endlegalese", CMD_ENDLEGALESE },
+ { "endlink", CMD_ENDLINK },
+ { "endlist", CMD_ENDLIST },
+ { "endmapref", CMD_ENDMAPREF },
+ { "endomit", CMD_ENDOMIT },
+ { "endquotation", CMD_ENDQUOTATION },
+ { "endraw", CMD_ENDRAW },
+ { "endsection1", CMD_ENDSECTION1 }, // ### don't document for now
+ { "endsection2", CMD_ENDSECTION2 }, // ### don't document for now
+ { "endsection3", CMD_ENDSECTION3 }, // ### don't document for now
+ { "endsection4", CMD_ENDSECTION4 }, // ### don't document for now
+ { "endsidebar", CMD_ENDSIDEBAR },
+ { "endtable", CMD_ENDTABLE },
+ { "footnote", CMD_FOOTNOTE },
+ { "generatelist", CMD_GENERATELIST },
+ { "header", CMD_HEADER },
+ { "hr", CMD_HR },
+ { "i", CMD_I, true },
+ { "if", CMD_IF },
+ { "image", CMD_IMAGE },
+ { "important", CMD_IMPORTANT },
+ { "include", CMD_INCLUDE },
+ { "inlineimage", CMD_INLINEIMAGE },
+ { "index", CMD_INDEX }, // ### don't document for now
+ { "input", CMD_INPUT },
+ { "keyword", CMD_KEYWORD },
+ { "l", CMD_L },
+ { "legalese", CMD_LEGALESE },
+ { "li", CMD_LI },
+ { "link", CMD_LINK },
+ { "list", CMD_LIST },
+ { "meta", CMD_META },
+ { "note", CMD_NOTE },
+ { "o", CMD_O },
+ { "omit", CMD_OMIT },
+ { "omitvalue", CMD_OMITVALUE },
+ { "overload", CMD_OVERLOAD },
+ { "printline", CMD_PRINTLINE },
+ { "printto", CMD_PRINTTO },
+ { "printuntil", CMD_PRINTUNTIL },
+ { "quotation", CMD_QUOTATION },
+ { "quotefile", CMD_QUOTEFILE },
+ { "quotefromfile", CMD_QUOTEFROMFILE },
+ { "raw", CMD_RAW },
+ { "row", CMD_ROW },
+ { "sa", CMD_SA },
+ { "section1", CMD_SECTION1 },
+ { "section2", CMD_SECTION2 },
+ { "section3", CMD_SECTION3 },
+ { "section4", CMD_SECTION4 },
+ { "sidebar", CMD_SIDEBAR },
+ { "sincelist", CMD_SINCELIST },
+ { "skipline", CMD_SKIPLINE },
+ { "skipto", CMD_SKIPTO },
+ { "skipuntil", CMD_SKIPUNTIL },
+ { "snippet", CMD_SNIPPET },
+ { "span", CMD_SPAN },
+ { "sub", CMD_SUB, true },
+ { "sup", CMD_SUP, true },
+ { "table", CMD_TABLE },
+ { "tableofcontents", CMD_TABLEOFCONTENTS },
+ { "target", CMD_TARGET },
+ { "tm", CMD_TM, true },
+ { "tt", CMD_TT, true },
+ { "uicontrol", CMD_UICONTROL, true },
+ { "underline", CMD_UNDERLINE, true },
+ { "unicode", CMD_UNICODE },
+ { "value", CMD_VALUE },
+ { "warning", CMD_WARNING },
+ { "qml", CMD_QML },
+ { "endqml", CMD_ENDQML },
+ { "cpp", CMD_CPP },
+ { "endcpp", CMD_ENDCPP },
+ { "cpptext", CMD_CPPTEXT },
+ { "endcpptext", CMD_ENDCPPTEXT },
+ { nullptr, 0 } };
+
+int DocParser::s_tabSize;
+QStringList DocParser::s_ignoreWords;
+bool DocParser::s_quoting = false;
+FileResolver *DocParser::file_resolver{ nullptr };
+static void processComparesWithCommand(DocPrivate *priv, const Location &location);
+
+static QString cleanLink(const QString &link)
+{
+ qsizetype colonPos = link.indexOf(':');
+ if ((colonPos == -1) || (!link.startsWith("file:") && !link.startsWith("mailto:")))
+ return link;
+ return link.mid(colonPos + 1).simplified();
+}
+
+void DocParser::initialize(const Config &config, FileResolver &file_resolver)
+{
+ s_tabSize = config.get(CONFIG_TABSIZE).asInt();
+ s_ignoreWords = config.get(CONFIG_IGNOREWORDS).asStringList();
+
+ int i = 0;
+ while (cmds[i].name) {
+ s_utilities.cmdHash.insert(cmds[i].name, cmds[i].no);
+
+ if (cmds[i].no != i)
+ Location::internalError(QStringLiteral("command %1 missing").arg(i));
+ ++i;
+ }
+
+ // If any of the formats define quotinginformation, activate quoting
+ DocParser::s_quoting = config.get(CONFIG_QUOTINGINFORMATION).asBool();
+ const auto &outputFormats = config.getOutputFormats();
+ for (const auto &format : outputFormats)
+ DocParser::s_quoting = DocParser::s_quoting
+ || config.get(format + Config::dot + CONFIG_QUOTINGINFORMATION).asBool();
+
+ // KLUDGE: file_resolver is temporarily a pointer. See the
+ // comment for file_resolver in the header file for more context.
+ DocParser::file_resolver = &file_resolver;
+}
+
+/*!
+ Parse the \a source string to build a Text data structure
+ in \a docPrivate. The Text data structure is a linked list
+ of Atoms.
+
+ \a metaCommandSet is the set of metacommands that may be
+ found in \a source. These metacommands are not markup text
+ commands. They are topic commands and related metacommands.
+ */
+void DocParser::parse(const QString &source, DocPrivate *docPrivate,
+ const QSet<QString> &metaCommandSet, const QSet<QString> &possibleTopics)
+{
+ m_input = source;
+ m_position = 0;
+ m_inputLength = m_input.size();
+ m_cachedLocation = docPrivate->m_start_loc;
+ m_cachedPosition = 0;
+ m_private = docPrivate;
+ m_private->m_text << Atom::Nop;
+ m_private->m_topics.clear();
+
+ m_paragraphState = OutsideParagraph;
+ m_inTableHeader = false;
+ m_inTableRow = false;
+ m_inTableItem = false;
+ m_indexStartedParagraph = false;
+ m_pendingParagraphLeftType = Atom::Nop;
+ m_pendingParagraphRightType = Atom::Nop;
+
+ m_braceDepth = 0;
+ m_currentSection = Doc::NoSection;
+ m_openedCommands.push(CMD_OMIT);
+ m_quoter.reset();
+
+ CodeMarker *marker = nullptr;
+ Atom *currentLinkAtom = nullptr;
+ QString p1, p2;
+ QStack<bool> preprocessorSkipping;
+ int numPreprocessorSkipping = 0;
+
+ while (m_position < m_inputLength) {
+ QChar ch = m_input.at(m_position);
+
+ switch (ch.unicode()) {
+ case '\\': {
+ QString cmdStr;
+ m_backslashPosition = m_position;
+ ++m_position;
+ while (m_position < m_inputLength) {
+ ch = m_input.at(m_position);
+ if (ch.isLetterOrNumber()) {
+ cmdStr += ch;
+ ++m_position;
+ } else {
+ break;
+ }
+ }
+ m_endPosition = m_position;
+ if (cmdStr.isEmpty()) {
+ if (m_position < m_inputLength) {
+ enterPara();
+ if (m_input.at(m_position).isSpace()) {
+ skipAllSpaces();
+ appendChar(QLatin1Char(' '));
+ } else {
+ appendChar(m_input.at(m_position++));
+ }
+ }
+ } else {
+ // Ignore quoting atoms to make appendToCode()
+ // append to the correct atom.
+ if (!s_quoting || !isQuote(m_private->m_text.lastAtom()))
+ m_lastAtom = m_private->m_text.lastAtom();
+
+ int cmd = s_utilities.cmdHash.value(cmdStr, NOT_A_CMD);
+ switch (cmd) {
+ case CMD_A:
+ enterPara();
+ p1 = getArgument();
+ appendAtom(Atom(Atom::FormattingLeft, ATOM_FORMATTING_PARAMETER));
+ appendAtom(Atom(Atom::String, p1));
+ appendAtom(Atom(Atom::FormattingRight, ATOM_FORMATTING_PARAMETER));
+ m_private->m_params.insert(p1);
+ break;
+ case CMD_BADCODE:
+ leavePara();
+ appendAtom(Atom(Atom::CodeBad,
+ getCode(CMD_BADCODE, marker, getMetaCommandArgument(cmdStr))));
+ break;
+ case CMD_BR:
+ enterPara();
+ appendAtom(Atom(Atom::BR));
+ break;
+ case CMD_BOLD:
+ location().warning(QStringLiteral("'\\bold' is deprecated. Use '\\b'"));
+ Q_FALLTHROUGH();
+ case CMD_B:
+ startFormat(ATOM_FORMATTING_BOLD, cmd);
+ break;
+ case CMD_BRIEF:
+ leavePara();
+ enterPara(Atom::BriefLeft, Atom::BriefRight);
+ break;
+ case CMD_C:
+ enterPara();
+ p1 = untabifyEtc(getArgument(ArgumentParsingOptions::Verbatim));
+ marker = CodeMarker::markerForCode(p1);
+ appendAtom(Atom(Atom::C, marker->markedUpCode(p1, nullptr, location())));
+ break;
+ case CMD_CAPTION:
+ leavePara();
+ enterPara(Atom::CaptionLeft, Atom::CaptionRight);
+ break;
+ case CMD_CODE:
+ leavePara();
+ appendAtom(Atom(Atom::Code, getCode(CMD_CODE, nullptr, getMetaCommandArgument(cmdStr))));
+ break;
+ case CMD_QML:
+ leavePara();
+ appendAtom(Atom(Atom::Qml,
+ getCode(CMD_QML, CodeMarker::markerForLanguage(QLatin1String("QML")),
+ getMetaCommandArgument(cmdStr))));
+ break;
+ case CMD_DETAILS:
+ leavePara();
+ appendAtom(Atom(Atom::DetailsLeft, getArgument()));
+ m_openedCommands.push(cmd);
+ break;
+ case CMD_ENDDETAILS:
+ leavePara();
+ appendAtom(Atom(Atom::DetailsRight));
+ closeCommand(cmd);
+ break;
+ case CMD_DIV:
+ leavePara();
+ p1 = getArgument(ArgumentParsingOptions::Verbatim);
+ appendAtom(Atom(Atom::DivLeft, p1));
+ m_openedCommands.push(cmd);
+ break;
+ case CMD_ENDDIV:
+ leavePara();
+ appendAtom(Atom(Atom::DivRight));
+ closeCommand(cmd);
+ break;
+ case CMD_CODELINE:
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, " "));
+ }
+ if (isCode(m_lastAtom) && m_lastAtom->string().endsWith("\n\n"))
+ m_lastAtom->chopString();
+ appendToCode("\n");
+ break;
+ case CMD_DOTS: {
+ QString arg = getOptionalArgument();
+ if (arg.isEmpty())
+ arg = "4";
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, arg));
+ }
+ if (isCode(m_lastAtom) && m_lastAtom->string().endsWith("\n\n"))
+ m_lastAtom->chopString();
+
+ int indent = arg.toInt();
+ for (int i = 0; i < indent; ++i)
+ appendToCode(" ");
+ appendToCode("...\n");
+ break;
+ }
+ case CMD_ELSE:
+ if (!preprocessorSkipping.empty()) {
+ if (preprocessorSkipping.top()) {
+ --numPreprocessorSkipping;
+ } else {
+ ++numPreprocessorSkipping;
+ }
+ preprocessorSkipping.top() = !preprocessorSkipping.top();
+ (void)getRestOfLine(); // ### should ensure that it's empty
+ if (numPreprocessorSkipping)
+ skipToNextPreprocessorCommand();
+ } else {
+ location().warning(
+ QStringLiteral("Unexpected '\\%1'").arg(cmdName(CMD_ELSE)));
+ }
+ break;
+ case CMD_ENDCODE:
+ closeCommand(cmd);
+ break;
+ case CMD_ENDQML:
+ closeCommand(cmd);
+ break;
+ case CMD_ENDFOOTNOTE:
+ if (closeCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::FootnoteRight));
+ }
+ break;
+ case CMD_ENDIF:
+ if (preprocessorSkipping.size() > 0) {
+ if (preprocessorSkipping.pop())
+ --numPreprocessorSkipping;
+ (void)getRestOfLine(); // ### should ensure that it's empty
+ if (numPreprocessorSkipping)
+ skipToNextPreprocessorCommand();
+ } else {
+ location().warning(
+ QStringLiteral("Unexpected '\\%1'").arg(cmdName(CMD_ENDIF)));
+ }
+ break;
+ case CMD_ENDLEGALESE:
+ if (closeCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::LegaleseRight));
+ }
+ break;
+ case CMD_ENDLINK:
+ if (closeCommand(cmd)) {
+ if (m_private->m_text.lastAtom()->type() == Atom::String
+ && m_private->m_text.lastAtom()->string().endsWith(QLatin1Char(' ')))
+ m_private->m_text.lastAtom()->chopString();
+ appendAtom(Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK));
+ }
+ break;
+ case CMD_ENDLIST:
+ if (closeCommand(cmd)) {
+ leavePara();
+ if (m_openedLists.top().isStarted()) {
+ appendAtom(Atom(Atom::ListItemRight, m_openedLists.top().styleString()));
+ appendAtom(Atom(Atom::ListRight, m_openedLists.top().styleString()));
+ }
+ m_openedLists.pop();
+ }
+ break;
+ case CMD_ENDOMIT:
+ closeCommand(cmd);
+ break;
+ case CMD_ENDQUOTATION:
+ if (closeCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::QuotationRight));
+ }
+ break;
+ case CMD_ENDRAW:
+ location().warning(
+ QStringLiteral("Unexpected '\\%1'").arg(cmdName(CMD_ENDRAW)));
+ break;
+ case CMD_ENDSECTION1:
+ endSection(Doc::Section1, cmd);
+ break;
+ case CMD_ENDSECTION2:
+ endSection(Doc::Section2, cmd);
+ break;
+ case CMD_ENDSECTION3:
+ endSection(Doc::Section3, cmd);
+ break;
+ case CMD_ENDSECTION4:
+ endSection(Doc::Section4, cmd);
+ break;
+ case CMD_ENDSIDEBAR:
+ if (closeCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::SidebarRight));
+ }
+ break;
+ case CMD_ENDTABLE:
+ if (closeCommand(cmd)) {
+ leaveTableRow();
+ appendAtom(Atom(Atom::TableRight));
+ }
+ break;
+ case CMD_FOOTNOTE:
+ if (openCommand(cmd)) {
+ enterPara();
+ appendAtom(Atom(Atom::FootnoteLeft));
+ }
+ break;
+ case CMD_ANNOTATEDLIST: {
+ // Optional sorting directive [ascending|descending]
+ if (isLeftBracketAhead())
+ p2 = getBracketedArgument();
+ else
+ p2.clear();
+ appendAtom(Atom(Atom::AnnotatedList, getArgument(), p2));
+ } break;
+ case CMD_SINCELIST:
+ leavePara();
+ appendAtom(Atom(Atom::SinceList, getRestOfLine().simplified()));
+ break;
+ case CMD_GENERATELIST: {
+ // Optional sorting directive [ascending|descending]
+ if (isLeftBracketAhead())
+ p2 = getBracketedArgument();
+ else
+ p2.clear();
+ QString arg1 = getArgument();
+ QString arg2 = getOptionalArgument();
+ if (!arg2.isEmpty())
+ arg1 += " " + arg2;
+ appendAtom(Atom(Atom::GeneratedList, arg1, p2));
+ } break;
+ case CMD_HEADER:
+ if (m_openedCommands.top() == CMD_TABLE) {
+ leaveTableRow();
+ appendAtom(Atom(Atom::TableHeaderLeft));
+ m_inTableHeader = true;
+ } else {
+ if (m_openedCommands.contains(CMD_TABLE))
+ location().warning(QStringLiteral("Cannot use '\\%1' within '\\%2'")
+ .arg(cmdName(CMD_HEADER),
+ cmdName(m_openedCommands.top())));
+ else
+ location().warning(
+ QStringLiteral("Cannot use '\\%1' outside of '\\%2'")
+ .arg(cmdName(CMD_HEADER), cmdName(CMD_TABLE)));
+ }
+ break;
+ case CMD_I:
+ location().warning(QStringLiteral(
+ "'\\i' is deprecated. Use '\\e' for italic or '\\li' for list item"));
+ Q_FALLTHROUGH();
+ case CMD_E:
+ startFormat(ATOM_FORMATTING_ITALIC, cmd);
+ break;
+ case CMD_HR:
+ leavePara();
+ appendAtom(Atom(Atom::HR));
+ break;
+ case CMD_IF:
+ preprocessorSkipping.push(!Tokenizer::isTrue(getRestOfLine()));
+ if (preprocessorSkipping.top())
+ ++numPreprocessorSkipping;
+ if (numPreprocessorSkipping)
+ skipToNextPreprocessorCommand();
+ break;
+ case CMD_IMAGE:
+ leaveValueList();
+ appendAtom(Atom(Atom::Image, getArgument()));
+ appendAtom(Atom(Atom::ImageText, getRestOfLine()));
+ break;
+ case CMD_IMPORTANT:
+ leavePara();
+ enterPara(Atom::ImportantLeft, Atom::ImportantRight);
+ break;
+ case CMD_INCLUDE:
+ case CMD_INPUT: {
+ QString fileName = getArgument();
+ QStringList parameters;
+ QString identifier;
+ if (isLeftBraceAhead()) {
+ identifier = getArgument();
+ while (isLeftBraceAhead() && parameters.size() < 9)
+ parameters << getArgument();
+ } else {
+ identifier = getRestOfLine();
+ }
+ include(fileName, identifier, parameters);
+ break;
+ }
+ case CMD_INLINEIMAGE:
+ enterPara();
+ appendAtom(Atom(Atom::InlineImage, getArgument()));
+ //Append ImageText only if the following
+ //argument is enclosed in braces.
+ if (isLeftBraceAhead()) {
+ appendAtom(Atom(Atom::ImageText, getArgument()));
+ appendAtom(Atom(Atom::String, " "));
+ }
+ break;
+ case CMD_INDEX:
+ if (m_paragraphState == OutsideParagraph) {
+ enterPara();
+ m_indexStartedParagraph = true;
+ } else {
+ const Atom *last = m_private->m_text.lastAtom();
+ if (m_indexStartedParagraph
+ && (last->type() != Atom::FormattingRight
+ || last->string() != ATOM_FORMATTING_INDEX))
+ m_indexStartedParagraph = false;
+ }
+ startFormat(ATOM_FORMATTING_INDEX, cmd);
+ break;
+ case CMD_KEYWORD:
+ leavePara();
+ insertKeyword(getRestOfLine());
+ break;
+ case CMD_L:
+ enterPara();
+ if (isLeftBracketAhead())
+ p2 = getBracketedArgument();
+
+ p1 = getArgument();
+
+ appendAtom(LinkAtom(p1, p2, location()));
+
+ if (isLeftBraceAhead()) {
+ currentLinkAtom = m_private->m_text.lastAtom();
+ startFormat(ATOM_FORMATTING_LINK, cmd);
+ } else {
+ appendAtom(Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK));
+ appendAtom(Atom(Atom::String, cleanLink(p1)));
+ appendAtom(Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK));
+ }
+
+ p2.clear();
+
+ break;
+ case CMD_LEGALESE:
+ leavePara();
+ if (openCommand(cmd))
+ appendAtom(Atom(Atom::LegaleseLeft));
+ docPrivate->m_hasLegalese = true;
+ break;
+ case CMD_LINK:
+ if (openCommand(cmd)) {
+ enterPara();
+ p1 = getArgument();
+ appendAtom(Atom(Atom::Link, p1));
+ appendAtom(Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK));
+ skipSpacesOrOneEndl();
+ }
+ break;
+ case CMD_LIST:
+ if (openCommand(cmd)) {
+ leavePara();
+ m_openedLists.push(OpenedList(location(), getOptionalArgument()));
+ }
+ break;
+ case CMD_META:
+ m_private->constructExtra();
+ p1 = getArgument();
+ m_private->extra->m_metaMap.insert(p1, getArgument());
+ break;
+ case CMD_NOTE:
+ leavePara();
+ enterPara(Atom::NoteLeft, Atom::NoteRight);
+ break;
+ case CMD_O:
+ location().warning(QStringLiteral("'\\o' is deprecated. Use '\\li'"));
+ Q_FALLTHROUGH();
+ case CMD_LI:
+ leavePara();
+ if (m_openedCommands.top() == CMD_LIST) {
+ if (m_openedLists.top().isStarted())
+ appendAtom(Atom(Atom::ListItemRight, m_openedLists.top().styleString()));
+ else
+ appendAtom(Atom(Atom::ListLeft, m_openedLists.top().styleString()));
+ m_openedLists.top().next();
+ appendAtom(Atom(Atom::ListItemNumber, m_openedLists.top().numberString()));
+ appendAtom(Atom(Atom::ListItemLeft, m_openedLists.top().styleString()));
+ enterPara();
+ } else if (m_openedCommands.top() == CMD_TABLE) {
+ p1 = "1,1";
+ p2.clear();
+ if (isLeftBraceAhead()) {
+ p1 = getArgument();
+ if (isLeftBraceAhead())
+ p2 = getArgument();
+ }
+
+ if (!m_inTableHeader && !m_inTableRow) {
+ location().warning(
+ QStringLiteral("Missing '\\%1' or '\\%2' before '\\%3'")
+ .arg(cmdName(CMD_HEADER), cmdName(CMD_ROW),
+ cmdName(CMD_LI)));
+ appendAtom(Atom(Atom::TableRowLeft));
+ m_inTableRow = true;
+ } else if (m_inTableItem) {
+ appendAtom(Atom(Atom::TableItemRight));
+ m_inTableItem = false;
+ }
+
+ appendAtom(Atom(Atom::TableItemLeft, p1, p2));
+ m_inTableItem = true;
+ } else
+ location().warning(
+ QStringLiteral("Command '\\%1' outside of '\\%2' and '\\%3'")
+ .arg(cmdName(cmd), cmdName(CMD_LIST), cmdName(CMD_TABLE)));
+ break;
+ case CMD_OMIT:
+ getUntilEnd(cmd);
+ break;
+ case CMD_OMITVALUE: {
+ leavePara();
+ p1 = getArgument();
+ if (!m_private->m_enumItemList.contains(p1))
+ m_private->m_enumItemList.append(p1);
+ if (!m_private->m_omitEnumItemList.contains(p1))
+ m_private->m_omitEnumItemList.append(p1);
+ skipSpacesOrOneEndl();
+ // Skip potential description paragraph
+ while (m_position < m_inputLength && !isBlankLine()) {
+ skipAllSpaces();
+ if (qsizetype pos = m_position; pos < m_input.size()
+ && m_input.at(pos++).unicode() == '\\') {
+ QString nextCmdStr;
+ while (pos < m_input.size() && m_input[pos].isLetterOrNumber())
+ nextCmdStr += m_input[pos++];
+ int nextCmd = s_utilities.cmdHash.value(cmdStr, NOT_A_CMD);
+ if (nextCmd == cmd || nextCmd == CMD_VALUE)
+ break;
+ }
+ getRestOfLine();
+ }
+ break;
+ }
+ case CMD_COMPARESWITH:
+ leavePara();
+ p1 = getRestOfLine();
+ if (openCommand(cmd))
+ appendAtom(Atom(Atom::ComparesLeft, p1));
+ break;
+ case CMD_ENDCOMPARESWITH:
+ if (closeCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::ComparesRight));
+ processComparesWithCommand(m_private, location());
+ }
+ break;
+ case CMD_PRINTLINE: {
+ leavePara();
+ QString rest = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, rest));
+ }
+ appendToCode(m_quoter.quoteLine(location(), cmdStr, rest));
+ break;
+ }
+ case CMD_PRINTTO: {
+ leavePara();
+ QString rest = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, rest));
+ }
+ appendToCode(m_quoter.quoteTo(location(), cmdStr, rest));
+ break;
+ }
+ case CMD_PRINTUNTIL: {
+ leavePara();
+ QString rest = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, rest));
+ }
+ appendToCode(m_quoter.quoteUntil(location(), cmdStr, rest));
+ break;
+ }
+ case CMD_QUOTATION:
+ if (openCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::QuotationLeft));
+ }
+ break;
+ case CMD_QUOTEFILE: {
+ leavePara();
+
+ QString fileName = getArgument();
+ quoteFromFile(fileName);
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, fileName));
+ }
+ appendAtom(Atom(Atom::Code, m_quoter.quoteTo(location(), cmdStr, QString())));
+ m_quoter.reset();
+ break;
+ }
+ case CMD_QUOTEFROMFILE: {
+ leavePara();
+ QString arg = getArgument();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, arg));
+ }
+ quoteFromFile(arg);
+ break;
+ }
+ case CMD_RAW:
+ leavePara();
+ p1 = getRestOfLine();
+ if (p1.isEmpty())
+ location().warning(QStringLiteral("Missing format name after '\\%1'")
+ .arg(cmdName(CMD_RAW)));
+ appendAtom(Atom(Atom::FormatIf, p1));
+ appendAtom(Atom(Atom::RawString, untabifyEtc(getUntilEnd(cmd))));
+ appendAtom(Atom(Atom::FormatElse));
+ appendAtom(Atom(Atom::FormatEndif));
+ break;
+ case CMD_ROW:
+ if (m_openedCommands.top() == CMD_TABLE) {
+ p1.clear();
+ if (isLeftBraceAhead())
+ p1 = getArgument(ArgumentParsingOptions::Verbatim);
+ leaveTableRow();
+ appendAtom(Atom(Atom::TableRowLeft, p1));
+ m_inTableRow = true;
+ } else {
+ if (m_openedCommands.contains(CMD_TABLE))
+ location().warning(QStringLiteral("Cannot use '\\%1' within '\\%2'")
+ .arg(cmdName(CMD_ROW),
+ cmdName(m_openedCommands.top())));
+ else
+ location().warning(QStringLiteral("Cannot use '\\%1' outside of '\\%2'")
+ .arg(cmdName(CMD_ROW), cmdName(CMD_TABLE)));
+ }
+ break;
+ case CMD_SA:
+ parseAlso();
+ break;
+ case CMD_SECTION1:
+ startSection(Doc::Section1, cmd);
+ break;
+ case CMD_SECTION2:
+ startSection(Doc::Section2, cmd);
+ break;
+ case CMD_SECTION3:
+ startSection(Doc::Section3, cmd);
+ break;
+ case CMD_SECTION4:
+ startSection(Doc::Section4, cmd);
+ break;
+ case CMD_SIDEBAR:
+ if (openCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::SidebarLeft));
+ }
+ break;
+ case CMD_SKIPLINE: {
+ leavePara();
+ QString rest = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, rest));
+ }
+ m_quoter.quoteLine(location(), cmdStr, rest);
+ break;
+ }
+ case CMD_SKIPTO: {
+ leavePara();
+ QString rest = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, rest));
+ }
+ m_quoter.quoteTo(location(), cmdStr, rest);
+ break;
+ }
+ case CMD_SKIPUNTIL: {
+ leavePara();
+ QString rest = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, rest));
+ }
+ m_quoter.quoteUntil(location(), cmdStr, rest);
+ break;
+ }
+ case CMD_SPAN:
+ p1 = ATOM_FORMATTING_SPAN + getArgument(ArgumentParsingOptions::Verbatim);
+ startFormat(p1, cmd);
+ break;
+ case CMD_SNIPPET: {
+ leavePara();
+ QString snippet = getArgument();
+ QString identifier = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::SnippetCommand, cmdStr));
+ appendAtom(Atom(Atom::SnippetLocation, snippet));
+ appendAtom(Atom(Atom::SnippetIdentifier, identifier));
+ }
+ marker = CodeMarker::markerForFileName(snippet);
+ quoteFromFile(snippet);
+ appendToCode(m_quoter.quoteSnippet(location(), identifier), marker->atomType());
+ break;
+ }
+ case CMD_SUB:
+ startFormat(ATOM_FORMATTING_SUBSCRIPT, cmd);
+ break;
+ case CMD_SUP:
+ startFormat(ATOM_FORMATTING_SUPERSCRIPT, cmd);
+ break;
+ case CMD_TABLE:
+ leaveValueList();
+ p1 = getOptionalArgument();
+ p2 = getOptionalArgument();
+ if (openCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::TableLeft, p1, p2));
+ m_inTableHeader = false;
+ m_inTableRow = false;
+ m_inTableItem = false;
+ }
+ break;
+ case CMD_TABLEOFCONTENTS:
+ p1 = "1";
+ if (isLeftBraceAhead())
+ p1 = getArgument();
+ p1 += QLatin1Char(',');
+ p1 += QString::number((int)getSectioningUnit());
+ appendAtom(Atom(Atom::TableOfContents, p1));
+ break;
+ case CMD_TARGET:
+ if (m_openedCommands.top() == CMD_TABLE && !m_inTableItem) {
+ location().warning("Found a \\target command outside table item in a table.\n"
+ "Move the \\target inside the \\li to resolve this warning.");
+ }
+ insertTarget(getRestOfLine());
+ break;
+ case CMD_TM:
+ // Ignore command while parsing \section<N> argument
+ if (m_paragraphState != InSingleLineParagraph)
+ startFormat(ATOM_FORMATTING_TRADEMARK, cmd);
+ break;
+ case CMD_TT:
+ startFormat(ATOM_FORMATTING_TELETYPE, cmd);
+ break;
+ case CMD_UICONTROL:
+ startFormat(ATOM_FORMATTING_UICONTROL, cmd);
+ break;
+ case CMD_UNDERLINE:
+ startFormat(ATOM_FORMATTING_UNDERLINE, cmd);
+ break;
+ case CMD_UNICODE: {
+ enterPara();
+ p1 = getArgument();
+ bool ok;
+ uint unicodeChar = p1.toUInt(&ok, 0);
+ if (!ok || (unicodeChar == 0x0000) || (unicodeChar > 0xFFFE))
+ location().warning(
+ QStringLiteral("Invalid Unicode character '%1' specified with '%2'")
+ .arg(p1, cmdName(CMD_UNICODE)));
+ else
+ appendAtom(Atom(Atom::String, QChar(unicodeChar)));
+ break;
+ }
+ case CMD_VALUE:
+ leaveValue();
+ if (m_openedLists.top().style() == OpenedList::Value) {
+ QString p2;
+ p1 = getArgument();
+ if (p1.startsWith(QLatin1String("[since "))
+ && p1.endsWith(QLatin1String("]"))) {
+ p2 = p1.mid(7, p1.size() - 8);
+ p1 = getArgument();
+ }
+ if (!m_private->m_enumItemList.contains(p1))
+ m_private->m_enumItemList.append(p1);
+
+ m_openedLists.top().next();
+ appendAtom(Atom(Atom::ListTagLeft, ATOM_LIST_VALUE));
+ appendAtom(Atom(Atom::String, p1));
+ appendAtom(Atom(Atom::ListTagRight, ATOM_LIST_VALUE));
+ if (!p2.isEmpty()) {
+ appendAtom(Atom(Atom::SinceTagLeft, ATOM_LIST_VALUE));
+ appendAtom(Atom(Atom::String, p2));
+ appendAtom(Atom(Atom::SinceTagRight, ATOM_LIST_VALUE));
+ }
+ appendAtom(Atom(Atom::ListItemLeft, ATOM_LIST_VALUE));
+
+ skipSpacesOrOneEndl();
+ if (isBlankLine())
+ appendAtom(Atom(Atom::Nop));
+ } else {
+ // ### unknown problems
+ }
+ break;
+ case CMD_WARNING:
+ leavePara();
+ enterPara(Atom::WarningLeft, Atom::WarningRight);
+ break;
+ case CMD_OVERLOAD:
+ leavePara();
+ m_private->m_metacommandsUsed.insert(cmdStr);
+ p1.clear();
+ if (!isBlankLine())
+ p1 = getRestOfLine();
+ if (!p1.isEmpty()) {
+ appendAtom(Atom(Atom::ParaLeft));
+ appendAtom(Atom(Atom::String, "This function overloads "));
+ appendAtom(Atom(Atom::AutoLink, p1));
+ appendAtom(Atom(Atom::String, "."));
+ appendAtom(Atom(Atom::ParaRight));
+ } else {
+ appendAtom(Atom(Atom::ParaLeft));
+ appendAtom(Atom(Atom::String, "This is an overloaded function."));
+ appendAtom(Atom(Atom::ParaRight));
+ p1 = getMetaCommandArgument(cmdStr);
+ }
+ m_private->m_metaCommandMap[cmdStr].append(ArgPair(p1, QString()));
+ break;
+ case NOT_A_CMD:
+ if (metaCommandSet.contains(cmdStr)) {
+ QString arg;
+ QString bracketedArg;
+ m_private->m_metacommandsUsed.insert(cmdStr);
+ if (isLeftBracketAhead())
+ bracketedArg = getBracketedArgument();
+ // Force a linebreak after \obsolete or \deprecated
+ // to treat potential arguments as a new text paragraph.
+ if (m_position < m_inputLength
+ && (cmdStr == QLatin1String("obsolete")
+ || cmdStr == QLatin1String("deprecated")))
+ m_input[m_position] = '\n';
+ else
+ arg = getMetaCommandArgument(cmdStr);
+ m_private->m_metaCommandMap[cmdStr].append(ArgPair(arg, bracketedArg));
+ if (possibleTopics.contains(cmdStr)) {
+ if (!cmdStr.endsWith(QLatin1String("propertygroup")))
+ m_private->m_topics.append(Topic(cmdStr, arg));
+ }
+ } else if (s_utilities.macroHash.contains(cmdStr)) {
+ const Macro &macro = s_utilities.macroHash.value(cmdStr);
+ QStringList macroArgs;
+ int numPendingFi = 0;
+ int numFormatDefs = 0;
+ for (auto it = macro.m_otherDefs.constBegin();
+ it != macro.m_otherDefs.constEnd(); ++it) {
+ if (it.key() != "match") {
+ if (numFormatDefs == 0)
+ macroArgs = getMacroArguments(cmdStr, macro);
+ appendAtom(Atom(Atom::FormatIf, it.key()));
+ expandMacro(*it, macroArgs);
+ ++numFormatDefs;
+ if (it == macro.m_otherDefs.constEnd()) {
+ appendAtom(Atom(Atom::FormatEndif));
+ } else {
+ appendAtom(Atom(Atom::FormatElse));
+ ++numPendingFi;
+ }
+ }
+ }
+ while (numPendingFi-- > 0)
+ appendAtom(Atom(Atom::FormatEndif));
+
+ if (!macro.m_defaultDef.isEmpty()) {
+ if (numFormatDefs > 0) {
+ macro.m_defaultDefLocation.warning(
+ QStringLiteral("Macro cannot have both "
+ "format-specific and qdoc-"
+ "syntax definitions"));
+ } else {
+ QString expanded = expandMacroToString(cmdStr, macro);
+ m_input.replace(m_backslashPosition,
+ m_endPosition - m_backslashPosition, expanded);
+ m_inputLength = m_input.size();
+ m_position = m_backslashPosition;
+ }
+ }
+ } else if (isAutoLinkString(cmdStr)) {
+ appendWord(cmdStr);
+ } else {
+ if (!cmdStr.endsWith("propertygroup")) {
+ // The QML property group commands are no longer required
+ // for grouping QML properties. They are allowed but ignored.
+ location().warning(QStringLiteral("Unknown command '\\%1'").arg(cmdStr),
+ detailsUnknownCommand(metaCommandSet, cmdStr));
+ }
+ enterPara();
+ appendAtom(Atom(Atom::UnknownCommand, cmdStr));
+ }
+ }
+ } // case '\\' (qdoc markup command)
+ break;
+ }
+ case '-': { // Catch en-dash (--) and em-dash (---) markup here.
+ enterPara();
+ qsizetype dashCount = 1;
+ ++m_position;
+
+ // Figure out how many hyphens in a row.
+ while ((m_position < m_inputLength) && (m_input.at(m_position) == '-')) {
+ ++dashCount;
+ ++m_position;
+ }
+
+ if (dashCount == 3) {
+ // 3 hyphens, append an em-dash character.
+ const QChar emDash(8212);
+ appendChar(emDash);
+ } else if (dashCount == 2) {
+ // 2 hyphens; append an en-dash character.
+ const QChar enDash(8211);
+ appendChar(enDash);
+ } else {
+ // dashCount is either one or more than three. Append a hyphen
+ // the appropriate number of times. This ensures '----' doesn't
+ // end up as an em-dash followed by a hyphen in the output.
+ for (qsizetype i = 0; i < dashCount; ++i)
+ appendChar('-');
+ }
+ break;
+ }
+ case '{':
+ enterPara();
+ appendChar('{');
+ ++m_braceDepth;
+ ++m_position;
+ break;
+ case '}': {
+ --m_braceDepth;
+ ++m_position;
+
+ auto format = m_pendingFormats.find(m_braceDepth);
+ if (format == m_pendingFormats.end()) {
+ enterPara();
+ appendChar('}');
+ } else {
+ const auto &last{m_private->m_text.lastAtom()->string()};
+ appendAtom(Atom(Atom::FormattingRight, *format));
+ if (*format == ATOM_FORMATTING_INDEX) {
+ if (m_indexStartedParagraph)
+ skipAllSpaces();
+ } else if (*format == ATOM_FORMATTING_LINK) {
+ // hack for C++ to support links like
+ // \l{QString::}{count()}
+ if (currentLinkAtom && currentLinkAtom->string().endsWith("::")) {
+ QString suffix =
+ Text::subText(currentLinkAtom, m_private->m_text.lastAtom())
+ .toString();
+ currentLinkAtom->concatenateString(suffix);
+ }
+ currentLinkAtom = nullptr;
+ } else if (*format == ATOM_FORMATTING_TRADEMARK) {
+ m_private->m_text.lastAtom()->append(last);
+ }
+ m_pendingFormats.erase(format);
+ }
+ break;
+ }
+ // Do not parse content after '//!' comments
+ case '/': {
+ if (m_position + 2 < m_inputLength)
+ if (m_input.at(m_position + 1) == '/')
+ if (m_input.at(m_position + 2) == '!') {
+ m_position += 2;
+ getRestOfLine();
+ if (m_input.at(m_position - 1) == '\n')
+ --m_position;
+ break;
+ }
+ Q_FALLTHROUGH(); // fall through
+ }
+ default: {
+ bool newWord;
+ switch (m_private->m_text.lastAtom()->type()) {
+ case Atom::ParaLeft:
+ newWord = true;
+ break;
+ default:
+ newWord = false;
+ }
+
+ if (m_paragraphState == OutsideParagraph) {
+ if (ch.isSpace()) {
+ ++m_position;
+ newWord = false;
+ } else {
+ enterPara();
+ newWord = true;
+ }
+ } else {
+ if (ch.isSpace()) {
+ ++m_position;
+ if ((ch == '\n')
+ && (m_paragraphState == InSingleLineParagraph || isBlankLine())) {
+ leavePara();
+ newWord = false;
+ } else {
+ appendChar(' ');
+ newWord = true;
+ }
+ } else {
+ newWord = true;
+ }
+ }
+
+ if (newWord) {
+ qsizetype startPos = m_position;
+ // No auto-linking inside links
+ bool autolink = (!m_pendingFormats.isEmpty() &&
+ m_pendingFormats.last() == ATOM_FORMATTING_LINK) ?
+ false : isAutoLinkString(m_input, m_position);
+ if (m_position == startPos) {
+ if (!ch.isSpace()) {
+ appendChar(ch);
+ ++m_position;
+ }
+ } else {
+ QString word = m_input.mid(startPos, m_position - startPos);
+ if (autolink) {
+ if (s_ignoreWords.contains(word) || word.startsWith(QString("__")))
+ appendWord(word);
+ else
+ appendAtom(Atom(Atom::AutoLink, word));
+ } else {
+ appendWord(word);
+ }
+ }
+ }
+ } // default:
+ } // switch (ch.unicode())
+ }
+ leaveValueList();
+
+ // for compatibility
+ if (m_openedCommands.top() == CMD_LEGALESE) {
+ appendAtom(Atom(Atom::LegaleseRight));
+ m_openedCommands.pop();
+ }
+
+ if (m_openedCommands.top() != CMD_OMIT) {
+ location().warning(
+ QStringLiteral("Missing '\\%1'").arg(endCmdName(m_openedCommands.top())));
+ } else if (preprocessorSkipping.size() > 0) {
+ location().warning(QStringLiteral("Missing '\\%1'").arg(cmdName(CMD_ENDIF)));
+ }
+
+ if (m_currentSection > Doc::NoSection) {
+ appendAtom(Atom(Atom::SectionRight, QString::number(m_currentSection)));
+ m_currentSection = Doc::NoSection;
+ }
+
+ m_private->m_text.stripFirstAtom();
+}
+
+/*!
+ Returns the current location.
+ */
+Location &DocParser::location()
+{
+ while (!m_openedInputs.isEmpty() && m_openedInputs.top() <= m_position) {
+ m_cachedLocation.pop();
+ m_cachedPosition = m_openedInputs.pop();
+ }
+ while (m_cachedPosition < m_position)
+ m_cachedLocation.advance(m_input.at(m_cachedPosition++));
+ return m_cachedLocation;
+}
+
+QString DocParser::detailsUnknownCommand(const QSet<QString> &metaCommandSet, const QString &str)
+{
+ QSet<QString> commandSet = metaCommandSet;
+ int i = 0;
+ while (cmds[i].name != nullptr) {
+ commandSet.insert(cmds[i].name);
+ ++i;
+ }
+
+ QString best = nearestName(str, commandSet);
+ if (best.isEmpty())
+ return QString();
+ return QStringLiteral("Maybe you meant '\\%1'?").arg(best);
+}
+
+/*!
+ \internal
+
+ Issues a warning about the duplicate definition of a target or keyword in
+ at \a location. \a duplicateDefinition is the target being processed; the
+ already registered definition is \a previousDefinition.
+ */
+static void warnAboutPreexistingTarget(const Location &location, const QString &duplicateDefinition, const QString &previousDefinition)
+{
+ location.warning(
+ QStringLiteral("Duplicate target name '%1'. The previous occurrence is here: %2")
+ .arg(duplicateDefinition, previousDefinition));
+}
+
+/*!
+ \internal
+
+ \brief Registers \a target as a linkable entity.
+
+ The main purpose of this method is to register a target as defined
+ along with its location, so that becomes a valid link target from other
+ parts of the documentation.
+
+ If the \a target name is already registered, a warning is issued,
+ as multiple definitions are problematic.
+
+ \sa insertKeyword, target-command
+ */
+void DocParser::insertTarget(const QString &target)
+{
+ if (m_targetMap.contains(target))
+ return warnAboutPreexistingTarget(location(), target, m_targetMap[target].toString());
+
+ m_targetMap.insert(target, location());
+ m_private->constructExtra();
+
+ appendAtom(Atom(Atom::Target, target));
+ m_private->extra->m_targets.append(m_private->m_text.lastAtom());
+}
+
+/*!
+ \internal
+
+ \brief Registers \a keyword as a linkable entity.
+
+ The main purpose of this method is to register a keyword as defined
+ along with its location, so that becomes a valid link target from other
+ parts of the documentation.
+
+ If the \a keyword name is already registered, a warning is issued,
+ as multiple definitions are problematic.
+
+ \sa insertTarget, keyword-command
+ */
+void DocParser::insertKeyword(const QString &keyword)
+{
+ if (m_targetMap.contains(keyword))
+ return warnAboutPreexistingTarget(location(), keyword, m_targetMap[keyword].toString());
+
+ m_targetMap.insert(keyword, location());
+ m_private->constructExtra();
+
+ appendAtom(Atom(Atom::Keyword, keyword));
+ m_private->extra->m_keywords.append(m_private->m_text.lastAtom());
+}
+
+void DocParser::include(const QString &fileName, const QString &identifier, const QStringList &parameters)
+{
+ if (location().depth() > 16)
+ location().fatal(QStringLiteral("Too many nested '\\%1's").arg(cmdName(CMD_INCLUDE)));
+ QString filePath = Config::instance().getIncludeFilePath(fileName);
+ if (filePath.isEmpty()) {
+ location().warning(QStringLiteral("Cannot find qdoc include file '%1'").arg(fileName));
+ } else {
+ QFile inFile(filePath);
+ if (!inFile.open(QFile::ReadOnly)) {
+ location().warning(
+ QStringLiteral("Cannot open qdoc include file '%1'").arg(filePath));
+ } else {
+ location().push(fileName);
+ QTextStream inStream(&inFile);
+ QString includedContent = inStream.readAll();
+ inFile.close();
+
+ if (identifier.isEmpty()) {
+ expandArgumentsInString(includedContent, parameters);
+ m_input.insert(m_position, includedContent);
+ m_inputLength = m_input.size();
+ m_openedInputs.push(m_position + includedContent.size());
+ } else {
+ QStringList lineBuffer = includedContent.split(QLatin1Char('\n'));
+ qsizetype bufLen{lineBuffer.size()};
+ qsizetype i;
+ QStringView trimmedLine;
+ for (i = 0; i < bufLen; ++i) {
+ trimmedLine = QStringView{lineBuffer[i]}.trimmed();
+ if (trimmedLine.startsWith(QLatin1String("//!")) &&
+ trimmedLine.contains(identifier))
+ break;
+ }
+ if (i < bufLen - 1) {
+ ++i;
+ } else {
+ location().warning(
+ QStringLiteral("Cannot find '%1' in '%2'").arg(identifier, filePath));
+ return;
+ }
+ QString result;
+ do {
+ trimmedLine = QStringView{lineBuffer[i]}.trimmed();
+ if (trimmedLine.startsWith(QLatin1String("//!")) &&
+ trimmedLine.contains(identifier))
+ break;
+ else
+ result += lineBuffer[i] + QLatin1Char('\n');
+ ++i;
+ } while (i < bufLen);
+
+ expandArgumentsInString(result, parameters);
+ if (result.isEmpty()) {
+ location().warning(QStringLiteral("Empty qdoc snippet '%1' in '%2'")
+ .arg(identifier, filePath));
+ } else {
+ m_input.insert(m_position, result);
+ m_inputLength = m_input.size();
+ m_openedInputs.push(m_position + result.size());
+ }
+ }
+ }
+ }
+}
+
+void DocParser::startFormat(const QString &format, int cmd)
+{
+ enterPara();
+
+ for (const auto &item : std::as_const(m_pendingFormats)) {
+ if (item == format) {
+ location().warning(QStringLiteral("Cannot nest '\\%1' commands").arg(cmdName(cmd)));
+ return;
+ }
+ }
+
+ appendAtom(Atom(Atom::FormattingLeft, format));
+
+ if (isLeftBraceAhead()) {
+ skipSpacesOrOneEndl();
+ m_pendingFormats.insert(m_braceDepth, format);
+ ++m_braceDepth;
+ ++m_position;
+ } else {
+ const auto &arg{getArgument()};
+ appendAtom(Atom(Atom::String, arg));
+ appendAtom(Atom(Atom::FormattingRight, format));
+ if (format == ATOM_FORMATTING_INDEX && m_indexStartedParagraph) {
+ skipAllSpaces();
+ m_indexStartedParagraph = false;
+ } else if (format == ATOM_FORMATTING_TRADEMARK) {
+ m_private->m_text.lastAtom()->append(arg);
+ }
+ }
+}
+
+bool DocParser::openCommand(int cmd)
+{
+ int outer = m_openedCommands.top();
+ bool ok = true;
+
+ if (cmd == CMD_COMPARESWITH && m_openedCommands.contains(cmd)) {
+ location().warning(u"Cannot nest '\\%1' commands"_s.arg(cmdName(cmd)));
+ return false;
+ } else if (cmd != CMD_LINK) {
+ if (outer == CMD_LIST) {
+ ok = (cmd == CMD_FOOTNOTE || cmd == CMD_LIST);
+ } else if (outer == CMD_SIDEBAR) {
+ ok = (cmd == CMD_LIST || cmd == CMD_QUOTATION || cmd == CMD_SIDEBAR);
+ } else if (outer == CMD_QUOTATION) {
+ ok = (cmd == CMD_LIST);
+ } else if (outer == CMD_TABLE) {
+ ok = (cmd == CMD_LIST || cmd == CMD_FOOTNOTE || cmd == CMD_QUOTATION);
+ } else if (outer == CMD_FOOTNOTE || outer == CMD_LINK) {
+ ok = false;
+ }
+ }
+
+ if (ok) {
+ m_openedCommands.push(cmd);
+ } else {
+ location().warning(
+ QStringLiteral("Can't use '\\%1' in '\\%2'").arg(cmdName(cmd), cmdName(outer)));
+ }
+ return ok;
+}
+
+/*!
+ Returns \c true if \a word qualifies for auto-linking.
+
+ A word qualifies for auto-linking if either:
+
+ \list
+ \li It is composed of only upper and lowercase characters
+ \li AND It contains at least one uppercase character that is not
+ the first character of word
+ \li AND it contains at least two lowercase characters
+ \endlist
+
+ Or
+
+ \list
+ \li It is composed only of uppercase characters, lowercase
+ characters, characters in [_@] and the \c {"::"} sequence.
+ \li It contains at least one uppercase character that is not
+ the first character of word or it contains at least one
+ lowercase character
+ \li AND it contains at least one character in [_@] or it
+ contains at least one \c {"::"} sequence.
+ \endlist
+
+ Inserting or suffixing, but not prefixing, any sequence in [0-9]+
+ in a word that qualifies for auto-linking by the above rules
+ preserves the auto-linkability of the word.
+
+ Suffixing the sequence \c {"()"} to a word that qualifies for
+ auto-linking by the above rules preserves the auto-linkability of
+ a word.
+
+ FInally, a word qualifies for auto-linking if:
+
+ \list
+ \li It is composed of only uppercase characters, lowercase
+ characters and the sequence \c {"()"}
+ \li AND it contains one lowercase character and a sequence of zero, one
+ or two upper or lowercase characters
+ \li AND it contains exactly one sequence \c {"()"}
+ \li AND it contains one sequence \c {"()"} as the last two
+ characters of word
+ \endlist
+
+ For example, \c {"fOo"}, \c {"FooBar"} and \c {"foobaR"} qualify
+ for auto-linking by the first rule.
+
+ \c {"QT_DEBUG"}, \c {"::Qt"} and \c {"std::move"} qualifies for
+ auto-linking by the second rule.
+
+ \c {"SIMDVector256"} qualifies by suffixing \c {"SIMDVector"},
+ which qualifies by the first rule, with the sequence \c {"256"}
+
+ \c {"FooBar::Bar()"} qualifies by suffixing \c {"FooBar::Bar"},
+ which qualifies by the first and second rule, with the sequence \c
+ {"()"}.
+
+ \c {"Foo()"} and \c {"a()"} qualifies by the last rule.
+
+ Instead, \c {"Q"}, \c {"flower"}, \c {"_"} and \c {"()"} do not
+ qualify for auto-linking.
+
+ The rules are intended as a heuristic to catch common cases in the
+ Qt documentation where a word might represent an important
+ documented element such as a class or a method that could be
+ linked to while at the same time avoiding catching common words
+ such as \c {"A"} or \c {"Nonetheless"}.
+
+ The heuristic assumes that Qt's codebase respects a style where
+ camelCasing is the standard for most of the elements, a function
+ call is identified by the use of parenthesis and certain elements,
+ such as macros, might be fully uppercase.
+
+ Furthemore, it assumes that the Qt codebase is written in a
+ language that has an identifier grammar similar to the one for
+ C++.
+*/
+inline bool DocParser::isAutoLinkString(const QString &word)
+{
+ qsizetype start = 0;
+ return isAutoLinkString(word, start) && (start == word.size());
+}
+
+/*!
+ Returns \c true if a prefix of a substring of \a word qualifies
+ for auto-linking.
+
+ Respects the same parsing rules as the unary overload.
+
+ \a curPos defines the offset, from the first character of \ word,
+ at which the parsed substring starts.
+
+ When the call completes, \a curPos represents the offset, from the
+ first character of word, that is the successor of the offset of
+ the last parsed character.
+
+ If the return value of the call is \c true, it is guaranteed that
+ the prefix of the substring of \word that contains the characters
+ from the initial value of \a curPos and up to but not including \a
+ curPos qualifies for auto-linking.
+
+ If \a curPos is initially zero, the considered substring is the
+ entirety of \a word.
+*/
+bool DocParser::isAutoLinkString(const QString &word, qsizetype &curPos)
+{
+ qsizetype len = word.size();
+ qsizetype startPos = curPos;
+ int numUppercase = 0;
+ int numLowercase = 0;
+ int numStrangeSymbols = 0;
+
+ while (curPos < len) {
+ unsigned char latin1Ch = word.at(curPos).toLatin1();
+ if (islower(latin1Ch)) {
+ ++numLowercase;
+ ++curPos;
+ } else if (isupper(latin1Ch)) {
+ if (curPos > startPos)
+ ++numUppercase;
+ ++curPos;
+ } else if (isdigit(latin1Ch)) {
+ if (curPos > startPos)
+ ++curPos;
+ else
+ break;
+ } else if (latin1Ch == '_' || latin1Ch == '@') {
+ ++numStrangeSymbols;
+ ++curPos;
+ } else if ((latin1Ch == ':') && (curPos < len - 1)
+ && (word.at(curPos + 1) == QLatin1Char(':'))) {
+ ++numStrangeSymbols;
+ curPos += 2;
+ } else if (latin1Ch == '(') {
+ if ((curPos < len - 1) && (word.at(curPos + 1) == QLatin1Char(')'))) {
+ ++numStrangeSymbols;
+ m_position += 2;
+ }
+
+ break;
+ } else {
+ break;
+ }
+ }
+
+ return ((numUppercase >= 1 && numLowercase >= 2) || (numStrangeSymbols > 0 && (numUppercase + numLowercase >= 1)));
+}
+
+bool DocParser::closeCommand(int endCmd)
+{
+ if (endCmdFor(m_openedCommands.top()) == endCmd && m_openedCommands.size() > 1) {
+ m_openedCommands.pop();
+ return true;
+ } else {
+ bool contains = false;
+ QStack<int> opened2 = m_openedCommands;
+ while (opened2.size() > 1) {
+ if (endCmdFor(opened2.top()) == endCmd) {
+ contains = true;
+ break;
+ }
+ opened2.pop();
+ }
+
+ if (contains) {
+ while (endCmdFor(m_openedCommands.top()) != endCmd && m_openedCommands.size() > 1) {
+ location().warning(
+ QStringLiteral("Missing '\\%1' before '\\%2'")
+ .arg(endCmdName(m_openedCommands.top()), cmdName(endCmd)));
+ m_openedCommands.pop();
+ }
+ } else {
+ location().warning(QStringLiteral("Unexpected '\\%1'").arg(cmdName(endCmd)));
+ }
+ return false;
+ }
+}
+
+void DocParser::startSection(Doc::Sections unit, int cmd)
+{
+ leaveValueList();
+
+ if (m_currentSection == Doc::NoSection) {
+ m_currentSection = static_cast<Doc::Sections>(unit);
+ m_private->constructExtra();
+ } else {
+ endSection(unit, cmd);
+ }
+
+ appendAtom(Atom(Atom::SectionLeft, QString::number(unit)));
+ m_private->constructExtra();
+ m_private->extra->m_tableOfContents.append(m_private->m_text.lastAtom());
+ m_private->extra->m_tableOfContentsLevels.append(unit);
+ enterPara(Atom::SectionHeadingLeft, Atom::SectionHeadingRight, QString::number(unit));
+ m_currentSection = unit;
+}
+
+void DocParser::endSection(int, int) // (int unit, int endCmd)
+{
+ leavePara();
+ appendAtom(Atom(Atom::SectionRight, QString::number(m_currentSection)));
+ m_currentSection = (Doc::NoSection);
+}
+
+/*!
+ \internal
+ \brief Parses arguments to QDoc's see also command.
+
+ Parses space or comma separated arguments passed to the \\sa command.
+ Multi-line input requires that the arguments are comma separated. Wrap
+ arguments in curly braces for multi-word targets, and for scope resolution
+ (for example, {QString::}{count()}).
+
+ This method updates the list of links for the See also section.
+
+ \sa {DocPrivate::}{addAlso()}, getArgument()
+ */
+void DocParser::parseAlso()
+{
+ auto line_comment = [this]() -> bool {
+ skipSpacesOnLine();
+ if (m_position + 2 > m_inputLength)
+ return false;
+ if (m_input[m_position].unicode() == '/') {
+ if (m_input[m_position + 1].unicode() == '/') {
+ if (m_input[m_position + 2].unicode() == '!') {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ auto skip_everything_until_newline = [this]() -> void {
+ while (m_position < m_inputLength && m_input[m_position] != '\n')
+ ++m_position;
+ };
+
+ leavePara();
+ skipSpacesOnLine();
+ while (m_position < m_inputLength && m_input[m_position] != '\n') {
+ QString target;
+ QString str;
+ bool skipMe = false;
+
+ if (m_input[m_position] == '{') {
+ target = getArgument();
+ skipSpacesOnLine();
+ if (m_position < m_inputLength && m_input[m_position] == '{') {
+ str = getArgument();
+
+ // hack for C++ to support links like \l{QString::}{count()}
+ if (target.endsWith("::"))
+ target += str;
+ } else {
+ str = target;
+ }
+ } else {
+ target = getArgument();
+ str = cleanLink(target);
+ if (target == QLatin1String("and") || target == QLatin1String("."))
+ skipMe = true;
+ }
+
+ if (!skipMe) {
+ Text also;
+ also << Atom(Atom::Link, target) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << str << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ m_private->addAlso(also);
+ }
+
+ skipSpacesOnLine();
+
+ if (line_comment())
+ skip_everything_until_newline();
+
+ if (m_position < m_inputLength && m_input[m_position] == ',') {
+ m_position++;
+ if (line_comment())
+ skip_everything_until_newline();
+ skipSpacesOrOneEndl();
+ } else if (m_position >= m_inputLength || m_input[m_position] != '\n') {
+ location().warning(QStringLiteral("Missing comma in '\\%1'").arg(cmdName(CMD_SA)));
+ }
+ }
+}
+
+void DocParser::appendAtom(const Atom& atom) {
+ m_private->m_text << atom;
+}
+
+void DocParser::appendAtom(const LinkAtom& atom) {
+ m_private->m_text << atom;
+}
+
+void DocParser::appendChar(QChar ch)
+{
+ if (m_private->m_text.lastAtom()->type() != Atom::String)
+ appendAtom(Atom(Atom::String));
+ Atom *atom = m_private->m_text.lastAtom();
+ if (ch == QLatin1Char(' ')) {
+ if (!atom->string().endsWith(QLatin1Char(' ')))
+ atom->appendChar(QLatin1Char(' '));
+ } else
+ atom->appendChar(ch);
+}
+
+void DocParser::appendWord(const QString &word)
+{
+ if (m_private->m_text.lastAtom()->type() != Atom::String) {
+ appendAtom(Atom(Atom::String, word));
+ } else
+ m_private->m_text.lastAtom()->concatenateString(word);
+}
+
+void DocParser::appendToCode(const QString &markedCode)
+{
+ if (!isCode(m_lastAtom)) {
+ appendAtom(Atom(Atom::Code));
+ m_lastAtom = m_private->m_text.lastAtom();
+ }
+ m_lastAtom->concatenateString(markedCode);
+}
+
+void DocParser::appendToCode(const QString &markedCode, Atom::AtomType defaultType)
+{
+ if (!isCode(m_lastAtom)) {
+ appendAtom(Atom(defaultType, markedCode));
+ m_lastAtom = m_private->m_text.lastAtom();
+ } else {
+ m_lastAtom->concatenateString(markedCode);
+ }
+}
+
+void DocParser::enterPara(Atom::AtomType leftType, Atom::AtomType rightType, const QString &string)
+{
+ if (m_paragraphState != OutsideParagraph)
+ return;
+
+ if ((m_private->m_text.lastAtom()->type() != Atom::ListItemLeft)
+ && (m_private->m_text.lastAtom()->type() != Atom::DivLeft)
+ && (m_private->m_text.lastAtom()->type() != Atom::DetailsLeft)) {
+ leaveValueList();
+ }
+
+ appendAtom(Atom(leftType, string));
+ m_indexStartedParagraph = false;
+ m_pendingParagraphLeftType = leftType;
+ m_pendingParagraphRightType = rightType;
+ m_pendingParagraphString = string;
+ if (leftType == Atom::SectionHeadingLeft) {
+ m_paragraphState = InSingleLineParagraph;
+ } else {
+ m_paragraphState = InMultiLineParagraph;
+ }
+ skipSpacesOrOneEndl();
+}
+
+void DocParser::leavePara()
+{
+ if (m_paragraphState == OutsideParagraph)
+ return;
+
+ if (!m_pendingFormats.isEmpty()) {
+ location().warning(QStringLiteral("Missing '}'"));
+ m_pendingFormats.clear();
+ }
+
+ if (m_private->m_text.lastAtom()->type() == m_pendingParagraphLeftType) {
+ m_private->m_text.stripLastAtom();
+ } else {
+ if (m_private->m_text.lastAtom()->type() == Atom::String
+ && m_private->m_text.lastAtom()->string().endsWith(QLatin1Char(' '))) {
+ m_private->m_text.lastAtom()->chopString();
+ }
+ appendAtom(Atom(m_pendingParagraphRightType, m_pendingParagraphString));
+ }
+ m_paragraphState = OutsideParagraph;
+ m_indexStartedParagraph = false;
+ m_pendingParagraphRightType = Atom::Nop;
+ m_pendingParagraphString.clear();
+}
+
+void DocParser::leaveValue()
+{
+ leavePara();
+ if (m_openedLists.isEmpty()) {
+ m_openedLists.push(OpenedList(OpenedList::Value));
+ appendAtom(Atom(Atom::ListLeft, ATOM_LIST_VALUE));
+ } else {
+ if (m_private->m_text.lastAtom()->type() == Atom::Nop)
+ m_private->m_text.stripLastAtom();
+ appendAtom(Atom(Atom::ListItemRight, ATOM_LIST_VALUE));
+ }
+}
+
+void DocParser::leaveValueList()
+{
+ leavePara();
+ if (!m_openedLists.isEmpty() && (m_openedLists.top().style() == OpenedList::Value)) {
+ if (m_private->m_text.lastAtom()->type() == Atom::Nop)
+ m_private->m_text.stripLastAtom();
+ appendAtom(Atom(Atom::ListItemRight, ATOM_LIST_VALUE));
+ appendAtom(Atom(Atom::ListRight, ATOM_LIST_VALUE));
+ m_openedLists.pop();
+ }
+}
+
+void DocParser::leaveTableRow()
+{
+ if (m_inTableItem) {
+ leavePara();
+ appendAtom(Atom(Atom::TableItemRight));
+ m_inTableItem = false;
+ }
+ if (m_inTableHeader) {
+ appendAtom(Atom(Atom::TableHeaderRight));
+ m_inTableHeader = false;
+ }
+ if (m_inTableRow) {
+ appendAtom(Atom(Atom::TableRowRight));
+ m_inTableRow = false;
+ }
+}
+
+void DocParser::quoteFromFile(const QString &filename)
+{
+ // KLUDGE: We dereference file_resolver as it is temporarily a pointer.
+ // See the comment for file_resolver in the header files for more context.
+ //
+ // We spefically dereference it, instead of using the arrow
+ // operator, to better represent that we do not consider this as
+ // an actual pointer, as it should not be.
+ //
+ // Do note that we are considering it informally safe to
+ // dereference the pointer, as we expect it to always hold a value
+ // at this point, but actual enforcement of this appears nowhere
+ // in the codebase.
+ auto maybe_resolved_file{(*file_resolver).resolve(filename)};
+ if (!maybe_resolved_file) {
+ // TODO: [uncentralized-admonition][failed-resolve-file]
+ // This warning is required in multiple places.
+ // To ensure the consistency of the warning and avoid
+ // duplicating code everywhere, provide a centralized effort
+ // where the warning message can be generated (but not
+ // issued).
+ // The current format is based on what was used before, review
+ // it when it is moved out.
+ QString details = std::transform_reduce(
+ (*file_resolver).get_search_directories().cbegin(),
+ (*file_resolver).get_search_directories().cend(),
+ u"Searched directories:"_s,
+ std::plus(),
+ [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
+ );
+
+ location().warning(u"Cannot find file to quote from: %1"_s.arg(filename), details);
+
+ // REMARK: The following is duplicated from
+ // Doc::quoteFromFile. If, for some reason (such as a file
+ // that is inaccessible), the quoting fails but, previously,
+ // the logic duplicated here was still run.
+ // This is not true anymore as quoteFromFile does require a
+ // resolved file to be run now.
+ // It is not entirely clear if this is required for the
+ // semantics of DocParser to be preserved, but for the sake of
+ // avoiding premature breakages this was retained.
+ // Do note that this should be considered temporary as the
+ // quoter state, if any will be preserved, should not be
+ // managed in such a spread and unlocal way.
+ m_quoter.reset();
+
+ CodeMarker *marker = CodeMarker::markerForFileName(QString{});
+ m_quoter.quoteFromFile(filename, QString{}, marker->markedUpCode(QString{}, nullptr, location()));
+ } else Doc::quoteFromFile(location(), m_quoter, *maybe_resolved_file);
+}
+
+/*!
+ Expands a macro in-place in input.
+
+ Expects the current \e pos in the input to point to a backslash, and the macro to have a
+ default definition. Format-specific macros are not expanded.
+
+ Behavior depends on \a options:
+
+ \value ArgumentParsingOptions::Default
+ Default macro expansion; the string following the backslash
+ must be a macro with a default definition.
+ \value ArgumentParsingOptions::Verbatim
+ The string following the backslash is rendered verbatim;
+ No macro expansion is performed.
+ \value ArgumentParsingOptions::MacroArguments
+ Used for parsing argument(s) for a macro. Allows expanding
+ macros, and also preserves a subset of commands (formatting
+ commands) within the macro argument.
+
+ \note In addition to macros, a valid use for a backslash in an argument include
+ escaping non-alnum characters, and splitting a single argument across multiple
+ lines by escaping newlines. Escaping is also handled here.
+
+ Returns \c true on successful macro expansion.
+ */
+bool DocParser::expandMacro(ArgumentParsingOptions options)
+{
+ Q_ASSERT(m_input[m_position].unicode() == '\\');
+
+ if (options == ArgumentParsingOptions::Verbatim)
+ return false;
+
+ QString cmdStr;
+ qsizetype backslashPos = m_position++;
+ while (m_position < m_input.size() && m_input[m_position].isLetterOrNumber())
+ cmdStr += m_input[m_position++];
+
+ m_endPosition = m_position;
+ if (!cmdStr.isEmpty()) {
+ if (s_utilities.macroHash.contains(cmdStr)) {
+ const Macro &macro = s_utilities.macroHash.value(cmdStr);
+ if (!macro.m_defaultDef.isEmpty()) {
+ QString expanded = expandMacroToString(cmdStr, macro);
+ m_input.replace(backslashPos, m_position - backslashPos, expanded);
+ m_inputLength = m_input.size();
+ m_position = backslashPos;
+ return true;
+ } else {
+ location().warning("Macro '%1' does not have a default definition"_L1.arg(cmdStr));
+ }
+ } else {
+ int cmd = s_utilities.cmdHash.value(cmdStr, NOT_A_CMD);
+ m_position = backslashPos;
+ if (options != ArgumentParsingOptions::MacroArguments
+ || cmd == NOT_A_CMD || !cmds[cmd].is_formatting_command) {
+ location().warning("Unknown macro '%1'"_L1.arg(cmdStr));
+ ++m_position;
+ }
+ }
+ } else if (m_input[m_position].isSpace()) {
+ skipAllSpaces();
+ } else if (m_input[m_position].unicode() == '\\') {
+ // allow escaping a backslash
+ m_input.remove(m_position--, 1);
+ --m_inputLength;
+ }
+ return false;
+}
+
+void DocParser::expandMacro(const QString &def, const QStringList &args)
+{
+ if (args.isEmpty()) {
+ appendAtom(Atom(Atom::RawString, def));
+ } else {
+ QString rawString;
+
+ for (int j = 0; j < def.size(); ++j) {
+ if (int paramNo = def[j].unicode(); paramNo >= 1 && paramNo <= args.length()) {
+ if (!rawString.isEmpty()) {
+ appendAtom(Atom(Atom::RawString, rawString));
+ rawString.clear();
+ }
+ appendAtom(Atom(Atom::String, args[paramNo - 1]));
+ } else {
+ rawString += def[j];
+ }
+ }
+ if (!rawString.isEmpty())
+ appendAtom(Atom(Atom::RawString, rawString));
+ }
+}
+
+QString DocParser::expandMacroToString(const QString &name, const Macro &macro)
+{
+ const QString &def{macro.m_defaultDef};
+ QString rawString;
+
+ if (macro.numParams == 0) {
+ rawString = macro.m_defaultDef;
+ } else {
+ QStringList args{getMacroArguments(name, macro)};
+
+ for (int j = 0; j < def.size(); ++j) {
+ int paramNo = def[j].unicode();
+ rawString += (paramNo >= 1 && paramNo <= args.length()) ? args[paramNo - 1] : def[j];
+ }
+ }
+ QString matchExpr{macro.m_otherDefs.value("match")};
+ if (matchExpr.isEmpty())
+ return rawString;
+
+ QString result;
+ QRegularExpression re(matchExpr);
+ int capStart = (re.captureCount() > 0) ? 1 : 0;
+ qsizetype i = 0;
+ QRegularExpressionMatch match;
+ while ((match = re.match(rawString, i)).hasMatch()) {
+ for (int c = capStart; c <= re.captureCount(); ++c)
+ result += match.captured(c);
+ i = match.capturedEnd();
+ }
+
+ return result;
+}
+
+Doc::Sections DocParser::getSectioningUnit()
+{
+ QString name = getOptionalArgument();
+
+ if (name == "section1") {
+ return Doc::Section1;
+ } else if (name == "section2") {
+ return Doc::Section2;
+ } else if (name == "section3") {
+ return Doc::Section3;
+ } else if (name == "section4") {
+ return Doc::Section4;
+ } else if (name.isEmpty()) {
+ return Doc::NoSection;
+ } else {
+ location().warning(QStringLiteral("Invalid section '%1'").arg(name));
+ return Doc::NoSection;
+ }
+}
+
+/*!
+ Gets an argument that is enclosed in braces and returns it
+ without the enclosing braces. On entry, the current character
+ is the left brace. On exit, the current character is the one
+ that comes after the right brace.
+
+ If \a options is ArgumentParsingOptions::Verbatim, no macro
+ expansion is performed, nor is the returned string stripped
+ of extra whitespace.
+ */
+QString DocParser::getBracedArgument(ArgumentParsingOptions options)
+{
+ QString arg;
+ int delimDepth = 0;
+ if (m_position < m_input.size() && m_input[m_position] == '{') {
+ ++m_position;
+ while (m_position < m_input.size() && delimDepth >= 0) {
+ switch (m_input[m_position].unicode()) {
+ case '{':
+ ++delimDepth;
+ arg += QLatin1Char('{');
+ ++m_position;
+ break;
+ case '}':
+ --delimDepth;
+ if (delimDepth >= 0)
+ arg += QLatin1Char('}');
+ ++m_position;
+ break;
+ case '\\':
+ if (!expandMacro(options))
+ arg += m_input[m_position++];
+ break;
+ default:
+ if (m_input[m_position].isSpace() && options != ArgumentParsingOptions::Verbatim)
+ arg += QChar(' ');
+ else
+ arg += m_input[m_position];
+ ++m_position;
+ }
+ }
+ if (delimDepth > 0)
+ location().warning(QStringLiteral("Missing '}'"));
+ }
+ m_endPosition = m_position;
+ return arg;
+}
+
+/*!
+ Parses and returns an argument for a command, using
+ specific parsing \a options.
+
+ Typically, an argument ends at the next white-space. However,
+ braces can be used to group words:
+
+ {a few words}
+
+ Also, opening and closing parentheses have to match. Thus,
+
+ printf("%d\n", x)
+
+ is an argument too, although it contains spaces. Finally,
+ trailing punctuation is not included in an argument, nor is 's.
+*/
+QString DocParser::getArgument(ArgumentParsingOptions options)
+{
+ skipSpacesOrOneEndl();
+
+ int delimDepth = 0;
+ qsizetype startPos = m_position;
+ QString arg = getBracedArgument(options);
+ if (arg.isEmpty()) {
+ while ((m_position < m_input.size())
+ && ((delimDepth > 0) || ((delimDepth == 0) && !m_input[m_position].isSpace()))) {
+ switch (m_input[m_position].unicode()) {
+ case '(':
+ case '[':
+ case '{':
+ ++delimDepth;
+ arg += m_input[m_position];
+ ++m_position;
+ break;
+ case ')':
+ case ']':
+ case '}':
+ --delimDepth;
+ if (m_position == startPos || delimDepth >= 0) {
+ arg += m_input[m_position];
+ ++m_position;
+ }
+ break;
+ case '\\':
+ if (!expandMacro(options))
+ arg += m_input[m_position++];
+ break;
+ default:
+ arg += m_input[m_position];
+ ++m_position;
+ }
+ }
+ m_endPosition = m_position;
+ if ((arg.size() > 1) && (QString(".,:;!?").indexOf(m_input[m_position - 1]) != -1)
+ && !arg.endsWith("...")) {
+ arg.truncate(arg.size() - 1);
+ --m_position;
+ }
+ if (arg.size() > 2 && m_input.mid(m_position - 2, 2) == "'s") {
+ arg.truncate(arg.size() - 2);
+ m_position -= 2;
+ }
+ }
+ return arg.simplified();
+}
+
+/*!
+ Gets an argument that is enclosed in brackets and returns it
+ without the enclosing brackets. On entry, the current character
+ is the left bracket. On exit, the current character is the one
+ that comes after the right bracket.
+ */
+QString DocParser::getBracketedArgument()
+{
+ QString arg;
+ int delimDepth = 0;
+ skipSpacesOrOneEndl();
+ if (m_position < m_input.size() && m_input[m_position] == '[') {
+ ++m_position;
+ while (m_position < m_input.size() && delimDepth >= 0) {
+ switch (m_input[m_position].unicode()) {
+ case '[':
+ ++delimDepth;
+ arg += QLatin1Char('[');
+ ++m_position;
+ break;
+ case ']':
+ --delimDepth;
+ if (delimDepth >= 0)
+ arg += QLatin1Char(']');
+ ++m_position;
+ break;
+ case '\\':
+ arg += m_input[m_position];
+ ++m_position;
+ break;
+ default:
+ arg += m_input[m_position];
+ ++m_position;
+ }
+ }
+ if (delimDepth > 0)
+ location().warning(QStringLiteral("Missing ']'"));
+ }
+ return arg;
+}
+
+
+/*!
+ Returns the list of arguments passed to a \a macro with name \a name.
+
+ If a macro takes more than a single argument, they are expected to be
+ wrapped in braces.
+*/
+QStringList DocParser::getMacroArguments(const QString &name, const Macro &macro)
+{
+ QStringList args;
+ for (int i = 0; i < macro.numParams; ++i) {
+ if (macro.numParams == 1 || isLeftBraceAhead()) {
+ args << getArgument(ArgumentParsingOptions::MacroArguments);
+ } else {
+ location().warning(QStringLiteral("Macro '\\%1' invoked with too few"
+ " arguments (expected %2, got %3)")
+ .arg(name)
+ .arg(macro.numParams)
+ .arg(i));
+ break;
+ }
+ }
+ return args;
+}
+
+QString DocParser::getOptionalArgument()
+{
+ skipSpacesOrOneEndl();
+ if (m_position + 1 < m_input.size() && m_input[m_position] == '\\'
+ && m_input[m_position + 1].isLetterOrNumber()) {
+ return QString();
+ } else {
+ return getArgument();
+ }
+}
+
+/*!
+ \brief Create a string that may optionally span multiple lines as one line.
+
+ Process a block of text that may span multiple lines using trailing
+ backslashes (`\`) as line continuation character. Trailing backslashes and
+ any newline character that follow them are removed.
+
+ Returns a string as if it was one continuous line of text. If trailing
+ backslashes are removed, the method returns a "simplified" QString, which
+ means any sequence of internal whitespace is replaced with a single space.
+
+ Whitespace at the start and end is always removed from the returned string.
+
+ \sa QString::simplified(), QString::trimmed().
+ */
+QString DocParser::getRestOfLine()
+{
+ auto lineHasTrailingBackslash = [this](bool trailingBackslash) -> bool {
+ while (m_position < m_inputLength && m_input[m_position] != '\n') {
+ if (m_input[m_position] == '\\' && !trailingBackslash) {
+ trailingBackslash = true;
+ ++m_position;
+ skipSpacesOnLine();
+ } else {
+ trailingBackslash = false;
+ ++m_position;
+ }
+ }
+ return trailingBackslash;
+ };
+
+ QString rest_of_line;
+ skipSpacesOnLine();
+ bool trailing_backslash{ false };
+ bool return_simplified_string{ false };
+
+ for (qsizetype start_position = m_position; m_position < m_inputLength; ++m_position) {
+ trailing_backslash = lineHasTrailingBackslash(trailing_backslash);
+
+ if (!rest_of_line.isEmpty())
+ rest_of_line += QLatin1Char(' ');
+ rest_of_line += m_input.sliced(start_position, m_position - start_position);
+
+ if (trailing_backslash) {
+ rest_of_line.truncate(rest_of_line.lastIndexOf('\\'));
+ return_simplified_string = true;
+ }
+
+ if (m_position < m_inputLength)
+ ++m_position;
+
+ if (!trailing_backslash)
+ break;
+ start_position = m_position;
+ }
+
+ if (return_simplified_string)
+ return rest_of_line.simplified();
+
+ return rest_of_line.trimmed();
+}
+
+/*!
+ The metacommand argument is normally the remaining text to
+ the right of the metacommand itself. The extra blanks are
+ stripped and the argument string is returned.
+ */
+QString DocParser::getMetaCommandArgument(const QString &cmdStr)
+{
+ skipSpacesOnLine();
+
+ qsizetype begin = m_position;
+ int parenDepth = 0;
+
+ while (m_position < m_input.size() && (m_input[m_position] != '\n' || parenDepth > 0)) {
+ if (m_input.at(m_position) == '(')
+ ++parenDepth;
+ else if (m_input.at(m_position) == ')')
+ --parenDepth;
+ else if (m_input.at(m_position) == '\\' && expandMacro(ArgumentParsingOptions::Default))
+ continue;
+ ++m_position;
+ }
+ if (m_position == m_input.size() && parenDepth > 0) {
+ m_position = begin;
+ location().warning(QStringLiteral("Unbalanced parentheses in '%1'").arg(cmdStr));
+ }
+
+ QString t = m_input.mid(begin, m_position - begin).simplified();
+ skipSpacesOnLine();
+ return t;
+}
+
+QString DocParser::getUntilEnd(int cmd)
+{
+ int endCmd = endCmdFor(cmd);
+ QRegularExpression rx("\\\\" + cmdName(endCmd) + "\\b");
+ QString t;
+ auto match = rx.match(m_input, m_position);
+
+ if (!match.hasMatch()) {
+ location().warning(QStringLiteral("Missing '\\%1'").arg(cmdName(endCmd)));
+ m_position = m_input.size();
+ } else {
+ qsizetype end = match.capturedStart();
+ t = m_input.mid(m_position, end - m_position);
+ m_position = match.capturedEnd();
+ }
+ return t;
+}
+
+void DocParser::expandArgumentsInString(QString &str, const QStringList &args)
+{
+ if (args.isEmpty())
+ return;
+
+ qsizetype paramNo;
+ qsizetype j = 0;
+ while (j < str.size()) {
+ if (str[j] == '\\' && j < str.size() - 1 && (paramNo = str[j + 1].digitValue()) >= 1
+ && paramNo <= args.size()) {
+ const QString &r = args[paramNo - 1];
+ str.replace(j, 2, r);
+ j += qMin(1, r.size());
+ } else {
+ ++j;
+ }
+ }
+}
+
+/*!
+ Returns the marked-up code following the code-quoting command \a cmd, expanding
+ any arguments passed in \a argStr.
+
+ Uses the \a marker to mark up the code. If it's \c nullptr, resolve the marker
+ based on the topic and the quoted code itself.
+*/
+QString DocParser::getCode(int cmd, CodeMarker *marker, const QString &argStr)
+{
+ QString code = untabifyEtc(getUntilEnd(cmd));
+ expandArgumentsInString(code, argStr.split(" ", Qt::SkipEmptyParts));
+
+ int indent = indentLevel(code);
+ code = dedent(indent, code);
+
+ // If we're in a QML topic, check if the QML marker recognizes the code
+ if (!marker && !m_private->m_topics.isEmpty()
+ && m_private->m_topics[0].m_topic.startsWith("qml")) {
+ auto qmlMarker = CodeMarker::markerForLanguage("QML");
+ marker = (qmlMarker && qmlMarker->recognizeCode(code)) ? qmlMarker : nullptr;
+ }
+ if (marker == nullptr)
+ marker = CodeMarker::markerForCode(code);
+ return marker->markedUpCode(code, nullptr, location());
+}
+
+bool DocParser::isBlankLine()
+{
+ qsizetype i = m_position;
+
+ while (i < m_inputLength && m_input[i].isSpace()) {
+ if (m_input[i] == '\n')
+ return true;
+ ++i;
+ }
+ return false;
+}
+
+bool DocParser::isLeftBraceAhead()
+{
+ int numEndl = 0;
+ qsizetype i = m_position;
+
+ while (i < m_inputLength && m_input[i].isSpace() && numEndl < 2) {
+ // ### bug with '\\'
+ if (m_input[i] == '\n')
+ numEndl++;
+ ++i;
+ }
+ return numEndl < 2 && i < m_inputLength && m_input[i] == '{';
+}
+
+bool DocParser::isLeftBracketAhead()
+{
+ int numEndl = 0;
+ qsizetype i = m_position;
+
+ while (i < m_inputLength && m_input[i].isSpace() && numEndl < 2) {
+ // ### bug with '\\'
+ if (m_input[i] == '\n')
+ numEndl++;
+ ++i;
+ }
+ return numEndl < 2 && i < m_inputLength && m_input[i] == '[';
+}
+
+/*!
+ Skips to the next non-space character or EOL.
+ */
+void DocParser::skipSpacesOnLine()
+{
+ while ((m_position < m_input.size()) && m_input[m_position].isSpace()
+ && (m_input[m_position].unicode() != '\n'))
+ ++m_position;
+}
+
+/*!
+ Skips spaces and one EOL.
+ */
+void DocParser::skipSpacesOrOneEndl()
+{
+ qsizetype firstEndl = -1;
+ while (m_position < m_input.size() && m_input[m_position].isSpace()) {
+ QChar ch = m_input[m_position];
+ if (ch == '\n') {
+ if (firstEndl == -1) {
+ firstEndl = m_position;
+ } else {
+ m_position = firstEndl;
+ break;
+ }
+ }
+ ++m_position;
+ }
+}
+
+void DocParser::skipAllSpaces()
+{
+ while (m_position < m_inputLength && m_input[m_position].isSpace())
+ ++m_position;
+}
+
+void DocParser::skipToNextPreprocessorCommand()
+{
+ QRegularExpression rx("\\\\(?:" + cmdName(CMD_IF) + QLatin1Char('|') + cmdName(CMD_ELSE)
+ + QLatin1Char('|') + cmdName(CMD_ENDIF) + ")\\b");
+ auto match = rx.match(m_input, m_position + 1); // ### + 1 necessary?
+
+ if (!match.hasMatch())
+ m_position = m_input.size();
+ else
+ m_position = match.capturedStart();
+}
+
+int DocParser::endCmdFor(int cmd)
+{
+ switch (cmd) {
+ case CMD_BADCODE:
+ return CMD_ENDCODE;
+ case CMD_CODE:
+ return CMD_ENDCODE;
+ case CMD_COMPARESWITH:
+ return CMD_ENDCOMPARESWITH;
+ case CMD_DETAILS:
+ return CMD_ENDDETAILS;
+ case CMD_DIV:
+ return CMD_ENDDIV;
+ case CMD_QML:
+ return CMD_ENDQML;
+ case CMD_FOOTNOTE:
+ return CMD_ENDFOOTNOTE;
+ case CMD_LEGALESE:
+ return CMD_ENDLEGALESE;
+ case CMD_LINK:
+ return CMD_ENDLINK;
+ case CMD_LIST:
+ return CMD_ENDLIST;
+ case CMD_OMIT:
+ return CMD_ENDOMIT;
+ case CMD_QUOTATION:
+ return CMD_ENDQUOTATION;
+ case CMD_RAW:
+ return CMD_ENDRAW;
+ case CMD_SECTION1:
+ return CMD_ENDSECTION1;
+ case CMD_SECTION2:
+ return CMD_ENDSECTION2;
+ case CMD_SECTION3:
+ return CMD_ENDSECTION3;
+ case CMD_SECTION4:
+ return CMD_ENDSECTION4;
+ case CMD_SIDEBAR:
+ return CMD_ENDSIDEBAR;
+ case CMD_TABLE:
+ return CMD_ENDTABLE;
+ default:
+ return cmd;
+ }
+}
+
+QString DocParser::cmdName(int cmd)
+{
+ return cmds[cmd].name;
+}
+
+QString DocParser::endCmdName(int cmd)
+{
+ return cmdName(endCmdFor(cmd));
+}
+
+QString DocParser::untabifyEtc(const QString &str)
+{
+ QString result;
+ result.reserve(str.size());
+ int column = 0;
+
+ for (const auto &character : str) {
+ if (character == QLatin1Char('\r'))
+ continue;
+ if (character == QLatin1Char('\t')) {
+ result += &" "[column % s_tabSize];
+ column = ((column / s_tabSize) + 1) * s_tabSize;
+ continue;
+ }
+ if (character == QLatin1Char('\n')) {
+ while (result.endsWith(QLatin1Char(' ')))
+ result.chop(1);
+ result += character;
+ column = 0;
+ continue;
+ }
+ result += character;
+ ++column;
+ }
+
+ while (result.endsWith("\n\n"))
+ result.truncate(result.size() - 1);
+ while (result.startsWith(QLatin1Char('\n')))
+ result = result.mid(1);
+
+ return result;
+}
+
+int DocParser::indentLevel(const QString &str)
+{
+ int minIndent = INT_MAX;
+ int column = 0;
+
+ for (const auto &character : str) {
+ if (character == '\n') {
+ column = 0;
+ } else {
+ if (character != ' ' && column < minIndent)
+ minIndent = column;
+ ++column;
+ }
+ }
+ return minIndent;
+}
+
+QString DocParser::dedent(int level, const QString &str)
+{
+ if (level == 0)
+ return str;
+
+ QString result;
+ int column = 0;
+
+ for (const auto &character : str) {
+ if (character == QLatin1Char('\n')) {
+ result += '\n';
+ column = 0;
+ } else {
+ if (column >= level)
+ result += character;
+ ++column;
+ }
+ }
+ return result;
+}
+
+/*!
+ Returns \c true if \a atom represents a code snippet.
+ */
+bool DocParser::isCode(const Atom *atom)
+{
+ Atom::AtomType type = atom->type();
+ return (type == Atom::Code || type == Atom::Qml);
+}
+
+/*!
+ Returns \c true if \a atom represents quoting information.
+ */
+bool DocParser::isQuote(const Atom *atom)
+{
+ Atom::AtomType type = atom->type();
+ return (type == Atom::CodeQuoteArgument || type == Atom::CodeQuoteCommand
+ || type == Atom::SnippetCommand || type == Atom::SnippetIdentifier
+ || type == Atom::SnippetLocation);
+}
+
+/*!
+ \internal
+ Processes the arguments passed to the \\compareswith block command.
+ The arguments are stored as text within the first atom of the block
+ (Atom::ComparesLeft).
+
+ Extracts the comparison category and the list of types, and stores
+ the information into a map accessed via \a priv.
+*/
+static void processComparesWithCommand(DocPrivate *priv, const Location &location)
+{
+ static auto take_while = [](QStringView input, auto predicate) {
+ QStringView::size_type end{0};
+
+ while (end < input.size() && std::invoke(predicate, input[end]))
+ ++end;
+
+ return std::make_tuple(input.sliced(0, end), input.sliced(end));
+ };
+
+ static auto peek = [](QStringView input, QChar c) {
+ return !input.empty() && input.first() == c;
+ };
+
+ static auto skip_one = [](QStringView input) {
+ if (input.empty()) return std::make_tuple(QStringView{}, input);
+ else return std::make_tuple(input.sliced(0, 1), input.sliced(1));
+ };
+
+ static auto enclosed = [](QStringView input, QChar open, QChar close) {
+ if (!peek(input, open)) return std::make_tuple(QStringView{}, input);
+
+ auto [opened, without_open] = skip_one(input);
+ auto [parsed, remaining] = take_while(without_open, [close](QChar c){ return c != close; });
+
+ if (remaining.empty()) return std::make_tuple(QStringView{}, input);
+
+ auto [closed, without_close] = skip_one(remaining);
+
+ return std::make_tuple(parsed.trimmed(), without_close);
+ };
+
+ static auto one_of = [](auto first, auto second) {
+ return [first, second](QStringView input) {
+ auto [parsed, remaining] = std::invoke(first, input);
+
+ if (parsed.empty()) return std::invoke(second, input);
+ else return std::make_tuple(parsed, remaining);
+ };
+ };
+
+ static auto collect = [](QStringView input, auto parser) {
+ QStringList collected{};
+
+ while (true) {
+ auto [parsed, remaining] = std::invoke(parser, input);
+
+ if (parsed.empty()) break;
+ collected.append(parsed.toString());
+
+ input = remaining;
+ };
+
+ return collected;
+ };
+
+ static auto spaces = [](QStringView input) {
+ return take_while(input, [](QChar c){ return c.isSpace(); });
+ };
+
+ static auto word = [](QStringView input) {
+ return take_while(input, [](QChar c){ return !c.isSpace(); });
+ };
+
+ static auto parse_argument = [](QStringView input) {
+ auto [_, without_spaces] = spaces(input);
+
+ return one_of(
+ [](QStringView input){ return enclosed(input, '{', '}'); },
+ word
+ )(without_spaces);
+ };
+
+ const QString cmd{DocParser::cmdName(CMD_COMPARESWITH)};
+ Text text = priv->m_text.splitAtFirst(Atom::ComparesLeft);
+
+ auto *atom = text.firstAtom();
+ QStringList segments = collect(atom->string(), parse_argument);
+
+ QString categoryString;
+ if (!segments.isEmpty())
+ categoryString = segments.takeFirst();
+ auto category = comparisonCategoryFromString(categoryString.toStdString());
+
+ if (category == ComparisonCategory::None) {
+ location.warning(u"Invalid argument to \\%1 command: `%2`"_s.arg(cmd, categoryString),
+ u"Valid arguments are `strong`, `weak`, `partial`, or `equality`."_s);
+ return;
+ }
+
+ if (segments.isEmpty()) {
+ location.warning(u"Missing argument to \\%1 command."_s.arg(cmd),
+ u"Provide at least one type name, or a list of types separated by spaces."_s);
+ return;
+ }
+
+ // Store cleaned-up type names back into the atom
+ segments.removeDuplicates();
+ atom->setString(segments.join(QLatin1Char(';')));
+
+ // Add an entry to meta-command map for error handling in CppCodeParser
+ priv->m_metaCommandMap[cmd].append(ArgPair(categoryString, atom->string()));
+ priv->m_metacommandsUsed.insert(cmd);
+
+ priv->constructExtra();
+ const auto end{priv->extra->m_comparesWithMap.cend()};
+ priv->extra->m_comparesWithMap.insert(end, category, text);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/docparser.h b/src/qdoc/qdoc/src/qdoc/docparser.h
new file mode 100644
index 000000000..c577e12ba
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docparser.h
@@ -0,0 +1,176 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef DOCPARSER_H
+#define DOCPARSER_H
+
+#include "atom.h"
+#include "config.h"
+#include "docutilities.h"
+#include "location.h"
+#include "openedlist.h"
+#include "quoter.h"
+
+#include "filesystem/fileresolver.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/qglobalstatic.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class Doc;
+class DocPrivate;
+class CodeMarker;
+struct Macro;
+
+class DocParser
+{
+public:
+ void parse(const QString &source, DocPrivate *docPrivate, const QSet<QString> &metaCommandSet,
+ const QSet<QString> &possibleTopics);
+
+ static void initialize(const Config &config, FileResolver& file_resolver);
+ static int endCmdFor(int cmd);
+ static QString cmdName(int cmd);
+ static QString endCmdName(int cmd);
+ static QString untabifyEtc(const QString &str);
+ static int indentLevel(const QString &str);
+ static QString dedent(int level, const QString &str);
+
+ static int s_tabSize;
+ static QStringList s_ignoreWords;
+ static bool s_quoting;
+
+private:
+
+ enum class ArgumentParsingOptions {
+ Default,
+ Verbatim,
+ MacroArguments
+ };
+
+ Location &location();
+ QString detailsUnknownCommand(const QSet<QString> &metaCommandSet, const QString &str);
+ void insertTarget(const QString &target);
+ void insertKeyword(const QString &keyword);
+ void include(const QString &fileName, const QString &identifier, const QStringList &parameters);
+ void startFormat(const QString &format, int cmd);
+ bool openCommand(int cmd);
+ bool closeCommand(int endCmd);
+ void startSection(Doc::Sections unit, int cmd);
+ void endSection(int unit, int endCmd);
+ void parseAlso();
+ void appendAtom(const Atom&);
+ void appendAtom(const LinkAtom&);
+ void appendChar(QChar ch);
+ void appendWord(const QString &word);
+ void appendToCode(const QString &code);
+ void appendToCode(const QString &code, Atom::AtomType defaultType);
+ void enterPara(Atom::AtomType leftType = Atom::ParaLeft,
+ Atom::AtomType rightType = Atom::ParaRight, const QString &string = QString());
+ void leavePara();
+ void leaveValue();
+ void leaveValueList();
+ void leaveTableRow();
+ void quoteFromFile(const QString& filename);
+ bool expandMacro(ArgumentParsingOptions options);
+ void expandMacro(const QString &def, const QStringList &args);
+ QString expandMacroToString(const QString &name, const Macro &macro);
+ Doc::Sections getSectioningUnit();
+ QString getArgument(ArgumentParsingOptions options = ArgumentParsingOptions::Default);
+ QString getBracedArgument(ArgumentParsingOptions options);
+ QString getBracketedArgument();
+ QStringList getMacroArguments(const QString &name, const Macro &macro);
+ QString getOptionalArgument();
+ QString getRestOfLine();
+ QString getMetaCommandArgument(const QString &cmdStr);
+ QString getUntilEnd(int cmd);
+ QString getCode(int cmd, CodeMarker *marker, const QString &argStr = QString());
+
+ inline bool isAutoLinkString(const QString &word);
+ bool isAutoLinkString(const QString &word, qsizetype &curPos);
+ bool isBlankLine();
+ bool isLeftBraceAhead();
+ bool isLeftBracketAhead();
+ void skipSpacesOnLine();
+ void skipSpacesOrOneEndl();
+ void skipAllSpaces();
+ void skipToNextPreprocessorCommand();
+ static bool isCode(const Atom *atom);
+ static bool isQuote(const Atom *atom);
+ static void expandArgumentsInString(QString &str, const QStringList &args);
+
+ QStack<qsizetype> m_openedInputs {};
+
+ QString m_input {};
+ qsizetype m_position {};
+ qsizetype m_backslashPosition {};
+ qsizetype m_endPosition {};
+ qsizetype m_inputLength {};
+ Location m_cachedLocation {};
+ qsizetype m_cachedPosition {};
+
+ DocPrivate *m_private { nullptr };
+ enum ParagraphState { OutsideParagraph, InSingleLineParagraph, InMultiLineParagraph };
+ ParagraphState m_paragraphState {};
+ bool m_inTableHeader {};
+ bool m_inTableRow {};
+ bool m_inTableItem {};
+ bool m_indexStartedParagraph {}; // ### rename
+ Atom::AtomType m_pendingParagraphLeftType {};
+ Atom::AtomType m_pendingParagraphRightType {};
+ QString m_pendingParagraphString {};
+
+ int m_braceDepth {};
+ Doc::Sections m_currentSection {};
+ QMap<QString, Location> m_targetMap {};
+ QMap<int, QString> m_pendingFormats {};
+ QStack<int> m_openedCommands {};
+ QStack<OpenedList> m_openedLists {};
+ Quoter m_quoter {};
+ Atom *m_lastAtom { nullptr };
+
+ static DocUtilities &s_utilities;
+
+ // KLUDGE: When parsing documentation, there is a need to find
+ // files to resolve quoting commands. Ideally, the system that
+ // takes care of this would be a non-static member that is a
+ // reference that is passed at
+ // construction time.
+ // Nonetheless, with how the current codebase is constructed, this
+ // has proven to be extremely difficult until more changes are
+ // done. In particular, the construction of a DocParser happens in
+ // multiple places at multiple depths and, in particular, happens
+ // in one of Doc's constructor.
+ // Doc itself is built, again, in multiple places at multiple
+ // depths, making it clumsy and sometimes infeasible to pass the
+ // dependency around so that it is available at the required
+ // places. In particular, this stems from the fact that Doc is
+ // holding many responsabilities and is spread troughtout much of
+ // the codebase in different ways. DocParser mostly depends on Doc
+ // and Doc currently depends on DocParser, making the two
+ // difficult to separate.
+ //
+ // In the future, we expect Doc to mostly be removed, such as to
+ // remove this dependencies and the parsing of documentation to
+ // happen near main and atomically from other endevours, producing
+ // an intermediate representation that is consumed by later
+ // phases.
+ // At that point, it should be possible to not have this kind of
+ // indirection while, for now, the only accessible way to pass
+ // this dependency is trough the initialize method which passes
+ // for Doc::initialize.
+ //
+ // Furthemore, as we cannot late-bind a reference, and having a
+ // desire to avoid an unnecessary copy, we are thus forced to use
+ // a different storage method, in this case a pointer.
+ // This too should be removed later on, using reference or move
+ // semantic depending on the required data-flow.
+ static FileResolver* file_resolver;
+};
+
+QT_END_NAMESPACE
+
+#endif // DOCPARSER_H
diff --git a/src/qdoc/qdoc/src/qdoc/docprivate.cpp b/src/qdoc/qdoc/src/qdoc/docprivate.cpp
new file mode 100644
index 000000000..a7c178d57
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docprivate.cpp
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "docprivate.h"
+
+#include "text.h"
+
+#include <QtCore/qhash.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Deletes the DocPrivateExtra.
+ */
+DocPrivate::~DocPrivate()
+{
+ delete extra;
+}
+
+void DocPrivate::addAlso(const Text &also)
+{
+ m_alsoList.append(also);
+}
+
+void DocPrivate::constructExtra()
+{
+ if (extra == nullptr)
+ extra = new DocPrivateExtra;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/docprivate.h b/src/qdoc/qdoc/src/qdoc/docprivate.h
new file mode 100644
index 000000000..7402290c9
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docprivate.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef DOCPRIVATE_H
+#define DOCPRIVATE_H
+
+#include "atom.h"
+#include "config.h"
+#include "codemarker.h"
+#include "doc.h"
+#include "editdistance.h"
+#include "generator.h"
+#include "utilities.h"
+#include "openedlist.h"
+#include "quoter.h"
+#include "text.h"
+#include "tokenizer.h"
+
+#include <QtCore/qdatetime.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qtextstream.h>
+
+#include <cctype>
+#include <climits>
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+typedef QMap<QString, ArgList> CommandMap;
+
+struct DocPrivateExtra
+{
+ QList<Atom *> m_tableOfContents {};
+ QList<int> m_tableOfContentsLevels {};
+ QList<Atom *> m_keywords {};
+ QList<Atom *> m_targets {};
+ QStringMultiMap m_metaMap {};
+ QMultiMap<ComparisonCategory, Text> m_comparesWithMap {};
+};
+
+class DocPrivate
+{
+public:
+ explicit DocPrivate(const Location &start = Location(), const Location &end = Location(),
+ QString source = QString())
+ : m_start_loc(start), m_end_loc(end), m_src(std::move(source)), m_hasLegalese(false) {};
+ ~DocPrivate();
+
+ void addAlso(const Text &also);
+ void constructExtra();
+ void ref() { ++count; }
+ bool deref() { return (--count == 0); }
+
+ int count { 1 };
+ // ### move some of this in DocPrivateExtra
+ Location m_start_loc {};
+ Location m_end_loc {};
+ QString m_src {};
+ Text m_text {};
+ QSet<QString> m_params {};
+ QList<Text> m_alsoList {};
+ QStringList m_enumItemList {};
+ QStringList m_omitEnumItemList {};
+ QSet<QString> m_metacommandsUsed {};
+ CommandMap m_metaCommandMap {};
+ DocPrivateExtra *extra { nullptr };
+ TopicList m_topics {};
+
+ bool m_hasLegalese : 1;
+};
+
+QT_END_NAMESPACE
+
+#endif // DOCPRIVATE_H
diff --git a/src/qdoc/qdoc/src/qdoc/docutilities.h b/src/qdoc/qdoc/src/qdoc/docutilities.h
new file mode 100644
index 000000000..d4483ac73
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docutilities.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef DOCUTILITIES_H
+#define DOCUTILITIES_H
+
+#include "macro.h"
+#include "singleton.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qmap.h>
+
+QT_BEGIN_NAMESPACE
+
+typedef QHash<QString, int> QHash_QString_int;
+typedef QHash<QString, Macro> QHash_QString_Macro;
+
+struct DocUtilities : public Singleton<DocUtilities>
+{
+public:
+ QHash_QString_int cmdHash;
+ QHash_QString_Macro macroHash;
+};
+
+QT_END_NAMESPACE
+
+#endif // DOCUTILITIES_H
diff --git a/src/qdoc/qdoc/src/qdoc/editdistance.cpp b/src/qdoc/qdoc/src/qdoc/editdistance.cpp
new file mode 100644
index 000000000..303979ec3
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/editdistance.cpp
@@ -0,0 +1,68 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "editdistance.h"
+
+QT_BEGIN_NAMESPACE
+
+int editDistance(const QString &s, const QString &t)
+{
+#define D(i, j) d[(i)*n + (j)]
+ int i;
+ int j;
+ qsizetype m = s.size() + 1;
+ qsizetype n = t.size() + 1;
+ int *d = new int[m * n];
+ int result;
+
+ for (i = 0; i < m; ++i)
+ D(i, 0) = i;
+ for (j = 0; j < n; ++j)
+ D(0, j) = j;
+ for (i = 1; i < m; ++i) {
+ for (j = 1; j < n; ++j) {
+ if (s[i - 1] == t[j - 1]) {
+ D(i, j) = D(i - 1, j - 1);
+ } else {
+ int x = D(i - 1, j);
+ int y = D(i - 1, j - 1);
+ int z = D(i, j - 1);
+ D(i, j) = 1 + qMin(qMin(x, y), z);
+ }
+ }
+ }
+ result = D(m - 1, n - 1);
+ delete[] d;
+ return result;
+#undef D
+}
+
+QString nearestName(const QString &actual, const QSet<QString> &candidates)
+{
+ if (actual.isEmpty())
+ return QString();
+
+ int deltaBest = 10000;
+ int numBest = 0;
+ QString best;
+
+ for (const auto &candidate : candidates) {
+ if (candidate[0] == actual[0]) {
+ int delta = editDistance(actual, candidate);
+ if (delta < deltaBest) {
+ deltaBest = delta;
+ numBest = 1;
+ best = candidate;
+ } else if (delta == deltaBest) {
+ ++numBest;
+ }
+ }
+ }
+
+ if (numBest == 1 && deltaBest <= 2 && actual.size() + best.size() >= 5)
+ return best;
+
+ return QString();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/editdistance.h b/src/qdoc/qdoc/src/qdoc/editdistance.h
new file mode 100644
index 000000000..dfa6a42dd
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/editdistance.h
@@ -0,0 +1,17 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef EDITDISTANCE_H
+#define EDITDISTANCE_H
+
+#include <QtCore/qset.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+int editDistance(const QString &s, const QString &t);
+QString nearestName(const QString &actual, const QSet<QString> &candidates);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/enumitem.h b/src/qdoc/qdoc/src/qdoc/enumitem.h
new file mode 100644
index 000000000..06e9d42a9
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/enumitem.h
@@ -0,0 +1,36 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef ENUMITEM_H
+#define ENUMITEM_H
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+class EnumItem
+{
+public:
+ EnumItem() = default;
+ EnumItem(QString name, QString value, QString since = QString())
+ : m_name(std::move(name)),
+ m_value(std::move(value)),
+ m_since(std::move(since)) {}
+
+ [[nodiscard]] const QString &name() const { return m_name; }
+ [[nodiscard]] const QString &value() const { return m_value; }
+ [[nodiscard]] const QString &since() const { return m_since; }
+ void setSince(const QString &since) { m_since = since; }
+
+private:
+ QString m_name {};
+ QString m_value {};
+ QString m_since {};
+};
+
+QT_END_NAMESPACE
+
+#endif // ENUMITEM_H
diff --git a/src/qdoc/qdoc/src/qdoc/enumnode.cpp b/src/qdoc/qdoc/src/qdoc/enumnode.cpp
new file mode 100644
index 000000000..48a2f81aa
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/enumnode.cpp
@@ -0,0 +1,82 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "enumnode.h"
+
+#include "aggregate.h"
+#include "typedefnode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class EnumNode
+ */
+
+/*!
+ Add \a item to the enum type's item list.
+ */
+void EnumNode::addItem(const EnumItem &item)
+{
+ m_items.append(item);
+ m_names.insert(item.name());
+}
+
+/*!
+ Returns the access level of the enumeration item named \a name.
+ Apparently it is private if it has been omitted by qdoc's
+ omitvalue command. Otherwise it is public.
+ */
+Access EnumNode::itemAccess(const QString &name) const
+{
+ if (doc().omitEnumItemNames().contains(name))
+ return Access::Private;
+ return Access::Public;
+}
+
+/*!
+ Returns the enum value associated with the enum \a name.
+ */
+QString EnumNode::itemValue(const QString &name) const
+{
+ for (const auto &item : std::as_const(m_items)) {
+ if (item.name() == name)
+ return item.value();
+ }
+ return QString();
+}
+
+/*!
+ Sets \a since information to a named enum \a value, if it
+ exists in this enum.
+*/
+void EnumNode::setSince(const QString &value, const QString &since)
+{
+ auto it = std::find_if(m_items.begin(), m_items.end(), [value](EnumItem ev) {
+ return ev.name() == value;
+ });
+ if (it != m_items.end())
+ it->setSince(since);
+}
+
+/*!
+ Clone this node on the heap and make the clone a child of
+ \a parent.
+
+ Returns a pointer to the clone.
+ */
+Node *EnumNode::clone(Aggregate *parent)
+{
+ auto *en = new EnumNode(*this); // shallow copy
+ en->setParent(nullptr);
+ parent->addChild(en);
+
+ return en;
+}
+
+void EnumNode::setFlagsType(TypedefNode *typedefNode)
+{
+ m_flagsType = typedefNode;
+ typedefNode->setAssociatedEnum(this);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/enumnode.h b/src/qdoc/qdoc/src/qdoc/enumnode.h
new file mode 100644
index 000000000..47139ae4d
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/enumnode.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef ENUMNODE_H
+#define ENUMNODE_H
+
+#include "access.h"
+#include "node.h"
+#include "typedefnode.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+class EnumNode : public Node
+{
+public:
+ EnumNode(Aggregate *parent, const QString &name, bool isScoped = false)
+ : Node(Enum, parent, name), m_isScoped(isScoped)
+ {
+ }
+
+ void addItem(const EnumItem &item);
+ void setFlagsType(TypedefNode *typedefNode);
+ bool hasItem(const QString &name) const { return m_names.contains(name); }
+ bool isScoped() const { return m_isScoped; }
+
+ const QList<EnumItem> &items() const { return m_items; }
+ Access itemAccess(const QString &name) const;
+ const TypedefNode *flagsType() const { return m_flagsType; }
+ QString itemValue(const QString &name) const;
+ Node *clone(Aggregate *parent) override;
+ void setSince(const QString &value, const QString &since);
+
+private:
+ QList<EnumItem> m_items {};
+ QSet<QString> m_names {};
+ const TypedefNode *m_flagsType { nullptr };
+ bool m_isScoped { false };
+};
+
+QT_END_NAMESPACE
+
+#endif // ENUMNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/examplenode.h b/src/qdoc/qdoc/src/qdoc/examplenode.h
new file mode 100644
index 000000000..920092e4e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/examplenode.h
@@ -0,0 +1,42 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef EXAMPLENODE_H
+#define EXAMPLENODE_H
+
+#include "pagenode.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class ExampleNode : public PageNode
+{
+public:
+ ExampleNode(Aggregate *parent, const QString &name) : PageNode(Node::Example, parent, name) {}
+ [[nodiscard]] QString imageFileName() const override { return m_imageFileName; }
+ void setImageFileName(const QString &ifn) override { m_imageFileName = ifn; }
+ [[nodiscard]] const QStringList &files() const { return m_files; }
+ [[nodiscard]] const QStringList &images() const { return m_images; }
+ [[nodiscard]] const QString &projectFile() const { return m_projectFile; }
+ void setFiles(const QStringList &files, const QString &projectFile)
+ {
+ m_files = files;
+ m_projectFile = projectFile;
+ }
+ void setImages(const QStringList &images) { m_images = images; }
+ void appendFile(QString &file) { m_files.append(file); }
+ void appendImage(QString &image) { m_images.append(image); }
+
+private:
+ QString m_imageFileName {};
+ QString m_projectFile {};
+ QStringList m_files {};
+ QStringList m_images {};
+};
+
+QT_END_NAMESPACE
+
+#endif // EXAMPLENODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/externalpagenode.cpp b/src/qdoc/qdoc/src/qdoc/externalpagenode.cpp
new file mode 100644
index 000000000..30a583d00
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/externalpagenode.cpp
@@ -0,0 +1,28 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "externalpagenode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class ExternalPageNode
+
+ \brief The ExternalPageNode represents an external documentation page.
+
+ Qdoc can generate links to pages that are not part of the documentation
+ being generated. 3rd party software pages are often referenced by links
+ from the QT documentation. Qdoc creates an ExternalPageNode when it sees
+ an \c {\\externalpage} command. The HTML generator can then use the node
+ when it needs to create links to the external page.
+
+ ExternalPageNode inherits PageNode.
+*/
+
+/*! \fn ExternalPageNode::ExternalPageNode(Aggregate *parent, const QString &name)
+ The constructor creates an ExternalPageNode as a child node of \a parent.
+ It's \a name is the argument from the \c {\\externalpage} command. The node
+ type is Node::ExternalPage, and the page type is Node::ArticlePage.
+ */
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/externalpagenode.h b/src/qdoc/qdoc/src/qdoc/externalpagenode.h
new file mode 100644
index 000000000..e67aab0e8
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/externalpagenode.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef EXTERNALPAGENODE_H
+#define EXTERNALPAGENODE_H
+
+#include "pagenode.h"
+
+#include <QtCore/qglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class ExternalPageNode : public PageNode
+{
+public:
+ ExternalPageNode(Aggregate *parent, const QString &url)
+ : PageNode(Node::ExternalPage, parent, url)
+ {
+ setUrl(url);
+ }
+};
+
+QT_END_NAMESPACE
+
+#endif // EXTERNALPAGENODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.cpp b/src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.cpp
new file mode 100644
index 000000000..aaa489085
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.cpp
@@ -0,0 +1,161 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "fileresolver.h"
+
+#include "qdoc/boundaries/filesystem/filepath.h"
+
+#include <QDir>
+
+#include <iostream>
+#include <algorithm>
+
+/*!
+ * \class FileResolver
+ * \brief Encapsulate the logic that QDoc uses to find files whose
+ * path is provided by the user and that are relative to the current
+ * configuration.
+ *
+ * A FileResolver instance is configured during creation, defining the
+ * root directories that the search should be performed on.
+ *
+ * Afterwards, it can be used to resolve paths relative to those
+ * directories, by querying through the resolve() method.
+ *
+ * Queries are resolved through a linear search through root
+ * directories, finding at most one file each time.
+ * A file is considered to be resolved if, from any root directory,
+ * the query represents an existing file.
+ *
+ * For example, consider the following directory structure on some
+ * filesystem:
+ *
+ * \badcode
+ * foo/
+ * |
+ * |-bar/
+ * |-|
+ * | |-anotherfile.txt
+ * |-file.txt
+ * \endcode
+ *
+ * And consider an instance of FileResolver tha considers \e{foo/} to
+ * be a root directory for search.
+ *
+ * Then, queries such as \e {bar/anotherfile.txt} and \e {file.txt}
+ * will be resolved.
+ *
+ * Instead, queries such as \e {foobar.cpp}, \e {bar}, and \e
+ * {foo/bar/anotherfile.txt} will not be resolved, as they do not
+ * represent any file reachable from a root directory for search.
+ *
+ * It is important to note that FileResolver always searches its root
+ * directories in an order that is based on the lexicographic ordering
+ * of the path of its root directories.
+ *
+ * For example, consider the following directory structure on some
+ * filesystem:
+ *
+ * \badcode
+ * foo/
+ * |
+ * |-bar/
+ * |-|
+ * | |-file.txt
+ * |-foobar/
+ * |-|
+ * | |-file.txt
+ * \endcode
+ *
+ * And consider an instance of FileResolver that considers \e
+ * {foo/bar/} and \e {foo/foobar/} to be root directories for search.
+ *
+ * Then, when the query \e {file.txt} is resolved, it will always
+ * resolve to the file in \e {bar}, as \e {bar} will be searched
+ * before \e {foobar}.
+ *
+ * We say that \e {foobar/file.txt} is shadowed by \e {bar/file.txt}.
+ *
+ * Currently, if this is an issue, it is possible to resolve it by
+ * using a common ancestor as a root directory instead of using
+ * multiples directories.
+ *
+ * In the previous example, if \e {foo} is instead chosen as the root
+ * directory for search, then queries \e {bar/file.txt} and \e
+ * {foobar/file.txt} can be used to uniquely resolve the two files,
+ * removing the shadowing.
+ * */
+
+/*!
+ * Constructs an instance of FileResolver with the directories in \a
+ * search_directories as root directories for searching.
+ *
+ * Duplicates in \a search_directories do not affect the resolution of
+ * files for the instance.
+ *
+ * For example, if \a search_directories contains some directory D
+ * more than once, the constructed instance will resolve files
+ * equivalently to an instance constructed with a single appearance of
+ * D.
+ *
+ * The order of \a search_directories does not affect the resolution
+ * of files for an instance.
+ *
+ * For example, if \a search_directories contains a permutation of
+ * directories D1, D2, ..., Dn, then the constructed instance will
+ * resolve files equivalently to an instance constructed from a
+ * difference permutation of the same directories.
+ */
+FileResolver::FileResolver(std::vector<DirectoryPath>&& search_directories)
+ : search_directories{std::move(search_directories)}
+{
+ std::sort(this->search_directories.begin(), this->search_directories.end());
+ this->search_directories.erase (
+ std::unique(this->search_directories.begin(), this->search_directories.end()),
+ this->search_directories.end()
+ );
+}
+
+// REMARK: Note that we do not treat absolute path specially.
+// This will in general mean that they cannot get resolved (albeit
+// there is a peculiar instance in which they can considering that
+// most path formats treat multiple adjacent separators as one).
+//
+// While we need to treat them at some point with a specific
+// intention, this was avoided at the current moment as it is
+// unrequired to build the actual documentation.
+//
+// In particular, avoiding this choice now allows us to move it to a
+// later stage where we can work with the origin of the data itself.
+// User-inputted paths come into the picture during the configuration
+// process and when parsing qdoc comments, there is a good chance that
+// some amount of sophistication will be required to handle this data
+// at the code level, for example to ensure that multiplatform
+// handling of paths is performed correctly.
+//
+// This will then define how we should handle absolute paths, if we
+// can receive them at all and so on.
+
+/*!
+* Returns a ResolvedFile if \a query can be resolved or std::nullopt
+* otherwise.
+*
+* The returned ResolvedFile, if any, will contain the provided \a
+* query and the path that the \a query was resolved to.
+*/
+[[nodiscard]] std::optional<ResolvedFile> FileResolver::resolve(QString query) const {
+ for (auto& directory_path : search_directories) {
+ auto maybe_filepath = FilePath::refine(QDir(directory_path.value() + "/" + query).path());
+ if (maybe_filepath) return ResolvedFile{std::move(query), std::move(*maybe_filepath)};
+ }
+
+ return std::nullopt;
+}
+
+/*!
+ * \fn FileResolver::get_search_directories() const
+ *
+ * Returns a const-reference to a collection of root search
+ * directories that this instance will use during the resolution of
+ * files.
+ */
diff --git a/src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.h b/src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.h
new file mode 100644
index 000000000..be574da30
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.h
@@ -0,0 +1,24 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "qdoc/boundaries/filesystem/directorypath.h"
+#include "qdoc/boundaries/filesystem/resolvedfile.h"
+
+#include <optional>
+#include <vector>
+
+#include <QtCore/qstring.h>
+
+class FileResolver {
+public:
+ FileResolver(std::vector<DirectoryPath>&& search_directories);
+
+ [[nodiscard]] std::optional<ResolvedFile> resolve(QString filename) const;
+
+ [[nodiscard]] const std::vector<DirectoryPath>& get_search_directories() const { return search_directories; }
+
+private:
+ std::vector<DirectoryPath> search_directories;
+};
diff --git a/src/qdoc/qdoc/src/qdoc/functionnode.cpp b/src/qdoc/qdoc/src/qdoc/functionnode.cpp
new file mode 100644
index 000000000..82933e0ac
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/functionnode.cpp
@@ -0,0 +1,473 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "functionnode.h"
+#include "propertynode.h"
+
+#include <string>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class FunctionNode
+
+ This node is used to represent any kind of function being
+ documented. It can represent a C++ class member function, a C++
+ global function, a QML method, or a macro, with or without
+ parameters.
+
+ A C++ function can be a signal, a slot, a constructor of any
+ kind, a destructor, a copy or move assignment operator, or
+ just a plain old member function or a global function.
+
+ A QML method can be a plain old method, or a
+ signal or signal handler.
+
+ If the function is an overload, its overload flag is
+ true.
+
+ The function node also has an overload number. If the
+ node's overload flag is set, this overload number is
+ positive; otherwise, the overload number is 0.
+ */
+
+/*!
+ Construct a function node for a C++ function. It's parent
+ is \a parent, and it's name is \a name.
+
+ \note The function node's overload flag is set to false, and
+ its overload number is set to 0. These data members are set
+ in normalizeOverloads(), when all the overloads are known.
+ */
+FunctionNode::FunctionNode(Aggregate *parent, const QString &name)
+ : Node(Function, parent, name),
+ m_const(false),
+ m_default(false),
+ m_static(false),
+ m_reimpFlag(false),
+ m_attached(false),
+ m_overloadFlag(false),
+ m_isFinal(false),
+ m_isOverride(false),
+ m_isRef(false),
+ m_isRefRef(false),
+ m_isInvokable(false),
+ m_explicit{false},
+ m_constexpr{false},
+ m_metaness(Plain),
+ m_virtualness(NonVirtual),
+ m_overloadNumber(0)
+{
+ // nothing
+}
+
+/*!
+ Construct a function node for a QML method or signal, specified
+ by ther Metaness value \a type. It's parent is \a parent, and
+ it's name is \a name. If \a attached is true, it is an attached
+ method or signal.
+
+ \note The function node's overload flag is set to false, and
+ its overload number is set to 0. These data members are set
+ in normalizeOverloads(), when all the overloads are known.
+ */
+FunctionNode::FunctionNode(Metaness kind, Aggregate *parent, const QString &name, bool attached)
+ : Node(Function, parent, name),
+ m_const(false),
+ m_default(false),
+ m_static(false),
+ m_reimpFlag(false),
+ m_attached(attached),
+ m_overloadFlag(false),
+ m_isFinal(false),
+ m_isOverride(false),
+ m_isRef(false),
+ m_isRefRef(false),
+ m_isInvokable(false),
+ m_explicit{false},
+ m_constexpr{false},
+ m_metaness(kind),
+ m_virtualness(NonVirtual),
+ m_overloadNumber(0)
+{
+ setGenus(getGenus(m_metaness));
+ if (!isCppNode() && name.startsWith("__"))
+ setStatus(Internal);
+}
+
+/*!
+ Clone this node on the heap and make the clone a child of
+ \a parent. Return the pointer to the clone.
+ */
+Node *FunctionNode::clone(Aggregate *parent)
+{
+ auto *fn = new FunctionNode(*this); // shallow copy
+ fn->setParent(nullptr);
+ parent->addChild(fn);
+ return fn;
+}
+
+/*!
+ Returns this function's virtualness value as a string
+ for use as an attribute value in index files.
+ */
+QString FunctionNode::virtualness() const
+{
+ switch (m_virtualness) {
+ case FunctionNode::NormalVirtual:
+ return QLatin1String("virtual");
+ case FunctionNode::PureVirtual:
+ return QLatin1String("pure");
+ case FunctionNode::NonVirtual:
+ default:
+ break;
+ }
+ return QLatin1String("non");
+}
+
+/*!
+ Sets the function node's virtualness value based on the value
+ of string \a value, which is the value of the function's \e{virtual}
+ attribute in an index file. If \a value is \e{pure}, and if the
+ parent() is a C++ class, set the parent's \e abstract flag to
+ \c {true}.
+ */
+void FunctionNode::setVirtualness(const QString &value)
+{
+ if (value == QLatin1String("pure")) {
+ m_virtualness = PureVirtual;
+ if (parent() && parent()->isClassNode())
+ parent()->setAbstract(true);
+ return;
+ }
+
+ m_virtualness = (value == QLatin1String("virtual")) ? NormalVirtual : NonVirtual;
+}
+
+static QMap<QString, FunctionNode::Metaness> metanessMap_;
+static void buildMetanessMap()
+{
+ metanessMap_["plain"] = FunctionNode::Plain;
+ metanessMap_["signal"] = FunctionNode::Signal;
+ metanessMap_["slot"] = FunctionNode::Slot;
+ metanessMap_["constructor"] = FunctionNode::Ctor;
+ metanessMap_["copy-constructor"] = FunctionNode::CCtor;
+ metanessMap_["move-constructor"] = FunctionNode::MCtor;
+ metanessMap_["destructor"] = FunctionNode::Dtor;
+ metanessMap_["macro"] = FunctionNode::MacroWithParams;
+ metanessMap_["macrowithparams"] = FunctionNode::MacroWithParams;
+ metanessMap_["macrowithoutparams"] = FunctionNode::MacroWithoutParams;
+ metanessMap_["copy-assign"] = FunctionNode::CAssign;
+ metanessMap_["move-assign"] = FunctionNode::MAssign;
+ metanessMap_["native"] = FunctionNode::Native;
+ metanessMap_["qmlsignal"] = FunctionNode::QmlSignal;
+ metanessMap_["qmlsignalhandler"] = FunctionNode::QmlSignalHandler;
+ metanessMap_["qmlmethod"] = FunctionNode::QmlMethod;
+}
+
+static QMap<QString, FunctionNode::Metaness> topicMetanessMap_;
+static void buildTopicMetanessMap()
+{
+ topicMetanessMap_["fn"] = FunctionNode::Plain;
+ topicMetanessMap_["qmlsignal"] = FunctionNode::QmlSignal;
+ topicMetanessMap_["qmlattachedsignal"] = FunctionNode::QmlSignal;
+ topicMetanessMap_["qmlmethod"] = FunctionNode::QmlMethod;
+ topicMetanessMap_["qmlattachedmethod"] = FunctionNode::QmlMethod;
+}
+
+/*!
+ Determines the Genus value for this FunctionNode given the
+ Metaness value \a metaness. Returns the Genus value. \a metaness must be
+ one of the values of Metaness. If not, Node::DontCare is
+ returned.
+ */
+Node::Genus FunctionNode::getGenus(FunctionNode::Metaness metaness)
+{
+ switch (metaness) {
+ case FunctionNode::Plain:
+ case FunctionNode::Signal:
+ case FunctionNode::Slot:
+ case FunctionNode::Ctor:
+ case FunctionNode::Dtor:
+ case FunctionNode::CCtor:
+ case FunctionNode::MCtor:
+ case FunctionNode::MacroWithParams:
+ case FunctionNode::MacroWithoutParams:
+ case FunctionNode::Native:
+ case FunctionNode::CAssign:
+ case FunctionNode::MAssign:
+ return Node::CPP;
+ case FunctionNode::QmlSignal:
+ case FunctionNode::QmlSignalHandler:
+ case FunctionNode::QmlMethod:
+ return Node::QML;
+ }
+
+ return Node::DontCare;
+}
+
+/*!
+ This static function converts the string \a value to an enum
+ value for the kind of function named by \a value.
+ */
+FunctionNode::Metaness FunctionNode::getMetaness(const QString &value)
+{
+ if (metanessMap_.isEmpty())
+ buildMetanessMap();
+ return metanessMap_[value];
+}
+
+/*!
+ This static function converts the topic string \a topic to an enum
+ value for the kind of function this FunctionNode represents.
+ */
+FunctionNode::Metaness FunctionNode::getMetanessFromTopic(const QString &topic)
+{
+ if (topicMetanessMap_.isEmpty())
+ buildTopicMetanessMap();
+ return topicMetanessMap_[topic];
+}
+
+/*!
+ Sets the function node's overload number to \a number. If \a number
+ is 0, the function node's overload flag is set to false. If
+ \a number is greater than 0, the overload flag is set to true.
+ */
+void FunctionNode::setOverloadNumber(signed short number)
+{
+ m_overloadNumber = number;
+ m_overloadFlag = (number > 0);
+}
+
+/*!
+ \fn void FunctionNode::setReimpFlag()
+
+ Sets the function node's reimp flag to \c true, which means
+ the \e {\\reimp} command was used in the qdoc comment. It is
+ supposed to mean that the function reimplements a virtual
+ function in a base class.
+ */
+
+/*!
+ Returns a string representing the kind of function this
+ Function node represents, which depends on the Metaness
+ value.
+ */
+QString FunctionNode::kindString() const
+{
+ switch (m_metaness) {
+ case FunctionNode::QmlSignal:
+ return "QML signal";
+ case FunctionNode::QmlSignalHandler:
+ return "QML signal handler";
+ case FunctionNode::QmlMethod:
+ return "QML method";
+ default:
+ return "function";
+ }
+}
+
+/*!
+ Returns a string representing the Metaness enum value for
+ this function. It is used in index files.
+ */
+QString FunctionNode::metanessString() const
+{
+ switch (m_metaness) {
+ case FunctionNode::Plain:
+ return "plain";
+ case FunctionNode::Signal:
+ return "signal";
+ case FunctionNode::Slot:
+ return "slot";
+ case FunctionNode::Ctor:
+ return "constructor";
+ case FunctionNode::CCtor:
+ return "copy-constructor";
+ case FunctionNode::MCtor:
+ return "move-constructor";
+ case FunctionNode::Dtor:
+ return "destructor";
+ case FunctionNode::MacroWithParams:
+ return "macrowithparams";
+ case FunctionNode::MacroWithoutParams:
+ return "macrowithoutparams";
+ case FunctionNode::Native:
+ return "native";
+ case FunctionNode::CAssign:
+ return "copy-assign";
+ case FunctionNode::MAssign:
+ return "move-assign";
+ case FunctionNode::QmlSignal:
+ return "qmlsignal";
+ case FunctionNode::QmlSignalHandler:
+ return "qmlsignalhandler";
+ case FunctionNode::QmlMethod:
+ return "qmlmethod";
+ default:
+ return "plain";
+ }
+}
+
+/*!
+ Adds the "associated" property \a p to this function node.
+ The function might be the setter or getter for a property,
+ for example.
+ */
+void FunctionNode::addAssociatedProperty(PropertyNode *p)
+{
+ m_associatedProperties.append(p);
+}
+
+/*!
+ \reimp
+
+ Returns \c true if this is an access function for an obsolete property,
+ otherwise calls the base implementation of isDeprecated().
+*/
+bool FunctionNode::isDeprecated() const
+{
+ auto it = std::find_if_not(m_associatedProperties.begin(), m_associatedProperties.end(),
+ [](const Node *p) -> bool { return p->isDeprecated(); });
+
+ if (!m_associatedProperties.isEmpty() && it == m_associatedProperties.end())
+ return true;
+
+ return Node::isDeprecated();
+}
+
+/*! \fn unsigned char FunctionNode::overloadNumber() const
+ Returns the overload number for this function.
+ */
+
+/*!
+ Reconstructs and returns the function's signature.
+
+ Specific parts of the signature are included according to
+ flags in \a options:
+
+ \value Node::SignaturePlain
+ Plain signature
+ \value Node::SignatureDefaultValues
+ Include any default argument values
+ \value Node::SignatureReturnType
+ Include return type
+ \value Node::SignatureTemplateParams
+ Include \c {template <parameter_list>} if one exists
+ */
+QString FunctionNode::signature(Node::SignatureOptions options) const
+{
+ QStringList elements;
+
+ if (options & Node::SignatureTemplateParams && templateDecl())
+ elements << (*templateDecl()).to_qstring();
+ if (options & Node::SignatureReturnType)
+ elements << m_returnType;
+ elements.removeAll(QString());
+
+ if (!isMacroWithoutParams()) {
+ elements << name() + QLatin1Char('(')
+ + m_parameters.signature(options & Node::SignatureDefaultValues)
+ + QLatin1Char(')');
+ if (!isMacro()) {
+ if (isConst())
+ elements << QStringLiteral("const");
+ if (isRef())
+ elements << QStringLiteral("&");
+ else if (isRefRef())
+ elements << QStringLiteral("&&");
+ }
+ } else {
+ elements << name();
+ }
+ return elements.join(QLatin1Char(' '));
+}
+
+/*!
+ \fn int FunctionNode::compare(const FunctionNode *f1, const FunctionNode *f2)
+
+ Compares FunctionNode \a f1 with \a f2, assumed to have identical names.
+ Returns an integer less than, equal to, or greater than zero if f1 is
+ considered less than, equal to, or greater than f2.
+
+ The main purpose is to provide stable ordering for function overloads.
+ */
+[[nodiscard]] int compare(const FunctionNode *f1, const FunctionNode *f2)
+{
+ // Compare parameter count
+ int param_count{f1->parameters().count()};
+
+ if (int param_diff = param_count - f2->parameters().count(); param_diff != 0)
+ return param_diff;
+
+ // Constness
+ if (f1->isConst() != f2->isConst())
+ return f1->isConst() ? 1 : -1;
+
+ // Reference qualifiers
+ if (f1->isRef() != f2->isRef())
+ return f1->isRef() ? 1 : -1;
+ if (f1->isRefRef() != f2->isRefRef())
+ return f1->isRefRef() ? 1 : -1;
+
+ // Attachedness (applies to QML methods)
+ if (f1->isAttached() != f2->isAttached())
+ return f1->isAttached() ? 1 : -1;
+
+ // Parameter types
+ const Parameters &p1{f1->parameters()};
+ const Parameters &p2{f2->parameters()};
+ for (qsizetype i = 0; i < param_count; ++i) {
+ if (int type_comp = QString::compare(p1.at(i).type(), p2.at(i).type());
+ type_comp != 0) {
+ return type_comp;
+ }
+ }
+
+ // Template declarations
+ const auto t1{f1->templateDecl()};
+ const auto t2{f2->templateDecl()};
+ if (!t1 && !t2)
+ return 0;
+
+ if (t1 && t2)
+ return (*t1).to_std_string().compare((*t2).to_std_string());
+
+ return t1 ? 1 : -1;
+}
+
+/*!
+ In some cases, it is ok for a public function to be not documented.
+ For example, the macro Q_OBJECT adds several functions to the API of
+ a class, but these functions are normally not meant to be documented.
+ So if a function node doesn't have documentation, then if its name is
+ in the list of functions that it is ok not to document, this function
+ returns true. Otherwise, it returns false.
+
+ These are the member function names added by macros. Usually they
+ are not documented, but they can be documented, so this test avoids
+ reporting an error if they are not documented.
+
+ But maybe we should generate a standard text for each of them?
+ */
+bool FunctionNode::isIgnored() const
+{
+ if (!hasDoc()) {
+ if (name().startsWith(QLatin1String("qt_")) || name() == QLatin1String("metaObject")
+ || name() == QLatin1String("tr") || name() == QLatin1String("trUtf8")
+ || name() == QLatin1String("d_func")) {
+ return true;
+ }
+ QString s = signature(Node::SignatureReturnType);
+ if (s.contains(QLatin1String("enum_type")) && s.contains(QLatin1String("operator|")))
+ return true;
+ }
+ return false;
+}
+
+/*!
+ \fn bool FunctionNode::hasOverloads() const
+ Returns \c true if this function has overloads.
+ */
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/functionnode.h b/src/qdoc/qdoc/src/qdoc/functionnode.h
new file mode 100644
index 000000000..dca8c7e44
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/functionnode.h
@@ -0,0 +1,202 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef FUNCTIONNODE_H
+#define FUNCTIONNODE_H
+
+#include "aggregate.h"
+#include "node.h"
+#include "parameters.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+class FunctionNode : public Node
+{
+public:
+ enum Virtualness { NonVirtual, NormalVirtual, PureVirtual };
+
+ enum Metaness {
+ Plain,
+ Signal,
+ Slot,
+ Ctor,
+ Dtor,
+ CCtor, // copy constructor
+ MCtor, // move-copy constructor
+ MacroWithParams,
+ MacroWithoutParams,
+ Native,
+ CAssign, // copy-assignment operator
+ MAssign, // move-assignment operator
+ QmlSignal,
+ QmlSignalHandler,
+ QmlMethod,
+ };
+
+ FunctionNode(Aggregate *parent, const QString &name); // C++ function (Plain)
+ FunctionNode(Metaness type, Aggregate *parent, const QString &name, bool attached = false);
+
+ Node *clone(Aggregate *parent) override;
+ [[nodiscard]] Metaness metaness() const { return m_metaness; }
+ [[nodiscard]] QString metanessString() const;
+ void setMetaness(Metaness metaness) { m_metaness = metaness; }
+ [[nodiscard]] QString kindString() const;
+ static Metaness getMetaness(const QString &value);
+ static Metaness getMetanessFromTopic(const QString &topic);
+ static Genus getGenus(Metaness metaness);
+
+ void setReturnType(const QString &type) { m_returnType = type; }
+ void setVirtualness(const QString &value);
+ void setVirtualness(Virtualness virtualness) { m_virtualness = virtualness; }
+ void setConst(bool b) { m_const = b; }
+ void setDefault(bool b) { m_default = b; }
+ void setStatic(bool b) { m_static = b; }
+ void setReimpFlag() { m_reimpFlag = true; }
+ void setOverridesThis(const QString &path) { m_overridesThis = path; }
+
+ [[nodiscard]] const QString &returnType() const { return m_returnType; }
+ [[nodiscard]] QString virtualness() const;
+ [[nodiscard]] bool isConst() const { return m_const; }
+ [[nodiscard]] bool isDefault() const override { return m_default; }
+ [[nodiscard]] bool isStatic() const override { return m_static; }
+ [[nodiscard]] bool isOverload() const { return m_overloadFlag; }
+ [[nodiscard]] bool isMarkedReimp() const override { return m_reimpFlag; }
+ [[nodiscard]] bool isSomeCtor() const { return isCtor() || isCCtor() || isMCtor(); }
+ [[nodiscard]] bool isMacroWithParams() const { return (m_metaness == MacroWithParams); }
+ [[nodiscard]] bool isMacroWithoutParams() const { return (m_metaness == MacroWithoutParams); }
+ [[nodiscard]] bool isMacro() const override
+ {
+ return (isMacroWithParams() || isMacroWithoutParams());
+ }
+ [[nodiscard]] bool isDeprecated() const override;
+
+ void markExplicit() { m_explicit = true; }
+ bool isExplicit() const { return m_explicit; }
+
+ void markConstexpr() { m_constexpr = true; }
+ bool isConstexpr() const { return m_constexpr; }
+
+ void markNoexcept(QString expression = "") { m_noexcept = expression; }
+ const std::optional<QString>& getNoexcept() const { return m_noexcept; }
+
+ [[nodiscard]] bool isCppFunction() const { return m_metaness == Plain; } // Is this correct?
+ [[nodiscard]] bool isSignal() const { return (m_metaness == Signal); }
+ [[nodiscard]] bool isSlot() const { return (m_metaness == Slot); }
+ [[nodiscard]] bool isCtor() const { return (m_metaness == Ctor); }
+ [[nodiscard]] bool isDtor() const { return (m_metaness == Dtor); }
+ [[nodiscard]] bool isCCtor() const { return (m_metaness == CCtor); }
+ [[nodiscard]] bool isMCtor() const { return (m_metaness == MCtor); }
+ [[nodiscard]] bool isCAssign() const { return (m_metaness == CAssign); }
+ [[nodiscard]] bool isMAssign() const { return (m_metaness == MAssign); }
+
+ [[nodiscard]] bool isQmlMethod() const { return (m_metaness == QmlMethod); }
+ [[nodiscard]] bool isQmlSignal() const { return (m_metaness == QmlSignal); }
+ [[nodiscard]] bool isQmlSignalHandler() const { return (m_metaness == QmlSignalHandler); }
+
+ [[nodiscard]] bool isSpecialMemberFunction() const
+ {
+ return (isCtor() || isDtor() || isCCtor() || isMCtor() || isCAssign() || isMAssign());
+ }
+ [[nodiscard]] bool isNonvirtual() const { return (m_virtualness == NonVirtual); }
+ [[nodiscard]] bool isVirtual() const { return (m_virtualness == NormalVirtual); }
+ [[nodiscard]] bool isPureVirtual() const { return (m_virtualness == PureVirtual); }
+ [[nodiscard]] bool returnsBool() const { return (m_returnType == QLatin1String("bool")); }
+
+ Parameters &parameters() { return m_parameters; }
+ [[nodiscard]] const Parameters &parameters() const { return m_parameters; }
+ [[nodiscard]] bool isPrivateSignal() const { return m_parameters.isPrivateSignal(); }
+ void setParameters(const QString &signature) { m_parameters.set(signature); }
+ [[nodiscard]] QString signature(Node::SignatureOptions options) const override;
+
+ [[nodiscard]] const QString &overridesThis() const { return m_overridesThis; }
+ [[nodiscard]] const QList<PropertyNode *> &associatedProperties() const { return m_associatedProperties; }
+ [[nodiscard]] bool hasAssociatedProperties() const { return !m_associatedProperties.isEmpty(); }
+ [[nodiscard]] bool hasOneAssociatedProperty() const
+ {
+ return (m_associatedProperties.size() == 1);
+ }
+ [[nodiscard]] QString element() const override { return parent()->name(); }
+ [[nodiscard]] bool isAttached() const override { return m_attached; }
+ [[nodiscard]] QString qmlTypeName() const override { return parent()->qmlTypeName(); }
+ [[nodiscard]] QString logicalModuleName() const override
+ {
+ return parent()->logicalModuleName();
+ }
+ [[nodiscard]] QString logicalModuleVersion() const override
+ {
+ return parent()->logicalModuleVersion();
+ }
+ [[nodiscard]] QString logicalModuleIdentifier() const override
+ {
+ return parent()->logicalModuleIdentifier();
+ }
+
+ void setFinal(bool b) { m_isFinal = b; }
+ [[nodiscard]] bool isFinal() const { return m_isFinal; }
+
+ void setOverride(bool b) { m_isOverride = b; }
+ [[nodiscard]] bool isOverride() const { return m_isOverride; }
+
+ void setRef(bool b) { m_isRef = b; }
+ [[nodiscard]] bool isRef() const { return m_isRef; }
+
+ void setRefRef(bool b) { m_isRefRef = b; }
+ [[nodiscard]] bool isRefRef() const { return m_isRefRef; }
+
+ void setInvokable(bool b) { m_isInvokable = b; }
+ [[nodiscard]] bool isInvokable() const { return m_isInvokable; }
+
+ [[nodiscard]] bool hasTag(const QString &tag) const override { return (m_tag == tag); }
+ void setTag(const QString &tag) { m_tag = tag; }
+ [[nodiscard]] const QString &tag() const { return m_tag; }
+ [[nodiscard]] bool isIgnored() const;
+ [[nodiscard]] bool hasOverloads() const
+ {
+ return (m_overloadFlag || (parent() && parent()->hasOverloads(this)));
+ }
+ void setOverloadFlag() { m_overloadFlag = true; }
+ void setOverloadNumber(signed short number);
+ [[nodiscard]] signed short overloadNumber() const { return m_overloadNumber; }
+
+ friend int compare(const FunctionNode *f1, const FunctionNode *f2);
+
+private:
+ void addAssociatedProperty(PropertyNode *property);
+
+ friend class Aggregate;
+ friend class PropertyNode;
+
+ bool m_const : 1;
+ bool m_default : 1;
+ bool m_static : 1;
+ bool m_reimpFlag : 1;
+ bool m_attached : 1;
+ bool m_overloadFlag : 1;
+ bool m_isFinal : 1;
+ bool m_isOverride : 1;
+ bool m_isRef : 1;
+ bool m_isRefRef : 1;
+ bool m_isInvokable : 1;
+ bool m_explicit;
+ bool m_constexpr;
+
+ std::optional<QString> m_noexcept;
+
+ Metaness m_metaness {};
+ Virtualness m_virtualness{ NonVirtual };
+ signed short m_overloadNumber {};
+ QString m_returnType {};
+ QString m_overridesThis {};
+ QString m_tag {};
+ QList<PropertyNode *> m_associatedProperties {};
+ Parameters m_parameters {};
+};
+
+QT_END_NAMESPACE
+
+#endif // FUNCTIONNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/generator.cpp b/src/qdoc/qdoc/src/qdoc/generator.cpp
new file mode 100644
index 000000000..d1b3642c3
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/generator.cpp
@@ -0,0 +1,2216 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "generator.h"
+
+#include "access.h"
+#include "aggregate.h"
+#include "classnode.h"
+#include "codemarker.h"
+#include "collectionnode.h"
+#include "comparisoncategory.h"
+#include "config.h"
+#include "doc.h"
+#include "editdistance.h"
+#include "enumnode.h"
+#include "examplenode.h"
+#include "functionnode.h"
+#include "node.h"
+#include "openedlist.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "qmltypenode.h"
+#include "qmlpropertynode.h"
+#include "quoter.h"
+#include "sharedcommentnode.h"
+#include "tokenizer.h"
+#include "typedefnode.h"
+#include "utilities.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qregularexpression.h>
+
+#ifndef QT_BOOTSTRAPPED
+# include "QtCore/qurl.h"
+#endif
+
+#include <string>
+
+using namespace std::literals::string_literals;
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+Generator *Generator::s_currentGenerator;
+QMap<QString, QMap<QString, QString>> Generator::s_fmtLeftMaps;
+QMap<QString, QMap<QString, QString>> Generator::s_fmtRightMaps;
+QList<Generator *> Generator::s_generators;
+QString Generator::s_outDir;
+QString Generator::s_outSubdir;
+QStringList Generator::s_outFileNames;
+QSet<QString> Generator::s_trademarks;
+QSet<QString> Generator::s_outputFormats;
+QHash<QString, QString> Generator::s_outputPrefixes;
+QHash<QString, QString> Generator::s_outputSuffixes;
+QString Generator::s_project;
+bool Generator::s_noLinkErrors = false;
+bool Generator::s_autolinkErrors = false;
+bool Generator::s_redirectDocumentationToDevNull = false;
+bool Generator::s_useOutputSubdirs = true;
+QmlTypeNode *Generator::s_qmlTypeContext = nullptr;
+
+static QRegularExpression tag("</?@[^>]*>");
+static QLatin1String amp("&amp;");
+static QLatin1String gt("&gt;");
+static QLatin1String lt("&lt;");
+static QLatin1String quot("&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(FileResolver& file_resolver)
+ : file_resolver{file_resolver}
+{
+ m_qdb = QDocDatabase::qdocDB();
+ s_generators.prepend(this);
+}
+
+/*!
+ Destroys the generator after removing it from the list of
+ output generators.
+ */
+Generator::~Generator()
+{
+ s_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);
+}
+
+/*!
+ Append the signature for the function named in \a node to
+ \a text, so that 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(Node::SignaturePlain))
+ << 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 << Utilities::comma(index++, classNames.size());
+ }
+ return index;
+}
+
+int Generator::appendSortedQmlNames(Text &text, const Node *base, const NodeList &subs)
+{
+ QMap<QString, Text> classMap;
+
+ for (const auto sub : subs) {
+ Text full_name;
+ appendFullName(full_name, sub, base);
+ classMap[full_name.toString().toLower()] = full_name;
+ }
+
+ int index = 0;
+ const auto &names = classMap.keys();
+ for (const auto &name : names)
+ text << classMap[name] << Utilities::comma(index++, names.size());
+ 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 beginSubPage()
+ */
+QFile *Generator::openSubPageFile(const Node *node, const QString &fileName)
+{
+ if (s_outFileNames.contains(fileName))
+ node->location().warning("Already generated %1 for this project"_L1.arg(fileName));
+
+ QString path = outputDir() + QLatin1Char('/') + fileName;
+
+ auto outPath = s_redirectDocumentationToDevNull ? QStringLiteral("/dev/null") : path;
+ auto outFile = new QFile(outPath);
+
+ if (!s_redirectDocumentationToDevNull && outFile->exists()) {
+ const QString warningText {"Output file already exists, overwriting %1"_L1.arg(outFile->fileName())};
+ if (qEnvironmentVariableIsSet("QDOC_ALL_OVERWRITES_ARE_WARNINGS"))
+ node->location().warning(warningText);
+ else
+ qCDebug(lcQdoc) << qUtf8Printable(warningText);
+ }
+
+ if (!outFile->open(QFile::WriteOnly | QFile::Text)) {
+ node->location().fatal(
+ QStringLiteral("Cannot open output file '%1'").arg(outFile->fileName()));
+ }
+
+ qCDebug(lcQdoc, "Writing: %s", qPrintable(path));
+ s_outFileNames << fileName;
+ s_trademarks.clear();
+ 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().
+ */
+void Generator::beginSubPage(const Node *node, const QString &fileName)
+{
+ QFile *outFile = openSubPageFile(node, fileName);
+ auto *out = new QTextStream(outFile);
+ outStreamStack.push(out);
+}
+
+/*!
+ 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();
+}
+
+QString Generator::fileBase(const Node *node) const
+{
+ if (!node->isPageNode() && !node->isCollectionNode())
+ node = node->parent();
+
+ if (node->hasFileNameBase())
+ return node->fileNameBase();
+
+ QString base{node->name()};
+ if (base.endsWith(".html"))
+ base.truncate(base.size() - 5);
+
+ if (node->isCollectionNode()) {
+ if (node->isQmlModule())
+ base.append("-qmlmodule");
+ else if (node->isModule())
+ base.append("-module");
+ base.append(outputSuffix(node));
+ } else if (node->isTextPageNode()) {
+ if (node->isExample()) {
+ base.prepend("%1-"_L1.arg(s_project.toLower()));
+ base.append("-example");
+ }
+ } else if (node->isQmlType()) {
+ /*
+ 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.
+
+ For historical reasons, skip the module name qualifier for QML value types
+ in order to avoid excess redirects in the online docs. TODO: re-assess
+ */
+ if (!node->logicalModuleName().isEmpty() && !node->isQmlBasicType()
+ && (!node->logicalModule()->isInternal() || m_showInternal))
+ base.prepend("%1%2-"_L1.arg(node->logicalModuleName(), outputSuffix(node)));
+
+ } else if (node->isProxyNode()) {
+ base.append("-%1-proxy"_L1.arg(node->tree()->physicalModuleName()));
+ } else {
+ base.clear();
+ 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('-'_L1);
+ p = pp;
+ }
+ if (node->isNamespace() && !node->name().isEmpty()) {
+ const auto *ns = static_cast<const NamespaceNode *>(node);
+ if (!ns->isDocumentedHere()) {
+ base.append(QLatin1String("-sub-"));
+ base.append(ns->tree()->camelCaseModuleName());
+ }
+ }
+ base.append(outputSuffix(node));
+ }
+
+ base.prepend(outputPrefix(node));
+ QString canonicalName{ Utilities::asAsciiPrintable(base) };
+ Node *n = const_cast<Node *>(node);
+ n->setFileNameBase(canonicalName);
+ return canonicalName;
+}
+
+/*!
+ Constructs an href link from an example file name, which
+ is a \a path to the example file. If \a fileExt is empty
+ (default value), retrieve the file extension from
+ the generator.
+ */
+QString Generator::linkForExampleFile(const QString &path, const QString &fileExt)
+{
+ QString link{path};
+ link.prepend(s_project.toLower() + QLatin1Char('-'));
+
+ QString canonicalName{ Utilities::asAsciiPrintable(link) };
+ canonicalName.append(QLatin1Char('.'));
+ canonicalName.append(fileExt.isEmpty() ? fileExtension() : fileExt);
+ return canonicalName;
+}
+
+/*!
+ 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 name + (extension.isNull() ? fileExtension() : extension);
+}
+
+/*!
+ Clean the given \a ref to be used as an HTML anchor or an \c xml:id.
+ If \a xmlCompliant is set to \c true, a stricter process is used, as XML
+ is more rigorous in what it accepts. Otherwise, if \a xmlCompliant is set to
+ \c false, the basic HTML transformations are applied.
+
+ More specifically, only XML NCNames are allowed
+ (https://www.w3.org/TR/REC-xml-names/#NT-NCName).
+ */
+QString Generator::cleanRef(const QString &ref, bool xmlCompliant)
+{
+ // XML-compliance is ensured in two ways:
+ // - no digit (0-9) at the beginning of an ID (many IDs do not respect this property)
+ // - no colon (:) anywhere in the ID (occurs very rarely)
+
+ 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') || (!xmlCompliant && u >= '0' && u <= '9')) {
+ clean += c;
+ } else if (xmlCompliant && u >= '0' && u <= '9') {
+ clean += QLatin1Char('A') + c;
+ } else if (u == '~') {
+ clean += "dtor.";
+ } else if (u == '_') {
+ clean += "underscore.";
+ } else {
+ clean += QLatin1Char('A');
+ }
+
+ for (int i = 1; i < ref.size(); 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 == '_' || (xmlCompliant && 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 s_fmtLeftMaps[format()];
+}
+
+QMap<QString, QString> &Generator::formattingRightMap()
+{
+ return s_fmtRightMaps[format()];
+}
+
+/*!
+ Returns the full document location.
+ */
+QString Generator::fullDocumentLocation(const Node *node)
+{
+ if (node == nullptr)
+ return QString();
+ if (!node->url().isEmpty())
+ return node->url();
+
+ QString parentName;
+ QString anchorRef;
+
+ 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()) {
+ return 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 auto *fn = static_cast<const FunctionNode *>(node);
+ switch (fn->metaness()) {
+ case FunctionNode::QmlSignal:
+ anchorRef = QLatin1Char('#') + node->name() + "-signal";
+ break;
+ case FunctionNode::QmlSignalHandler:
+ anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
+ break;
+ 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->associatedProperties()[0]);
+ 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::Typedef: {
+ const auto *tdef = static_cast<const TypedefNode *>(node);
+ if (tdef->associatedEnum())
+ return fullDocumentLocation(tdef->associatedEnum());
+ } Q_FALLTHROUGH();
+ case Node::TypeAlias:
+ anchorRef = QLatin1Char('#') + node->name() + "-typedef";
+ break;
+ case Node::Property:
+ anchorRef = QLatin1Char('#') + node->name() + "-prop";
+ break;
+ case Node::SharedComment: {
+ if (!node->isPropertyGroup())
+ break;
+ } Q_FALLTHROUGH();
+ 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::QmlType:
+ case Node::Page:
+ case Node::Group:
+ case Node::HeaderFile:
+ case Node::Module:
+ 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->isDeprecated())
+ parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
+ "-obsolete." + currentGenerator()->fileExtension());
+ }
+
+ return 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) << Utilities::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(), 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()) {
+ /*
+ 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->isProperty()) {
+ if (static_cast<const PropertyNode *>(node)->propertyType() != PropertyNode::PropertyType::StandardProperty)
+ 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 auto *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.size() > definedItems.size()
+ || allItems.size() > documentedItems.size()) {
+ 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, node->plainFullName()),
+ details);
+ } else if (!documentedItems.contains(it)) {
+ node->doc().location().warning(
+ QStringLiteral("Undocumented enum item '%1' in %2")
+ .arg(it, 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()) {
+ // Require no parameter documentation for overrides and overloads,
+ // and only require it for non-overloaded constructors.
+ if (!fn->isMarkedReimp() && !fn->isOverload() &&
+ !(fn->isSomeCtor() && fn->hasOverloads())) {
+ fn->doc().location().warning(
+ QStringLiteral("Undocumented parameter '%1' in %2")
+ .arg(name, 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, fn->plainFullName()),
+ details);
+ }
+ }
+ }
+ /*
+ This return value check should be implemented
+ for all functions with a return type.
+ mws 13/12/2018
+ */
+ if (!fn->isDeprecated() && 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"));
+ }
+ }
+ }
+ generateEnumValuesForQmlProperty(node, marker);
+ 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 auto *en = static_cast<const ExampleNode *>(node);
+ QString exampleUrl{Config::instance().get(CONFIG_URL + Config::dot + CONFIG_EXAMPLES).asString()};
+
+ 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().get(CONFIG_EXAMPLESINSTALLPATH).asString();
+ QStringList path = QStringList() << pathRoot << en->name();
+ path.removeAll(QString());
+
+ 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 ResolvedFile& resolved_file)
+{
+ QDir dirInfo;
+ // TODO: [uncentralized-output-directory-structure]
+ const QString prefix("/images/used-in-examples");
+
+ // TODO: Generators probably should not need to keep track of which files were generated.
+ // Understand if we really need this information and where it should
+ // belong, considering that it should be part of whichever system
+ // would actually store the file itself.
+ s_outFileNames << prefix.mid(1) + "/" + resolved_file.get_query();
+
+
+ // TODO: [uncentralized-output-directory-structure]
+ QString imgOutDir = s_outDir + prefix + "/" + QFileInfo{resolved_file.get_query()}.path();
+ if (!dirInfo.mkpath(imgOutDir))
+ en->location().fatal(QStringLiteral("Cannot create output directory '%1'").arg(imgOutDir));
+ Config::copyFile(en->location(), resolved_file.get_path(), QFileInfo{resolved_file.get_query()}.fileName(), imgOutDir);
+}
+
+// TODO: [multi-purpose-function-with-flag][generate-file-list]
+// Avoid the use of a boolean flag to dispatch to the correct
+// implementation trough branching.
+// We always have to process both images and files, such that we
+// should consider to remove the branching altogheter, performing both
+// operations in a single call.
+// Otherwise, if this turns out to be infeasible, complex or
+// possibly-confusing, consider extracting the processing code outside
+// the function and provide two higer-level dispathing functions for
+// files and images.
+
+/*!
+ 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());
+
+ for (const auto &path : std::as_const(paths)) {
+ auto maybe_resolved_file{file_resolver.resolve(path)};
+ if (!maybe_resolved_file) {
+ // TODO: [uncentralized-admonition][failed-resolve-file]
+ QString details = std::transform_reduce(
+ file_resolver.get_search_directories().cbegin(),
+ file_resolver.get_search_directories().cend(),
+ u"Searched directories:"_s,
+ std::plus(),
+ [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
+ );
+
+ en->location().warning(u"(Generator)Cannot find file to quote from: %1"_s.arg(path), details);
+
+ continue;
+ }
+
+ auto file{*maybe_resolved_file};
+ if (images) 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.get_query()) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << file.get_query()
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight
+ << Atom(Atom::ListItemRight, openedList.styleString());
+ }
+ text << Atom(Atom::ListRight, openedList.styleString());
+ if (!paths.isEmpty())
+ generateText(text, en, 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, or QML
+ 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, or \qmlmodule 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 or QML 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.
+ */
+ auto *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()) {
+ beginSubPage(node, fileName(node));
+ auto *qcn = static_cast<QmlTypeNode *>(node);
+ generateQmlTypePage(qcn, marker);
+ endSubPage();
+ } else if (node->isProxyNode()) {
+ beginSubPage(node, fileName(node));
+ generateProxyPage(static_cast<Aggregate *>(node), marker);
+ endSubPage();
+ }
+ }
+ }
+
+ if (node->isAggregate()) {
+ auto *aggregate = static_cast<Aggregate *>(node);
+ const NodeList &children = aggregate->childNodes();
+ for (auto *child : children) {
+ if (child->isPageNode() && !child->isPrivate()) {
+ generateDocumentation(child);
+ } else if (!node->parent() && child->isInAPI() && !child->isRelatedNonmember()) {
+ // Warn if there are documented non-page-generating nodes in the root namespace
+ child->location().warning(u"No documentation generated for %1 '%2' in global scope."_s
+ .arg(typeString(child), child->name()),
+ u"Maybe you forgot to use the '\\relates' command?"_s);
+ child->setStatus(Node::DontDocument);
+ } else if (child->isQmlModule() && !child->wasSeen()) {
+ // An undocumented QML module that was constructed as a placeholder
+ auto *qmlModule = static_cast<CollectionNode *>(child);
+ for (const auto *member : qmlModule->members()) {
+ member->location().warning(
+ u"Undocumented QML module '%1' referred by type '%2' or its members"_s
+ .arg(qmlModule->name(), member->name()),
+ u"Maybe you forgot to document '\\qmlmodule %1'?"_s
+ .arg(qmlModule->name()));
+ }
+ } else if (child->isQmlType() && !child->hasDoc()) {
+ // A placeholder QML type with incorrect module identifier
+ auto *qmlType = static_cast<QmlTypeNode *>(child);
+ if (auto qmid = qmlType->logicalModuleName(); !qmid.isEmpty())
+ qmlType->location().warning(u"No such type '%1' in QML module '%2'"_s
+ .arg(qmlType->name(), qmid));
+ }
+ }
+ }
+}
+
+void Generator::generateReimplementsClause(const FunctionNode *fn, CodeMarker *marker)
+{
+ if (fn->overridesThis().isEmpty() || !fn->parent()->isClassNode())
+ return;
+
+ auto *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(Node::SignaturePlain);
+ 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.size() == 1)
+ return "Qt " + since[0];
+
+ // Otherwise, use the original <project> <version> string.
+ return node->since();
+}
+
+/*!
+ \internal
+ Returns a string representing status information of a \a node.
+
+ If a status description is returned, it is one of:
+ \list
+ \li Custom status set explicitly in node's documentation using
+ \c {\meta {status} {<description>}},
+ \li 'Deprecated [since <version>]' (\\deprecated [<version>]),
+ \li 'Until <version>',
+ \li 'Preliminary' (\\preliminary), or
+ \li The description adopted from associated module's state:
+ \c {\modulestate {<description>}}.
+ \endlist
+
+ Otherwise, returns \c std::nullopt.
+*/
+std::optional<QString> formatStatus(const Node *node, QDocDatabase *qdb)
+{
+ QString status;
+
+ if (const auto metaMap = node->doc().metaTagMap(); metaMap) {
+ status = metaMap->value("status");
+ if (!status.isEmpty())
+ return {status};
+ }
+ const auto since = node->deprecatedSince();
+ if (node->status() == Node::Deprecated) {
+ status = u"Deprecated"_s;
+ if (!since.isEmpty())
+ status += " since %1"_L1.arg(since);
+ } else if (!since.isEmpty()) {
+ status = "Until %1"_L1.arg(since);
+ } else if (node->status() == Node::Preliminary) {
+ status = u"Preliminary"_s;
+ } else if (const auto collection = qdb->getModuleNode(node); collection) {
+ status = collection->state();
+ }
+
+ return status.isEmpty() ? std::nullopt : std::optional(status);
+}
+
+void Generator::generateSince(const Node *node, CodeMarker *marker)
+{
+ if (!node->since().isEmpty()) {
+ Text text;
+ text << Atom::ParaLeft << "This " << typeString(node) << " was introduced in "
+ << formatSince(node) << "." << Atom::ParaRight;
+ generateText(text, node, marker);
+ }
+}
+
+void Generator::generateNoexceptNote(const Node* node, CodeMarker* marker) {
+ std::vector<const Node*> nodes;
+ if (node->isSharedCommentNode()) {
+ auto shared_node = static_cast<const SharedCommentNode*>(node);
+ nodes.reserve(shared_node->collective().size());
+ nodes.insert(nodes.begin(), shared_node->collective().begin(), shared_node->collective().end());
+ } else nodes.push_back(node);
+
+ std::size_t counter{1};
+ for (const Node* node : nodes) {
+ if (node->isFunction(Node::CPP)) {
+ if (auto exception_info = static_cast<const FunctionNode*>(node)->getNoexcept(); exception_info && !(*exception_info).isEmpty()) {
+ Text text;
+ text << Atom::NoteLeft
+ << (nodes.size() > 1 ? QString::fromStdString(" ("s + std::to_string(counter) + ")"s) : QString::fromStdString("This ") + typeString(node))
+ << " does not throw any exception when " << "\"" << *exception_info << "\"" << " is true."
+ << Atom::NoteRight;
+ generateText(text, node, marker);
+ }
+ }
+
+ ++counter;
+ }
+}
+
+void Generator::generateStatus(const Node *node, CodeMarker *marker)
+{
+ Text text;
+
+ switch (node->status()) {
+ case Node::Active:
+ // Output the module 'state' description if set.
+ if (node->isModule() || node->isQmlModule()) {
+ const QString &state = static_cast<const CollectionNode*>(node)->state();
+ if (!state.isEmpty()) {
+ text << Atom::ParaLeft << "This " << typeString(node) << " is in "
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_ITALIC) << state
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_ITALIC) << " state."
+ << Atom::ParaRight;
+ break;
+ }
+ }
+ if (const auto version = node->deprecatedSince(); !version.isEmpty()) {
+ text << Atom::ParaLeft << "This " << typeString(node)
+ << " is scheduled for deprecation in version "
+ << version << "." << Atom::ParaRight;
+ }
+ 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 (const QString &version = node->deprecatedSince(); !version.isEmpty()) {
+ text << " since ";
+ if (node->isQmlNode() && !node->logicalModuleName().isEmpty())
+ text << node->logicalModuleName() << " ";
+ text << version;
+ }
+
+ text << ". We strongly advise against using it in new code.";
+ if (node->isAggregate())
+ text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
+ text << 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(Atom::DivLeft,
+ "class=\"admonition %1\""_L1.arg(generateNote ? u"note"_s : u"auto"_s));
+ 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::AutoLink, "Q_INVOKABLE")
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << 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());
+ qsizetype prefixLocation = handler.lastIndexOf('.', -2) + 1;
+ handler[prefixLocation] = handler[prefixLocation].toTitleCase();
+ handler.insert(prefixLocation, 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 auto *fn = static_cast<const FunctionNode *>(node);
+ auto nodes = fn->associatedProperties();
+ if (nodes.isEmpty())
+ return;
+ std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan);
+ for (const auto *n : std::as_const(nodes)) {
+ QString msg;
+ const auto *pn = static_cast<const PropertyNode *>(n);
+ switch (pn->role(fn)) {
+ case PropertyNode::FunctionRole::Getter:
+ msg = QStringLiteral("Getter function");
+ break;
+ case PropertyNode::FunctionRole::Setter:
+ msg = QStringLiteral("Setter function");
+ break;
+ case PropertyNode::FunctionRole::Resetter:
+ msg = QStringLiteral("Resetter function");
+ break;
+ case PropertyNode::FunctionRole::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 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
+ << Atom::DivRight;
+ 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->isDeprecated()) {
+ 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;
+}
+
+/*!
+ Returns \c true if a trademark symbol should be appended to the
+ output as determined by \a atom. Trademarks are tracked via the
+ use of the \\tm formatting command.
+
+ Returns true if:
+
+ \list
+ \li \a atom is of type Atom::FormattingRight containing
+ ATOM_FORMATTING_TRADEMARK, and
+ \li The trademarked string is the first appearance on the
+ current sub-page.
+ \endlist
+*/
+bool Generator::appendTrademark(const Atom *atom)
+{
+ if (atom->type() != Atom::FormattingRight)
+ return false;
+ if (atom->string() != ATOM_FORMATTING_TRADEMARK)
+ return false;
+
+ if (atom->count() > 1) {
+ if (s_trademarks.contains(atom->string(1)))
+ return false;
+ s_trademarks << atom->string(1);
+ }
+
+ return true;
+}
+
+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);
+ }
+ }
+ }
+}
+
+/*!
+ \internal
+
+ Generates text that describes the comparison category of \a node.
+ The CodeMarker \a marker is passed along to generateText().
+ */
+bool Generator::generateComparisonCategory(const Node *node, CodeMarker *marker)
+{
+ auto category{node->comparisonCategory()};
+ if (category == ComparisonCategory::None)
+ return false;
+
+ Text text;
+ text << Atom::ParaLeft << "This %1 is "_L1.arg(typeString(node))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_ITALIC)
+ << QString::fromStdString(comparisonCategoryAsString(category))
+ << ((category == ComparisonCategory::Equality) ? "-"_L1 : "ly "_L1)
+ << Atom(Atom::String, "comparable"_L1)
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_ITALIC)
+ << "."_L1 << Atom::ParaRight;
+ generateText(text, node, marker);
+ return true;
+}
+
+/*!
+ Generates a list of types that compare to \a node with the comparison
+ category that applies for the relationship, followed by (an optional)
+ descriptive text.
+
+ Returns \c true if text was generated, \c false otherwise.
+ */
+bool Generator::generateComparisonList(const Node *node)
+{
+ Q_ASSERT(node);
+ if (!node->doc().comparesWithMap())
+ return false;
+
+ Text relationshipText;
+ for (auto [key, description] : node->doc().comparesWithMap()->asKeyValueRange()) {
+ const QString &category = QString::fromStdString(comparisonCategoryAsString(key));
+
+ relationshipText << Atom::ParaLeft << "This %1 is "_L1.arg(typeString(node))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << category
+ << ((key == ComparisonCategory::Equality) ? "-"_L1 : "ly "_L1)
+ << "comparable"_L1
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
+ << " with "_L1;
+
+ const QStringList types{description.firstAtom()->string().split(';'_L1)};
+ for (const auto &name : types)
+ relationshipText << Atom(Atom::AutoLink, name)
+ << Utilities::separator(types.indexOf(name), types.size());
+
+ relationshipText << Atom(Atom::ParaRight) << description;
+ }
+
+ generateText(relationshipText, node, nullptr);
+ return !relationshipText.isEmpty();
+}
+
+/*!
+ 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()
+{
+ s_currentGenerator = this;
+ generateDocumentation(m_qdb->primaryTreeRoot());
+}
+
+Generator *Generator::generatorForFormat(const QString &format)
+{
+ for (const auto &generator : std::as_const(s_generators)) {
+ if (generator->format() == format)
+ return generator;
+ }
+ return nullptr;
+}
+
+QString Generator::indent(int level, const QString &markedCode)
+{
+ if (level == 0)
+ return markedCode;
+
+ QString t;
+ int column = 0;
+
+ int i = 0;
+ while (i < markedCode.size()) {
+ 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();
+ s_outputFormats = config.getOutputFormats();
+ s_redirectDocumentationToDevNull = config.get(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL).asBool();
+
+ for (auto &g : s_generators) {
+ if (s_outputFormats.contains(g->format())) {
+ s_currentGenerator = g;
+ g->initializeGenerator();
+ }
+ }
+
+ const auto &configFormatting = config.subVars(CONFIG_FORMATTING);
+ for (const auto &n : configFormatting) {
+ QString formattingDotName = CONFIG_FORMATTING + Config::dot + n;
+ const auto &formattingDotNames = config.subVars(formattingDotName);
+ for (const auto &f : formattingDotNames) {
+ const auto &configVar = config.get(formattingDotName + Config::dot + f);
+ QString def{configVar.asString()};
+ if (!def.isEmpty()) {
+ int numParams = Config::numParams(def);
+ int numOccs = def.count("\1");
+ if (numParams != 1) {
+ configVar.location().warning(QStringLiteral("Formatting '%1' must "
+ "have exactly one "
+ "parameter (found %2)")
+ .arg(n, numParams));
+ } else if (numOccs > 1) {
+ configVar.location().fatal(QStringLiteral("Formatting '%1' must "
+ "contain exactly one "
+ "occurrence of '\\1' "
+ "(found %2)")
+ .arg(n, numOccs));
+ } else {
+ int paramPos = def.indexOf("\1");
+ s_fmtLeftMaps[f].insert(n, def.left(paramPos));
+ s_fmtRightMaps[f].insert(n, def.mid(paramPos + 1));
+ }
+ }
+ }
+ }
+
+ s_project = config.get(CONFIG_PROJECT).asString();
+ s_outDir = config.getOutputDir();
+ s_outSubdir = s_outDir.mid(s_outDir.lastIndexOf('/') + 1);
+
+ s_outputPrefixes.clear();
+ QStringList items{config.get(CONFIG_OUTPUTPREFIXES).asStringList()};
+ if (!items.isEmpty()) {
+ for (const auto &prefix : items)
+ s_outputPrefixes[prefix] =
+ config.get(CONFIG_OUTPUTPREFIXES + Config::dot + prefix).asString();
+ }
+ if (!items.contains(u"QML"_s))
+ s_outputPrefixes[u"QML"_s] = u"qml-"_s;
+
+ s_outputSuffixes.clear();
+ for (const auto &suffix : config.get(CONFIG_OUTPUTSUFFIXES).asStringList())
+ s_outputSuffixes[suffix] = config.get(CONFIG_OUTPUTSUFFIXES
+ + Config::dot + suffix).asString();
+
+ s_noLinkErrors = config.get(CONFIG_NOLINKERRORS).asBool();
+ s_autolinkErrors = config.get(CONFIG_AUTOLINKERRORS).asBool();
+}
+
+/*!
+ 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)
+{
+ // TODO: [resolving-files-unlinked-to-doc]
+ // This is another case of resolving files, albeit it doesn't use Doc::resolveFile.
+ // While it may be left out of a first iteration of the file
+ // resolution logic, it should later be integrated into it.
+ // This should come naturally when the output directory logic is
+ // extracted and copying a file should require a validated
+ // intermediate format.
+ // Do note that what is done here is a bit different from the
+ // resolve file routine that is done for other user-given paths.
+ // Thas is, the paths will always be absolute and not relative as
+ // they are resolved from the configuration.
+ // Ideally, this could be solved in the configuration already,
+ // together with the other configuration resolution processes that
+ // do not abide by the same constraints that, for example, snippet
+ // resolution uses.
+ Config &config = Config::instance();
+ QStringList files = config.getCanonicalPathList(configVar, Config::Validate);
+ const auto &loc = config.get(configVar).location();
+ if (!files.isEmpty()) {
+ QDir dirInfo;
+ // TODO: [uncentralized-output-directory-structure]
+ // As with other places in the generation pass, the details of
+ // where something is saved in the output directory are spread
+ // to whichever part of the generation does the saving.
+ // It is hence complex to build a model of how an output
+ // directory looks like, as the knowledge has no specific
+ // entry point or chain-path that can be followed in full.
+ // Each of those operations should be centralized in a system
+ // that uniquely knows what the format of the output-directory
+ // is and how to perform operations on it.
+ // Later, move this operation to that centralized system.
+ QString templateDir = s_outDir + QLatin1Char('/') + subDir;
+ if (!dirInfo.exists(templateDir) && !dirInfo.mkdir(templateDir)) {
+ // TODO: [uncentralized-admonition]
+ loc.fatal(QStringLiteral("Cannot create %1 directory '%2'").arg(subDir, templateDir));
+ } else {
+ for (const auto &file : files) {
+ if (!file.isEmpty())
+ Config::copyFile(loc, 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();
+ s_outFileNames.clear();
+ s_useOutputSubdirs = true;
+ if (config.get(format() + Config::dot + "nosubdirs").asBool())
+ resetUseOutputSubdirs();
+
+ if (s_outputFormats.isEmpty())
+ return;
+
+ s_outDir = config.getOutputDir(format());
+ if (s_outDir.isEmpty()) {
+ Location().fatal(QStringLiteral("No output directory specified in "
+ "configuration file or on the command line"));
+ } else {
+ s_outSubdir = s_outDir.mid(s_outDir.lastIndexOf('/') + 1);
+ }
+
+ QDir outputDir(s_outDir);
+ if (outputDir.exists()) {
+ if (!config.generating() && Generator::useOutputSubdirs()) {
+ if (!outputDir.isEmpty())
+ Location().error(QStringLiteral("Output directory '%1' exists but is not empty")
+ .arg(s_outDir));
+ }
+ } else if (!outputDir.mkpath(QStringLiteral("."))) {
+ Location().fatal(QStringLiteral("Cannot create output directory '%1'").arg(s_outDir));
+ }
+
+ // Output directory exists, which is enough for prepare phase.
+ if (config.preparing())
+ return;
+
+ const QLatin1String imagesDir("images");
+ if (!outputDir.exists(imagesDir) && !outputDir.mkdir(imagesDir))
+ Location().fatal(QStringLiteral("Cannot create images directory '%1'").arg(outputDir.filePath(imagesDir)));
+
+ 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.get(format() + Config::dot + CONFIG_QUOTINGINFORMATION).asBool();
+ else
+ m_quoting = config.get(CONFIG_QUOTINGINFORMATION).asBool();
+}
+
+/*!
+ 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)
+{
+ // Omit prefix for module pages
+ if (node->isPageNode() && !node->isCollectionNode()) {
+ switch (node->genus()) {
+ case Node::QML:
+ return s_outputPrefixes[u"QML"_s];
+ case Node::CPP:
+ return s_outputPrefixes[u"CPP"_s];
+ default:
+ break;
+ }
+ }
+ return QString();
+}
+
+QString Generator::outputSuffix(const Node *node)
+{
+ if (node->isPageNode()) {
+ switch (node->genus()) {
+ case Node::QML:
+ return s_outputSuffixes[u"QML"_s];
+ case Node::CPP:
+ return s_outputSuffixes[u"CPP"_s];
+ default:
+ break;
+ }
+ }
+
+ return QString();
+}
+
+bool Generator::parseArg(const QString &src, const QString &tag, int *pos, int n,
+ QStringView *contents, QStringView *par1)
+{
+#define SKIP_CHAR(c) \
+ if (i >= n || src[i] != c) \
+ return false; \
+ ++i;
+
+#define SKIP_SPACE \
+ while (i < n && src[i] == ' ') \
+ ++i;
+
+ qsizetype i = *pos;
+ qsizetype j {};
+
+ // assume "<@" has been parsed outside
+ // SKIP_CHAR('<');
+ // SKIP_CHAR('@');
+
+ if (tag != QStringView(src).mid(i, tag.size())) {
+ return false;
+ }
+
+ // skip tag
+ i += tag.size();
+
+ // 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] == '=') {
+ 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;
+ }
+ }
+ SKIP_SPACE;
+ SKIP_CHAR('>');
+
+ // find contents up to closing "</@tag>
+ j = i;
+ for (; true; ++i) {
+ if (i + 4 + tag.size() > 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.size()))
+ continue;
+ if (src[i + 3 + tag.size()] != '>')
+ continue;
+ break;
+ }
+
+ *contents = QStringView(src).mid(j, i - j);
+
+ i += tag.size() + 4;
+
+ *pos = i;
+ return true;
+#undef SKIP_CHAR
+#undef SKIP_SPACE
+}
+
+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;
+}
+
+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()) {
+ if (alternateFunc->isDeprecated() && !fn->isDeprecated())
+ return;
+ 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::generateEnumValuesForQmlProperty(const Node *node, CodeMarker *marker)
+{
+ if (!node->isQmlProperty())
+ return;
+
+ const auto *qpn = static_cast<const QmlPropertyNode*>(node);
+
+ if (!qpn->enumNode())
+ return;
+
+ // Retrieve atoms from C++ enum \value list
+ const auto body{qpn->enumNode()->doc().body()};
+ const auto *start{body.firstAtom()};
+ Text text;
+
+ while ((start = start->find(Atom::ListLeft, ATOM_LIST_VALUE))) {
+ const auto end = start->find(Atom::ListRight, ATOM_LIST_VALUE);
+ // Skip subsequent ListLeft atoms, collating multiple lists into one
+ text << body.subText(text.isEmpty() ? start : start->next(), end);
+ start = end;
+ }
+ if (text.isEmpty())
+ return;
+
+ text << Atom(Atom::ListRight, ATOM_LIST_VALUE);
+ if (marker)
+ generateText(text, qpn, marker);
+ else
+ generateText(text, qpn);
+}
+
+void Generator::terminate()
+{
+ for (const auto &generator : std::as_const(s_generators)) {
+ if (s_outputFormats.contains(generator->format()))
+ generator->terminateGenerator();
+ }
+
+ // REMARK: Generators currently, due to recent changes and the
+ // transitive nature of the current codebase, receive some of
+ // their dependencies in the constructor and some of them in their
+ // initialize-terminate lifetime.
+ // This means that generators need to be constructed and
+ // destructed between usages such that if multiple usages are
+ // required, the generators present in the list will have been
+ // destroyed by then such that accessing them would be an error.
+ // The current codebase calls initialize and the correspective
+ // terminate with the same scope as the lifetime of the
+ // generators.
+ // Then, clearing the list ensures that, if another generator
+ // execution is needed, the stale generators will not be removed
+ // as to be replaced by newly constructed ones.
+ // Do note that it is not clear that this will happen for any call
+ // in Qt's documentation and this should work only because of the
+ // form of the current codebase and the scoping of the
+ // initialize-terminate calls. As such, this should be considered
+ // a patchwork that may or may not be doing anything and that may
+ // break due to changes in other parts of the codebase.
+ //
+ // This is still to be considered temporary as the whole
+ // initialize-terminate idiom must be removed from the codebase.
+ s_generators.clear();
+
+ s_fmtLeftMaps.clear();
+ s_fmtRightMaps.clear();
+ s_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.size() > 0 && trimmed[trimmed.size() - 1].isSpace())
+ trimmed.truncate(trimmed.size() - 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::QmlValueType:
+ return "type";
+ case Node::Page:
+ return "documentation";
+ case Node::Enum:
+ return "enum";
+ case Node::Typedef:
+ case Node::TypeAlias:
+ return "typedef";
+ case Node::Function: {
+ const auto fn = static_cast<const FunctionNode *>(node);
+ switch (fn->metaness()) {
+ case FunctionNode::QmlSignal:
+ return "signal";
+ case FunctionNode::QmlSignalHandler:
+ return "signal handler";
+ case FunctionNode::QmlMethod:
+ return "method";
+ case FunctionNode::MacroWithParams:
+ case FunctionNode::MacroWithoutParams:
+ return "macro";
+ default:
+ break;
+ }
+ return "function";
+ }
+ case Node::Property:
+ case Node::QmlProperty:
+ return "property";
+ case Node::Module:
+ 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(), format()));
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/generator.h b/src/qdoc/qdoc/src/qdoc/generator.h
new file mode 100644
index 000000000..164882a8f
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/generator.h
@@ -0,0 +1,212 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef GENERATOR_H
+#define GENERATOR_H
+
+#include "text.h"
+#include "utilities.h"
+#include "filesystem/fileresolver.h"
+
+#include <QtCore/qlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qtextstream.h>
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+typedef QMultiMap<QString, Node *> NodeMultiMap;
+
+class Aggregate;
+class CodeMarker;
+class ExampleNode;
+class FunctionNode;
+class Location;
+class Node;
+class QDocDatabase;
+
+class Generator
+{
+public:
+ enum ListType { Generic, Obsolete };
+
+ enum Addendum {
+ Invokable,
+ PrivateSignal,
+ QmlSignalHandler,
+ AssociatedProperties,
+ BindableProperty
+ };
+
+ Generator(FileResolver& file_resolver);
+ virtual ~Generator();
+
+ virtual bool canHandleFormat(const QString &format) { return format == this->format(); }
+ virtual QString format() = 0;
+ virtual void generateDocs();
+ virtual void initializeGenerator();
+ virtual void initializeFormat();
+ virtual void terminateGenerator();
+ virtual QString typeString(const Node *node);
+
+ QString fullDocumentLocation(const Node *node);
+ QString linkForExampleFile(const QString &path, const QString &fileExt = QString());
+ static QString exampleFileTitle(const ExampleNode *relative, const QString &fileName);
+ static Generator *currentGenerator() { return s_currentGenerator; }
+ static Generator *generatorForFormat(const QString &format);
+ static void initialize();
+ static const QString &outputDir() { return s_outDir; }
+ static const QString &outputSubdir() { return s_outSubdir; }
+ static void terminate();
+ static const QStringList &outputFileNames() { return s_outFileNames; }
+ static bool noLinkErrors() { return s_noLinkErrors; }
+ static bool autolinkErrors() { return s_autolinkErrors; }
+ static QString defaultModuleName() { return s_project; }
+ static void resetUseOutputSubdirs() { s_useOutputSubdirs = false; }
+ static bool useOutputSubdirs() { return s_useOutputSubdirs; }
+ static void setQmlTypeContext(QmlTypeNode *t) { s_qmlTypeContext = t; }
+ static QmlTypeNode *qmlTypeContext() { return s_qmlTypeContext; }
+ static QString cleanRef(const QString &ref, bool xmlCompliant = false);
+ static QString plainCode(const QString &markedCode);
+ virtual QString fileBase(const Node *node) const;
+
+protected:
+ static QFile *openSubPageFile(const Node *node, const QString &fileName);
+ void beginSubPage(const Node *node, const QString &fileName);
+ void endSubPage();
+ [[nodiscard]] virtual QString fileExtension() const = 0;
+ virtual void generateExampleFilePage(const Node *, ResolvedFile, CodeMarker * = nullptr) {}
+ virtual void generateAlsoList(const Node *node, CodeMarker *marker);
+ virtual void generateAlsoList(const Node *node) { generateAlsoList(node, nullptr); }
+ virtual qsizetype generateAtom(const Atom *, const Node *, CodeMarker *) { return 0; }
+ virtual void generateBody(const Node *node, CodeMarker *marker);
+ virtual void generateCppReferencePage(Aggregate *, CodeMarker *) {}
+ virtual void generateProxyPage(Aggregate *, CodeMarker *) {}
+ virtual void generateQmlTypePage(QmlTypeNode *, CodeMarker *) {}
+ virtual void generatePageNode(PageNode *, CodeMarker *) {}
+ virtual void generateCollectionNode(CollectionNode *, CodeMarker *) {}
+ virtual void generateGenericCollectionPage(CollectionNode *, CodeMarker *) {}
+ virtual void generateDocumentation(Node *node);
+ virtual bool generateText(const Text &text, const Node *relative, CodeMarker *marker);
+ virtual bool generateText(const Text &text, const Node *relative)
+ {
+ return generateText(text, relative, nullptr);
+ };
+ virtual int skipAtoms(const Atom *atom, Atom::AtomType type) const;
+
+ static bool matchAhead(const Atom *atom, Atom::AtomType expectedAtomType);
+ static QString outputPrefix(const Node *node);
+ static QString outputSuffix(const Node *node);
+ static void supplementAlsoList(const Node *node, QList<Text> &alsoList);
+ static QString trimmedTrailing(const QString &string, const QString &prefix,
+ const QString &suffix);
+ void initializeTextOutput();
+ QString fileName(const Node *node, const QString &extension = QString()) const;
+ QMap<QString, QString> &formattingLeftMap();
+ QMap<QString, QString> &formattingRightMap();
+ const Atom *generateAtomList(const Atom *atom, const Node *relative, CodeMarker *marker,
+ bool generate, int &numGeneratedAtoms);
+ void generateEnumValuesForQmlProperty(const Node *node, CodeMarker *marker);
+ void generateRequiredLinks(const Node *node, CodeMarker *marker);
+ void generateLinkToExample(const ExampleNode *en, CodeMarker *marker,
+ const QString &exampleUrl);
+ virtual void generateFileList(const ExampleNode *en, CodeMarker *marker, bool images);
+ static QString formatSince(const Node *node);
+ void generateSince(const Node *node, CodeMarker *marker);
+ void generateNoexceptNote(const Node *node, CodeMarker *marker);
+ void generateStatus(const Node *node, CodeMarker *marker);
+ virtual void generateAddendum(const Node *node, Addendum type, CodeMarker *marker,
+ bool generateNote);
+ virtual void generateAddendum(const Node *node, Addendum type, CodeMarker *marker)
+ {
+ generateAddendum(node, type, marker, true);
+ };
+ void generateThreadSafeness(const Node *node, CodeMarker *marker);
+ bool generateComparisonCategory(const Node *node, CodeMarker *marker = nullptr);
+ bool generateComparisonList(const Node *node);
+
+ void generateOverloadedSignal(const Node *node, CodeMarker *marker);
+ static QString getOverloadedSignalCode(const Node *node);
+ QString indent(int level, const QString &markedCode);
+ QTextStream &out();
+ QString outFileName();
+ bool parseArg(const QString &src, const QString &tag, int *pos, int n, QStringView *contents,
+ QStringView *par1 = nullptr);
+ void unknownAtom(const Atom *atom);
+ int appendSortedQmlNames(Text &text, const Node *base, const NodeList &subs);
+
+ static bool hasExceptions(const Node *node, NodeList &reentrant, NodeList &threadsafe,
+ NodeList &nonreentrant);
+
+ QString naturalLanguage;
+ QString tagFile_;
+ QStack<QTextStream *> outStreamStack;
+
+ void appendFullName(Text &text, const Node *apparentNode, const Node *relative,
+ const Node *actualNode = nullptr);
+ void appendFullName(Text &text, const Node *apparentNode, const QString &fullName,
+ const Node *actualNode);
+ int appendSortedNames(Text &text, const ClassNode *classe,
+ const QList<RelatedClass> &classes);
+ void appendSignature(Text &text, const Node *node);
+ void signatureList(const NodeList &nodes, const Node *relative, CodeMarker *marker);
+
+ void addImageToCopy(const ExampleNode *en, const ResolvedFile& resolved_file);
+ // TODO: This seems to be used as the predicate in std::sort calls.
+ // Remove it as it is unneeded.
+ // Indeed, it could be replaced by std::less and, furthermore,
+ // std::sort already defaults to operator< when no predicate is
+ // provided.
+ static bool comparePaths(const QString &a, const QString &b) { return (a < b); }
+ static bool appendTrademark(const Atom *atom);
+
+ static Qt::SortOrder sortOrder(const QString &str)
+ {
+ return (str == "descending") ? Qt::DescendingOrder : Qt::AscendingOrder;
+ }
+
+private:
+ static Generator *s_currentGenerator;
+ static QMap<QString, QMap<QString, QString>> s_fmtLeftMaps;
+ static QMap<QString, QMap<QString, QString>> s_fmtRightMaps;
+ static QList<Generator *> s_generators;
+ static QString s_project;
+ static QString s_outDir;
+ static QString s_outSubdir;
+ static QStringList s_outFileNames;
+ static QSet<QString> s_outputFormats;
+ static QSet<QString> s_trademarks;
+ static QHash<QString, QString> s_outputPrefixes;
+ static QHash<QString, QString> s_outputSuffixes;
+ static bool s_noLinkErrors;
+ static bool s_autolinkErrors;
+ static bool s_redirectDocumentationToDevNull;
+ static bool s_useOutputSubdirs;
+ static QmlTypeNode *s_qmlTypeContext;
+
+ void generateReimplementsClause(const FunctionNode *fn, CodeMarker *marker);
+ static void copyTemplateFiles(const QString &configVar, const QString &subDir);
+
+protected:
+ FileResolver& file_resolver;
+
+ QDocDatabase *m_qdb { nullptr };
+ bool m_inLink { false };
+ bool m_inContents { false };
+ bool m_inSectionHeading { false };
+ bool m_inTableHeader { false };
+ bool m_threeColumnEnumValueTable { true };
+ bool m_showInternal { false };
+ bool m_quoting { false };
+ int m_numTableRows { 0 };
+ QString m_link {};
+ QString m_sectionNumber {};
+};
+
+std::optional<QString> formatStatus(const Node *node, QDocDatabase *qdb);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/headernode.cpp b/src/qdoc/qdoc/src/qdoc/headernode.cpp
new file mode 100644
index 000000000..ab576fbd6
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/headernode.cpp
@@ -0,0 +1,43 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "headernode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class Headernode
+ \brief This class represents a C++ header file.
+ */
+
+HeaderNode::HeaderNode(Aggregate *parent, const QString &name) : Aggregate(HeaderFile, parent, name)
+{
+ // Set the include file with enclosing angle brackets removed
+ if (name.startsWith(QChar('<')) && name.size() > 2)
+ Aggregate::setIncludeFile(name.mid(1).chopped(1));
+ else
+ Aggregate::setIncludeFile(name);
+}
+
+/*!
+ Returns true if this header file node is not private and
+ contains at least one public child node with documentation.
+ */
+bool HeaderNode::docMustBeGenerated() const
+{
+ if (isInAPI())
+ return true;
+ return hasDocumentedChildren();
+}
+
+/*!
+ Returns true if this header file node contains at least one
+ child that has documentation and is not private or internal.
+ */
+bool HeaderNode::hasDocumentedChildren() const
+{
+ return std::any_of(m_children.cbegin(), m_children.cend(),
+ [](Node *child) { return child->isInAPI(); });
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/headernode.h b/src/qdoc/qdoc/src/qdoc/headernode.h
new file mode 100644
index 000000000..b20ff8fdb
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/headernode.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef HEADERNODE_H
+#define HEADERNODE_H
+
+#include "aggregate.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class HeaderNode : public Aggregate
+{
+public:
+ HeaderNode(Aggregate *parent, const QString &name);
+ [[nodiscard]] bool docMustBeGenerated() const override;
+ [[nodiscard]] bool isFirstClassAggregate() const override { return true; }
+ [[nodiscard]] bool isRelatableType() const override { return true; }
+ [[nodiscard]] QString title() const override { return (m_title.isEmpty() ? name() : m_title); }
+ [[nodiscard]] QString subtitle() const override { return m_subtitle; }
+ [[nodiscard]] QString fullTitle() const override
+ {
+ return (m_title.isEmpty() ? name() : name() + " - " + m_title);
+ }
+ bool setTitle(const QString &title) override
+ {
+ m_title = title;
+ return true;
+ }
+ bool setSubtitle(const QString &subtitle) override
+ {
+ m_subtitle = subtitle;
+ return true;
+ }
+ [[nodiscard]] bool hasDocumentedChildren() const;
+
+private:
+ QString m_title {};
+ QString m_subtitle {};
+};
+
+QT_END_NAMESPACE
+
+#endif // HEADERNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/helpprojectwriter.cpp b/src/qdoc/qdoc/src/qdoc/helpprojectwriter.cpp
new file mode 100644
index 000000000..968bb7b25
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/helpprojectwriter.cpp
@@ -0,0 +1,769 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "helpprojectwriter.h"
+
+#include "access.h"
+#include "aggregate.h"
+#include "atom.h"
+#include "classnode.h"
+#include "collectionnode.h"
+#include "config.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "htmlgenerator.h"
+#include "node.h"
+#include "qdocdatabase.h"
+#include "typedefnode.h"
+
+#include <QtCore/qhash.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+HelpProjectWriter::HelpProjectWriter(const QString &defaultFileName, Generator *g)
+{
+ reset(defaultFileName, g);
+}
+
+void HelpProjectWriter::reset(const QString &defaultFileName, Generator *g)
+{
+ m_projects.clear();
+ m_gen = g;
+ /*
+ Get the pointer to the singleton for the qdoc database and
+ store it locally. This replaces all the local accesses to
+ the node tree, which are now private.
+ */
+ m_qdb = QDocDatabase::qdocDB();
+
+ // The output directory should already have been checked by the calling
+ // generator.
+ Config &config = Config::instance();
+ m_outputDir = config.getOutputDir();
+
+ const QStringList names{config.get(CONFIG_QHP + Config::dot + "projects").asStringList()};
+
+ for (const auto &projectName : names) {
+ HelpProject project;
+ project.m_name = projectName;
+
+ QString prefix = CONFIG_QHP + Config::dot + projectName + Config::dot;
+ project.m_helpNamespace = config.get(prefix + "namespace").asString();
+ project.m_virtualFolder = config.get(prefix + "virtualFolder").asString();
+ project.m_version = config.get(CONFIG_VERSION).asString();
+ project.m_fileName = config.get(prefix + "file").asString();
+ if (project.m_fileName.isEmpty())
+ project.m_fileName = defaultFileName;
+ project.m_extraFiles = config.get(prefix + "extraFiles").asStringSet();
+ project.m_extraFiles += config.get(CONFIG_QHP + Config::dot + "extraFiles").asStringSet();
+ project.m_indexTitle = config.get(prefix + "indexTitle").asString();
+ project.m_indexRoot = config.get(prefix + "indexRoot").asString();
+ project.m_filterAttributes = config.get(prefix + "filterAttributes").asStringSet();
+ project.m_includeIndexNodes = config.get(prefix + "includeIndexNodes").asBool();
+ const QSet<QString> customFilterNames = config.subVars(prefix + "customFilters");
+ for (const auto &filterName : customFilterNames) {
+ QString name{config.get(prefix + "customFilters" + Config::dot + filterName
+ + Config::dot + "name").asString()};
+ project.m_customFilters[name] =
+ config.get(prefix + "customFilters" + Config::dot + filterName
+ + Config::dot + "filterAttributes").asStringSet();
+ }
+
+ const auto excludedPrefixes = config.get(prefix + "excluded").asStringSet();
+ for (auto name : excludedPrefixes)
+ project.m_excluded.insert(name.replace(QLatin1Char('\\'), QLatin1Char('/')));
+
+ const auto subprojectPrefixes{config.get(prefix + "subprojects").asStringList()};
+ for (const auto &name : subprojectPrefixes) {
+ SubProject subproject;
+ QString subprefix = prefix + "subprojects" + Config::dot + name + Config::dot;
+ subproject.m_title = config.get(subprefix + "title").asString();
+ if (subproject.m_title.isEmpty())
+ continue;
+ subproject.m_indexTitle = config.get(subprefix + "indexTitle").asString();
+ subproject.m_sortPages = config.get(subprefix + "sortPages").asBool();
+ subproject.m_type = config.get(subprefix + "type").asString();
+ readSelectors(subproject, config.get(subprefix + "selectors").asStringList());
+ project.m_subprojects.append(subproject);
+ }
+
+ if (project.m_subprojects.isEmpty()) {
+ SubProject subproject;
+ readSelectors(subproject, config.get(prefix + "selectors").asStringList());
+ project.m_subprojects.insert(0, subproject);
+ }
+
+ m_projects.append(project);
+ }
+}
+
+void HelpProjectWriter::readSelectors(SubProject &subproject, const QStringList &selectors)
+{
+ QHash<QString, Node::NodeType> typeHash;
+ typeHash["namespace"] = Node::Namespace;
+ typeHash["class"] = Node::Class;
+ typeHash["struct"] = Node::Struct;
+ typeHash["union"] = Node::Union;
+ typeHash["header"] = Node::HeaderFile;
+ typeHash["headerfile"] = Node::HeaderFile;
+ typeHash["doc"] = Node::Page; // Unused (supported but ignored as a prefix)
+ typeHash["fake"] = Node::Page; // Unused (supported but ignored as a prefix)
+ typeHash["page"] = Node::Page;
+ typeHash["enum"] = Node::Enum;
+ typeHash["example"] = Node::Example;
+ typeHash["externalpage"] = Node::ExternalPage;
+ typeHash["typedef"] = Node::Typedef;
+ typeHash["typealias"] = Node::TypeAlias;
+ typeHash["function"] = Node::Function;
+ typeHash["property"] = Node::Property;
+ typeHash["variable"] = Node::Variable;
+ typeHash["group"] = Node::Group;
+ typeHash["module"] = Node::Module;
+ typeHash["qmlmodule"] = Node::QmlModule;
+ typeHash["qmlproperty"] = Node::QmlProperty;
+ typeHash["qmlclass"] = Node::QmlType; // Legacy alias for 'qmltype'
+ typeHash["qmltype"] = Node::QmlType;
+ typeHash["qmlbasictype"] = Node::QmlValueType; // Legacy alias for 'qmlvaluetype'
+ typeHash["qmlvaluetype"] = Node::QmlValueType;
+
+ for (const QString &selector : selectors) {
+ QStringList pieces = selector.split(QLatin1Char(':'));
+ // Remove doc: or fake: prefix
+ if (pieces.size() > 1 && typeHash.value(pieces[0].toLower()) == Node::Page)
+ pieces.takeFirst();
+
+ QString typeName = pieces.takeFirst().toLower();
+ if (!typeHash.contains(typeName))
+ continue;
+
+ subproject.m_selectors << typeHash.value(typeName);
+ if (!pieces.isEmpty()) {
+ pieces = pieces[0].split(QLatin1Char(','));
+ for (const auto &piece : std::as_const(pieces)) {
+ if (typeHash[typeName] == Node::Group
+ || typeHash[typeName] == Node::Module
+ || typeHash[typeName] == Node::QmlModule) {
+ subproject.m_groups << piece.toLower();
+ }
+ }
+ }
+ }
+}
+
+void HelpProjectWriter::addExtraFile(const QString &file)
+{
+ for (HelpProject &project : m_projects)
+ project.m_extraFiles.insert(file);
+}
+
+Keyword HelpProjectWriter::keywordDetails(const Node *node) const
+{
+ QString ref = m_gen->fullDocumentLocation(node);
+
+ if (node->parent() && !node->parent()->name().isEmpty()) {
+ QString name = (node->isEnumType() || node->isTypedef())
+ ? node->parent()->name()+"::"+node->name()
+ : node->name();
+ QString id = (!node->isRelatedNonmember())
+ ? node->parent()->name()+"::"+node->name()
+ : node->name();
+ return Keyword(name, id, ref);
+ } else if (node->isQmlType()) {
+ const QString &name = node->name();
+ QString moduleName = node->logicalModuleName();
+ QStringList ids("QML." + name);
+ if (!moduleName.isEmpty()) {
+ QString majorVersion = node->logicalModule()
+ ? node->logicalModule()->logicalModuleVersion().split('.')[0]
+ : QString();
+ ids << "QML." + moduleName + majorVersion + "." + name;
+ }
+ return Keyword(name, ids, ref);
+ } else if (node->isQmlModule()) {
+ const QLatin1Char delim('.');
+ QStringList parts = node->logicalModuleName().split(delim) << "QML";
+ std::reverse(parts.begin(), parts.end());
+ return Keyword(node->logicalModuleName(), parts.join(delim), ref);
+ } else if (node->isTextPageNode()) {
+ const auto *pageNode = static_cast<const PageNode *>(node);
+ return Keyword(pageNode->fullTitle(), pageNode->fullTitle(), ref);
+ } else {
+ return Keyword(node->name(), node->name(), ref);
+ }
+}
+
+bool HelpProjectWriter::generateSection(HelpProject &project, QXmlStreamWriter & /* writer */,
+ const Node *node)
+{
+ if (!node->url().isEmpty() && !(project.m_includeIndexNodes && !node->url().startsWith("http")))
+ return false;
+
+ if (node->isPrivate() || node->isInternal() || node->isDontDocument())
+ return false;
+
+ if (node->name().isEmpty())
+ return true;
+
+ QString docPath = node->doc().location().filePath();
+ if (!docPath.isEmpty() && project.m_excluded.contains(docPath))
+ return false;
+
+ QString objName = node->isTextPageNode() ? node->fullTitle() : node->fullDocumentName();
+ // Only add nodes to the set for each subproject if they match a selector.
+ // Those that match will be listed in the table of contents.
+
+ for (int i = 0; i < project.m_subprojects.size(); i++) {
+ SubProject subproject = project.m_subprojects[i];
+ // No selectors: accept all nodes.
+ if (subproject.m_selectors.isEmpty()) {
+ project.m_subprojects[i].m_nodes[objName] = node;
+ } else if (subproject.m_selectors.contains(node->nodeType())) {
+ // Add all group members for '[group|module|qmlmodule]:name' selector
+ if (node->isCollectionNode()) {
+ if (project.m_subprojects[i].m_groups.contains(node->name().toLower())) {
+ const auto *cn = static_cast<const CollectionNode *>(node);
+ const auto members = cn->members();
+ for (const Node *m : members) {
+ if (!m->isInAPI())
+ continue;
+ QString memberName =
+ m->isTextPageNode() ? m->fullTitle() : m->fullDocumentName();
+ project.m_subprojects[i].m_nodes[memberName] = m;
+ }
+ continue;
+ } else if (!project.m_subprojects[i].m_groups.isEmpty()) {
+ continue; // Node does not represent specified group(s)
+ }
+ } else if (node->isTextPageNode()) {
+ if (node->isExternalPage() || node->fullTitle().isEmpty())
+ continue;
+ }
+ project.m_subprojects[i].m_nodes[objName] = node;
+ }
+ }
+
+ switch (node->nodeType()) {
+
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ project.m_keywords.append(keywordDetails(node));
+ break;
+ case Node::QmlType:
+ case Node::QmlValueType:
+ if (node->doc().hasKeywords()) {
+ const auto keywords = node->doc().keywords();
+ for (const Atom *keyword : keywords) {
+ if (!keyword->string().isEmpty()) {
+ project.m_keywords.append(Keyword(keyword->string(), keyword->string(),
+ m_gen->fullDocumentLocation(node)));
+ }
+ else
+ node->doc().location().warning(
+ QStringLiteral("Bad keyword in %1")
+ .arg(m_gen->fullDocumentLocation(node)));
+ }
+ }
+ project.m_keywords.append(keywordDetails(node));
+ break;
+
+ case Node::Namespace:
+ project.m_keywords.append(keywordDetails(node));
+ break;
+
+ case Node::Enum:
+ project.m_keywords.append(keywordDetails(node));
+ {
+ const auto *enumNode = static_cast<const EnumNode *>(node);
+ const auto items = enumNode->items();
+ for (const auto &item : items) {
+ if (enumNode->itemAccess(item.name()) == Access::Private)
+ continue;
+
+ QString name;
+ QString id;
+ if (!node->parent()->name().isEmpty()) {
+ name = id = node->parent()->name() + "::" + item.name();
+ } else {
+ name = id = item.name();
+ }
+ QString ref = m_gen->fullDocumentLocation(node);
+ project.m_keywords.append(Keyword(name, id, ref));
+ }
+ }
+ break;
+
+ case Node::Group:
+ case Node::Module:
+ case Node::QmlModule: {
+ const auto *cn = static_cast<const CollectionNode *>(node);
+ if (!cn->fullTitle().isEmpty()) {
+ if (cn->doc().hasKeywords()) {
+ const auto keywords = cn->doc().keywords();
+ for (const Atom *keyword : keywords) {
+ if (!keyword->string().isEmpty()) {
+ project.m_keywords.append(
+ Keyword(keyword->string(), keyword->string(),
+ m_gen->fullDocumentLocation(node)));
+ } else
+ cn->doc().location().warning(
+ QStringLiteral("Bad keyword in %1")
+ .arg(m_gen->fullDocumentLocation(node)));
+ }
+ }
+ project.m_keywords.append(keywordDetails(node));
+ }
+ } break;
+
+ case Node::Property:
+ case Node::QmlProperty:
+ project.m_keywords.append(keywordDetails(node));
+ break;
+
+ case Node::Function: {
+ const auto *funcNode = static_cast<const FunctionNode *>(node);
+
+ /*
+ QML methods, signals, and signal handlers used to be node types,
+ but now they are Function nodes with a Metaness value that specifies
+ what kind of function they are, QmlSignal, QmlMethod, etc.
+ */
+ if (funcNode->isQmlNode()) {
+ project.m_keywords.append(keywordDetails(node));
+ break;
+ }
+ // Only insert keywords for non-constructors. Constructors are covered
+ // by the classes themselves.
+
+ if (!funcNode->isSomeCtor())
+ project.m_keywords.append(keywordDetails(node));
+
+ // Insert member status flags into the entries for the parent
+ // node of the function, or the node it is related to.
+ // Since parent nodes should have already been inserted into
+ // the set of files, we only need to ensure that related nodes
+ // are inserted.
+
+ if (node->parent())
+ project.m_memberStatus[node->parent()].insert(node->status());
+ } break;
+ case Node::TypeAlias:
+ case Node::Typedef: {
+ const auto *typedefNode = static_cast<const TypedefNode *>(node);
+ Keyword typedefDetails = keywordDetails(node);
+ const EnumNode *enumNode = typedefNode->associatedEnum();
+ // Use the location of any associated enum node in preference
+ // to that of the typedef.
+ if (enumNode)
+ typedefDetails.m_ref = m_gen->fullDocumentLocation(enumNode);
+
+ project.m_keywords.append(typedefDetails);
+ } break;
+
+ case Node::Variable: {
+ project.m_keywords.append(keywordDetails(node));
+ } break;
+
+ // Page nodes (such as manual pages) contain subtypes, titles and other
+ // attributes.
+ case Node::Page: {
+ const auto *pn = static_cast<const PageNode *>(node);
+ if (!pn->fullTitle().isEmpty()) {
+ if (pn->doc().hasKeywords()) {
+ const auto keywords = pn->doc().keywords();
+ for (const Atom *keyword : keywords) {
+ if (!keyword->string().isEmpty()) {
+ project.m_keywords.append(
+ Keyword(keyword->string(), keyword->string(),
+ m_gen->fullDocumentLocation(node)));
+ } else {
+ QString loc = m_gen->fullDocumentLocation(node);
+ pn->doc().location().warning(QStringLiteral("Bad keyword in %1").arg(loc));
+ }
+ }
+ }
+ project.m_keywords.append(keywordDetails(node));
+ }
+ break;
+ }
+ default:;
+ }
+
+ // Add all images referenced in the page to the set of files to include.
+ const Atom *atom = node->doc().body().firstAtom();
+ while (atom) {
+ if (atom->type() == Atom::Image || atom->type() == Atom::InlineImage) {
+ // Images are all placed within a single directory regardless of
+ // whether the source images are in a nested directory structure.
+ QStringList pieces = atom->string().split(QLatin1Char('/'));
+ project.m_files.insert("images/" + pieces.last());
+ }
+ atom = atom->next();
+ }
+
+ return true;
+}
+
+void HelpProjectWriter::generateSections(HelpProject &project, QXmlStreamWriter &writer,
+ const Node *node)
+{
+ /*
+ Don't include index nodes in the help file.
+ */
+ if (node->isIndexNode())
+ return;
+ if (!generateSection(project, writer, node))
+ return;
+
+ if (node->isAggregate()) {
+ const auto *aggregate = static_cast<const Aggregate *>(node);
+
+ // Ensure that we don't visit nodes more than once.
+ NodeList childSet;
+ NodeList children = aggregate->childNodes();
+ std::sort(children.begin(), children.end(), Node::nodeNameLessThan);
+ for (auto *child : children) {
+ // Skip related non-members adopted by some other aggregate
+ if (child->parent() != aggregate)
+ continue;
+ if (child->isIndexNode() || child->isPrivate())
+ continue;
+ if (child->isTextPageNode()) {
+ if (!childSet.contains(child))
+ childSet << child;
+ } else {
+ // Store member status of children
+ project.m_memberStatus[node].insert(child->status());
+ if (child->isFunction() && static_cast<const FunctionNode *>(child)->isOverload())
+ continue;
+ if (!childSet.contains(child))
+ childSet << child;
+ }
+ }
+ for (const auto *child : std::as_const(childSet))
+ generateSections(project, writer, child);
+ }
+}
+
+void HelpProjectWriter::generate()
+{
+ // Warn if .qhp configuration was expected but not provided
+ if (auto &config = Config::instance(); m_projects.isEmpty() && config.get(CONFIG_QHP).asBool()) {
+ config.location().warning(u"Documentation configuration for '%1' doesn't define a help project (qhp)"_s
+ .arg(config.get(CONFIG_PROJECT).asString()));
+ }
+ for (HelpProject &project : m_projects)
+ generateProject(project);
+}
+
+void HelpProjectWriter::writeSection(QXmlStreamWriter &writer, const QString &path,
+ const QString &value)
+{
+ writer.writeStartElement(QStringLiteral("section"));
+ writer.writeAttribute(QStringLiteral("ref"), path);
+ writer.writeAttribute(QStringLiteral("title"), value);
+ writer.writeEndElement(); // section
+}
+
+/*!
+ Write subsections for all members, compatibility members and obsolete members.
+ */
+void HelpProjectWriter::addMembers(HelpProject &project, QXmlStreamWriter &writer, const Node *node)
+{
+ QString href = m_gen->fullDocumentLocation(node);
+ href = href.left(href.size() - 5);
+ if (href.isEmpty())
+ return;
+
+ bool derivedClass = false;
+ if (node->isClassNode())
+ derivedClass = !(static_cast<const ClassNode *>(node)->baseClasses().isEmpty());
+
+ // Do not generate a 'List of all members' for namespaces or header files,
+ // but always generate it for derived classes and QML types (but not QML value types)
+ if (!node->isNamespace() && !node->isHeader() && !node->isQmlBasicType()
+ && (derivedClass || node->isQmlType() || !project.m_memberStatus[node].isEmpty())) {
+ QString membersPath = href + QStringLiteral("-members.html");
+ writeSection(writer, membersPath, QStringLiteral("List of all members"));
+ }
+ if (project.m_memberStatus[node].contains(Node::Deprecated)) {
+ QString obsoletePath = href + QStringLiteral("-obsolete.html");
+ writeSection(writer, obsoletePath, QStringLiteral("Obsolete members"));
+ }
+}
+
+void HelpProjectWriter::writeNode(HelpProject &project, QXmlStreamWriter &writer, const Node *node)
+{
+ QString href = m_gen->fullDocumentLocation(node);
+ QString objName = node->name();
+
+ switch (node->nodeType()) {
+
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ case Node::QmlType:
+ case Node::QmlValueType: {
+ QString typeStr = m_gen->typeString(node);
+ if (!typeStr.isEmpty())
+ typeStr[0] = typeStr[0].toTitleCase();
+ writer.writeStartElement("section");
+ writer.writeAttribute("ref", href);
+ if (node->parent() && !node->parent()->name().isEmpty())
+ writer.writeAttribute("title",
+ QStringLiteral("%1::%2 %3 Reference")
+ .arg(node->parent()->name(), objName, typeStr));
+ else
+ writer.writeAttribute("title", QStringLiteral("%1 %2 Reference").arg(objName, typeStr));
+
+ addMembers(project, writer, node);
+ writer.writeEndElement(); // section
+ } break;
+
+ case Node::Namespace:
+ writeSection(writer, href, objName);
+ break;
+
+ case Node::Example:
+ case Node::HeaderFile:
+ case Node::Page:
+ case Node::Group:
+ case Node::Module:
+ case Node::QmlModule: {
+ writer.writeStartElement("section");
+ writer.writeAttribute("ref", href);
+ writer.writeAttribute("title", node->fullTitle());
+ if (node->nodeType() == Node::HeaderFile)
+ addMembers(project, writer, node);
+ writer.writeEndElement(); // section
+ } break;
+ default:;
+ }
+}
+
+void HelpProjectWriter::generateProject(HelpProject &project)
+{
+ const Node *rootNode;
+
+ // Restrict searching only to the local (primary) tree
+ QList<Tree *> searchOrder = m_qdb->searchOrder();
+ m_qdb->setLocalSearch();
+
+ if (!project.m_indexRoot.isEmpty())
+ rootNode = m_qdb->findPageNodeByTitle(project.m_indexRoot);
+ else
+ rootNode = m_qdb->primaryTreeRoot();
+
+ if (rootNode == nullptr)
+ return;
+
+ project.m_files.clear();
+ project.m_keywords.clear();
+
+ QFile file(m_outputDir + QDir::separator() + project.m_fileName);
+ if (!file.open(QFile::WriteOnly))
+ return;
+
+ QXmlStreamWriter writer(&file);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument();
+ writer.writeStartElement("QtHelpProject");
+ writer.writeAttribute("version", "1.0");
+
+ // Write metaData, virtualFolder and namespace elements.
+ writer.writeTextElement("namespace", project.m_helpNamespace);
+ writer.writeTextElement("virtualFolder", project.m_virtualFolder);
+ writer.writeStartElement("metaData");
+ writer.writeAttribute("name", "version");
+ writer.writeAttribute("value", project.m_version);
+ writer.writeEndElement();
+
+ // Write customFilter elements.
+ for (auto it = project.m_customFilters.constBegin(); it != project.m_customFilters.constEnd();
+ ++it) {
+ writer.writeStartElement("customFilter");
+ writer.writeAttribute("name", it.key());
+ QStringList sortedAttributes = it.value().values();
+ sortedAttributes.sort();
+ for (const auto &filter : std::as_const(sortedAttributes))
+ writer.writeTextElement("filterAttribute", filter);
+ writer.writeEndElement(); // customFilter
+ }
+
+ // Start the filterSection.
+ writer.writeStartElement("filterSection");
+
+ // Write filterAttribute elements.
+ QStringList sortedFilterAttributes = project.m_filterAttributes.values();
+ sortedFilterAttributes.sort();
+ for (const auto &filterName : std::as_const(sortedFilterAttributes))
+ writer.writeTextElement("filterAttribute", filterName);
+
+ writer.writeStartElement("toc");
+ writer.writeStartElement("section");
+ const Node *node = m_qdb->findPageNodeByTitle(project.m_indexTitle);
+ if (!node)
+ node = m_qdb->findNodeByNameAndType(QStringList(project.m_indexTitle), &Node::isPageNode);
+ if (!node)
+ node = m_qdb->findNodeByNameAndType(QStringList("index.html"), &Node::isPageNode);
+ QString indexPath;
+ if (node)
+ indexPath = m_gen->fullDocumentLocation(node);
+ else
+ indexPath = "index.html";
+ writer.writeAttribute("ref", indexPath);
+ writer.writeAttribute("title", project.m_indexTitle);
+
+ generateSections(project, writer, rootNode);
+
+ for (int i = 0; i < project.m_subprojects.size(); i++) {
+ SubProject subproject = project.m_subprojects[i];
+
+ if (subproject.m_type == QLatin1String("manual")) {
+
+ const Node *indexPage = m_qdb->findNodeForTarget(subproject.m_indexTitle, nullptr);
+ if (indexPage) {
+ Text indexBody = indexPage->doc().body();
+ const Atom *atom = indexBody.firstAtom();
+ QStack<int> sectionStack;
+ bool inItem = false;
+
+ while (atom) {
+ switch (atom->type()) {
+ case Atom::ListLeft:
+ sectionStack.push(0);
+ break;
+ case Atom::ListRight:
+ if (sectionStack.pop() > 0)
+ writer.writeEndElement(); // section
+ break;
+ case Atom::ListItemLeft:
+ inItem = true;
+ break;
+ case Atom::ListItemRight:
+ inItem = false;
+ break;
+ case Atom::Link:
+ if (inItem) {
+ if (sectionStack.top() > 0)
+ writer.writeEndElement(); // section
+
+ const Node *page = m_qdb->findNodeForTarget(atom->string(), nullptr);
+ writer.writeStartElement("section");
+ QString indexPath = m_gen->fullDocumentLocation(page);
+ writer.writeAttribute("ref", indexPath);
+ writer.writeAttribute("title", atom->linkText());
+
+ sectionStack.top() += 1;
+ }
+ break;
+ default:;
+ }
+
+ if (atom == indexBody.lastAtom())
+ break;
+ atom = atom->next();
+ }
+ } else
+ rootNode->doc().location().warning(
+ QStringLiteral("Failed to find index: %1").arg(subproject.m_indexTitle));
+
+ } else {
+
+ writer.writeStartElement("section");
+ QString indexPath = m_gen->fullDocumentLocation(
+ m_qdb->findNodeForTarget(subproject.m_indexTitle, nullptr));
+ writer.writeAttribute("ref", indexPath);
+ writer.writeAttribute("title", subproject.m_title);
+
+ if (subproject.m_sortPages) {
+ QStringList titles = subproject.m_nodes.keys();
+ titles.sort();
+ for (const auto &title : std::as_const(titles)) {
+ writeNode(project, writer, subproject.m_nodes[title]);
+ }
+ } else {
+ // Find a contents node and navigate from there, using the NextLink values.
+ QSet<QString> visited;
+ bool contentsFound = false;
+ for (const auto *node : std::as_const(subproject.m_nodes)) {
+ QString nextTitle = node->links().value(Node::NextLink).first;
+ if (!nextTitle.isEmpty()
+ && node->links().value(Node::ContentsLink).first.isEmpty()) {
+
+ const Node *nextPage = m_qdb->findNodeForTarget(nextTitle, nullptr);
+
+ // Write the contents node.
+ writeNode(project, writer, node);
+ contentsFound = true;
+
+ while (nextPage) {
+ writeNode(project, writer, nextPage);
+ nextTitle = nextPage->links().value(Node::NextLink).first;
+ if (nextTitle.isEmpty() || visited.contains(nextTitle))
+ break;
+ nextPage = m_qdb->findNodeForTarget(nextTitle, nullptr);
+ visited.insert(nextTitle);
+ }
+ break;
+ }
+ }
+ // No contents/nextpage links found, write all nodes unsorted
+ if (!contentsFound) {
+ QList<const Node *> subnodes = subproject.m_nodes.values();
+
+ std::sort(subnodes.begin(), subnodes.end(), Node::nodeNameLessThan);
+
+ for (const auto *node : std::as_const(subnodes))
+ writeNode(project, writer, node);
+ }
+ }
+
+ writer.writeEndElement(); // section
+ }
+ }
+
+ // Restore original search order
+ m_qdb->setSearchOrder(searchOrder);
+
+ writer.writeEndElement(); // section
+ writer.writeEndElement(); // toc
+
+ writer.writeStartElement("keywords");
+ std::sort(project.m_keywords.begin(), project.m_keywords.end());
+ for (const auto &k : std::as_const(project.m_keywords)) {
+ for (const auto &id : std::as_const(k.m_ids)) {
+ writer.writeStartElement("keyword");
+ writer.writeAttribute("name", k.m_name);
+ writer.writeAttribute("id", id);
+ writer.writeAttribute("ref", k.m_ref);
+ writer.writeEndElement(); //keyword
+ }
+ }
+ writer.writeEndElement(); // keywords
+
+ writer.writeStartElement("files");
+
+ // The list of files to write is the union of generated files and
+ // other files (images and extras) included in the project
+ QSet<QString> files =
+ QSet<QString>(m_gen->outputFileNames().cbegin(), m_gen->outputFileNames().cend());
+ files.unite(project.m_files);
+ files.unite(project.m_extraFiles);
+ QStringList sortedFiles = files.values();
+ sortedFiles.sort();
+ for (const auto &usedFile : std::as_const(sortedFiles)) {
+ if (!usedFile.isEmpty())
+ writer.writeTextElement("file", usedFile);
+ }
+ writer.writeEndElement(); // files
+
+ writer.writeEndElement(); // filterSection
+ writer.writeEndElement(); // QtHelpProject
+ writer.writeEndDocument();
+ file.close();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/helpprojectwriter.h b/src/qdoc/qdoc/src/qdoc/helpprojectwriter.h
new file mode 100644
index 000000000..11dd67fb1
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/helpprojectwriter.h
@@ -0,0 +1,106 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef HELPPROJECTWRITER_H
+#define HELPPROJECTWRITER_H
+
+#include "node.h"
+
+#include <QtCore/qstring.h>
+#include <QtCore/qxmlstream.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+class QDocDatabase;
+class Generator;
+
+using NodeTypeSet = QSet<unsigned char>;
+
+struct SubProject
+{
+ QString m_title {};
+ QString m_indexTitle {};
+ NodeTypeSet m_selectors {};
+ bool m_sortPages {};
+ QString m_type {};
+ QHash<QString, const Node *> m_nodes {};
+ QStringList m_groups {};
+};
+
+/*
+ * Name is the human-readable name to be shown in Assistant.
+ * Ids is a list of unique identifiers.
+ * Ref is the location of the documentation for the keyword.
+ */
+struct Keyword {
+ QString m_name {};
+ QStringList m_ids {};
+ QString m_ref {};
+ Keyword(QString name, const QString &id, QString ref)
+ : m_name(std::move(name)), m_ids(QStringList(id)), m_ref(std::move(ref))
+ {
+ }
+ Keyword(QString name, QStringList ids, QString ref)
+ : m_name(std::move(name)), m_ids(std::move(ids)), m_ref(std::move(ref))
+ {
+ }
+ bool operator<(const Keyword &o) const
+ {
+ // Order by name; use ref as a secondary sort key
+ return (m_name == o.m_name) ? m_ref < o.m_ref : m_name < o.m_name;
+ }
+};
+
+struct HelpProject
+{
+ using NodeStatusSet = QSet<unsigned char>;
+
+ QString m_name {};
+ QString m_helpNamespace {};
+ QString m_virtualFolder {};
+ QString m_version {};
+ QString m_fileName {};
+ QString m_indexRoot {};
+ QString m_indexTitle {};
+ QList<Keyword> m_keywords {};
+ QSet<QString> m_files {};
+ QSet<QString> m_extraFiles {};
+ QSet<QString> m_filterAttributes {};
+ QHash<QString, QSet<QString>> m_customFilters {};
+ QSet<QString> m_excluded {};
+ QList<SubProject> m_subprojects {};
+ QHash<const Node *, NodeStatusSet> m_memberStatus {};
+ bool m_includeIndexNodes {};
+};
+
+
+class HelpProjectWriter
+{
+public:
+ HelpProjectWriter(const QString &defaultFileName, Generator *g);
+ void reset(const QString &defaultFileName, Generator *g);
+ void addExtraFile(const QString &file);
+ void generate();
+
+private:
+ void generateProject(HelpProject &project);
+ void generateSections(HelpProject &project, QXmlStreamWriter &writer, const Node *node);
+ bool generateSection(HelpProject &project, QXmlStreamWriter &writer, const Node *node);
+ Keyword keywordDetails(const Node *node) const;
+ void writeNode(HelpProject &project, QXmlStreamWriter &writer, const Node *node);
+ void readSelectors(SubProject &subproject, const QStringList &selectors);
+ void addMembers(HelpProject &project, QXmlStreamWriter &writer, const Node *node);
+ void writeSection(QXmlStreamWriter &writer, const QString &path, const QString &value);
+
+ QDocDatabase *m_qdb {};
+ Generator *m_gen {};
+
+ QString m_outputDir {};
+ QList<HelpProject> m_projects {};
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/htmlgenerator.cpp b/src/qdoc/qdoc/src/qdoc/htmlgenerator.cpp
new file mode 100644
index 000000000..e18cac8b6
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/htmlgenerator.cpp
@@ -0,0 +1,3733 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "htmlgenerator.h"
+
+#include "access.h"
+#include "aggregate.h"
+#include "classnode.h"
+#include "collectionnode.h"
+#include "config.h"
+#include "codemarker.h"
+#include "codeparser.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "helpprojectwriter.h"
+#include "manifestwriter.h"
+#include "node.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "qmlpropertynode.h"
+#include "sharedcommentnode.h"
+#include "tagfilewriter.h"
+#include "tree.h"
+#include "quoter.h"
+#include "utilities.h"
+
+#include <QtCore/qlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/quuid.h>
+#include <QtCore/qversionnumber.h>
+#include <QtCore/qregularexpression.h>
+
+#include <cctype>
+#include <deque>
+#include <string>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+bool HtmlGenerator::s_inUnorderedList { false };
+
+HtmlGenerator::HtmlGenerator(FileResolver& file_resolver) : XmlGenerator(file_resolver) {}
+
+static void addLink(const QString &linkTarget, QStringView nestedStuff, QString *res)
+{
+ if (!linkTarget.isEmpty()) {
+ *res += QLatin1String("<a href=\"");
+ *res += linkTarget;
+ *res += QLatin1String("\" translate=\"no\">");
+ *res += nestedStuff;
+ *res += QLatin1String("</a>");
+ } else {
+ *res += nestedStuff;
+ }
+}
+
+/*!
+ \internal
+ Convenience method that starts an unordered list if not in one.
+ */
+inline void HtmlGenerator::openUnorderedList()
+{
+ if (!s_inUnorderedList) {
+ out() << "<ul>\n";
+ s_inUnorderedList = true;
+ }
+}
+
+/*!
+ \internal
+ Convenience method that closes an unordered list if in one.
+ */
+inline void HtmlGenerator::closeUnorderedList()
+{
+ if (s_inUnorderedList) {
+ out() << "</ul>\n";
+ s_inUnorderedList = false;
+ }
+}
+
+/*!
+ Destroys the HTML output generator. Deletes the singleton
+ instance of HelpProjectWriter and the ManifestWriter instance.
+ */
+HtmlGenerator::~HtmlGenerator()
+{
+ if (m_helpProjectWriter) {
+ delete m_helpProjectWriter;
+ m_helpProjectWriter = nullptr;
+ }
+
+ if (m_manifestWriter) {
+ delete m_manifestWriter;
+ m_manifestWriter = nullptr;
+ }
+}
+
+/*!
+ Initializes the HTML output generator's data structures
+ from the configuration (Config) singleton.
+ */
+void HtmlGenerator::initializeGenerator()
+{
+ static const struct
+ {
+ const char *key;
+ const char *left;
+ const char *right;
+ } defaults[] = { { ATOM_FORMATTING_BOLD, "<b>", "</b>" },
+ { ATOM_FORMATTING_INDEX, "<!--", "-->" },
+ { ATOM_FORMATTING_ITALIC, "<i>", "</i>" },
+ { ATOM_FORMATTING_PARAMETER, "<i translate=\"no\">", "</i>" },
+ { ATOM_FORMATTING_SUBSCRIPT, "<sub>", "</sub>" },
+ { ATOM_FORMATTING_SUPERSCRIPT, "<sup>", "</sup>" },
+ { ATOM_FORMATTING_TELETYPE, "<code translate=\"no\">",
+ "</code>" }, // <tt> tag is not supported in HTML5
+ { ATOM_FORMATTING_TRADEMARK, "<span translate=\"no\">", "&#8482;" },
+ { ATOM_FORMATTING_UICONTROL, "<b translate=\"no\">", "</b>" },
+ { ATOM_FORMATTING_UNDERLINE, "<u>", "</u>" },
+ { nullptr, nullptr, nullptr } };
+
+ Generator::initializeGenerator();
+ config = &Config::instance();
+
+ /*
+ The formatting maps are owned by Generator. They are cleared in
+ Generator::terminate().
+ */
+ for (int i = 0; defaults[i].key; ++i) {
+ formattingLeftMap().insert(QLatin1String(defaults[i].key), QLatin1String(defaults[i].left));
+ formattingRightMap().insert(QLatin1String(defaults[i].key),
+ QLatin1String(defaults[i].right));
+ }
+
+ QString formatDot{HtmlGenerator::format() + Config::dot};
+ m_endHeader = config->get(formatDot + CONFIG_ENDHEADER).asString();
+ m_postHeader = config->get(formatDot + HTMLGENERATOR_POSTHEADER).asString();
+ m_postPostHeader = config->get(formatDot + HTMLGENERATOR_POSTPOSTHEADER).asString();
+ m_prologue = config->get(formatDot + HTMLGENERATOR_PROLOGUE).asString();
+
+ m_footer = config->get(formatDot + HTMLGENERATOR_FOOTER).asString();
+ m_address = config->get(formatDot + HTMLGENERATOR_ADDRESS).asString();
+ m_noNavigationBar = config->get(formatDot + HTMLGENERATOR_NONAVIGATIONBAR).asBool();
+ m_navigationSeparator = config->get(formatDot + HTMLGENERATOR_NAVIGATIONSEPARATOR).asString();
+ tocDepth = config->get(formatDot + HTMLGENERATOR_TOCDEPTH).asInt();
+
+ m_project = config->get(CONFIG_PROJECT).asString();
+ m_projectDescription = config->get(CONFIG_DESCRIPTION)
+ .asString(m_project + QLatin1String(" Reference Documentation"));
+
+ m_projectUrl = config->get(CONFIG_URL).asString();
+ tagFile_ = config->get(CONFIG_TAGFILE).asString();
+ naturalLanguage = config->get(CONFIG_NATURALLANGUAGE).asString(QLatin1String("en"));
+
+ m_codeIndent = config->get(CONFIG_CODEINDENT).asInt();
+ m_codePrefix = config->get(CONFIG_CODEPREFIX).asString();
+ m_codeSuffix = config->get(CONFIG_CODESUFFIX).asString();
+
+ /*
+ The help file write should be allocated once and only once
+ per qdoc execution.
+ */
+ if (m_helpProjectWriter)
+ m_helpProjectWriter->reset(m_project.toLower() + ".qhp", this);
+ else
+ m_helpProjectWriter = new HelpProjectWriter(m_project.toLower() + ".qhp", this);
+
+ if (!m_manifestWriter)
+ m_manifestWriter = new ManifestWriter();
+
+ // Documentation template handling
+ m_headerScripts = config->get(formatDot + CONFIG_HEADERSCRIPTS).asString();
+ m_headerStyles = config->get(formatDot + CONFIG_HEADERSTYLES).asString();
+
+ // Retrieve the config for the navigation bar
+ m_homepage = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_HOMEPAGE).asString();
+
+ m_hometitle = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_HOMETITLE)
+ .asString(m_homepage);
+
+ m_landingpage = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_LANDINGPAGE).asString();
+
+ m_landingtitle = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_LANDINGTITLE)
+ .asString(m_landingpage);
+
+ m_cppclassespage = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_CPPCLASSESPAGE).asString();
+
+ m_cppclassestitle = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_CPPCLASSESTITLE)
+ .asString(QLatin1String("C++ Classes"));
+
+ m_qmltypespage = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_QMLTYPESPAGE).asString();
+
+ m_qmltypestitle = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_QMLTYPESTITLE)
+ .asString(QLatin1String("QML Types"));
+
+ m_trademarkspage = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_TRADEMARKSPAGE).asString();
+
+ m_buildversion = config->get(CONFIG_BUILDVERSION).asString();
+}
+
+/*!
+ Gracefully terminates the HTML output generator.
+ */
+void HtmlGenerator::terminateGenerator()
+{
+ Generator::terminateGenerator();
+}
+
+QString HtmlGenerator::format()
+{
+ return "HTML";
+}
+
+/*!
+ If qdoc is in the \c {-prepare} phase, traverse the primary
+ tree to generate the index file for the current module.
+
+ If qdoc is in the \c {-generate} phase, traverse the primary
+ tree to generate all the HTML documentation for the current
+ module. Then generate the help file and the tag file.
+ */
+void HtmlGenerator::generateDocs()
+{
+ Node *qflags = m_qdb->findClassNode(QStringList("QFlags"));
+ if (qflags)
+ m_qflagsHref = linkForNode(qflags, nullptr);
+ if (!config->preparing())
+ Generator::generateDocs();
+
+ if (!config->generating()) {
+ QString fileBase =
+ m_project.toLower().simplified().replace(QLatin1Char(' '), QLatin1Char('-'));
+ m_qdb->generateIndex(outputDir() + QLatin1Char('/') + fileBase + ".index", m_projectUrl,
+ m_projectDescription, this);
+ }
+
+ if (!config->preparing()) {
+ m_helpProjectWriter->generate();
+ m_manifestWriter->generateManifestFiles();
+ /*
+ Generate the XML tag file, if it was requested.
+ */
+ if (!tagFile_.isEmpty()) {
+ TagFileWriter tagFileWriter;
+ tagFileWriter.generateTagFile(tagFile_, this);
+ }
+ }
+}
+
+/*!
+ Generate an html file with the contents of a C++ or QML source file.
+ */
+void HtmlGenerator::generateExampleFilePage(const Node *en, ResolvedFile resolved_file, CodeMarker *marker)
+{
+ SubTitleSize subTitleSize = LargeSubTitle;
+ QString fullTitle = en->fullTitle();
+
+ beginSubPage(en, linkForExampleFile(resolved_file.get_query()));
+ generateHeader(fullTitle, en, marker);
+ generateTitle(fullTitle, Text() << en->subtitle(), subTitleSize, en, marker);
+
+ Text text;
+ Quoter quoter;
+ Doc::quoteFromFile(en->doc().location(), quoter, resolved_file);
+ QString code = quoter.quoteTo(en->location(), QString(), QString());
+ CodeMarker *codeMarker = CodeMarker::markerForFileName(resolved_file.get_path());
+ text << Atom(codeMarker->atomType(), code);
+ Atom a(codeMarker->atomType(), code);
+
+ generateText(text, en, codeMarker);
+ endSubPage();
+}
+
+/*!
+ Generate html from an instance of Atom.
+ */
+qsizetype HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker)
+{
+ qsizetype idx, skipAhead = 0;
+ static bool in_para = false;
+ Node::Genus genus = Node::DontCare;
+
+ switch (atom->type()) {
+ case Atom::AutoLink: {
+ QString name = atom->string();
+ if (relative && relative->name() == name.replace(QLatin1String("()"), QLatin1String())) {
+ out() << protectEnc(atom->string());
+ break;
+ }
+ // Allow auto-linking to nodes in API reference
+ genus = Node::API;
+ }
+ Q_FALLTHROUGH();
+ case Atom::NavAutoLink:
+ if (!m_inLink && !m_inContents && !m_inSectionHeading) {
+ const Node *node = nullptr;
+ QString link = getAutoLink(atom, relative, &node, genus);
+ if (link.isEmpty()) {
+ if (autolinkErrors() && relative)
+ relative->doc().location().warning(
+ QStringLiteral("Can't autolink to '%1'").arg(atom->string()));
+ } else if (node && node->isDeprecated()) {
+ if (relative && (relative->parent() != node) && !relative->isDeprecated())
+ link.clear();
+ }
+ if (link.isEmpty()) {
+ out() << protectEnc(atom->string());
+ } else {
+ beginLink(link, node, relative);
+ generateLink(atom);
+ endLink();
+ }
+ } else {
+ out() << protectEnc(atom->string());
+ }
+ break;
+ case Atom::BaseName:
+ break;
+ case Atom::BriefLeft:
+ if (!hasBrief(relative)) {
+ skipAhead = skipAtoms(atom, Atom::BriefRight);
+ break;
+ }
+ out() << "<p>";
+ rewritePropertyBrief(atom, relative);
+ break;
+ case Atom::BriefRight:
+ if (hasBrief(relative))
+ out() << "</p>\n";
+ break;
+ case Atom::C:
+ // This may at one time have been used to mark up C++ code but it is
+ // now widely used to write teletype text. As a result, text marked
+ // with the \c command is not passed to a code marker.
+ out() << formattingLeftMap()[ATOM_FORMATTING_TELETYPE];
+ out() << protectEnc(plainCode(atom->string()));
+ out() << formattingRightMap()[ATOM_FORMATTING_TELETYPE];
+ break;
+ case Atom::CaptionLeft:
+ out() << "<p class=\"figCaption\">";
+ in_para = true;
+ break;
+ case Atom::CaptionRight:
+ endLink();
+ if (in_para) {
+ out() << "</p>\n";
+ in_para = false;
+ }
+ break;
+ case Atom::Qml:
+ out() << "<pre class=\"qml\" translate=\"no\">"
+ << trimmedTrailing(highlightedCode(indent(m_codeIndent, atom->string()), relative,
+ false, Node::QML),
+ m_codePrefix, m_codeSuffix)
+ << "</pre>\n";
+ break;
+ case Atom::Code:
+ out() << "<pre class=\"cpp\" translate=\"no\">"
+ << trimmedTrailing(highlightedCode(indent(m_codeIndent, atom->string()), relative),
+ m_codePrefix, m_codeSuffix)
+ << "</pre>\n";
+ break;
+ case Atom::CodeBad:
+ out() << "<pre class=\"cpp plain\" translate=\"no\">"
+ << trimmedTrailing(protectEnc(plainCode(indent(m_codeIndent, atom->string()))),
+ m_codePrefix, m_codeSuffix)
+ << "</pre>\n";
+ break;
+ case Atom::DetailsLeft:
+ out() << "<details>\n";
+ if (!atom->string().isEmpty())
+ out() << "<summary>" << protectEnc(atom->string()) << "</summary>\n";
+ else
+ out() << "<summary>...</summary>\n";
+ break;
+ case Atom::DetailsRight:
+ out() << "</details>\n";
+ break;
+ case Atom::DivLeft:
+ out() << "<div";
+ if (!atom->string().isEmpty())
+ out() << ' ' << atom->string();
+ out() << '>';
+ break;
+ case Atom::DivRight:
+ out() << "</div>";
+ break;
+ case Atom::FootnoteLeft:
+ // ### For now
+ if (in_para) {
+ out() << "</p>\n";
+ in_para = false;
+ }
+ out() << "<!-- ";
+ break;
+ case Atom::FootnoteRight:
+ // ### For now
+ out() << "-->\n";
+ break;
+ case Atom::FormatElse:
+ case Atom::FormatEndif:
+ case Atom::FormatIf:
+ break;
+ case Atom::FormattingLeft:
+ if (atom->string().startsWith("span "))
+ out() << '<' + atom->string() << '>';
+ else
+ out() << formattingLeftMap()[atom->string()];
+ break;
+ case Atom::FormattingRight:
+ if (atom->string() == ATOM_FORMATTING_LINK) {
+ endLink();
+ } else if (atom->string() == ATOM_FORMATTING_TRADEMARK) {
+ if (appendTrademark(atom)) {
+ // Make the trademark symbol a link to navigation.trademarkspage (if set)
+ const Node *node{nullptr};
+ const Atom tm_link(Atom::NavLink, m_trademarkspage);
+ if (const auto &link = getLink(&tm_link, relative, &node);
+ !link.isEmpty() && node != relative)
+ out() << "<a href=\"%1\">%2</a>"_L1.arg(link, formattingRightMap()[atom->string()]);
+ else
+ out() << formattingRightMap()[atom->string()];
+ }
+ out() << "</span>";
+ } else if (atom->string().startsWith("span ")) {
+ out() << "</span>";
+ } else {
+ out() << formattingRightMap()[atom->string()];
+ }
+ break;
+ case Atom::AnnotatedList: {
+ if (const auto *cn = m_qdb->getCollectionNode(atom->string(), Node::Group); cn)
+ generateList(cn, marker, atom->string(), Generator::sortOrder(atom->strings().last()));
+ } break;
+ case Atom::GeneratedList: {
+ const auto sortOrder{Generator::sortOrder(atom->strings().last())};
+ if (atom->string() == QLatin1String("annotatedclasses")) {
+ generateAnnotatedList(relative, marker, m_qdb->getCppClasses().values(), sortOrder);
+ } else if (atom->string() == QLatin1String("annotatedexamples")) {
+ generateAnnotatedLists(relative, marker, m_qdb->getExamples());
+ } else if (atom->string() == QLatin1String("annotatedattributions")) {
+ generateAnnotatedLists(relative, marker, m_qdb->getAttributions());
+ } else if (atom->string() == QLatin1String("classes")) {
+ generateCompactList(Generic, relative, m_qdb->getCppClasses(), true,
+ QStringLiteral(""));
+ } else if (atom->string().contains("classes ")) {
+ QString rootName = atom->string().mid(atom->string().indexOf("classes") + 7).trimmed();
+ generateCompactList(Generic, relative, m_qdb->getCppClasses(), true, rootName);
+ } else if (atom->string() == QLatin1String("qmlvaluetypes")
+ || atom->string() == QLatin1String("qmlbasictypes")) {
+ generateCompactList(Generic, relative, m_qdb->getQmlValueTypes(), true,
+ QStringLiteral(""));
+ } else if (atom->string() == QLatin1String("qmltypes")) {
+ generateCompactList(Generic, relative, m_qdb->getQmlTypes(), true, QStringLiteral(""));
+ } else if ((idx = atom->string().indexOf(QStringLiteral("bymodule"))) != -1) {
+ QDocDatabase *qdb = QDocDatabase::qdocDB();
+ QString moduleName = atom->string().mid(idx + 8).trimmed();
+ Node::NodeType moduleType = typeFromString(atom);
+ if (const auto *cn = qdb->getCollectionNode(moduleName, moduleType)) {
+ NodeMap map;
+ switch (moduleType) {
+ case Node::Module:
+ // classesbymodule <module_name>
+ map = cn->getMembers([](const Node *n) { return n->isClassNode(); });
+ generateAnnotatedList(relative, marker, map.values(), sortOrder);
+ break;
+ case Node::QmlModule:
+ if (atom->string().contains(QLatin1String("qmlvaluetypes")))
+ map = cn->getMembers(Node::QmlValueType); // qmlvaluetypesbymodule <module_name>
+ else
+ map = cn->getMembers(Node::QmlType); // qmltypesbymodule <module_name>
+ generateAnnotatedList(relative, marker, map.values(), sortOrder);
+ break;
+ default: // fall back to listing all members
+ generateAnnotatedList(relative, marker, cn->members(), sortOrder);
+ break;
+ }
+ }
+ } else if (atom->string() == QLatin1String("classhierarchy")) {
+ generateClassHierarchy(relative, m_qdb->getCppClasses());
+ } else if (atom->string() == QLatin1String("obsoleteclasses")) {
+ generateCompactList(Generic, relative, m_qdb->getObsoleteClasses(), false,
+ QStringLiteral("Q"));
+ } else if (atom->string() == QLatin1String("obsoleteqmltypes")) {
+ generateCompactList(Generic, relative, m_qdb->getObsoleteQmlTypes(), false,
+ QStringLiteral(""));
+ } else if (atom->string() == QLatin1String("obsoletecppmembers")) {
+ generateCompactList(Obsolete, relative, m_qdb->getClassesWithObsoleteMembers(), false,
+ QStringLiteral("Q"));
+ } else if (atom->string() == QLatin1String("obsoleteqmlmembers")) {
+ generateCompactList(Obsolete, relative, m_qdb->getQmlTypesWithObsoleteMembers(), false,
+ QStringLiteral(""));
+ } else if (atom->string() == QLatin1String("functionindex")) {
+ generateFunctionIndex(relative);
+ } else if (atom->string() == QLatin1String("attributions")) {
+ generateAnnotatedList(relative, marker, m_qdb->getAttributions().values(), sortOrder);
+ } else if (atom->string() == QLatin1String("legalese")) {
+ generateLegaleseList(relative, marker);
+ } else if (atom->string() == QLatin1String("overviews")) {
+ generateList(relative, marker, "overviews", sortOrder);
+ } else if (atom->string() == QLatin1String("cpp-modules")) {
+ generateList(relative, marker, "cpp-modules", sortOrder);
+ } else if (atom->string() == QLatin1String("qml-modules")) {
+ generateList(relative, marker, "qml-modules", sortOrder);
+ } else if (atom->string() == QLatin1String("namespaces")) {
+ generateAnnotatedList(relative, marker, m_qdb->getNamespaces().values(), sortOrder);
+ } else if (atom->string() == QLatin1String("related")) {
+ generateList(relative, marker, "related", sortOrder);
+ } else {
+ const CollectionNode *cn = m_qdb->getCollectionNode(atom->string(), Node::Group);
+ if (cn) {
+ if (!generateGroupList(const_cast<CollectionNode *>(cn), sortOrder))
+ relative->location().warning(
+ QString("'\\generatelist %1' group is empty").arg(atom->string()));
+ } else {
+ relative->location().warning(
+ QString("'\\generatelist %1' no such group").arg(atom->string()));
+ }
+ }
+ } break;
+ case Atom::SinceList: {
+ const NodeMultiMap &nsmap = m_qdb->getSinceMap(atom->string());
+ if (nsmap.isEmpty())
+ break;
+
+ const NodeMultiMap &ncmap = m_qdb->getClassMap(atom->string());
+ const NodeMultiMap &nqcmap = m_qdb->getQmlTypeMap(atom->string());
+
+ Sections sections(nsmap);
+ out() << "<ul>\n";
+ const QList<Section> sinceSections = sections.sinceSections();
+ for (const auto &section : sinceSections) {
+ if (!section.members().isEmpty()) {
+ out() << "<li>"
+ << "<a href=\"#" << Utilities::asAsciiPrintable(section.title()) << "\">"
+ << section.title() << "</a></li>\n";
+ }
+ }
+ out() << "</ul>\n";
+
+ int index = 0;
+ for (const auto &section : sinceSections) {
+ if (!section.members().isEmpty()) {
+ out() << "<h3 id=\"" << Utilities::asAsciiPrintable(section.title()) << "\">"
+ << protectEnc(section.title()) << "</h3>\n";
+ if (index == Sections::SinceClasses)
+ generateCompactList(Generic, relative, ncmap, false, QStringLiteral("Q"));
+ else if (index == Sections::SinceQmlTypes)
+ generateCompactList(Generic, relative, nqcmap, false, QStringLiteral(""));
+ else if (index == Sections::SinceMemberFunctions
+ || index == Sections::SinceQmlMethods
+ || index == Sections::SinceQmlProperties) {
+
+ QMap<QString, NodeMultiMap> parentmaps;
+
+ const QList<Node *> &members = section.members();
+ for (const auto &member : members) {
+ QString parent_full_name = (*member).parent()->fullName();
+
+ auto parent_entry = parentmaps.find(parent_full_name);
+ if (parent_entry == parentmaps.end())
+ parent_entry = parentmaps.insert(parent_full_name, NodeMultiMap());
+ parent_entry->insert(member->name(), member);
+ }
+
+ for (auto map = parentmaps.begin(); map != parentmaps.end(); ++map) {
+ NodeVector nv = map->values().toVector();
+ auto parent = nv.front()->parent();
+
+ out() << ((index == Sections::SinceMemberFunctions) ? "<p>Class " : "<p>QML Type ");
+
+ out() << "<a href=\"" << linkForNode(parent, relative) << "\" translate=\"no\">";
+ QStringList pieces = parent->fullName().split("::");
+ out() << protectEnc(pieces.last());
+ out() << "</a>"
+ << ":</p>\n";
+
+ generateSection(nv, relative, marker);
+ out() << "<br/>";
+ }
+ } else if (index == Sections::SinceEnumValues) {
+ out() << "<div class=\"table\"><table class=\"alignedsummary\" translate=\"no\">\n";
+ const auto map_it = m_qdb->newEnumValueMaps().constFind(atom->string());
+ for (auto it = map_it->cbegin(); it != map_it->cend(); ++it) {
+ out() << "<tr><td class=\"memItemLeft\"> enum value </td><td class=\"memItemRight\">"
+ << "<b><a href=\"" << linkForNode(it.value(), nullptr) << "\">"
+ << it.key() << "</a></b></td></tr>\n";
+ }
+ out() << "</table></div>\n";
+ } else {
+ generateSection(section.members(), relative, marker);
+ }
+ }
+ ++index;
+ }
+ } break;
+ case Atom::BR:
+ out() << "<br />\n";
+ break;
+ case Atom::HR:
+ out() << "<hr />\n";
+ break;
+ case Atom::Image:
+ case Atom::InlineImage: {
+ QString text;
+ if (atom->next() && atom->next()->type() == Atom::ImageText)
+ text = atom->next()->string();
+ if (atom->type() == Atom::Image)
+ out() << "<p class=\"centerAlign\">";
+
+ auto maybe_resolved_file{file_resolver.resolve(atom->string())};
+ if (!maybe_resolved_file) {
+ // TODO: [uncentralized-admonition]
+ relative->location().warning(
+ QStringLiteral("Missing image: %1").arg(protectEnc(atom->string())));
+ out() << "<font color=\"red\">[Missing image " << protectEnc(atom->string())
+ << "]</font>";
+ } else {
+ ResolvedFile file{*maybe_resolved_file};
+ QString file_name{QFileInfo{file.get_path()}.fileName()};
+
+ // TODO: [operation-can-fail-making-the-output-incorrect]
+ // The operation of copying the file can fail, making the
+ // output refer to an image that does not exist.
+ // This should be fine as HTML will take care of managing
+ // the rendering of a missing image, but what html will
+ // render is in stark contrast with what we do when the
+ // image does not exist at all.
+ // It may be more correct to unify the behavior between
+ // the two either by considering images that cannot be
+ // copied as missing or letting the HTML renderer
+ // always taking care of the two cases.
+ // Do notice that effectively doing this might be
+ // unnecessary as extracting the output directory logic
+ // should ensure that a safe assumption for copy should be
+ // made at the API boundary.
+
+ // TODO: [uncentralized-output-directory-structure]
+ Config::copyFile(relative->doc().location(), file.get_path(), file_name, outputDir() + QLatin1String("/images"));
+
+ // TODO: [uncentralized-output-directory-structure]
+ out() << "<img src=\"" << "images/" + protectEnc(file_name) << '"';
+
+ // TODO: [same-result-branching]
+ // If text is empty protectEnc should return the empty
+ // string itself, such that the two branches would still
+ // result in the same output.
+ // Ensure that this is the case and then flatten the branch if so.
+ if (!text.isEmpty())
+ out() << " alt=\"" << protectEnc(text) << '"';
+ else
+ out() << " alt=\"\"";
+
+ out() << " />";
+
+ // TODO: [uncentralized-output-directory-structure]
+ m_helpProjectWriter->addExtraFile("images/" + file_name);
+ setImageFileName(relative, "images/" + file_name);
+ }
+
+ if (atom->type() == Atom::Image)
+ out() << "</p>";
+ } break;
+ case Atom::ImageText:
+ break;
+ // Admonitions
+ case Atom::ImportantLeft:
+ case Atom::NoteLeft:
+ case Atom::WarningLeft: {
+ QString admonType = atom->typeString();
+ // Remove 'Left' from atom type to get the admonition type
+ admonType.chop(4);
+ out() << "<div class=\"admonition " << admonType.toLower() << "\">\n"
+ << "<p>";
+ out() << formattingLeftMap()[ATOM_FORMATTING_BOLD];
+ out() << admonType << ": ";
+ out() << formattingRightMap()[ATOM_FORMATTING_BOLD];
+ } break;
+ case Atom::ImportantRight:
+ case Atom::NoteRight:
+ case Atom::WarningRight:
+ out() << "</p>\n"
+ << "</div>\n";
+ break;
+ case Atom::LegaleseLeft:
+ out() << "<div class=\"LegaleseLeft\">";
+ break;
+ case Atom::LegaleseRight:
+ out() << "</div>";
+ break;
+ case Atom::LineBreak:
+ out() << "<br/>";
+ break;
+ case Atom::Link:
+ // Prevent nested links in table of contents
+ if (m_inContents)
+ break;
+ Q_FALLTHROUGH();
+ case Atom::NavLink: {
+ const Node *node = nullptr;
+ QString link = getLink(atom, relative, &node);
+ if (link.isEmpty() && (node != relative) && !noLinkErrors()) {
+ Location location = atom->isLinkAtom() ? static_cast<const LinkAtom*>(atom)->location
+ : relative->doc().location();
+ location.warning(
+ QStringLiteral("Can't link to '%1'").arg(atom->string()));
+ }
+ beginLink(link, node, relative);
+ skipAhead = 1;
+ } break;
+ case Atom::ExampleFileLink: {
+ QString link = linkForExampleFile(atom->string());
+ beginLink(link);
+ skipAhead = 1;
+ } break;
+ case Atom::ExampleImageLink: {
+ QString link = atom->string();
+ link = "images/used-in-examples/" + link;
+ beginLink(link);
+ skipAhead = 1;
+ } break;
+ case Atom::LinkNode: {
+ const Node *node = CodeMarker::nodeForString(atom->string());
+ beginLink(linkForNode(node, relative), node, relative);
+ skipAhead = 1;
+ } break;
+ case Atom::ListLeft:
+ if (in_para) {
+ out() << "</p>\n";
+ in_para = false;
+ }
+ if (atom->string() == ATOM_LIST_BULLET) {
+ out() << "<ul>\n";
+ } else if (atom->string() == ATOM_LIST_TAG) {
+ out() << "<dl>\n";
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ out() << R"(<div class="table"><table class="valuelist">)";
+ m_threeColumnEnumValueTable = isThreeColumnEnumValueTable(atom);
+ if (m_threeColumnEnumValueTable) {
+ if (++m_numTableRows % 2 == 1)
+ out() << R"(<tr valign="top" class="odd">)";
+ else
+ out() << R"(<tr valign="top" class="even">)";
+
+ out() << "<th class=\"tblConst\">Constant</th>";
+
+ // If not in \enum topic, skip the value column
+ if (relative->isEnumType())
+ out() << "<th class=\"tblval\">Value</th>";
+
+ out() << "<th class=\"tbldscr\">Description</th></tr>\n";
+ } else {
+ out() << "<tr><th class=\"tblConst\">Constant</th><th "
+ "class=\"tblVal\">Value</th></tr>\n";
+ }
+ } else {
+ QString olType;
+ if (atom->string() == ATOM_LIST_UPPERALPHA) {
+ olType = "A";
+ } else if (atom->string() == ATOM_LIST_LOWERALPHA) {
+ olType = "a";
+ } else if (atom->string() == ATOM_LIST_UPPERROMAN) {
+ olType = "I";
+ } else if (atom->string() == ATOM_LIST_LOWERROMAN) {
+ olType = "i";
+ } else { // (atom->string() == ATOM_LIST_NUMERIC)
+ olType = "1";
+ }
+
+ if (atom->next() != nullptr && atom->next()->string().toInt() > 1) {
+ out() << QString(R"(<ol class="%1" type="%1" start="%2">)")
+ .arg(olType, atom->next()->string());
+ } else
+ out() << QString(R"(<ol class="%1" type="%1">)").arg(olType);
+ }
+ break;
+ case Atom::ListItemNumber:
+ break;
+ case Atom::ListTagLeft:
+ if (atom->string() == ATOM_LIST_TAG) {
+ out() << "<dt>";
+ } else { // (atom->string() == ATOM_LIST_VALUE)
+ std::pair<QString, int> pair = getAtomListValue(atom);
+ skipAhead = pair.second;
+ QString t = protectEnc(plainCode(marker->markedUpEnumValue(pair.first, relative)));
+ out() << "<tr><td class=\"topAlign\"><code translate=\"no\">" << t << "</code>";
+
+ if (relative->isEnumType()) {
+ out() << "</td><td class=\"topAlign tblval\">";
+ const auto *enume = static_cast<const EnumNode *>(relative);
+ QString itemValue = enume->itemValue(atom->next()->string());
+ if (itemValue.isEmpty())
+ out() << '?';
+ else
+ out() << "<code translate=\"no\">" << protectEnc(itemValue) << "</code>";
+ }
+ }
+ break;
+ case Atom::SinceTagRight:
+ case Atom::ListTagRight:
+ if (atom->string() == ATOM_LIST_TAG)
+ out() << "</dt>\n";
+ break;
+ case Atom::ListItemLeft:
+ if (atom->string() == ATOM_LIST_TAG) {
+ out() << "<dd>";
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ if (m_threeColumnEnumValueTable) {
+ out() << "</td><td class=\"topAlign\">";
+ if (matchAhead(atom, Atom::ListItemRight))
+ out() << "&nbsp;";
+ }
+ } else {
+ out() << "<li>";
+ }
+ if (matchAhead(atom, Atom::ParaLeft))
+ skipAhead = 1;
+ break;
+ case Atom::ListItemRight:
+ if (atom->string() == ATOM_LIST_TAG) {
+ out() << "</dd>\n";
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ out() << "</td></tr>\n";
+ } else {
+ out() << "</li>\n";
+ }
+ break;
+ case Atom::ListRight:
+ if (atom->string() == ATOM_LIST_BULLET) {
+ out() << "</ul>\n";
+ } else if (atom->string() == ATOM_LIST_TAG) {
+ out() << "</dl>\n";
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ out() << "</table></div>\n";
+ } else {
+ out() << "</ol>\n";
+ }
+ break;
+ case Atom::Nop:
+ break;
+ case Atom::ParaLeft:
+ out() << "<p>";
+ in_para = true;
+ break;
+ case Atom::ParaRight:
+ endLink();
+ if (in_para) {
+ out() << "</p>\n";
+ in_para = false;
+ }
+ // if (!matchAhead(atom, Atom::ListItemRight) && !matchAhead(atom, Atom::TableItemRight))
+ // out() << "</p>\n";
+ break;
+ case Atom::QuotationLeft:
+ out() << "<blockquote>";
+ break;
+ case Atom::QuotationRight:
+ out() << "</blockquote>\n";
+ break;
+ case Atom::RawString:
+ out() << atom->string();
+ break;
+ case Atom::SectionLeft:
+ case Atom::SectionRight:
+ break;
+ case Atom::SectionHeadingLeft: {
+ int unit = atom->string().toInt() + hOffset(relative);
+ out() << "<h" + QString::number(unit) + QLatin1Char(' ') << "id=\""
+ << Tree::refForAtom(atom) << "\">";
+ m_inSectionHeading = true;
+ break;
+ }
+ case Atom::SectionHeadingRight:
+ out() << "</h" + QString::number(atom->string().toInt() + hOffset(relative)) + ">\n";
+ m_inSectionHeading = false;
+ break;
+ case Atom::SidebarLeft:
+ Q_FALLTHROUGH();
+ case Atom::SidebarRight:
+ break;
+ case Atom::String:
+ if (m_inLink && !m_inContents && !m_inSectionHeading) {
+ generateLink(atom);
+ } else {
+ out() << protectEnc(atom->string());
+ }
+ break;
+ case Atom::TableLeft: {
+ std::pair<QString, QString> pair = getTableWidthAttr(atom);
+ QString attr = pair.second;
+ QString width = pair.first;
+
+ if (in_para) {
+ out() << "</p>\n";
+ in_para = false;
+ }
+
+ out() << R"(<div class="table"><table class=")" << attr << '"';
+ if (!width.isEmpty())
+ out() << " width=\"" << width << '"';
+ out() << ">\n ";
+ m_numTableRows = 0;
+ } break;
+ case Atom::TableRight:
+ out() << "</table></div>\n";
+ break;
+ case Atom::TableHeaderLeft:
+ out() << "<thead><tr class=\"qt-style\">";
+ m_inTableHeader = true;
+ break;
+ case Atom::TableHeaderRight:
+ out() << "</tr>";
+ if (matchAhead(atom, Atom::TableHeaderLeft)) {
+ skipAhead = 1;
+ out() << "\n<tr class=\"qt-style\">";
+ } else {
+ out() << "</thead>\n";
+ m_inTableHeader = false;
+ }
+ break;
+ case Atom::TableRowLeft:
+ if (!atom->string().isEmpty())
+ out() << "<tr " << atom->string() << '>';
+ else if (++m_numTableRows % 2 == 1)
+ out() << R"(<tr valign="top" class="odd">)";
+ else
+ out() << R"(<tr valign="top" class="even">)";
+ break;
+ case Atom::TableRowRight:
+ out() << "</tr>\n";
+ break;
+ case Atom::TableItemLeft: {
+ if (m_inTableHeader)
+ out() << "<th ";
+ else
+ out() << "<td ";
+
+ for (int i = 0; i < atom->count(); ++i) {
+ if (i > 0)
+ out() << ' ';
+ const QString &p = atom->string(i);
+ if (p.contains('=')) {
+ out() << p;
+ } else {
+ QStringList spans = p.split(QLatin1Char(','));
+ if (spans.size() == 2) {
+ if (spans.at(0) != "1")
+ out() << " colspan=\"" << spans.at(0) << '"';
+ if (spans.at(1) != "1")
+ out() << " rowspan=\"" << spans.at(1) << '"';
+ }
+ }
+ }
+ out() << '>';
+ if (matchAhead(atom, Atom::ParaLeft))
+ skipAhead = 1;
+ } break;
+ case Atom::TableItemRight:
+ if (m_inTableHeader)
+ out() << "</th>";
+ else {
+ out() << "</td>";
+ }
+ if (matchAhead(atom, Atom::ParaLeft))
+ skipAhead = 1;
+ break;
+ case Atom::TableOfContents:
+ Q_FALLTHROUGH();
+ case Atom::Keyword:
+ break;
+ case Atom::Target:
+ out() << "<span id=\"" << Utilities::asAsciiPrintable(atom->string()) << "\"></span>";
+ break;
+ case Atom::UnhandledFormat:
+ out() << "<b class=\"redFont\">&lt;Missing HTML&gt;</b>";
+ break;
+ case Atom::UnknownCommand:
+ out() << R"(<b class="redFont"><code translate=\"no\">\)" << protectEnc(atom->string()) << "</code></b>";
+ break;
+ case Atom::CodeQuoteArgument:
+ case Atom::CodeQuoteCommand:
+ case Atom::ComparesLeft:
+ case Atom::ComparesRight:
+ case Atom::SnippetCommand:
+ case Atom::SnippetIdentifier:
+ case Atom::SnippetLocation:
+ // no HTML output (ignore)
+ break;
+ default:
+ unknownAtom(atom);
+ }
+ return skipAhead;
+}
+
+/*!
+ * Return a string representing a text that exposes information about
+ * the user-visible groups that the \a node is part of. A user-visible
+ * group is a group that generates an output page, that is, a \\group
+ * topic exists for the group and can be linked to.
+ *
+ * The returned string is composed of comma separated links to the
+ * groups, with their title as the user-facing text, surrounded by
+ * some introductory text.
+ *
+ * For example, if a node named N is part of the groups with title A
+ * and B, the line rendered form of the line will be "N is part of the
+ * A, B groups", where A and B are clickable links that target the
+ * respective page of each group.
+ *
+ * If a node has a single group, the comma is removed for readability
+ * pusposes and "groups" is expressed as a singular noun.
+ * For example, "N is part of the A group".
+ *
+ * The returned string is empty when the node is not linked to any
+ * group that has a valid link target.
+ *
+ * This string is used in the summary of c++ classes or qml types to
+ * link them to some of the overview documentation that is generated
+ * through the "\group" command.
+ *
+ * Note that this is currently, incorrectly, a member of
+ * HtmlGenerator as it requires access to some protected/private
+ * members for escaping and linking.
+ */
+QString HtmlGenerator::groupReferenceText(PageNode* node) {
+ auto link_for_group = [this](const CollectionNode *group) -> QString {
+ QString target{linkForNode(group, nullptr)};
+ return (target.isEmpty()) ? protectEnc(group->name()) : "<a href=\"" + target + "\">" + protectEnc(group->fullTitle()) + "</a>";
+ };
+
+ QString text{};
+
+ const QStringList &groups_names{node->groupNames()};
+ if (groups_names.isEmpty())
+ return text;
+
+ std::vector<CollectionNode *> groups_nodes(groups_names.size(), nullptr);
+ std::transform(groups_names.cbegin(), groups_names.cend(), groups_nodes.begin(),
+ [this](const QString &group_name) -> CollectionNode* {
+ CollectionNode *group{m_qdb->groups()[group_name]};
+ m_qdb->mergeCollections(group);
+ return (group && group->wasSeen()) ? group : nullptr;
+ });
+ groups_nodes.erase(std::remove(groups_nodes.begin(), groups_nodes.end(), nullptr), groups_nodes.end());
+
+ if (!groups_nodes.empty()) {
+ text += node->name() + " is part of ";
+
+ for (std::vector<CollectionNode *>::size_type index{0}; index < groups_nodes.size(); ++index) {
+ text += link_for_group(groups_nodes[index]) + Utilities::separator(index, groups_nodes.size());
+ }
+ }
+ return text;
+}
+
+/*!
+ Generate a reference page for the C++ class, namespace, or
+ header file documented in \a node using the code \a marker
+ provided.
+ */
+void HtmlGenerator::generateCppReferencePage(Aggregate *aggregate, CodeMarker *marker)
+{
+ QString title;
+ QString rawTitle;
+ QString fullTitle;
+ NamespaceNode *ns = nullptr;
+ SectionVector *summarySections = nullptr;
+ SectionVector *detailsSections = nullptr;
+
+ Sections sections(aggregate);
+ QString word = aggregate->typeWord(true);
+ auto templateDecl = aggregate->templateDecl();
+ if (aggregate->isNamespace()) {
+ rawTitle = aggregate->plainName();
+ fullTitle = aggregate->plainFullName();
+ title = rawTitle + " Namespace";
+ ns = static_cast<NamespaceNode *>(aggregate);
+ summarySections = &sections.stdSummarySections();
+ detailsSections = &sections.stdDetailsSections();
+ } else if (aggregate->isClassNode()) {
+ rawTitle = aggregate->plainName();
+ fullTitle = aggregate->plainFullName();
+ title = rawTitle + QLatin1Char(' ') + word;
+ summarySections = &sections.stdCppClassSummarySections();
+ detailsSections = &sections.stdCppClassDetailsSections();
+ } else if (aggregate->isHeader()) {
+ title = fullTitle = rawTitle = aggregate->fullTitle();
+ summarySections = &sections.stdSummarySections();
+ detailsSections = &sections.stdDetailsSections();
+ }
+
+ Text subtitleText;
+ if (rawTitle != fullTitle || templateDecl) {
+ if (aggregate->isClassNode()) {
+ if (templateDecl)
+ subtitleText << (*templateDecl).to_qstring() + QLatin1Char(' ');
+ subtitleText << aggregate->typeWord(false) + QLatin1Char(' ');
+ const QStringList ancestors = fullTitle.split(QLatin1String("::"));
+ for (const auto &a : ancestors) {
+ if (a == rawTitle) {
+ subtitleText << a;
+ break;
+ } else {
+ subtitleText << Atom(Atom::AutoLink, a) << "::";
+ }
+ }
+ } else {
+ subtitleText << fullTitle;
+ }
+ }
+
+ generateHeader(title, aggregate, marker);
+ generateTableOfContents(aggregate, marker, summarySections);
+ generateTitle(title, subtitleText, SmallSubTitle, aggregate, marker);
+ if (ns && !ns->hasDoc() && ns->docNode()) {
+ NamespaceNode *NS = ns->docNode();
+ Text brief;
+ brief << "The " << ns->name() << " namespace includes the following elements from module "
+ << ns->tree()->camelCaseModuleName() << ". The full namespace is "
+ << "documented in module " << NS->tree()->camelCaseModuleName()
+ << Atom(Atom::LinkNode, CodeMarker::stringForNode(NS))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, " here.")
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ out() << "<p>";
+ generateText(brief, ns, marker);
+ out() << "</p>\n";
+ } else
+ generateBrief(aggregate, marker);
+
+ const auto parentIsClass = aggregate->parent()->isClassNode();
+
+ if (!parentIsClass)
+ generateRequisites(aggregate, marker);
+ generateStatus(aggregate, marker);
+ if (parentIsClass)
+ generateSince(aggregate, marker);
+
+ QString membersLink = generateAllMembersFile(Sections::allMembersSection(), marker);
+ if (!membersLink.isEmpty()) {
+ openUnorderedList();
+ out() << "<li><a href=\"" << membersLink << "\">"
+ << "List of all members, including inherited members</a></li>\n";
+ }
+ QString obsoleteLink = generateObsoleteMembersFile(sections, marker);
+ if (!obsoleteLink.isEmpty()) {
+ openUnorderedList();
+ out() << "<li><a href=\"" << obsoleteLink << "\">"
+ << "Deprecated members</a></li>\n";
+ }
+
+ if (QString groups_text{groupReferenceText(aggregate)}; !groups_text.isEmpty()) {
+ openUnorderedList();
+
+ out() << "<li>" << groups_text << "</li>\n";
+ }
+
+ closeUnorderedList();
+ generateComparisonCategory(aggregate, marker);
+ generateComparisonList(aggregate);
+
+ generateThreadSafeness(aggregate, marker);
+
+ bool needOtherSection = false;
+
+ for (const auto &section : std::as_const(*summarySections)) {
+ if (section.members().isEmpty() && section.reimplementedMembers().isEmpty()) {
+ if (!section.inheritedMembers().isEmpty())
+ needOtherSection = true;
+ } else {
+ if (!section.members().isEmpty()) {
+ QString ref = registerRef(section.title().toLower());
+ out() << "<h2 id=\"" << ref << "\">" << protectEnc(section.title()) << "</h2>\n";
+ generateSection(section.members(), aggregate, marker);
+ }
+ if (!section.reimplementedMembers().isEmpty()) {
+ QString name = QString("Reimplemented ") + section.title();
+ QString ref = registerRef(name.toLower());
+ out() << "<h2 id=\"" << ref << "\">" << protectEnc(name) << "</h2>\n";
+ generateSection(section.reimplementedMembers(), aggregate, marker);
+ }
+
+ if (!section.inheritedMembers().isEmpty()) {
+ out() << "<ul>\n";
+ generateSectionInheritedList(section, aggregate);
+ out() << "</ul>\n";
+ }
+ }
+ }
+
+ if (needOtherSection) {
+ out() << "<h3>Additional Inherited Members</h3>\n"
+ "<ul>\n";
+
+ for (const auto &section : std::as_const(*summarySections)) {
+ if (section.members().isEmpty() && !section.inheritedMembers().isEmpty())
+ generateSectionInheritedList(section, aggregate);
+ }
+ out() << "</ul>\n";
+ }
+
+ if (aggregate->doc().isEmpty()) {
+ QString command = "documentation";
+ if (aggregate->isClassNode())
+ command = R"('\class' comment)";
+ if (!ns || ns->isDocumentedHere()) {
+ aggregate->location().warning(
+ QStringLiteral("No %1 for '%2'").arg(command, aggregate->plainSignature()));
+ }
+ } else {
+ generateExtractionMark(aggregate, DetailedDescriptionMark);
+ out() << "<div class=\"descr\">\n"
+ << "<h2 id=\"" << registerRef("details") << "\">"
+ << "Detailed Description"
+ << "</h2>\n";
+ generateBody(aggregate, marker);
+ out() << "</div>\n";
+ generateAlsoList(aggregate, marker);
+ generateExtractionMark(aggregate, EndMark);
+ }
+
+ for (const auto &section : std::as_const(*detailsSections)) {
+ bool headerGenerated = false;
+ if (section.isEmpty())
+ continue;
+
+ const QList<Node *> &members = section.members();
+ for (const auto &member : members) {
+ if (member->access() == Access::Private) // ### check necessary?
+ continue;
+ if (!headerGenerated) {
+ if (!section.divClass().isEmpty())
+ out() << "<div class=\"" << section.divClass() << "\">\n";
+ out() << "<h2>" << protectEnc(section.title()) << "</h2>\n";
+ headerGenerated = true;
+ }
+ if (!member->isClassNode())
+ generateDetailedMember(member, aggregate, marker);
+ else {
+ out() << "<h3> class ";
+ generateFullName(member, aggregate);
+ out() << "</h3>";
+ generateBrief(member, marker, aggregate);
+ }
+
+ QStringList names;
+ names << member->name();
+ if (member->isFunction()) {
+ const auto *func = reinterpret_cast<const FunctionNode *>(member);
+ if (func->isSomeCtor() || func->isDtor() || func->overloadNumber() != 0)
+ names.clear();
+ } else if (member->isProperty()) {
+ const auto *prop = reinterpret_cast<const PropertyNode *>(member);
+ if (!prop->getters().isEmpty() && !names.contains(prop->getters().first()->name()))
+ names << prop->getters().first()->name();
+ if (!prop->setters().isEmpty())
+ names << prop->setters().first()->name();
+ if (!prop->resetters().isEmpty())
+ names << prop->resetters().first()->name();
+ if (!prop->notifiers().isEmpty())
+ names << prop->notifiers().first()->name();
+ } else if (member->isEnumType()) {
+ const auto *enume = reinterpret_cast<const EnumNode *>(member);
+ if (enume->flagsType())
+ names << enume->flagsType()->name();
+ const auto &enumItemNameList = enume->doc().enumItemNames();
+ const auto &omitEnumItemNameList = enume->doc().omitEnumItemNames();
+ const auto items = QSet<QString>(enumItemNameList.cbegin(), enumItemNameList.cend())
+ - QSet<QString>(omitEnumItemNameList.cbegin(), omitEnumItemNameList.cend());
+ for (const QString &enumName : items) {
+ names << plainCode(marker->markedUpEnumValue(enumName, enume));
+ }
+ }
+ }
+ if (headerGenerated && !section.divClass().isEmpty())
+ out() << "</div>\n";
+ }
+ generateFooter(aggregate);
+}
+
+void HtmlGenerator::generateProxyPage(Aggregate *aggregate, CodeMarker *marker)
+{
+ Q_ASSERT(aggregate->isProxyNode());
+
+ QString title;
+ QString rawTitle;
+ QString fullTitle;
+ Text subtitleText;
+ SectionVector *summarySections = nullptr;
+ SectionVector *detailsSections = nullptr;
+
+ Sections sections(aggregate);
+ rawTitle = aggregate->plainName();
+ fullTitle = aggregate->plainFullName();
+ title = rawTitle + " Proxy Page";
+ summarySections = &sections.stdSummarySections();
+ detailsSections = &sections.stdDetailsSections();
+ generateHeader(title, aggregate, marker);
+ generateTitle(title, subtitleText, SmallSubTitle, aggregate, marker);
+ generateBrief(aggregate, marker);
+ for (auto it = summarySections->constBegin(); it != summarySections->constEnd(); ++it) {
+ if (!it->members().isEmpty()) {
+ QString ref = registerRef(it->title().toLower());
+ out() << "<h2 id=\"" << ref << "\">" << protectEnc(it->title()) << "</h2>\n";
+ generateSection(it->members(), aggregate, marker);
+ }
+ }
+
+ if (!aggregate->doc().isEmpty()) {
+ generateExtractionMark(aggregate, DetailedDescriptionMark);
+ out() << "<div class=\"descr\">\n"
+ << "<h2 id=\"" << registerRef("details") << "\">"
+ << "Detailed Description"
+ << "</h2>\n";
+ generateBody(aggregate, marker);
+ out() << "</div>\n";
+ generateAlsoList(aggregate, marker);
+ generateExtractionMark(aggregate, EndMark);
+ }
+
+ for (const auto &section : std::as_const(*detailsSections)) {
+ if (section.isEmpty())
+ continue;
+
+ if (!section.divClass().isEmpty())
+ out() << "<div class=\"" << section.divClass() << "\">\n";
+ out() << "<h2>" << protectEnc(section.title()) << "</h2>\n";
+
+ const QList<Node *> &members = section.members();
+ for (const auto &member : members) {
+ if (!member->isPrivate()) { // ### check necessary?
+ if (!member->isClassNode())
+ generateDetailedMember(member, aggregate, marker);
+ else {
+ out() << "<h3> class ";
+ generateFullName(member, aggregate);
+ out() << "</h3>";
+ generateBrief(member, marker, aggregate);
+ }
+
+ QStringList names;
+ names << member->name();
+ if (member->isFunction()) {
+ const auto *func = reinterpret_cast<const FunctionNode *>(member);
+ if (func->isSomeCtor() || func->isDtor() || func->overloadNumber() != 0)
+ names.clear();
+ } else if (member->isEnumType()) {
+ const auto *enume = reinterpret_cast<const EnumNode *>(member);
+ if (enume->flagsType())
+ names << enume->flagsType()->name();
+ const auto &enumItemNameList = enume->doc().enumItemNames();
+ const auto &omitEnumItemNameList = enume->doc().omitEnumItemNames();
+ const auto items =
+ QSet<QString>(enumItemNameList.cbegin(), enumItemNameList.cend())
+ - QSet<QString>(omitEnumItemNameList.cbegin(),
+ omitEnumItemNameList.cend());
+ for (const QString &enumName : items)
+ names << plainCode(marker->markedUpEnumValue(enumName, enume));
+ }
+ }
+ }
+ if (!section.divClass().isEmpty())
+ out() << "</div>\n";
+ }
+ generateFooter(aggregate);
+}
+
+/*!
+ Generate the HTML page for a QML type. \qcn is the QML type.
+ \marker is the code markeup object.
+ */
+void HtmlGenerator::generateQmlTypePage(QmlTypeNode *qcn, CodeMarker *marker)
+{
+ Generator::setQmlTypeContext(qcn);
+ SubTitleSize subTitleSize = LargeSubTitle;
+ QString htmlTitle = qcn->fullTitle();
+ if (qcn->isQmlBasicType())
+ htmlTitle.append(" QML Value Type");
+ else
+ htmlTitle.append(" QML Type");
+
+
+ generateHeader(htmlTitle, qcn, marker);
+ Sections sections(qcn);
+ generateTableOfContents(qcn, marker, &sections.stdQmlTypeSummarySections());
+ marker = CodeMarker::markerForLanguage(QLatin1String("QML"));
+ generateTitle(htmlTitle, Text() << qcn->subtitle(), subTitleSize, qcn, marker);
+ generateBrief(qcn, marker);
+ generateQmlRequisites(qcn, marker);
+ generateStatus(qcn, marker);
+
+ QString allQmlMembersLink;
+
+ // No 'All Members' file for QML value types
+ if (!qcn->isQmlBasicType())
+ allQmlMembersLink = generateAllQmlMembersFile(sections, marker);
+ QString obsoleteLink = generateObsoleteQmlMembersFile(sections, marker);
+ if (!allQmlMembersLink.isEmpty() || !obsoleteLink.isEmpty()) {
+ openUnorderedList();
+
+ if (!allQmlMembersLink.isEmpty()) {
+ out() << "<li><a href=\"" << allQmlMembersLink << "\">"
+ << "List of all members, including inherited members</a></li>\n";
+ }
+ if (!obsoleteLink.isEmpty()) {
+ out() << "<li><a href=\"" << obsoleteLink << "\">"
+ << "Deprecated members</a></li>\n";
+ }
+ }
+
+ if (QString groups_text{groupReferenceText(qcn)}; !groups_text.isEmpty()) {
+ openUnorderedList();
+
+ out() << "<li>" << groups_text << "</li>\n";
+ }
+
+ closeUnorderedList();
+
+ const QList<Section> &stdQmlTypeSummarySections = sections.stdQmlTypeSummarySections();
+ for (const auto &section : stdQmlTypeSummarySections) {
+ if (!section.isEmpty()) {
+ QString ref = registerRef(section.title().toLower());
+ out() << "<h2 id=\"" << ref << "\">" << protectEnc(section.title()) << "</h2>\n";
+ generateQmlSummary(section.members(), qcn, marker);
+ }
+ }
+
+ generateExtractionMark(qcn, DetailedDescriptionMark);
+ out() << "<h2 id=\"" << registerRef("details") << "\">"
+ << "Detailed Description"
+ << "</h2>\n";
+ generateBody(qcn, marker);
+ generateAlsoList(qcn, marker);
+ generateExtractionMark(qcn, EndMark);
+
+ const QList<Section> &stdQmlTypeDetailsSections = sections.stdQmlTypeDetailsSections();
+ for (const auto &section : stdQmlTypeDetailsSections) {
+ if (!section.isEmpty()) {
+ out() << "<h2>" << protectEnc(section.title()) << "</h2>\n";
+ const QList<Node *> &members = section.members();
+ for (const auto member : members) {
+ generateDetailedQmlMember(member, qcn, marker);
+ out() << "<br/>\n";
+ }
+ }
+ }
+ generateFooter(qcn);
+ Generator::setQmlTypeContext(nullptr);
+}
+
+/*!
+ Generate the HTML page for an entity that doesn't map
+ to any underlying parsable C++ or QML element.
+ */
+void HtmlGenerator::generatePageNode(PageNode *pn, CodeMarker *marker)
+{
+ SubTitleSize subTitleSize = LargeSubTitle;
+ QString fullTitle = pn->fullTitle();
+
+ generateHeader(fullTitle, pn, marker);
+ /*
+ Generate the TOC for the new doc format.
+ Don't generate a TOC for the home page.
+ */
+ if ((pn->name() != QLatin1String("index.html")))
+ generateTableOfContents(pn, marker, nullptr);
+
+ generateTitle(fullTitle, Text() << pn->subtitle(), subTitleSize, pn, marker);
+ if (pn->isExample()) {
+ generateBrief(pn, marker, nullptr, false);
+ }
+
+ generateExtractionMark(pn, DetailedDescriptionMark);
+ out() << R"(<div class="descr" id=")" << registerRef("details")
+ << "\">\n";
+
+ generateBody(pn, marker);
+ out() << "</div>\n";
+ generateAlsoList(pn, marker);
+ generateExtractionMark(pn, EndMark);
+
+ generateFooter(pn);
+}
+
+/*!
+ Generate the HTML page for a group, module, or QML module.
+ */
+void HtmlGenerator::generateCollectionNode(CollectionNode *cn, CodeMarker *marker)
+{
+ SubTitleSize subTitleSize = LargeSubTitle;
+ QString fullTitle = cn->fullTitle();
+ QString ref;
+
+ generateHeader(fullTitle, cn, marker);
+ generateTableOfContents(cn, marker, nullptr);
+ generateTitle(fullTitle, Text() << cn->subtitle(), subTitleSize, cn, marker);
+
+ // Generate brief for C++ modules, status for all modules.
+ if (cn->genus() != Node::DOC && cn->genus() != Node::DontCare) {
+ if (cn->isModule())
+ generateBrief(cn, marker);
+ generateStatus(cn, marker);
+ generateSince(cn, marker);
+ }
+
+ if (cn->isModule()) {
+ if (!cn->noAutoList()) {
+ NodeMap nmm{cn->getMembers(Node::Namespace)};
+ if (!nmm.isEmpty()) {
+ ref = registerRef("namespaces");
+ out() << "<h2 id=\"" << ref << "\">Namespaces</h2>\n";
+ generateAnnotatedList(cn, marker, nmm.values());
+ }
+ nmm = cn->getMembers([](const Node *n){ return n->isClassNode(); });
+ if (!nmm.isEmpty()) {
+ ref = registerRef("classes");
+ out() << "<h2 id=\"" << ref << "\">Classes</h2>\n";
+ generateAnnotatedList(cn, marker, nmm.values());
+ }
+ }
+ }
+
+ if (cn->isModule() && !cn->doc().briefText().isEmpty()) {
+ generateExtractionMark(cn, DetailedDescriptionMark);
+ ref = registerRef("details");
+ out() << "<div class=\"descr\">\n";
+ out() << "<h2 id=\"" << ref << "\">"
+ << "Detailed Description"
+ << "</h2>\n";
+ } else {
+ generateExtractionMark(cn, DetailedDescriptionMark);
+ out() << R"(<div class="descr" id=")" << registerRef("details")
+ << "\">\n";
+ }
+
+ generateBody(cn, marker);
+ out() << "</div>\n";
+ generateAlsoList(cn, marker);
+ generateExtractionMark(cn, EndMark);
+
+ if (!cn->noAutoList()) {
+ if (cn->isGroup() || cn->isQmlModule())
+ generateAnnotatedList(cn, marker, cn->members());
+ }
+ generateFooter(cn);
+}
+
+/*!
+ Generate the HTML page for a generic collection. This is usually
+ a collection of C++ elements that are related to an element in
+ a different module.
+ */
+void HtmlGenerator::generateGenericCollectionPage(CollectionNode *cn, CodeMarker *marker)
+{
+ SubTitleSize subTitleSize = LargeSubTitle;
+ QString fullTitle = cn->name();
+
+ generateHeader(fullTitle, cn, marker);
+ generateTitle(fullTitle, Text() << cn->subtitle(), subTitleSize, cn, marker);
+
+ Text brief;
+ brief << "Each function or type documented here is related to a class or "
+ << "namespace that is documented in a different module. The reference "
+ << "page for that class or namespace will link to the function or type "
+ << "on this page.";
+ out() << "<p>";
+ generateText(brief, cn, marker);
+ out() << "</p>\n";
+
+ const QList<Node *> members = cn->members();
+ for (const auto &member : members)
+ generateDetailedMember(member, cn, marker);
+
+ generateFooter(cn);
+}
+
+/*!
+ Returns "html" for this subclass of Generator.
+ */
+QString HtmlGenerator::fileExtension() const
+{
+ return "html";
+}
+
+/*!
+ Output a navigation bar (breadcrumbs) for the html file.
+ For API reference pages, items for the navigation bar are (in order):
+ \table
+ \header \li Item \li Related configuration variable \li Notes
+ \row \li home \li navigation.homepage \li e.g. 'Qt 6.2'
+ \row \li landing \li navigation.landingpage \li Module landing page
+ \row \li types \li navigation.cppclassespage (C++)\br
+ navigation.qmltypespage (QML) \li Types only
+ \row \li module \li n/a (automatic) \li Module page if different
+ from previous item
+ \row \li page \li n/a \li Current page title
+ \endtable
+
+ For other page types (page nodes) the navigation bar is constructed from home
+ page, landing page, and the chain of PageNode::navigationParent() items (if one exists).
+ This chain is constructed from the \\list structure on a page or pages defined in
+ \c navigation.toctitles configuration variable.
+
+ Finally, if no other navigation data exists for a page but it is a member of a
+ single group (using \\ingroup), add that group page to the navigation bar.
+ */
+void HtmlGenerator::generateNavigationBar(const QString &title, const Node *node,
+ CodeMarker *marker, const QString &buildversion,
+ bool tableItems)
+{
+ if (m_noNavigationBar || node == nullptr)
+ return;
+
+ Text navigationbar;
+
+ // Set list item types based on the navigation bar type
+ // TODO: Do we still need table items?
+ Atom::AtomType itemLeft = tableItems ? Atom::TableItemLeft : Atom::ListItemLeft;
+ Atom::AtomType itemRight = tableItems ? Atom::TableItemRight : Atom::ListItemRight;
+
+ // Helper to add an item to navigation bar based on a string link target
+ auto addNavItem = [&](const QString &link, const QString &title) {
+ navigationbar << Atom(itemLeft) << Atom(Atom::NavLink, link)
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, title)
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(itemRight);
+ };
+
+ // Helper to add an item to navigation bar based on a target node
+ auto addNavItemNode = [&](const Node *node, const QString &title) {
+ navigationbar << Atom(itemLeft) << Atom(Atom::LinkNode, CodeMarker::stringForNode(node))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, title)
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(itemRight);
+ };
+
+ // Resolve the associated module (collection) node and its 'state' description
+ const auto *moduleNode = m_qdb->getModuleNode(node);
+ QString moduleState;
+ if (moduleNode && !moduleNode->state().isEmpty())
+ moduleState = QStringLiteral(" (%1)").arg(moduleNode->state());
+
+ if (m_hometitle == title)
+ return;
+ if (!m_homepage.isEmpty())
+ addNavItem(m_homepage, m_hometitle);
+ if (!m_landingpage.isEmpty() && m_landingtitle != title)
+ addNavItem(m_landingpage, m_landingtitle);
+
+ if (node->isClassNode()) {
+ if (!m_cppclassespage.isEmpty() && !m_cppclassestitle.isEmpty())
+ addNavItem(m_cppclassespage, m_cppclassestitle);
+ if (!node->physicalModuleName().isEmpty()) {
+ // Add explicit link to the \module page if:
+ // - It's not the C++ classes page that's already added, OR
+ // - It has a \modulestate associated with it
+ if (moduleNode && (!moduleState.isEmpty() || moduleNode->title() != m_cppclassespage))
+ addNavItemNode(moduleNode, moduleNode->name() + moduleState);
+ }
+ navigationbar << Atom(itemLeft) << Atom(Atom::String, node->name()) << Atom(itemRight);
+ } else if (node->isQmlType()) {
+ if (!m_qmltypespage.isEmpty() && !m_qmltypestitle.isEmpty())
+ addNavItem(m_qmltypespage, m_qmltypestitle);
+ // Add explicit link to the \qmlmodule page if:
+ // - It's not the QML types page that's already added, OR
+ // - It has a \modulestate associated with it
+ if (moduleNode && (!moduleState.isEmpty() || moduleNode->title() != m_qmltypespage)) {
+ addNavItemNode(moduleNode, moduleNode->name() + moduleState);
+ }
+ navigationbar << Atom(itemLeft) << Atom(Atom::String, node->name()) << Atom(itemRight);
+ } else {
+ if (node->isPageNode()) {
+ auto currentNode{static_cast<const PageNode*>(node)};
+ std::deque<const Node *> navNodes;
+ // Cutoff at 16 items in case there's a circular dependency
+ qsizetype navItems = 0;
+ while (currentNode->navigationParent() && ++navItems < 16) {
+ if (std::find(navNodes.cbegin(), navNodes.cend(),
+ currentNode->navigationParent()) == navNodes.cend())
+ navNodes.push_front(currentNode->navigationParent());
+ currentNode = currentNode->navigationParent();
+ }
+ // If no nav. parent was found but the page is a \group member, add a link to the
+ // (first) group page.
+ if (navNodes.empty()) {
+ const QStringList groups = static_cast<const PageNode *>(node)->groupNames();
+ for (const auto &groupName : groups) {
+ const auto *groupNode = m_qdb->findNodeByNameAndType(QStringList{groupName}, &Node::isGroup);
+ if (groupNode && !groupNode->title().isEmpty()) {
+ navNodes.push_front(groupNode);
+ break;
+ }
+ }
+ }
+ while (!navNodes.empty()) {
+ if (navNodes.front()->isPageNode())
+ addNavItemNode(navNodes.front(), navNodes.front()->title());
+ navNodes.pop_front();
+ }
+ }
+ if (!navigationbar.isEmpty()) {
+ navigationbar << Atom(itemLeft) << Atom(Atom::String, title) << Atom(itemRight);
+ }
+ }
+
+ generateText(navigationbar, node, marker);
+
+ if (buildversion.isEmpty())
+ return;
+
+ navigationbar.clear();
+
+ if (tableItems) {
+ out() << "</tr></table><table class=\"buildversion\"><tr>\n"
+ << R"(<td id="buildversion" width="100%" align="right">)";
+ } else {
+ out() << "<li id=\"buildversion\">";
+ }
+
+ // Link buildversion string to navigation.landingpage
+ if (!m_landingpage.isEmpty() && m_landingtitle != title) {
+ navigationbar << Atom(Atom::NavLink, m_landingpage)
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, buildversion)
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ generateText(navigationbar, node, marker);
+ } else {
+ out() << buildversion;
+ }
+ if (tableItems)
+ out() << "</td>\n";
+ else
+ out() << "</li>\n";
+}
+
+void HtmlGenerator::generateHeader(const QString &title, const Node *node, CodeMarker *marker)
+{
+ out() << "<!DOCTYPE html>\n";
+ out() << QString("<html lang=\"%1\">\n").arg(naturalLanguage);
+ out() << "<head>\n";
+ out() << " <meta charset=\"utf-8\">\n";
+ if (node && !node->doc().location().isEmpty())
+ out() << "<!-- " << node->doc().location().fileName() << " -->\n";
+
+ if (node && !node->doc().briefText().isEmpty()) {
+ out() << " <meta name=\"description\" content=\""
+ << protectEnc(node->doc().briefText().toString())
+ << "\">\n";
+ }
+
+ // determine the rest of the <title> element content: "title | titleSuffix version"
+ QString titleSuffix;
+ if (!m_landingtitle.isEmpty()) {
+ // for normal pages: "title | landingtitle version"
+ titleSuffix = m_landingtitle;
+ } else if (!m_hometitle.isEmpty()) {
+ // for pages that set the homepage title but not landing page title:
+ // "title | hometitle version"
+ if (title != m_hometitle)
+ titleSuffix = m_hometitle;
+ } else if (!m_project.isEmpty()) {
+ // for projects outside of Qt or Qt 5: "title | project version"
+ if (title != m_project)
+ titleSuffix = m_project;
+ } else
+ // default: "title | Qt version"
+ titleSuffix = QLatin1String("Qt ");
+
+ if (title == titleSuffix)
+ titleSuffix.clear();
+
+ QString divider;
+ if (!titleSuffix.isEmpty() && !title.isEmpty())
+ divider = QLatin1String(" | ");
+
+ // Generating page title
+ out() << " <title>" << protectEnc(title) << divider << titleSuffix;
+
+ // append a full version to the suffix if neither suffix nor title
+ // include (a prefix of) version information
+ QVersionNumber projectVersion = QVersionNumber::fromString(m_qdb->version());
+ if (!projectVersion.isNull()) {
+ QVersionNumber titleVersion;
+ static const QRegularExpression re(QLatin1String(R"(\d+\.\d+)"));
+ const QString &versionedTitle = titleSuffix.isEmpty() ? title : titleSuffix;
+ auto match = re.match(versionedTitle);
+ if (match.hasMatch())
+ titleVersion = QVersionNumber::fromString(match.captured());
+ if (titleVersion.isNull() || !titleVersion.isPrefixOf(projectVersion))
+ out() << QLatin1Char(' ') << projectVersion.toString();
+ }
+ out() << "</title>\n";
+
+ // Include style sheet and script links.
+ out() << m_headerStyles;
+ out() << m_headerScripts;
+ if (m_endHeader.isEmpty())
+ out() << "</head>\n<body>\n";
+ else
+ out() << m_endHeader;
+
+ out() << QString(m_postHeader).replace("\\" + COMMAND_VERSION, m_qdb->version());
+ bool usingTable = m_postHeader.trimmed().endsWith(QLatin1String("<tr>"));
+ generateNavigationBar(title, node, marker, m_buildversion, usingTable);
+ out() << QString(m_postPostHeader).replace("\\" + COMMAND_VERSION, m_qdb->version());
+
+ m_navigationLinks.clear();
+ refMap.clear();
+
+ if (node && !node->links().empty()) {
+ std::pair<QString, QString> linkPair;
+ std::pair<QString, QString> anchorPair;
+ const Node *linkNode;
+ bool useSeparator = false;
+
+ if (node->links().contains(Node::PreviousLink)) {
+ linkPair = node->links()[Node::PreviousLink];
+ linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
+ if (linkNode == nullptr && !noLinkErrors())
+ node->doc().location().warning(
+ QStringLiteral("Cannot link to '%1'").arg(linkPair.first));
+ if (linkNode == nullptr || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ out() << R"( <link rel="prev" href=")" << anchorPair.first << "\" />\n";
+
+ m_navigationLinks += R"(<a class="prevPage" href=")" + anchorPair.first + "\">";
+ if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
+ m_navigationLinks += protect(anchorPair.second);
+ else
+ m_navigationLinks += protect(linkPair.second);
+ m_navigationLinks += "</a>\n";
+ useSeparator = !m_navigationSeparator.isEmpty();
+ }
+ if (node->links().contains(Node::NextLink)) {
+ linkPair = node->links()[Node::NextLink];
+ linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
+ if (linkNode == nullptr && !noLinkErrors())
+ node->doc().location().warning(
+ QStringLiteral("Cannot link to '%1'").arg(linkPair.first));
+ if (linkNode == nullptr || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ out() << R"( <link rel="next" href=")" << anchorPair.first << "\" />\n";
+
+ if (useSeparator)
+ m_navigationLinks += m_navigationSeparator;
+
+ m_navigationLinks += R"(<a class="nextPage" href=")" + anchorPair.first + "\">";
+ if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
+ m_navigationLinks += protect(anchorPair.second);
+ else
+ m_navigationLinks += protect(linkPair.second);
+ m_navigationLinks += "</a>\n";
+ }
+ if (node->links().contains(Node::StartLink)) {
+ linkPair = node->links()[Node::StartLink];
+ linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
+ if (linkNode == nullptr && !noLinkErrors())
+ node->doc().location().warning(
+ QStringLiteral("Cannot link to '%1'").arg(linkPair.first));
+ if (linkNode == nullptr || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+ out() << R"( <link rel="start" href=")" << anchorPair.first << "\" />\n";
+ }
+ }
+
+ if (node && !node->links().empty())
+ out() << "<p class=\"naviNextPrevious headerNavi\">\n" << m_navigationLinks << "</p>\n";
+}
+
+void HtmlGenerator::generateTitle(const QString &title, const Text &subtitle,
+ SubTitleSize subTitleSize, const Node *relative,
+ CodeMarker *marker)
+{
+ out() << QString(m_prologue).replace("\\" + COMMAND_VERSION, m_qdb->version());
+ QString attribute;
+ if (relative->genus() & Node::API)
+ attribute = R"( translate="no")";
+
+ if (!title.isEmpty())
+ out() << "<h1 class=\"title\"" << attribute << ">" << protectEnc(title) << "</h1>\n";
+ if (!subtitle.isEmpty()) {
+ out() << "<span";
+ if (subTitleSize == SmallSubTitle)
+ out() << " class=\"small-subtitle\"" << attribute << ">";
+ else
+ out() << " class=\"subtitle\"" << attribute << ">";
+ generateText(subtitle, relative, marker);
+ out() << "</span>\n";
+ }
+}
+
+void HtmlGenerator::generateFooter(const Node *node)
+{
+ if (node && !node->links().empty())
+ out() << "<p class=\"naviNextPrevious footerNavi\">\n" << m_navigationLinks << "</p>\n";
+
+ out() << QString(m_footer).replace("\\" + COMMAND_VERSION, m_qdb->version())
+ << QString(m_address).replace("\\" + COMMAND_VERSION, m_qdb->version());
+
+ out() << "</body>\n";
+ out() << "</html>\n";
+}
+
+/*!
+Lists the required imports and includes in a table.
+The number of rows is known.
+*/
+void HtmlGenerator::generateRequisites(Aggregate *aggregate, CodeMarker *marker)
+{
+ QMap<QString, Text> requisites;
+ Text text;
+
+ const QString headerText = "Header";
+ const QString sinceText = "Since";
+ const QString inheritedBytext = "Inherited By";
+ const QString inheritsText = "Inherits";
+ const QString nativeTypeText = "In QML";
+ const QString qtVariableText = "qmake";
+ const QString cmakeText = "CMake";
+ const QString statusText = "Status";
+
+ // The order of the requisites matter
+ const QStringList requisiteorder { headerText, cmakeText, qtVariableText, sinceText,
+ nativeTypeText, inheritsText, inheritedBytext, statusText };
+
+ addIncludeFileToMap(aggregate, marker, requisites, text, headerText);
+ addSinceToMap(aggregate, requisites, &text, sinceText);
+
+ if (aggregate->isClassNode() || aggregate->isNamespace()) {
+ addCMakeInfoToMap(aggregate, requisites, &text, cmakeText);
+ addQtVariableToMap(aggregate, requisites, &text, qtVariableText);
+ }
+
+ if (aggregate->isClassNode()) {
+ auto *classe = dynamic_cast<ClassNode *>(aggregate);
+ if (classe->isQmlNativeType() && !classe->isInternal())
+ addQmlNativeTypesToMap(requisites, &text, nativeTypeText, classe);
+
+ addInheritsToMap(requisites, &text, inheritsText, classe);
+ addInheritedByToMap(requisites, &text, inheritedBytext, classe);
+ }
+
+ // Add the state description (if any) to the map
+ addStatusToMap(aggregate, requisites, text, statusText);
+
+ if (!requisites.isEmpty()) {
+ // generate the table
+ generateTheTable(requisiteorder, requisites, headerText, aggregate, marker);
+ }
+}
+
+/*!
+ * \internal
+ */
+void HtmlGenerator::generateTheTable(const QStringList &requisiteOrder,
+ const QMap<QString, Text> &requisites,
+ const QString &headerText, const Aggregate *aggregate,
+ CodeMarker *marker)
+{
+ out() << "<div class=\"table\"><table class=\"alignedsummary\" translate=\"no\">\n";
+
+ for (auto it = requisiteOrder.constBegin(); it != requisiteOrder.constEnd(); ++it) {
+
+ if (requisites.contains(*it)) {
+ out() << "<tr>"
+ << "<td class=\"memItemLeft rightAlign topAlign\"> " << *it
+ << ":"
+ "</td><td class=\"memItemRight bottomAlign\"> ";
+
+ if (*it == headerText)
+ out() << requisites.value(*it).toString();
+ else
+ generateText(requisites.value(*it), aggregate, marker);
+ out() << "</td></tr>\n";
+ }
+ }
+ out() << "</table></div>\n";
+}
+
+/*!
+ * \internal
+ * Adds inherited by information to the map.
+ */
+void HtmlGenerator::addInheritedByToMap(QMap<QString, Text> &requisites, Text *text,
+ const QString &inheritedBytext, ClassNode *classe)
+{
+ if (!classe->derivedClasses().isEmpty()) {
+ text->clear();
+ *text << Atom::ParaLeft;
+ int count = appendSortedNames(*text, classe, classe->derivedClasses());
+ *text << Atom::ParaRight;
+ if (count > 0)
+ requisites.insert(inheritedBytext, *text);
+ }
+}
+
+/*!
+ * \internal
+ * Adds base classes to the map.
+ */
+void HtmlGenerator::addInheritsToMap(QMap<QString, Text> &requisites, Text *text,
+ const QString &inheritsText, ClassNode *classe)
+{
+ if (!classe->baseClasses().isEmpty()) {
+ int index = 0;
+ text->clear();
+ const auto 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 << Utilities::comma(index++, classe->baseClasses().size());
+ }
+ }
+ *text << Atom::ParaRight;
+ if (index > 0)
+ requisites.insert(inheritsText, *text);
+ }
+}
+
+/*!
+ \internal
+ Add the QML/C++ native type information to the map.
+ */
+ void HtmlGenerator::addQmlNativeTypesToMap(QMap<QString, Text> &requisites, Text *text,
+ const QString &nativeTypeText, ClassNode *classe) const
+{
+ if (!text)
+ return;
+
+ text->clear();
+
+ QList<QmlTypeNode *> nativeTypes { classe->qmlNativeTypes().cbegin(), classe->qmlNativeTypes().cend()};
+ std::sort(nativeTypes.begin(), nativeTypes.end(), Node::nodeNameLessThan);
+ qsizetype index { 0 };
+
+ for (const auto &item : std::as_const(nativeTypes)) {
+ *text << Atom(Atom::LinkNode, CodeMarker::stringForNode(item))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, item->name())
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ *text << Utilities::comma(index++, nativeTypes.size());
+ }
+ requisites.insert(nativeTypeText, *text);
+}
+
+/*!
+ * \internal
+ * Adds the CMake package and link library information to the map.
+ */
+void HtmlGenerator::addCMakeInfoToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites,
+ Text *text, const QString &CMakeInfo) const
+{
+ if (!aggregate->physicalModuleName().isEmpty() && text != nullptr) {
+ const CollectionNode *cn =
+ m_qdb->getCollectionNode(aggregate->physicalModuleName(), Node::Module);
+ if (!cn || cn->qtCMakeComponent().isEmpty())
+ return;
+
+ text->clear();
+ const QString qtComponent = "Qt" + QString::number(QT_VERSION_MAJOR);
+ const QString findPackageText = "find_package(" + qtComponent + " REQUIRED COMPONENTS "
+ + cn->qtCMakeComponent() + ")";
+ const QString targetText = cn->qtCMakeTargetItem().isEmpty() ? cn->qtCMakeComponent() : cn->qtCMakeTargetItem();
+ const QString targetLinkLibrariesText = "target_link_libraries(mytarget PRIVATE "
+ + qtComponent + "::" + targetText + ")";
+ const Atom lineBreak = Atom(Atom::RawString, " <br/>\n");
+ *text << findPackageText << lineBreak << targetLinkLibrariesText;
+ requisites.insert(CMakeInfo, *text);
+ }
+}
+
+/*!
+ * \internal
+ * Adds the Qt variable (from the \\qtvariable command) to the map.
+ */
+void HtmlGenerator::addQtVariableToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites,
+ Text *text, const QString &qtVariableText) const
+{
+ if (!aggregate->physicalModuleName().isEmpty()) {
+ const CollectionNode *cn =
+ m_qdb->getCollectionNode(aggregate->physicalModuleName(), Node::Module);
+
+ if (cn && !cn->qtVariable().isEmpty()) {
+ text->clear();
+ *text << "QT += " + cn->qtVariable();
+ requisites.insert(qtVariableText, *text);
+ }
+ }
+}
+
+/*!
+ * \internal
+ * Adds the since information (from the \\since command) to the map.
+ *
+ */
+void HtmlGenerator::addSinceToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites,
+ Text *text, const QString &sinceText) const
+{
+ if (!aggregate->since().isEmpty() && text != nullptr) {
+ text->clear();
+ *text << formatSince(aggregate) << Atom::ParaRight;
+ requisites.insert(sinceText, *text);
+ }
+}
+
+/*!
+ * \internal
+ * Adds the status description for \a aggregate, together with a <span> element, to the \a
+ * requisites map.
+ *
+ * The span element can be used for adding CSS styling/icon associated with a specific status.
+ * The span class name is constructed by converting the description (sans \\deprecated
+ * version info) to lowercase and replacing all non-alphanum characters with hyphens. In
+ * addition, the span has a class \c status. For example,
+ * 'Tech Preview' -> class="status tech-preview"
+*/
+void HtmlGenerator::addStatusToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites,
+ Text &text, const QString &statusText) const
+{
+ auto status{formatStatus(aggregate, m_qdb)};
+ if (!status)
+ return;
+
+ QString spanClass;
+ if (aggregate->status() == Node::Deprecated)
+ spanClass = u"deprecated"_s; // Disregard any version info
+ else
+ spanClass = Utilities::asAsciiPrintable(status.value());
+
+ text.clear();
+ text << Atom(Atom::String, status.value())
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_SPAN +
+ "class=\"status %1\""_L1.arg(spanClass))
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_SPAN);
+ requisites.insert(statusText, text);
+}
+
+/*!
+ * \internal
+ * Adds the includes (from the \\includefile command) to the map.
+ */
+void HtmlGenerator::addIncludeFileToMap(const Aggregate *aggregate, CodeMarker *marker,
+ QMap<QString, Text> &requisites, Text& text,
+ const QString &headerText)
+{
+ if (aggregate->includeFile()) {
+ text.clear();
+ text << highlightedCode(
+ indent(m_codeIndent, marker->markedUpInclude(*aggregate->includeFile())),
+ aggregate
+ );
+
+ requisites.insert(headerText, text);
+ }
+}
+
+/*!
+Lists the required imports and includes in a table.
+The number of rows is known.
+*/
+void HtmlGenerator::generateQmlRequisites(QmlTypeNode *qcn, CodeMarker *marker)
+{
+ if (qcn == nullptr)
+ return;
+ QMap<QString, Text> requisites;
+ Text text;
+
+ const QString importText = "Import Statement:";
+ const QString sinceText = "Since:";
+ const QString inheritedBytext = "Inherited By:";
+ const QString inheritsText = "Inherits:";
+ const QString nativeTypeText = "In C++:";
+ const QString statusText = "Status:";
+
+ // add the module name and version to the map
+ QString logicalModuleVersion;
+ const CollectionNode *collection = qcn->logicalModule();
+
+ // skip import statement of \internal collections
+ if (!qcn->logicalModuleName().isEmpty() && (!collection || !collection->isInternal() || m_showInternal)) {
+ QStringList parts = QStringList() << "import" << qcn->logicalModuleName() << qcn->logicalModuleVersion();
+ text.clear();
+ text << parts.join(' ').trimmed();
+ requisites.insert(importText, text);
+ } else if (!qcn->isQmlBasicType() && qcn->logicalModuleName().isEmpty()) {
+ qcn->doc().location().warning(QStringLiteral("Could not resolve QML import statement for type '%1'").arg(qcn->name()),
+ QStringLiteral("Maybe you forgot to use the '\\%1' command?").arg(COMMAND_INQMLMODULE));
+ }
+
+ // add the since and project into the map
+ if (!qcn->since().isEmpty()) {
+ text.clear();
+ text << formatSince(qcn) << Atom::ParaRight;
+ requisites.insert(sinceText, text);
+ }
+
+ // add the native type to the map
+ ClassNode *cn = qcn->classNode();
+ if (cn && cn->isQmlNativeType() && !cn->isInternal()) {
+ text.clear();
+ text << Atom(Atom::LinkNode, CodeMarker::stringForNode(cn));
+ text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
+ text << Atom(Atom::String, cn->name());
+ text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ requisites.insert(nativeTypeText, text);
+ }
+
+ // add the inherits to the map
+ QmlTypeNode *base = qcn->qmlBaseNode();
+ while (base && base->isInternal()) {
+ base = base->qmlBaseNode();
+ }
+ if (base) {
+ text.clear();
+ text << Atom::ParaLeft << Atom(Atom::LinkNode, CodeMarker::stringForNode(base))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, base->name())
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight;
+ requisites.insert(inheritsText, text);
+ }
+
+ // add the inherited-by to the map
+ NodeList subs;
+ QmlTypeNode::subclasses(qcn, subs);
+ if (!subs.isEmpty()) {
+ text.clear();
+ text << Atom::ParaLeft;
+ int count = appendSortedQmlNames(text, qcn, subs);
+ text << Atom::ParaRight;
+ if (count > 0)
+ requisites.insert(inheritedBytext, text);
+ }
+
+ // Add the state description (if any) to the map
+ addStatusToMap(qcn, requisites, text, statusText);
+
+ // The order of the requisites matter
+ const QStringList requisiteorder { importText, sinceText, nativeTypeText, inheritsText,
+ inheritedBytext, statusText };
+
+ if (!requisites.isEmpty()) {
+ // generate the table
+ out() << "<div class=\"table\"><table class=\"alignedsummary\" translate=\"no\">\n";
+ for (const auto &requisite : requisiteorder) {
+
+ if (requisites.contains(requisite)) {
+ out() << "<tr>"
+ << "<td class=\"memItemLeft rightAlign topAlign\"> " << requisite
+ << "</td><td class=\"memItemRight bottomAlign\"> ";
+
+ if (requisite == importText)
+ out() << requisites.value(requisite).toString();
+ else
+ generateText(requisites.value(requisite), qcn, marker);
+ out() << "</td></tr>";
+ }
+ }
+ out() << "</table></div>";
+ }
+}
+
+void HtmlGenerator::generateBrief(const Node *node, CodeMarker *marker, const Node *relative,
+ bool addLink)
+{
+ Text brief = node->doc().briefText();
+
+ if (!brief.isEmpty()) {
+ if (!brief.lastAtom()->string().endsWith('.')) {
+ brief << Atom(Atom::String, ".");
+ node->doc().location().warning(
+ QStringLiteral("'\\brief' statement does not end with a full stop."));
+ }
+ generateExtractionMark(node, BriefMark);
+ out() << "<p>";
+ generateText(brief, node, marker);
+
+ if (addLink) {
+ if (!relative || node == relative)
+ out() << " <a href=\"#";
+ else
+ out() << " <a href=\"" << linkForNode(node, relative) << '#';
+ out() << registerRef("details") << "\">More...</a>";
+ }
+
+ out() << "</p>\n";
+ generateExtractionMark(node, EndMark);
+ }
+}
+
+/*!
+ Revised for the new doc format.
+ Generates a table of contents beginning at \a node.
+ */
+void HtmlGenerator::generateTableOfContents(const Node *node, CodeMarker *marker,
+ QList<Section> *sections)
+{
+ QList<Atom *> toc;
+ if (node->doc().hasTableOfContents())
+ toc = node->doc().tableOfContents();
+ if (tocDepth == 0 || (toc.isEmpty() && !sections && !node->isModule())) {
+ generateSidebar();
+ return;
+ }
+
+ int sectionNumber = 1;
+ int detailsBase = 0;
+
+ // disable nested links in table of contents
+ m_inContents = true;
+
+ out() << "<div class=\"sidebar\">\n";
+ out() << "<div class=\"toc\">\n";
+ out() << "<h3 id=\"toc\">Contents</h3>\n";
+
+ if (node->isModule()) {
+ openUnorderedList();
+ if (!static_cast<const CollectionNode *>(node)->noAutoList()) {
+ if (node->hasNamespaces()) {
+ out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
+ << registerRef("namespaces") << "\">Namespaces</a></li>\n";
+ }
+ if (node->hasClasses()) {
+ out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
+ << registerRef("classes") << "\">Classes</a></li>\n";
+ }
+ }
+ out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#" << registerRef("details")
+ << "\">Detailed Description</a></li>\n";
+ for (const auto &entry : std::as_const(toc)) {
+ if (entry->string().toInt() == 1) {
+ detailsBase = 1;
+ break;
+ }
+ }
+ } else if (sections && (node->isClassNode() || node->isNamespace() || node->isQmlType())) {
+ for (const auto &section : std::as_const(*sections)) {
+ if (!section.members().isEmpty()) {
+ openUnorderedList();
+ out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
+ << registerRef(section.plural()) << "\">" << section.title() << "</a></li>\n";
+ }
+ if (!section.reimplementedMembers().isEmpty()) {
+ openUnorderedList();
+ QString ref = QString("Reimplemented ") + section.plural();
+ out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
+ << registerRef(ref.toLower()) << "\">"
+ << QString("Reimplemented ") + section.title() << "</a></li>\n";
+ }
+ }
+ if (!node->isNamespace() || node->hasDoc()) {
+ openUnorderedList();
+ out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
+ << registerRef("details") << "\">Detailed Description</a></li>\n";
+ }
+ for (const auto &entry : toc) {
+ if (entry->string().toInt() == 1) {
+ detailsBase = 1;
+ break;
+ }
+ }
+ }
+
+ for (const auto &atom : toc) {
+ sectionNumber = atom->string().toInt() + detailsBase;
+ // restrict the ToC depth to the one set by the HTML.tocdepth variable or
+ // print all levels if tocDepth is not set.
+ if (sectionNumber <= tocDepth || tocDepth < 0) {
+ openUnorderedList();
+ int numAtoms;
+ Text headingText = Text::sectionHeading(atom);
+ out() << "<li class=\"level" << sectionNumber << "\">";
+ out() << "<a href=\"" << '#' << Tree::refForAtom(atom) << "\">";
+ generateAtomList(headingText.firstAtom(), node, marker, true, numAtoms);
+ out() << "</a></li>\n";
+ }
+ }
+ closeUnorderedList();
+ out() << "</div>\n";
+ out() << R"(<div class="sidebar-content" id="sidebar-content"></div>)";
+ out() << "</div>\n";
+ m_inContents = false;
+ m_inLink = false;
+}
+
+/*!
+ Outputs a placeholder div where the style can add customized sidebar content.
+ */
+void HtmlGenerator::generateSidebar()
+{
+ out() << "<div class=\"sidebar\">";
+ out() << R"(<div class="sidebar-content" id="sidebar-content"></div>)";
+ out() << "</div>\n";
+}
+
+QString HtmlGenerator::generateAllMembersFile(const Section &section, CodeMarker *marker)
+{
+ if (section.isEmpty())
+ return QString();
+
+ const Aggregate *aggregate = section.aggregate();
+ QString fileName = fileBase(aggregate) + "-members." + fileExtension();
+ beginSubPage(aggregate, fileName);
+ QString title = "List of All Members for " + aggregate->name();
+ generateHeader(title, aggregate, marker);
+ generateSidebar();
+ generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
+ out() << "<p>This is the complete list of members for ";
+ generateFullName(aggregate, nullptr);
+ out() << ", including inherited members.</p>\n";
+
+ generateSectionList(section, aggregate, marker);
+
+ generateFooter();
+ endSubPage();
+ return fileName;
+}
+
+/*!
+ This function creates an html page on which are listed all
+ the members of the QML class used to generte the \a sections,
+ including the inherited members. The \a marker is used for
+ formatting stuff.
+ */
+QString HtmlGenerator::generateAllQmlMembersFile(const Sections &sections, CodeMarker *marker)
+{
+
+ if (sections.allMembersSection().isEmpty())
+ return QString();
+
+ const Aggregate *aggregate = sections.aggregate();
+ QString fileName = fileBase(aggregate) + "-members." + fileExtension();
+ beginSubPage(aggregate, fileName);
+ QString title = "List of All Members for " + aggregate->name();
+ generateHeader(title, aggregate, marker);
+ generateSidebar();
+ generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
+ out() << "<p>This is the complete list of members for ";
+ generateFullName(aggregate, nullptr);
+ out() << ", including inherited members.</p>\n";
+
+ ClassNodesList &cknl = sections.allMembersSection().classNodesList();
+ for (int i = 0; i < cknl.size(); i++) {
+ ClassNodes ckn = cknl[i];
+ const QmlTypeNode *qcn = ckn.first;
+ NodeVector &nodes = ckn.second;
+ if (nodes.isEmpty())
+ continue;
+ if (i != 0) {
+ out() << "<p>The following members are inherited from ";
+ generateFullName(qcn, nullptr);
+ out() << ".</p>\n";
+ }
+ openUnorderedList();
+ for (int j = 0; j < nodes.size(); j++) {
+ Node *node = nodes[j];
+ if (node->access() == Access::Private || node->isInternal())
+ continue;
+ if (node->isSharingComment() && node->sharedCommentNode()->isPropertyGroup())
+ continue;
+
+ std::function<void(Node *)> generate = [&](Node *n) {
+ out() << "<li class=\"fn\" translate=\"no\">";
+ generateQmlItem(n, aggregate, marker, true);
+ if (n->isDefault())
+ out() << " [default]";
+ else if (n->isAttached())
+ out() << " [attached]";
+ // Indent property group members
+ if (n->isPropertyGroup()) {
+ out() << "<ul>\n";
+ const QList<Node *> &collective =
+ static_cast<SharedCommentNode *>(n)->collective();
+ std::for_each(collective.begin(), collective.end(), generate);
+ out() << "</ul>\n";
+ }
+ out() << "</li>\n";
+ };
+ generate(node);
+ }
+ closeUnorderedList();
+ }
+
+
+ generateFooter();
+ endSubPage();
+ return fileName;
+}
+
+QString HtmlGenerator::generateObsoleteMembersFile(const Sections &sections, CodeMarker *marker)
+{
+ SectionPtrVector summary_spv;
+ SectionPtrVector details_spv;
+ if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
+ return QString();
+
+ Aggregate *aggregate = sections.aggregate();
+ QString title = "Obsolete Members for " + aggregate->name();
+ QString fileName = fileBase(aggregate) + "-obsolete." + fileExtension();
+
+ beginSubPage(aggregate, fileName);
+ generateHeader(title, aggregate, marker);
+ generateSidebar();
+ generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
+
+ out() << "<p><b>The following members of class "
+ << "<a href=\"" << linkForNode(aggregate, nullptr) << "\" translate=\"no\">"
+ << protectEnc(aggregate->name()) << "</a>"
+ << " are deprecated.</b> "
+ << "They are provided to keep old source code working. "
+ << "We strongly advise against using them in new code.</p>\n";
+
+ for (const auto &section : summary_spv) {
+ out() << "<h2>" << protectEnc(section->title()) << "</h2>\n";
+ generateSectionList(*section, aggregate, marker, true);
+ }
+
+ for (const auto &section : details_spv) {
+ out() << "<h2>" << protectEnc(section->title()) << "</h2>\n";
+
+ const NodeVector &members = section->obsoleteMembers();
+ for (const auto &member : members) {
+ if (member->access() != Access::Private)
+ generateDetailedMember(member, aggregate, marker);
+ }
+ }
+
+ generateFooter();
+ endSubPage();
+ return fileName;
+}
+
+/*!
+ Generates a separate file where deprecated members of the QML
+ type \a qcn are listed. The \a marker is used to generate
+ the section lists, which are then traversed and output here.
+ */
+QString HtmlGenerator::generateObsoleteQmlMembersFile(const Sections &sections, CodeMarker *marker)
+{
+ SectionPtrVector summary_spv;
+ SectionPtrVector details_spv;
+ if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
+ return QString();
+
+ Aggregate *aggregate = sections.aggregate();
+ QString title = "Obsolete Members for " + aggregate->name();
+ QString fileName = fileBase(aggregate) + "-obsolete." + fileExtension();
+
+ beginSubPage(aggregate, fileName);
+ generateHeader(title, aggregate, marker);
+ generateSidebar();
+ generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
+
+ out() << "<p><b>The following members of QML type "
+ << "<a href=\"" << linkForNode(aggregate, nullptr) << "\">"
+ << protectEnc(aggregate->name()) << "</a>"
+ << " are deprecated.</b> "
+ << "They are provided to keep old source code working. "
+ << "We strongly advise against using them in new code.</p>\n";
+
+ for (const auto &section : summary_spv) {
+ QString ref = registerRef(section->title().toLower());
+ out() << "<h2 id=\"" << ref << "\">" << protectEnc(section->title()) << "</h2>\n";
+ generateQmlSummary(section->obsoleteMembers(), aggregate, marker);
+ }
+
+ for (const auto &section : details_spv) {
+ out() << "<h2>" << protectEnc(section->title()) << "</h2>\n";
+ const NodeVector &members = section->obsoleteMembers();
+ for (const auto &member : members) {
+ generateDetailedQmlMember(member, aggregate, marker);
+ out() << "<br/>\n";
+ }
+ }
+
+ generateFooter();
+ endSubPage();
+ return fileName;
+}
+
+void HtmlGenerator::generateClassHierarchy(const Node *relative, NodeMultiMap &classMap)
+{
+ if (classMap.isEmpty())
+ return;
+
+ NodeMap topLevel;
+ for (const auto &it : classMap) {
+ auto *classe = static_cast<ClassNode *>(it);
+ if (classe->baseClasses().isEmpty())
+ topLevel.insert(classe->name(), classe);
+ }
+
+ QStack<NodeMap> stack;
+ stack.push(topLevel);
+
+ out() << "<ul>\n";
+ while (!stack.isEmpty()) {
+ if (stack.top().isEmpty()) {
+ stack.pop();
+ out() << "</ul>\n";
+ } else {
+ ClassNode *child = static_cast<ClassNode *>(*stack.top().begin());
+ out() << "<li>";
+ generateFullName(child, relative);
+ out() << "</li>\n";
+ stack.top().erase(stack.top().begin());
+
+ NodeMap newTop;
+ const auto derivedClasses = child->derivedClasses();
+ for (const RelatedClass &d : derivedClasses) {
+ if (d.m_node && d.m_node->isInAPI())
+ newTop.insert(d.m_node->name(), d.m_node);
+ }
+ if (!newTop.isEmpty()) {
+ stack.push(newTop);
+ out() << "<ul>\n";
+ }
+ }
+ }
+}
+
+/*!
+ Outputs an annotated list of the nodes in \a unsortedNodes.
+ A two-column table is output.
+ */
+void HtmlGenerator::generateAnnotatedList(const Node *relative, CodeMarker *marker,
+ const NodeList &unsortedNodes, Qt::SortOrder sortOrder)
+{
+ if (unsortedNodes.isEmpty() || relative == nullptr)
+ return;
+
+ NodeMultiMap nmm;
+ bool allInternal = true;
+ for (auto *node : unsortedNodes) {
+ if (!node->isInternal() && !node->isDeprecated()) {
+ allInternal = false;
+ nmm.insert(node->fullName(relative), node);
+ }
+ }
+ if (allInternal)
+ return;
+ out() << "<div class=\"table\"><table class=\"annotated\">\n";
+ int row = 0;
+ NodeList nodes = nmm.values();
+
+ if (sortOrder == Qt::DescendingOrder)
+ std::sort(nodes.rbegin(), nodes.rend(), Node::nodeSortKeyOrNameLessThan);
+ else
+ std::sort(nodes.begin(), nodes.end(), Node::nodeSortKeyOrNameLessThan);
+
+ for (const auto *node : std::as_const(nodes)) {
+ if (++row % 2 == 1)
+ out() << "<tr class=\"odd topAlign\">";
+ else
+ out() << "<tr class=\"even topAlign\">";
+ out() << "<td class=\"tblName\" translate=\"no\"><p>";
+ generateFullName(node, relative);
+ out() << "</p></td>";
+
+ if (!node->isTextPageNode()) {
+ Text brief = node->doc().trimmedBriefText(node->name());
+ if (!brief.isEmpty()) {
+ out() << "<td class=\"tblDescr\"><p>";
+ generateText(brief, node, marker);
+ out() << "</p></td>";
+ } else if (!node->reconstitutedBrief().isEmpty()) {
+ out() << "<td class=\"tblDescr\"><p>";
+ out() << node->reconstitutedBrief();
+ out() << "</p></td>";
+ }
+ } else {
+ out() << "<td class=\"tblDescr\"><p>";
+ if (!node->reconstitutedBrief().isEmpty()) {
+ out() << node->reconstitutedBrief();
+ } else
+ out() << protectEnc(node->doc().briefText().toString());
+ out() << "</p></td>";
+ }
+ out() << "</tr>\n";
+ }
+ out() << "</table></div>\n";
+}
+
+/*!
+ Outputs a series of annotated lists from the nodes in \a nmm,
+ divided into sections based by the key names in the multimap.
+ */
+void HtmlGenerator::generateAnnotatedLists(const Node *relative, CodeMarker *marker,
+ const NodeMultiMap &nmm)
+{
+ const auto &uniqueKeys = nmm.uniqueKeys();
+ for (const QString &name : uniqueKeys) {
+ if (!name.isEmpty()) {
+ out() << "<h2 id=\"" << registerRef(name.toLower()) << "\">" << protectEnc(name)
+ << "</h2>\n";
+ }
+ generateAnnotatedList(relative, marker, nmm.values(name));
+ }
+}
+
+/*!
+ This function finds the common prefix of the names of all
+ the classes in the class map \a nmm and then generates a
+ compact list of the class names alphabetized on the part
+ of the name not including the common prefix. You can tell
+ the function to use \a commonPrefix as the common prefix,
+ but normally you let it figure it out itself by looking at
+ the name of the first and last classes in the class map
+ \a nmm.
+ */
+void HtmlGenerator::generateCompactList(ListType listType, const Node *relative,
+ const NodeMultiMap &nmm, bool includeAlphabet,
+ const QString &commonPrefix)
+{
+ if (nmm.isEmpty())
+ return;
+
+ const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_'
+ qsizetype commonPrefixLen = commonPrefix.size();
+
+ /*
+ Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z,
+ underscore (_). QAccel will fall in paragraph 10 (A) and
+ QXtWidget in paragraph 33 (X). This is the only place where we
+ assume that NumParagraphs is 37. Each paragraph is a NodeMultiMap.
+ */
+ NodeMultiMap paragraph[NumParagraphs + 1];
+ QString paragraphName[NumParagraphs + 1];
+ QSet<char> usedParagraphNames;
+
+ for (auto c = nmm.constBegin(); c != nmm.constEnd(); ++c) {
+ QStringList pieces = c.key().split("::");
+ int idx = commonPrefixLen;
+ if (idx > 0 && !pieces.last().startsWith(commonPrefix, Qt::CaseInsensitive))
+ idx = 0;
+ QString last = pieces.last().toLower();
+ QString key = last.mid(idx);
+
+ int paragraphNr = NumParagraphs - 1;
+
+ if (key[0].digitValue() != -1) {
+ paragraphNr = key[0].digitValue();
+ } else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) {
+ paragraphNr = 10 + key[0].unicode() - 'a';
+ }
+
+ paragraphName[paragraphNr] = key[0].toUpper();
+ usedParagraphNames.insert(key[0].toLower().cell());
+ paragraph[paragraphNr].insert(last, c.value());
+ }
+
+ /*
+ Each paragraph j has a size: paragraph[j].count(). In the
+ discussion, we will assume paragraphs 0 to 5 will have sizes
+ 3, 1, 4, 1, 5, 9.
+
+ We now want to compute the paragraph offset. Paragraphs 0 to 6
+ start at offsets 0, 3, 4, 8, 9, 14, 23.
+ */
+ qsizetype paragraphOffset[NumParagraphs + 1]; // 37 + 1
+ paragraphOffset[0] = 0;
+ for (int i = 0; i < NumParagraphs; i++) // i = 0..36
+ paragraphOffset[i + 1] = paragraphOffset[i] + paragraph[i].size();
+
+ /*
+ Output the alphabet as a row of links.
+ */
+ if (includeAlphabet) {
+ out() << "<p class=\"centerAlign functionIndex\" translate=\"no\"><b>";
+ for (int i = 0; i < 26; i++) {
+ QChar ch('a' + i);
+ if (usedParagraphNames.contains(char('a' + i)))
+ out() << QString("<a href=\"#%1\">%2</a>&nbsp;").arg(ch).arg(ch.toUpper());
+ }
+ out() << "</b></p>\n";
+ }
+
+ /*
+ Output a <div> element to contain all the <dl> elements.
+ */
+ out() << "<div class=\"flowListDiv\" translate=\"no\">\n";
+ m_numTableRows = 0;
+
+ int curParNr = 0;
+ int curParOffset = 0;
+ QString previousName;
+ bool multipleOccurrences = false;
+
+ for (int i = 0; i < nmm.size(); i++) {
+ while ((curParNr < NumParagraphs) && (curParOffset == paragraph[curParNr].size())) {
+ ++curParNr;
+ curParOffset = 0;
+ }
+
+ /*
+ Starting a new paragraph means starting a new <dl>.
+ */
+ if (curParOffset == 0) {
+ if (i > 0)
+ out() << "</dl>\n";
+ if (++m_numTableRows % 2 == 1)
+ out() << "<dl class=\"flowList odd\">";
+ else
+ out() << "<dl class=\"flowList even\">";
+ out() << "<dt class=\"alphaChar\"";
+ if (includeAlphabet)
+ out() << QString(" id=\"%1\"").arg(paragraphName[curParNr][0].toLower());
+ out() << "><b>" << paragraphName[curParNr] << "</b></dt>\n";
+ }
+
+ /*
+ Output a <dd> for the current offset in the current paragraph.
+ */
+ out() << "<dd>";
+ if ((curParNr < NumParagraphs) && !paragraphName[curParNr].isEmpty()) {
+ NodeMultiMap::Iterator it;
+ NodeMultiMap::Iterator next;
+ it = paragraph[curParNr].begin();
+ for (int j = 0; j < curParOffset; j++)
+ ++it;
+
+ if (listType == Generic) {
+ /*
+ Previously, we used generateFullName() for this, but we
+ require some special formatting.
+ */
+ out() << "<a href=\"" << linkForNode(it.value(), relative) << "\">";
+ } else if (listType == Obsolete) {
+ QString fileName = fileBase(it.value()) + "-obsolete." + fileExtension();
+ QString link;
+ if (useOutputSubdirs())
+ link = "../%1/"_L1.arg(it.value()->tree()->physicalModuleName());
+ link += fileName;
+ out() << "<a href=\"" << link << "\">";
+ }
+
+ QStringList pieces{it.value()->fullName(relative).split("::"_L1)};
+ const auto &name{pieces.last()};
+ next = it;
+ ++next;
+ if (name != previousName)
+ multipleOccurrences = false;
+ if ((next != paragraph[curParNr].end()) && (name == next.value()->name())) {
+ multipleOccurrences = true;
+ previousName = name;
+ }
+ if (multipleOccurrences && pieces.size() == 1)
+ pieces.last().append(": %1"_L1.arg(it.value()->tree()->camelCaseModuleName()));
+
+ out() << protectEnc(pieces.last());
+ out() << "</a>";
+ if (pieces.size() > 1) {
+ out() << " (";
+ generateFullName(it.value()->parent(), relative);
+ out() << ')';
+ }
+ }
+ out() << "</dd>\n";
+ curParOffset++;
+ }
+ if (nmm.size() > 0)
+ out() << "</dl>\n";
+
+ out() << "</div>\n";
+}
+
+void HtmlGenerator::generateFunctionIndex(const Node *relative)
+{
+ out() << "<p class=\"centerAlign functionIndex\" translate=\"no\"><b>";
+ for (int i = 0; i < 26; i++) {
+ QChar ch('a' + i);
+ out() << QString("<a href=\"#%1\">%2</a>&nbsp;").arg(ch).arg(ch.toUpper());
+ }
+ out() << "</b></p>\n";
+
+ char nextLetter = 'a';
+
+ out() << "<ul translate=\"no\">\n";
+ NodeMapMap &funcIndex = m_qdb->getFunctionIndex();
+ for (auto fnMap = funcIndex.constBegin(); fnMap != funcIndex.constEnd(); ++fnMap) {
+ const QString &key = fnMap.key();
+ const QChar firstLetter = key.isEmpty() ? QChar('A') : key.front();
+ Q_ASSERT_X(firstLetter.unicode() < 256, "generateFunctionIndex",
+ "Only valid C++ identifiers were expected");
+ const char currentLetter = firstLetter.isLower() ? firstLetter.unicode() : nextLetter - 1;
+
+ if (currentLetter < nextLetter) {
+ out() << "<li>";
+ } else {
+ // TODO: This is not covered by our tests
+ while (nextLetter < currentLetter)
+ out() << QStringLiteral("<li id=\"%1\"></li>").arg(nextLetter++);
+ Q_ASSERT(nextLetter == currentLetter);
+ out() << QStringLiteral("<li id=\"%1\">").arg(nextLetter++);
+ }
+ out() << protectEnc(key) << ':';
+
+ for (auto it = (*fnMap).constBegin(); it != (*fnMap).constEnd(); ++it) {
+ out() << ' ';
+ generateFullName((*it)->parent(), relative, *it);
+ }
+ out() << "</li>\n";
+ }
+ while (nextLetter <= 'z')
+ out() << QStringLiteral("<li id=\"%1\"></li>").arg(nextLetter++);
+ out() << "</ul>\n";
+}
+
+void HtmlGenerator::generateLegaleseList(const Node *relative, CodeMarker *marker)
+{
+ TextToNodeMap &legaleseTexts = m_qdb->getLegaleseTexts();
+ for (auto it = legaleseTexts.cbegin(), end = legaleseTexts.cend(); it != end; ++it) {
+ Text text = it.key();
+ generateText(text, relative, marker);
+ out() << "<ul>\n";
+ do {
+ out() << "<li>";
+ generateFullName(it.value(), relative);
+ out() << "</li>\n";
+ ++it;
+ } while (it != legaleseTexts.constEnd() && it.key() == text);
+ out() << "</ul>\n";
+ }
+}
+
+void HtmlGenerator::generateQmlItem(const Node *node, const Node *relative, CodeMarker *marker,
+ bool summary)
+{
+ QString marked = marker->markedUpQmlItem(node, summary);
+ marked.replace("@param>", "i>");
+
+ marked.replace("<@extra>", "<code class=\"%1 extra\" translate=\"no\">"_L1
+ .arg(summary ? "summary"_L1 : "details"_L1));
+ marked.replace("</@extra>", "</code>");
+
+
+ if (summary) {
+ marked.remove("<@name>");
+ marked.remove("</@name>");
+ marked.remove("<@type>");
+ marked.remove("</@type>");
+ }
+ out() << highlightedCode(marked, relative, false, Node::QML);
+}
+
+/*!
+ This function generates a simple list (without annotations) for
+ the members of collection node \a {cn}. The list is sorted
+ according to \a sortOrder.
+
+ Returns \c true if the list was generated (collection has members),
+ \c false otherwise.
+ */
+bool HtmlGenerator::generateGroupList(CollectionNode *cn, Qt::SortOrder sortOrder)
+{
+ m_qdb->mergeCollections(cn);
+ if (cn->members().isEmpty())
+ return false;
+
+ NodeList members{cn->members()};
+ if (sortOrder == Qt::DescendingOrder)
+ std::sort(members.rbegin(), members.rend(), Node::nodeSortKeyOrNameLessThan);
+ else
+ std::sort(members.begin(), members.end(), Node::nodeSortKeyOrNameLessThan);
+ out() << "<ul>\n";
+ for (const auto *node : std::as_const(members)) {
+ out() << "<li translate=\"no\">";
+ generateFullName(node, nullptr);
+ out() << "</li>\n";
+ }
+ out() << "</ul>\n";
+ return true;
+}
+
+void HtmlGenerator::generateList(const Node *relative, CodeMarker *marker,
+ const QString &selector, Qt::SortOrder sortOrder)
+{
+ CNMap cnm;
+ Node::NodeType type = Node::NoType;
+ if (selector == QLatin1String("overviews"))
+ type = Node::Group;
+ else if (selector == QLatin1String("cpp-modules"))
+ type = Node::Module;
+ else if (selector == QLatin1String("qml-modules"))
+ type = Node::QmlModule;
+ if (type != Node::NoType) {
+ NodeList nodeList;
+ m_qdb->mergeCollections(type, cnm, relative);
+ const auto collectionList = cnm.values();
+ nodeList.reserve(collectionList.size());
+ for (auto *collectionNode : collectionList)
+ nodeList.append(collectionNode);
+ generateAnnotatedList(relative, marker, nodeList, sortOrder);
+ } else {
+ /*
+ \generatelist {selector} is only allowed in a
+ comment where the topic is \group, \module, or
+ \qmlmodule.
+ */
+ if (relative && !relative->isCollectionNode()) {
+ relative->doc().location().warning(
+ QStringLiteral("\\generatelist {%1} is only allowed in \\group, "
+ "\\module and \\qmlmodule comments.")
+ .arg(selector));
+ return;
+ }
+ auto *node = const_cast<Node *>(relative);
+ auto *collectionNode = static_cast<CollectionNode *>(node);
+ m_qdb->mergeCollections(collectionNode);
+ generateAnnotatedList(collectionNode, marker, collectionNode->members(), sortOrder);
+ }
+}
+
+void HtmlGenerator::generateSection(const NodeVector &nv, const Node *relative, CodeMarker *marker)
+{
+ bool alignNames = true;
+ if (!nv.isEmpty()) {
+ bool twoColumn = false;
+ if (nv.first()->isProperty()) {
+ twoColumn = (nv.size() >= 5);
+ alignNames = false;
+ }
+ if (alignNames) {
+ out() << "<div class=\"table\"><table class=\"alignedsummary\" translate=\"no\">\n";
+ } else {
+ if (twoColumn)
+ out() << "<div class=\"table\"><table class=\"propsummary\" translate=\"no\">\n"
+ << "<tr><td class=\"topAlign\">";
+ out() << "<ul>\n";
+ }
+
+ int i = 0;
+ for (const auto &member : nv) {
+ if (member->access() == Access::Private)
+ continue;
+
+ if (alignNames) {
+ out() << "<tr><td class=\"memItemLeft rightAlign topAlign\"> ";
+ } else {
+ if (twoColumn && i == (nv.size() + 1) / 2)
+ out() << "</ul></td><td class=\"topAlign\"><ul>\n";
+ out() << "<li class=\"fn\" translate=\"no\">";
+ }
+
+ generateSynopsis(member, relative, marker, Section::Summary, alignNames);
+ if (alignNames)
+ out() << "</td></tr>\n";
+ else
+ out() << "</li>\n";
+ i++;
+ }
+ if (alignNames)
+ out() << "</table></div>\n";
+ else {
+ out() << "</ul>\n";
+ if (twoColumn)
+ out() << "</td></tr>\n</table></div>\n";
+ }
+ }
+}
+
+void HtmlGenerator::generateSectionList(const Section &section, const Node *relative,
+ CodeMarker *marker, bool useObsoleteMembers)
+{
+ bool alignNames = true;
+ const NodeVector &members =
+ (useObsoleteMembers ? section.obsoleteMembers() : section.members());
+ if (!members.isEmpty()) {
+ bool hasPrivateSignals = false;
+ bool isInvokable = false;
+ bool twoColumn = false;
+ if (section.style() == Section::AllMembers) {
+ alignNames = false;
+ twoColumn = (members.size() >= 16);
+ } else if (members.first()->isProperty()) {
+ twoColumn = (members.size() >= 5);
+ alignNames = false;
+ }
+ if (alignNames) {
+ out() << "<div class=\"table\"><table class=\"alignedsummary\" translate=\"no\">\n";
+ } else {
+ if (twoColumn)
+ out() << "<div class=\"table\"><table class=\"propsummary\" translate=\"no\">\n"
+ << "<tr><td class=\"topAlign\">";
+ out() << "<ul>\n";
+ }
+
+ int i = 0;
+ for (const auto &member : members) {
+ if (member->access() == Access::Private)
+ continue;
+
+ if (alignNames) {
+ out() << "<tr><td class=\"memItemLeft topAlign rightAlign\"> ";
+ } else {
+ if (twoColumn && i == (members.size() + 1) / 2)
+ out() << "</ul></td><td class=\"topAlign\"><ul>\n";
+ out() << "<li class=\"fn\" translate=\"no\">";
+ }
+
+ generateSynopsis(member, relative, marker, section.style(), alignNames);
+ if (member->isFunction()) {
+ const auto *fn = static_cast<const FunctionNode *>(member);
+ if (fn->isPrivateSignal()) {
+ hasPrivateSignals = true;
+ if (alignNames)
+ out() << "</td><td class=\"memItemRight bottomAlign\">[see note below]";
+ } else if (fn->isInvokable()) {
+ isInvokable = true;
+ if (alignNames)
+ out() << "</td><td class=\"memItemRight bottomAlign\">[see note below]";
+ }
+ }
+ if (alignNames)
+ out() << "</td></tr>\n";
+ else
+ out() << "</li>\n";
+ i++;
+ }
+ if (alignNames)
+ out() << "</table></div>\n";
+ else {
+ out() << "</ul>\n";
+ if (twoColumn)
+ out() << "</td></tr>\n</table></div>\n";
+ }
+ if (alignNames) {
+ if (hasPrivateSignals)
+ generateAddendum(relative, Generator::PrivateSignal, marker);
+ if (isInvokable)
+ generateAddendum(relative, Generator::Invokable, marker);
+ }
+ }
+
+ if (!useObsoleteMembers && section.style() == Section::Summary
+ && !section.inheritedMembers().isEmpty()) {
+ out() << "<ul>\n";
+ generateSectionInheritedList(section, relative);
+ out() << "</ul>\n";
+ }
+}
+
+void HtmlGenerator::generateSectionInheritedList(const Section &section, const Node *relative)
+{
+ const QList<std::pair<Aggregate *, int>> &inheritedMembers = section.inheritedMembers();
+ for (const auto &member : inheritedMembers) {
+ out() << "<li class=\"fn\" translate=\"no\">";
+ out() << member.second << ' ';
+ if (member.second == 1) {
+ out() << section.singular();
+ } else {
+ out() << section.plural();
+ }
+ out() << " inherited from <a href=\"" << fileName(member.first) << '#'
+ << Generator::cleanRef(section.title().toLower()) << "\">"
+ << protectEnc(member.first->plainFullName(relative)) << "</a></li>\n";
+ }
+}
+
+void HtmlGenerator::generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker,
+ Section::Style style, bool alignNames)
+{
+ QString marked = marker->markedUpSynopsis(node, relative, style);
+ marked.replace("@param>", "i>");
+
+ if (style == Section::Summary) {
+ marked.remove("<@name>");
+ marked.remove("</@name>");
+ }
+
+ if (style == Section::AllMembers) {
+ static const QRegularExpression extraRegExp("<@extra>.*</@extra>",
+ QRegularExpression::InvertedGreedinessOption);
+ marked.remove(extraRegExp);
+ } else {
+ marked.replace("<@extra>", "<code class=\"%1 extra\" translate=\"no\">"_L1
+ .arg(style == Section::Summary ? "summary"_L1 : "details"_L1));
+ marked.replace("</@extra>", "</code>");
+ }
+
+ if (style != Section::Details) {
+ marked.remove("<@type>");
+ marked.remove("</@type>");
+ }
+
+ out() << highlightedCode(marked, relative, alignNames);
+}
+
+QString HtmlGenerator::highlightedCode(const QString &markedCode, const Node *relative,
+ bool alignNames, Node::Genus genus)
+{
+ QString src = markedCode;
+ QString html;
+ html.reserve(src.size());
+ QStringView arg;
+ QStringView par1;
+
+ const QChar charLangle = '<';
+ const QChar charAt = '@';
+
+ static const QString typeTag("type");
+ static const QString headerTag("headerfile");
+ static const QString funcTag("func");
+ static const QString linkTag("link");
+
+ // replace all <@link> tags: "(<@link node=\"([^\"]+)\">).*(</@link>)"
+ // replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)(</@func>)"
+ // replace all "(<@(type|headerfile)(?: +[^>]*)?>)(.*)(</@\\2>)" tags
+ bool done = false;
+ for (int i = 0, srcSize = src.size(); i < srcSize;) {
+ if (src.at(i) == charLangle && src.at(i + 1) == charAt) {
+ if (alignNames && !done) {
+ html += QLatin1String("</td><td class=\"memItemRight bottomAlign\">");
+ done = true;
+ }
+ i += 2;
+ if (parseArg(src, linkTag, &i, srcSize, &arg, &par1)) {
+ html += QLatin1String("<b>");
+ const Node *n = CodeMarker::nodeForString(par1.toString());
+ QString link = linkForNode(n, relative);
+ addLink(link, arg, &html);
+ html += QLatin1String("</b>");
+ } else if (parseArg(src, funcTag, &i, srcSize, &arg, &par1)) {
+ const FunctionNode *fn = m_qdb->findFunctionNode(par1.toString(), relative, genus);
+ QString link = linkForNode(fn, relative);
+ addLink(link, arg, &html);
+ par1 = QStringView();
+ } else if (parseArg(src, typeTag, &i, srcSize, &arg, &par1)) {
+ par1 = QStringView();
+ const Node *n = m_qdb->findTypeNode(arg.toString(), relative, genus);
+ html += QLatin1String("<span class=\"type\">");
+ if (n && (n->isQmlBasicType())) {
+ if (relative && (relative->genus() == n->genus() || genus == n->genus()))
+ addLink(linkForNode(n, relative), arg, &html);
+ else
+ html += arg;
+ } else
+ addLink(linkForNode(n, relative), arg, &html);
+ html += QLatin1String("</span>");
+ } else if (parseArg(src, headerTag, &i, srcSize, &arg, &par1)) {
+ par1 = QStringView();
+ if (arg.startsWith(QLatin1Char('&')))
+ html += arg;
+ else {
+ const Node *n = m_qdb->findNodeForInclude(QStringList(arg.toString()));
+ if (n && n != relative)
+ addLink(linkForNode(n, relative), arg, &html);
+ else
+ html += arg;
+ }
+ } else {
+ html += charLangle;
+ html += charAt;
+ }
+ } else {
+ html += src.at(i++);
+ }
+ }
+
+ // replace all
+ // "<@comment>" -> "<span class=\"comment\">";
+ // "<@preprocessor>" -> "<span class=\"preprocessor\">";
+ // "<@string>" -> "<span class=\"string\">";
+ // "<@char>" -> "<span class=\"char\">";
+ // "<@number>" -> "<span class=\"number\">";
+ // "<@op>" -> "<span class=\"operator\">";
+ // "<@type>" -> "<span class=\"type\">";
+ // "<@name>" -> "<span class=\"name\">";
+ // "<@keyword>" -> "<span class=\"keyword\">";
+ // "</@(?:comment|preprocessor|string|char|number|op|type|name|keyword)>" -> "</span>"
+ src = html;
+ html = QString();
+ html.reserve(src.size());
+ static const QLatin1String spanTags[] = {
+ QLatin1String("comment>"), QLatin1String("<span class=\"comment\">"),
+ QLatin1String("preprocessor>"), QLatin1String("<span class=\"preprocessor\">"),
+ QLatin1String("string>"), QLatin1String("<span class=\"string\">"),
+ QLatin1String("char>"), QLatin1String("<span class=\"char\">"),
+ QLatin1String("number>"), QLatin1String("<span class=\"number\">"),
+ QLatin1String("op>"), QLatin1String("<span class=\"operator\">"),
+ QLatin1String("type>"), QLatin1String("<span class=\"type\">"),
+ QLatin1String("name>"), QLatin1String("<span class=\"name\">"),
+ QLatin1String("keyword>"), QLatin1String("<span class=\"keyword\">")
+ };
+ int nTags = 9;
+ // Update the upper bound of k in the following code to match the length
+ // of the above array.
+ for (int i = 0, n = src.size(); i < n;) {
+ if (src.at(i) == QLatin1Char('<')) {
+ if (src.at(i + 1) == QLatin1Char('@')) {
+ i += 2;
+ bool handled = false;
+ for (int k = 0; k != nTags; ++k) {
+ const QLatin1String &tag = spanTags[2 * k];
+ if (i + tag.size() <= src.size() && tag == QStringView(src).mid(i, tag.size())) {
+ html += spanTags[2 * k + 1];
+ i += tag.size();
+ handled = true;
+ break;
+ }
+ }
+ if (!handled) {
+ // drop 'our' unknown tags (the ones still containing '@')
+ while (i < n && src.at(i) != QLatin1Char('>'))
+ ++i;
+ ++i;
+ }
+ continue;
+ } else if (src.at(i + 1) == QLatin1Char('/') && src.at(i + 2) == QLatin1Char('@')) {
+ i += 3;
+ bool handled = false;
+ for (int k = 0; k != nTags; ++k) {
+ const QLatin1String &tag = spanTags[2 * k];
+ if (i + tag.size() <= src.size() && tag == QStringView(src).mid(i, tag.size())) {
+ html += QLatin1String("</span>");
+ i += tag.size();
+ handled = true;
+ break;
+ }
+ }
+ if (!handled) {
+ // drop 'our' unknown tags (the ones still containing '@')
+ while (i < n && src.at(i) != QLatin1Char('>'))
+ ++i;
+ ++i;
+ }
+ continue;
+ }
+ }
+ html += src.at(i);
+ ++i;
+ }
+ return html;
+}
+
+void HtmlGenerator::generateLink(const Atom *atom)
+{
+ Q_ASSERT(m_inLink);
+
+ if (m_linkNode && m_linkNode->isFunction()) {
+ auto match = XmlGenerator::m_funcLeftParen.match(atom->string());
+ if (match.hasMatch()) {
+ // C++: move () outside of link
+ qsizetype leftParenLoc = match.capturedStart(1);
+ out() << protectEnc(atom->string().left(leftParenLoc));
+ endLink();
+ out() << protectEnc(atom->string().mid(leftParenLoc));
+ return;
+ }
+ }
+ out() << protectEnc(atom->string());
+}
+
+QString HtmlGenerator::protectEnc(const QString &string)
+{
+ return protect(string);
+}
+
+QString HtmlGenerator::protect(const QString &string)
+{
+#define APPEND(x) \
+ if (html.isEmpty()) { \
+ html = string; \
+ html.truncate(i); \
+ } \
+ html += (x);
+
+ QString html;
+ qsizetype n = string.size();
+
+ for (int i = 0; i < n; ++i) {
+ QChar ch = string.at(i);
+
+ if (ch == QLatin1Char('&')) {
+ APPEND("&amp;");
+ } else if (ch == QLatin1Char('<')) {
+ APPEND("&lt;");
+ } else if (ch == QLatin1Char('>')) {
+ APPEND("&gt;");
+ } else if (ch == QChar(8211)) {
+ APPEND("&ndash;");
+ } else if (ch == QChar(8212)) {
+ APPEND("&mdash;");
+ } else if (ch == QLatin1Char('"')) {
+ APPEND("&quot;");
+ } else {
+ if (!html.isEmpty())
+ html += ch;
+ }
+ }
+
+ if (!html.isEmpty())
+ return html;
+ return string;
+
+#undef APPEND
+}
+
+QString HtmlGenerator::fileBase(const Node *node) const
+{
+ QString result = Generator::fileBase(node);
+ if (!node->isAggregate() && node->isDeprecated())
+ result += QLatin1String("-obsolete");
+ return result;
+}
+
+QString HtmlGenerator::fileName(const Node *node)
+{
+ if (node->isExternalPage())
+ return node->name();
+ return Generator::fileName(node);
+}
+
+void HtmlGenerator::generateFullName(const Node *apparentNode, const Node *relative,
+ const Node *actualNode)
+{
+ if (actualNode == nullptr)
+ actualNode = apparentNode;
+ bool link = !linkForNode(actualNode, relative).isEmpty();
+ if (link) {
+ out() << "<a href=\"" << linkForNode(actualNode, relative);
+ if (actualNode->isDeprecated())
+ out() << "\" class=\"obsolete";
+ out() << "\">";
+ }
+ out() << protectEnc(apparentNode->fullName(relative));
+ if (link)
+ out() << "</a>";
+}
+
+void HtmlGenerator::generateDetailedMember(const Node *node, const PageNode *relative,
+ CodeMarker *marker)
+{
+ const EnumNode *etn;
+ generateExtractionMark(node, MemberMark);
+ QString nodeRef = nullptr;
+ if (node->isSharedCommentNode()) {
+ const auto *scn = reinterpret_cast<const SharedCommentNode *>(node);
+ const QList<Node *> &collective = scn->collective();
+ if (collective.size() > 1)
+ out() << "<div class=\"fngroup\">\n";
+ for (const auto *sharedNode : collective) {
+ nodeRef = refForNode(sharedNode);
+ out() << R"(<h3 class="fn fngroupitem" translate="no" id=")" << nodeRef << "\">";
+ generateSynopsis(sharedNode, relative, marker, Section::Details);
+ out() << "</h3>";
+ }
+ if (collective.size() > 1)
+ out() << "</div>";
+ out() << '\n';
+ } else {
+ nodeRef = refForNode(node);
+ if (node->isEnumType() && (etn = static_cast<const EnumNode *>(node))->flagsType()) {
+ out() << R"(<h3 class="flags" id=")" << nodeRef << "\">";
+ generateSynopsis(etn, relative, marker, Section::Details);
+ out() << "<br/>";
+ generateSynopsis(etn->flagsType(), relative, marker, Section::Details);
+ out() << "</h3>\n";
+ } else {
+ out() << R"(<h3 class="fn" translate="no" id=")" << nodeRef << "\">";
+ generateSynopsis(node, relative, marker, Section::Details);
+ out() << "</h3>" << '\n';
+ }
+ }
+
+ generateStatus(node, marker);
+ generateBody(node, marker);
+ generateOverloadedSignal(node, marker);
+ generateComparisonCategory(node, marker);
+ generateThreadSafeness(node, marker);
+ generateSince(node, marker);
+ generateNoexceptNote(node, marker);
+
+ if (node->isProperty()) {
+ const auto property = static_cast<const PropertyNode *>(node);
+ if (property->propertyType() == PropertyNode::PropertyType::StandardProperty) {
+ Section section("", "", "", "", Section::Accessors);
+
+ section.appendMembers(property->getters().toVector());
+ section.appendMembers(property->setters().toVector());
+ section.appendMembers(property->resetters().toVector());
+
+ if (!section.members().isEmpty()) {
+ out() << "<p><b>Access functions:</b></p>\n";
+ generateSectionList(section, node, marker);
+ }
+
+ Section notifiers("", "", "", "", Section::Accessors);
+ notifiers.appendMembers(property->notifiers().toVector());
+
+ if (!notifiers.members().isEmpty()) {
+ out() << "<p><b>Notifier signal:</b></p>\n";
+ generateSectionList(notifiers, node, marker);
+ }
+ }
+ } else if (node->isEnumType()) {
+ const auto *enumTypeNode = static_cast<const EnumNode *>(node);
+ if (enumTypeNode->flagsType()) {
+ out() << "<p>The " << protectEnc(enumTypeNode->flagsType()->name())
+ << " type is a typedef for "
+ << "<a href=\"" << m_qflagsHref << "\">QFlags</a>&lt;"
+ << protectEnc(enumTypeNode->name()) << "&gt;. It stores an OR combination of "
+ << protectEnc(enumTypeNode->name()) << " values.</p>\n";
+ }
+ }
+ generateAlsoList(node, marker);
+ generateExtractionMark(node, EndMark);
+}
+
+/*!
+ This version of the function is called when outputting the link
+ to an example file or example image, where the \a link is known
+ to be correct.
+ */
+void HtmlGenerator::beginLink(const QString &link)
+{
+ m_link = link;
+ m_inLink = true;
+ m_linkNode = nullptr;
+
+ if (!m_link.isEmpty())
+ out() << "<a href=\"" << m_link << "\" translate=\"no\">";
+}
+
+void HtmlGenerator::beginLink(const QString &link, const Node *node, const Node *relative)
+{
+ m_link = link;
+ m_inLink = true;
+ m_linkNode = node;
+ if (m_link.isEmpty())
+ return;
+
+ const QString &translate_attr =
+ (node && node->genus() & Node::API) ? " translate=\"no\""_L1 : ""_L1;
+
+ if (node == nullptr || (relative != nullptr && node->status() == relative->status()))
+ out() << "<a href=\"" << m_link << "\"%1>"_L1.arg(translate_attr);
+ else if (node->isDeprecated())
+ out() << "<a href=\"" << m_link << "\" class=\"obsolete\"%1>"_L1.arg(translate_attr);
+ else
+ out() << "<a href=\"" << m_link << "\"%1>"_L1.arg(translate_attr);
+}
+
+void HtmlGenerator::endLink()
+{
+ if (!m_inLink)
+ return;
+
+ m_inLink = false;
+ m_linkNode = nullptr;
+
+ if (!m_link.isEmpty())
+ out() << "</a>";
+}
+
+/*!
+ Generates the summary list for the \a members. Only used for
+ sections of QML element documentation.
+ */
+void HtmlGenerator::generateQmlSummary(const NodeVector &members, const Node *relative,
+ CodeMarker *marker)
+{
+ if (!members.isEmpty()) {
+ out() << "<ul>\n";
+ for (const auto &member : members) {
+ out() << "<li class=\"fn\" translate=\"no\">";
+ generateQmlItem(member, relative, marker, true);
+ if (member->isPropertyGroup()) {
+ const auto *scn = static_cast<const SharedCommentNode *>(member);
+ if (scn->count() > 0) {
+ out() << "<ul>\n";
+ const QList<Node *> &sharedNodes = scn->collective();
+ for (const auto &node : sharedNodes) {
+ if (node->isQmlProperty()) {
+ out() << "<li class=\"fn\" translate=\"no\">";
+ generateQmlItem(node, relative, marker, true);
+ out() << "</li>\n";
+ }
+ }
+ out() << "</ul>\n";
+ }
+ }
+ out() << "</li>\n";
+ }
+ out() << "</ul>\n";
+ }
+}
+
+/*!
+ Outputs the html detailed documentation for a section
+ on a QML element reference page.
+ */
+void HtmlGenerator::generateDetailedQmlMember(Node *node, const Aggregate *relative,
+ CodeMarker *marker)
+{
+ generateExtractionMark(node, MemberMark);
+
+ QString qmlItemHeader("<div class=\"qmlproto\" translate=\"no\">\n"
+ "<div class=\"table\"><table class=\"qmlname\">\n");
+
+ QString qmlItemStart("<tr valign=\"top\" class=\"odd\" id=\"%1\">\n"
+ "<td class=\"%2\"><p>\n");
+ QString qmlItemEnd("</p></td></tr>\n");
+
+ QString qmlItemFooter("</table></div></div>\n");
+
+ auto generateQmlProperty = [&](Node *n) {
+ out() << qmlItemStart.arg(refForNode(n), "tblQmlPropNode");
+ generateQmlItem(n, relative, marker, false);
+ out() << qmlItemEnd;
+ };
+
+ auto generateQmlMethod = [&](Node *n) {
+ out() << qmlItemStart.arg(refForNode(n), "tblQmlFuncNode");
+ generateSynopsis(n, relative, marker, Section::Details, false);
+ out() << qmlItemEnd;
+ };
+
+ out() << "<div class=\"qmlitem\">";
+ if (node->isPropertyGroup()) {
+ const auto *scn = static_cast<const SharedCommentNode *>(node);
+ out() << qmlItemHeader;
+ if (!scn->name().isEmpty()) {
+ const QString nodeRef = refForNode(scn);
+ out() << R"(<tr valign="top" class="even" id=")" << nodeRef << "\">";
+ out() << "<th class=\"centerAlign\"><p>";
+ out() << "<b>" << scn->name() << " group</b>";
+ out() << "</p></th></tr>\n";
+ }
+ const QList<Node *> sharedNodes = scn->collective();
+ for (const auto &sharedNode : sharedNodes) {
+ if (sharedNode->isQmlProperty())
+ generateQmlProperty(sharedNode);
+ }
+ out() << qmlItemFooter;
+ } else if (node->isQmlProperty()) {
+ out() << qmlItemHeader;
+ generateQmlProperty(node);
+ out() << qmlItemFooter;
+ } else if (node->isSharedCommentNode()) {
+ const auto *scn = reinterpret_cast<const SharedCommentNode *>(node);
+ const QList<Node *> &sharedNodes = scn->collective();
+ if (sharedNodes.size() > 1)
+ out() << "<div class=\"fngroup\">\n";
+ out() << qmlItemHeader;
+ for (const auto &sharedNode : sharedNodes) {
+ // Generate the node only if it's a QML method
+ if (sharedNode->isFunction(Node::QML))
+ generateQmlMethod(sharedNode);
+ else if (sharedNode->isQmlProperty())
+ generateQmlProperty(sharedNode);
+ }
+ out() << qmlItemFooter;
+ if (sharedNodes.size() > 1)
+ out() << "</div>"; // fngroup
+ } else { // assume the node is a method/signal handler
+ out() << qmlItemHeader;
+ generateQmlMethod(node);
+ out() << qmlItemFooter;
+ }
+
+ out() << "<div class=\"qmldoc\">";
+ generateStatus(node, marker);
+ generateBody(node, marker);
+ generateThreadSafeness(node, marker);
+ generateSince(node, marker);
+ generateAlsoList(node, marker);
+ out() << "</div></div>";
+ generateExtractionMark(node, EndMark);
+}
+
+void HtmlGenerator::generateExtractionMark(const Node *node, ExtractionMarkType markType)
+{
+ if (markType != EndMark) {
+ out() << "<!-- $$$" + node->name();
+ if (markType == MemberMark) {
+ if (node->isFunction()) {
+ const auto *func = static_cast<const FunctionNode *>(node);
+ if (!func->hasAssociatedProperties()) {
+ if (func->overloadNumber() == 0)
+ out() << "[overload1]";
+ out() << "$$$" + func->name() + func->parameters().rawSignature().remove(' ');
+ }
+ } else if (node->isProperty()) {
+ out() << "-prop";
+ const auto *prop = static_cast<const PropertyNode *>(node);
+ const NodeList &list = prop->functions();
+ for (const auto *propFuncNode : list) {
+ if (propFuncNode->isFunction()) {
+ const auto *func = static_cast<const FunctionNode *>(propFuncNode);
+ out() << "$$$" + func->name()
+ + func->parameters().rawSignature().remove(' ');
+ }
+ }
+ } else if (node->isEnumType()) {
+ const auto *enumNode = static_cast<const EnumNode *>(node);
+ const auto &items = enumNode->items();
+ for (const auto &item : items)
+ out() << "$$$" + item.name();
+ }
+ } else if (markType == BriefMark) {
+ out() << "-brief";
+ } else if (markType == DetailedDescriptionMark) {
+ out() << "-description";
+ }
+ out() << " -->\n";
+ } else {
+ out() << "<!-- @@@" + node->name() + " -->\n";
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/htmlgenerator.h b/src/qdoc/qdoc/src/qdoc/htmlgenerator.h
new file mode 100644
index 000000000..299240c24
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/htmlgenerator.h
@@ -0,0 +1,181 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef HTMLGENERATOR_H
+#define HTMLGENERATOR_H
+
+#include "codemarker.h"
+#include "xmlgenerator.h"
+#include "filesystem/fileresolver.h"
+
+#include <QtCore/qhash.h>
+#include <QtCore/qregularexpression.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+class Config;
+class ExampleNode;
+class HelpProjectWriter;
+class ManifestWriter;
+
+class HtmlGenerator : public XmlGenerator
+{
+public:
+ HtmlGenerator(FileResolver& file_resolver);
+ ~HtmlGenerator() override;
+
+ void initializeGenerator() override;
+ void terminateGenerator() override;
+ QString format() override;
+ void generateDocs() override;
+
+ QString protectEnc(const QString &string);
+ static QString protect(const QString &string);
+
+protected:
+ void generateExampleFilePage(const Node *en, ResolvedFile resolved_file, CodeMarker *marker) override;
+ qsizetype generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker) override;
+ void generateCppReferencePage(Aggregate *aggregate, CodeMarker *marker) override;
+ void generateProxyPage(Aggregate *aggregate, CodeMarker *marker) override;
+ void generateQmlTypePage(QmlTypeNode *qcn, CodeMarker *marker) override;
+ void generatePageNode(PageNode *pn, CodeMarker *marker) override;
+ void generateCollectionNode(CollectionNode *cn, CodeMarker *marker) override;
+ void generateGenericCollectionPage(CollectionNode *cn, CodeMarker *marker) override;
+ [[nodiscard]] QString fileExtension() const override;
+
+private:
+ enum SubTitleSize { SmallSubTitle, LargeSubTitle };
+ enum ExtractionMarkType { BriefMark, DetailedDescriptionMark, MemberMark, EndMark };
+
+ void generateNavigationBar(const QString &title, const Node *node, CodeMarker *marker,
+ const QString &buildversion, bool tableItems = false);
+ void generateHeader(const QString &title, const Node *node = nullptr,
+ CodeMarker *marker = nullptr);
+ void generateTitle(const QString &title, const Text &subTitle, SubTitleSize subTitleSize,
+ const Node *relative, CodeMarker *marker);
+ void generateFooter(const Node *node = nullptr);
+ void generateRequisites(Aggregate *inner, CodeMarker *marker);
+ void generateQmlRequisites(QmlTypeNode *qcn, CodeMarker *marker);
+ void generateBrief(const Node *node, CodeMarker *marker, const Node *relative = nullptr,
+ bool addLink = true);
+ void generateTableOfContents(const Node *node, CodeMarker *marker,
+ QList<Section> *sections = nullptr);
+ void generateSidebar();
+ QString generateAllMembersFile(const Section &section, CodeMarker *marker);
+ QString generateAllQmlMembersFile(const Sections &sections, CodeMarker *marker);
+ QString generateObsoleteMembersFile(const Sections &sections, CodeMarker *marker);
+ QString generateObsoleteQmlMembersFile(const Sections &sections, CodeMarker *marker);
+ void generateClassHierarchy(const Node *relative, NodeMultiMap &classMap);
+ void generateAnnotatedLists(const Node *relative, CodeMarker *marker,
+ const NodeMultiMap &nodeMap);
+ void generateAnnotatedList(const Node *relative, CodeMarker *marker, const NodeList &nodes,
+ Qt::SortOrder sortOrder = Qt::AscendingOrder);
+ void generateCompactList(ListType listType, const Node *relative, const NodeMultiMap &classMap,
+ bool includeAlphabet, const QString &commonPrefix);
+ void generateFunctionIndex(const Node *relative);
+ void generateLegaleseList(const Node *relative, CodeMarker *marker);
+ bool generateGroupList(CollectionNode *cn, Qt::SortOrder sortOrder = Qt::AscendingOrder);
+ void generateList(const Node *relative, CodeMarker *marker, const QString &selector,
+ Qt::SortOrder sortOrder = Qt::AscendingOrder);
+ void generateSectionList(const Section &section, const Node *relative, CodeMarker *marker,
+ bool useObsoloteMembers = false);
+ void generateQmlSummary(const NodeVector &members, const Node *relative, CodeMarker *marker);
+ void generateQmlItem(const Node *node, const Node *relative, CodeMarker *marker, bool summary);
+ void generateDetailedQmlMember(Node *node, const Aggregate *relative, CodeMarker *marker);
+
+ void generateSection(const NodeVector &nv, const Node *relative, CodeMarker *marker);
+ void generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker,
+ Section::Style style, bool alignNames = false);
+ void generateSectionInheritedList(const Section &section, const Node *relative);
+ QString highlightedCode(const QString &markedCode, const Node *relative,
+ bool alignNames = false, Node::Genus genus = Node::DontCare);
+
+ void generateFullName(const Node *apparentNode, const Node *relative,
+ const Node *actualNode = nullptr);
+ void generateDetailedMember(const Node *node, const PageNode *relative, CodeMarker *marker);
+ void generateLink(const Atom *atom);
+
+ QString fileBase(const Node *node) const override;
+ QString fileName(const Node *node);
+
+ void beginLink(const QString &link);
+ void beginLink(const QString &link, const Node *node, const Node *relative);
+ void endLink();
+ void generateExtractionMark(const Node *node, ExtractionMarkType markType);
+ void addIncludeFileToMap(const Aggregate *aggregate, CodeMarker *marker,
+ QMap<QString, Text> &requisites, Text& text,
+ const QString &headerText);
+ void addSinceToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites, Text *text,
+ const QString &sinceText) const;
+ void addStatusToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites, Text &text,
+ const QString &statusText) const;
+ void addCMakeInfoToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites, Text *text,
+ const QString &CMakeInfo) const;
+ void addQtVariableToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites, Text *text,
+ const QString &qtVariableText) const;
+ void addQmlNativeTypesToMap(QMap<QString, Text> &requisites, Text *text,
+ const QString &nativeTypeText,
+ ClassNode *classe) const;
+ void addInheritsToMap(QMap<QString, Text> &requisites, Text *text, const QString &inheritsText,
+ ClassNode *classe);
+ void addInheritedByToMap(QMap<QString, Text> &requisites, Text *text,
+ const QString &inheritedBytext, ClassNode *classe);
+ void generateTheTable(const QStringList &requisiteOrder, const QMap<QString, Text> &requisites,
+ const QString &headerText, const Aggregate *aggregate,
+ CodeMarker *marker);
+ inline void openUnorderedList();
+ inline void closeUnorderedList();
+
+ QString groupReferenceText(PageNode* node);
+
+ static bool s_inUnorderedList;
+
+ int m_codeIndent { 0 };
+ QString m_codePrefix {};
+ QString m_codeSuffix {};
+ HelpProjectWriter *m_helpProjectWriter { nullptr };
+ ManifestWriter *m_manifestWriter { nullptr };
+ QString m_headerScripts {};
+ QString m_headerStyles {};
+ QString m_endHeader {};
+ QString m_postHeader {};
+ QString m_postPostHeader {};
+ QString m_prologue {};
+ QString m_footer {};
+ QString m_address {};
+ bool m_noNavigationBar { false };
+ QString m_project {};
+ QString m_projectDescription {};
+ QString m_projectUrl {};
+ QString m_navigationLinks {};
+ QString m_navigationSeparator {};
+ QString m_homepage {};
+ QString m_hometitle {};
+ QString m_landingpage {};
+ QString m_landingtitle {};
+ QString m_cppclassespage {};
+ QString m_cppclassestitle {};
+ QString m_qmltypespage {};
+ QString m_qmltypestitle {};
+ QString m_trademarkspage {};
+ QString m_buildversion {};
+ QString m_qflagsHref {};
+ int tocDepth {};
+
+ Config *config { nullptr };
+
+};
+
+#define HTMLGENERATOR_ADDRESS "address"
+#define HTMLGENERATOR_FOOTER "footer"
+#define HTMLGENERATOR_POSTHEADER "postheader"
+#define HTMLGENERATOR_POSTPOSTHEADER "postpostheader"
+#define HTMLGENERATOR_PROLOGUE "prologue"
+#define HTMLGENERATOR_NONAVIGATIONBAR "nonavigationbar"
+#define HTMLGENERATOR_NAVIGATIONSEPARATOR "navigationseparator"
+#define HTMLGENERATOR_TOCDEPTH "tocdepth"
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/importrec.h b/src/qdoc/qdoc/src/qdoc/importrec.h
new file mode 100644
index 000000000..84f8f35ac
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/importrec.h
@@ -0,0 +1,33 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef IMPORTREC_H
+#define IMPORTREC_H
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+struct ImportRec
+{
+ QString m_moduleName {};
+ QString m_majorMinorVersion {};
+ QString m_importUri {}; // subdirectory of module directory
+
+ ImportRec(QString name, QString version, QString importUri)
+ : m_moduleName(std::move(name)),
+ m_majorMinorVersion(std::move(version)),
+ m_importUri(std::move(importUri))
+ {
+ }
+ QString &name() { return m_moduleName; }
+ QString &version() { return m_majorMinorVersion; }
+ [[nodiscard]] bool isEmpty() const { return m_moduleName.isEmpty(); }
+};
+
+QT_END_NAMESPACE
+
+#endif // IMPORTREC_H
diff --git a/src/qdoc/qdoc/src/qdoc/location.cpp b/src/qdoc/qdoc/src/qdoc/location.cpp
new file mode 100644
index 000000000..714e232d7
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/location.cpp
@@ -0,0 +1,423 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "location.h"
+
+#include "config.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qregularexpression.h>
+
+#include <climits>
+#include <cstdio>
+#include <cstdlib>
+
+QT_BEGIN_NAMESPACE
+
+int Location::s_tabSize;
+int Location::s_warningCount = 0;
+int Location::s_warningLimit = -1;
+QString Location::s_programName;
+QString Location::s_project;
+QRegularExpression *Location::s_spuriousRegExp = nullptr;
+
+/*!
+ \class Location
+
+ \brief The Location class provides a way to mark a location in a file.
+
+ It maintains a stack of file positions. A file position
+ consists of the file path, line number, and column number.
+ The location is used for printing error messages that are
+ tied to a location in a file.
+ */
+
+/*!
+ Constructs an empty location.
+ */
+Location::Location() : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
+{
+ // nothing.
+}
+
+/*!
+ Constructs a location with (fileName, 1, 1) on its file
+ position stack.
+ */
+Location::Location(const QString &fileName)
+ : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
+{
+ push(fileName);
+}
+
+/*!
+ The copy constructor copies the contents of \a other into
+ this Location using the assignment operator.
+ */
+Location::Location(const Location &other)
+ : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
+{
+ *this = other;
+}
+
+/*!
+ The assignment operator does a deep copy of the entire
+ state of \a other into this Location.
+ */
+Location &Location::operator=(const Location &other)
+{
+ if (this == &other)
+ return *this;
+
+ QStack<StackEntry> *oldStk = m_stk;
+
+ m_stkBottom = other.m_stkBottom;
+ if (other.m_stk == nullptr) {
+ m_stk = nullptr;
+ m_stkTop = &m_stkBottom;
+ } else {
+ m_stk = new QStack<StackEntry>(*other.m_stk);
+ m_stkTop = &m_stk->top();
+ }
+ m_stkDepth = other.m_stkDepth;
+ m_etc = other.m_etc;
+ delete oldStk;
+ return *this;
+}
+
+/*!
+ If the file position on top of the stack has a line number
+ less than 1, set its line number to 1 and its column number
+ to 1. Otherwise, do nothing.
+ */
+void Location::start()
+{
+ if (m_stkTop->m_lineNo < 1) {
+ m_stkTop->m_lineNo = 1;
+ m_stkTop->m_columnNo = 1;
+ }
+}
+
+/*!
+ Advance the current file position, using \a ch to decide how to do
+ that. If \a ch is a \c{'\\n'}, increment the current line number and
+ set the column number to 1. If \ch is a \c{'\\t'}, increment to the
+ next tab column. Otherwise, increment the column number by 1.
+
+ The current file position is the one on top of the position stack.
+ */
+void Location::advance(QChar ch)
+{
+ if (ch == QLatin1Char('\n')) {
+ m_stkTop->m_lineNo++;
+ m_stkTop->m_columnNo = 1;
+ } else if (ch == QLatin1Char('\t')) {
+ m_stkTop->m_columnNo = 1 + s_tabSize * (m_stkTop->m_columnNo + s_tabSize - 1) / s_tabSize;
+ } else {
+ m_stkTop->m_columnNo++;
+ }
+}
+
+/*!
+ Pushes \a filePath onto the file position stack. The current
+ file position becomes (\a filePath, 1, 1).
+
+ \sa pop()
+*/
+void Location::push(const QString &filePath)
+{
+ if (m_stkDepth++ >= 1) {
+ if (m_stk == nullptr)
+ m_stk = new QStack<StackEntry>;
+ m_stk->push(StackEntry());
+ m_stkTop = &m_stk->top();
+ }
+
+ m_stkTop->m_filePath = filePath;
+ m_stkTop->m_lineNo = INT_MIN;
+ m_stkTop->m_columnNo = 1;
+}
+
+/*!
+ Pops the top of the internal stack. The current file position
+ becomes the next one in the new top of stack.
+
+ \sa push()
+*/
+void Location::pop()
+{
+ if (--m_stkDepth == 0) {
+ m_stkBottom = StackEntry();
+ } else {
+ if (!m_stk)
+ return;
+ m_stk->pop();
+ if (m_stk->isEmpty()) {
+ delete m_stk;
+ m_stk = nullptr;
+ m_stkTop = &m_stkBottom;
+ } else {
+ m_stkTop = &m_stk->top();
+ }
+ }
+}
+
+/*! \fn bool Location::isEmpty() const
+
+ Returns \c true if there is no file name set yet; returns \c false
+ otherwise. The functions filePath(), lineNo() and columnNo()
+ must not be called on an empty Location object.
+ */
+
+/*! \fn const QString &Location::filePath() const
+ Returns the current path and file name. If the Location is
+ empty, the returned string is null.
+
+ \sa lineNo(), columnNo()
+ */
+
+/*!
+ Returns the file name part of the file path, ie the current
+ file. Returns an empty string if the file path is empty.
+ */
+QString Location::fileName() const
+{
+ QFileInfo fi(filePath());
+ return fi.fileName();
+}
+
+/*!
+ Returns the suffix of the file name. Returns an empty string
+ if the file path is empty.
+ */
+QString Location::fileSuffix() const
+{
+ QString fp = filePath();
+ return (fp.isEmpty() ? fp : fp.mid(fp.lastIndexOf('.') + 1));
+}
+
+/*! \fn int Location::lineNo() const
+ Returns the current line number.
+ Must not be called on an empty Location object.
+
+ \sa filePath(), columnNo()
+*/
+
+/*! \fn int Location::columnNo() const
+ Returns the current column number.
+ Must not be called on an empty Location object.
+
+ \sa filePath(), lineNo()
+*/
+
+/*!
+ Writes \a message and \a details to stderr as a formatted
+ warning message. Does not write the message if qdoc is in
+ the Prepare phase.
+ */
+void Location::warning(const QString &message, const QString &details) const
+{
+ const auto &config = Config::instance();
+ if (!config.preparing() || config.singleExec())
+ emitMessage(Warning, message, details);
+}
+
+/*!
+ Writes \a message and \a details to stderr as a formatted
+ error message. Does not write the message if qdoc is in
+ the Prepare phase.
+ */
+void Location::error(const QString &message, const QString &details) const
+{
+ const auto &config = Config::instance();
+ if (!config.preparing() || config.singleExec())
+ emitMessage(Error, message, details);
+}
+
+/*!
+ Returns the error code QDoc should exit with; EXIT_SUCCESS
+ or the number of documentation warnings if they exceeded
+ the limit set by warninglimit configuration variable.
+ */
+int Location::exitCode()
+{
+ if (s_warningLimit < 0 || s_warningCount <= s_warningLimit)
+ return EXIT_SUCCESS;
+
+ Location().emitMessage(
+ Error,
+ QStringLiteral("Documentation warnings (%1) exceeded the limit (%2) for '%3'.")
+ .arg(QString::number(s_warningCount), QString::number(s_warningLimit),
+ s_project),
+ QString());
+ return s_warningCount;
+}
+
+/*!
+ Writes \a message and \a details to stderr as a formatted
+ error message and then exits the program. qdoc prints fatal
+ errors in either phase (Prepare or Generate).
+ */
+void Location::fatal(const QString &message, const QString &details) const
+{
+ emitMessage(Error, message, details);
+ information(message);
+ information(details);
+ information("Aborting");
+ exit(EXIT_FAILURE);
+}
+
+/*!
+ Writes \a message and \a details 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 config, including
+ tab size, program name, and a regular expression that
+ appears to be used for matching certain error messages
+ so that emitMessage() can avoid printing them.
+ */
+void Location::initialize()
+{
+ Config &config = Config::instance();
+ s_tabSize = config.get(CONFIG_TABSIZE).asInt();
+ s_programName = config.programName();
+ s_project = config.get(CONFIG_PROJECT).asString();
+ if (!config.singleExec())
+ s_warningCount = 0;
+ if (qEnvironmentVariableIsSet("QDOC_ENABLE_WARNINGLIMIT")
+ || config.get(CONFIG_WARNINGLIMIT + Config::dot + "enabled").asBool())
+ s_warningLimit = config.get(CONFIG_WARNINGLIMIT).asInt();
+
+ QRegularExpression regExp = config.getRegExp(CONFIG_SPURIOUS);
+ if (regExp.isValid()) {
+ s_spuriousRegExp = new QRegularExpression(regExp);
+ } else {
+ config.get(CONFIG_SPURIOUS).location()
+ .warning(QStringLiteral("Invalid regular expression '%1'")
+ .arg(regExp.pattern()));
+ }
+}
+
+/*!
+ Apparently, all this does is delete the regular expression
+ used for intercepting certain error messages that should
+ not be emitted by emitMessage().
+ */
+void Location::terminate()
+{
+ delete s_spuriousRegExp;
+ s_spuriousRegExp = nullptr;
+}
+
+/*!
+ Prints \a message to \c stdout followed by a \c{'\n'}.
+ */
+void Location::information(const QString &message)
+{
+ printf("%s\n", message.toLatin1().data());
+ fflush(stdout);
+}
+
+/*!
+ Report a program bug, including the \a hint.
+ */
+void Location::internalError(const QString &hint)
+{
+ Location().fatal(QStringLiteral("Internal error (%1)").arg(hint),
+ QStringLiteral("There is a bug in %1. Seek advice from your local"
+ " %2 guru.")
+ .arg(s_programName, s_programName));
+}
+
+/*!
+ Formats \a message and \a details into a single string
+ and outputs that string to \c stderr. \a type specifies
+ whether the \a message is an error or a warning.
+ */
+void Location::emitMessage(MessageType type, const QString &message, const QString &details) const
+{
+ if (type == Warning && s_spuriousRegExp != nullptr) {
+ auto match = s_spuriousRegExp->match(message, 0, QRegularExpression::NormalMatch,
+ QRegularExpression::AnchorAtOffsetMatchOption);
+ if (match.hasMatch() && match.capturedLength() == message.size())
+ return;
+ }
+
+ QString result = message;
+ if (!details.isEmpty())
+ result += "\n[" + details + QLatin1Char(']');
+ result.replace("\n", "\n ");
+ if (isEmpty()) {
+ if (type == Error)
+ result.prepend(QStringLiteral(": error: "));
+ else if (type == Warning) {
+ result.prepend(QStringLiteral(": warning: "));
+ ++s_warningCount;
+ }
+ } else {
+ if (type == Error)
+ result.prepend(QStringLiteral(": (qdoc) error: "));
+ else if (type == Warning) {
+ result.prepend(QStringLiteral(": (qdoc) warning: "));
+ ++s_warningCount;
+ }
+ }
+ if (type != Report)
+ result.prepend(toString());
+ fprintf(stderr, "%s\n", result.toLatin1().data());
+ fflush(stderr);
+}
+
+/*!
+ Converts the location to a string to be prepended to error
+ messages.
+ */
+QString Location::toString() const
+{
+ QString str;
+
+ if (isEmpty()) {
+ str = s_programName;
+ } else {
+ Location loc2 = *this;
+ loc2.setEtc(false);
+ loc2.pop();
+ if (!loc2.isEmpty()) {
+ QString blah = QStringLiteral("In file included from ");
+ for (;;) {
+ str += blah;
+ str += loc2.top();
+ loc2.pop();
+ if (loc2.isEmpty())
+ break;
+ str += QStringLiteral(",\n");
+ blah.fill(' ');
+ }
+ str += QStringLiteral(":\n");
+ }
+ str += top();
+ }
+ return str;
+}
+
+QString Location::top() const
+{
+ QDir path(filePath());
+ QString str = path.absolutePath();
+ if (lineNo() >= 1) {
+ str += QLatin1Char(':');
+ str += QString::number(lineNo());
+ }
+ if (etc())
+ str += QLatin1String(" (etc.)");
+ return str;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/location.h b/src/qdoc/qdoc/src/qdoc/location.h
new file mode 100644
index 000000000..8427bc917
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/location.h
@@ -0,0 +1,92 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef LOCATION_H
+#define LOCATION_H
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qstack.h>
+
+QT_BEGIN_NAMESPACE
+
+class QRegularExpression;
+
+class Location
+{
+public:
+ Location();
+ explicit Location(const QString &filePath);
+ Location(const Location &other);
+ ~Location() { delete m_stk; }
+
+ Location &operator=(const Location &other);
+
+ void start();
+ void advance(QChar ch);
+ void advanceLines(int n)
+ {
+ m_stkTop->m_lineNo += n;
+ m_stkTop->m_columnNo = 1;
+ }
+
+ void push(const QString &filePath);
+ void pop();
+ void setEtc(bool etc) { m_etc = etc; }
+ void setLineNo(int no) { m_stkTop->m_lineNo = no; }
+ void setColumnNo(int no) { m_stkTop->m_columnNo = no; }
+
+ [[nodiscard]] bool isEmpty() const { return m_stkDepth == 0; }
+ [[nodiscard]] int depth() const { return m_stkDepth; }
+ [[nodiscard]] const QString &filePath() const { return m_stkTop->m_filePath; }
+ [[nodiscard]] QString fileName() const;
+ [[nodiscard]] QString fileSuffix() const;
+ [[nodiscard]] int lineNo() const { return m_stkTop->m_lineNo; }
+ [[nodiscard]] int columnNo() const { return m_stkTop->m_columnNo; }
+ [[nodiscard]] bool etc() const { return m_etc; }
+ [[nodiscard]] QString toString() const;
+ void warning(const QString &message, const QString &details = QString()) const;
+ void error(const QString &message, 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 void initialize();
+
+ static void terminate();
+ static void information(const QString &message);
+ static void internalError(const QString &hint);
+ static int exitCode();
+
+private:
+ enum MessageType { Warning, Error, Report };
+
+ struct StackEntry
+ {
+ QString m_filePath {};
+ int m_lineNo {};
+ int m_columnNo {};
+ };
+ friend class QTypeInfo<StackEntry>;
+
+ void emitMessage(MessageType type, const QString &message, const QString &details) const;
+ [[nodiscard]] QString top() const;
+
+private:
+ StackEntry m_stkBottom {};
+ QStack<StackEntry> *m_stk {};
+ StackEntry *m_stkTop {};
+ int m_stkDepth {};
+ bool m_etc {};
+
+ static int s_tabSize;
+ static int s_warningCount;
+ static int s_warningLimit;
+ static QString s_programName;
+ static QString s_project;
+ static QRegularExpression *s_spuriousRegExp;
+};
+Q_DECLARE_TYPEINFO(Location::StackEntry, Q_RELOCATABLE_TYPE);
+Q_DECLARE_TYPEINFO(Location, Q_COMPLEX_TYPE); // stkTop = &stkBottom
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/macro.h b/src/qdoc/qdoc/src/qdoc/macro.h
new file mode 100644
index 000000000..11e77fa29
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/macro.h
@@ -0,0 +1,27 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef MACRO_H
+#define MACRO_H
+
+#include "location.h"
+
+#include <QtCore/qmap.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * Simple structure used by the Doc and DocParser classes.
+ */
+struct Macro
+{
+public:
+ QString m_defaultDef {};
+ Location m_defaultDefLocation {};
+ QMap<QString, QString> m_otherDefs {};
+ int numParams {};
+};
+
+QT_END_NAMESPACE
+
+#endif // MACRO_H
diff --git a/src/qdoc/qdoc/src/qdoc/main.cpp b/src/qdoc/qdoc/src/qdoc/main.cpp
new file mode 100644
index 000000000..5e48a6a8a
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/main.cpp
@@ -0,0 +1,720 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "clangcodeparser.h"
+#include "codemarker.h"
+#include "codeparser.h"
+#include "config.h"
+#include "cppcodemarker.h"
+#include "doc.h"
+#include "docbookgenerator.h"
+#include "htmlgenerator.h"
+#include "location.h"
+#include "puredocparser.h"
+#include "qdocdatabase.h"
+#include "qmlcodemarker.h"
+#include "qmlcodeparser.h"
+#include "sourcefileparser.h"
+#include "utilities.h"
+#include "tokenizer.h"
+#include "tree.h"
+#include "webxmlgenerator.h"
+
+#include "filesystem/fileresolver.h"
+#include "boundaries/filesystem/directorypath.h"
+
+#include <QtCore/qcompilerdetection.h>
+#include <QtCore/qdatetime.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qhashfunctions.h>
+
+#include <set>
+
+#ifndef QT_BOOTSTRAPPED
+# include <QtCore/qcoreapplication.h>
+#endif
+
+#include <algorithm>
+#include <cstdlib>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+bool creationTimeBefore(const QFileInfo &fi1, const QFileInfo &fi2)
+{
+ return fi1.lastModified() < fi2.lastModified();
+}
+
+/*!
+ \internal
+ Inspects each file path in \a sources. File paths with a known
+ source file type are parsed to extract user-provided
+ documentation and information about source code level elements.
+
+ \note Unknown source file types are silently ignored.
+
+ The validity or availability of the file paths may or may not cause QDoc
+ to generate warnings; this depends on the implementation of
+ parseSourceFile() for the relevant parser.
+
+ \sa CodeParser::parserForSourceFile, CodeParser::sourceFileNameFilter
+*/
+static void parseSourceFiles(
+ std::vector<QString>&& sources,
+ SourceFileParser& source_file_parser,
+ CppCodeParser& cpp_code_parser
+) {
+ ParserErrorHandler error_handler{};
+ std::stable_sort(sources.begin(), sources.end());
+
+ sources.erase (
+ std::unique(sources.begin(), sources.end()),
+ sources.end()
+ );
+
+ auto qml_sources =
+ std::stable_partition(sources.begin(), sources.end(), [](const QString& source){
+ return CodeParser::parserForSourceFile(source) == CodeParser::parserForLanguage("QML");
+ });
+
+
+ std::for_each(qml_sources, sources.end(),
+ [&source_file_parser, &cpp_code_parser, &error_handler](const QString& source){
+ qCDebug(lcQdoc, "Parsing %s", qPrintable(source));
+
+ auto [untied_documentation, tied_documentation] = source_file_parser(tag_source_file(source));
+ std::vector<FnMatchError> errors{};
+
+ for (auto untied : untied_documentation) {
+ auto result = cpp_code_parser.processTopicArgs(untied);
+ tied_documentation.insert(tied_documentation.end(), result.first.begin(), result.first.end());
+ };
+
+ cpp_code_parser.processMetaCommands(tied_documentation);
+
+ // Process errors that occurred during parsing
+ for (const auto &e : errors)
+ error_handler(e);
+ });
+
+ std::for_each(sources.begin(), qml_sources, [&cpp_code_parser](const QString& source){
+ auto *codeParser = CodeParser::parserForSourceFile(source);
+ if (!codeParser) return;
+
+ qCDebug(lcQdoc, "Parsing %s", qPrintable(source));
+ codeParser->parseSourceFile(Config::instance().location(), source, cpp_code_parser);
+ });
+
+}
+
+/*!
+ Read some XML indexes containing definitions from other
+ documentation sets. \a config contains a variable that
+ lists directories where index files can be found. It also
+ contains the \c depends variable, which lists the modules
+ that the current module depends on. \a formats contains
+ a list of output formats; each format may have a different
+ output subdirectory where index files are located.
+*/
+static void loadIndexFiles(const QSet<QString> &formats)
+{
+ Config &config = Config::instance();
+ QDocDatabase *qdb = QDocDatabase::qdocDB();
+ QStringList indexFiles;
+ const QStringList configIndexes{config.get(CONFIG_INDEXES).asStringList()};
+ bool warn = !config.get(CONFIG_NOLINKERRORS).asBool();
+
+ for (const auto &index : configIndexes) {
+ QFileInfo fi(index);
+ if (fi.exists() && fi.isFile())
+ indexFiles << index;
+ else if (warn)
+ Location().warning(QString("Index file not found: %1").arg(index));
+ }
+
+ config.dependModules() += config.get(CONFIG_DEPENDS).asStringList();
+ config.dependModules().removeDuplicates();
+ bool useNoSubDirs = false;
+ QSet<QString> subDirs;
+
+ // Add format-specific output subdirectories to the set of
+ // subdirectories where we look for index files
+ for (const auto &format : formats) {
+ if (config.get(format + Config::dot + "nosubdirs").asBool()) {
+ useNoSubDirs = true;
+ const auto singleOutputSubdir{QDir(config.getOutputDir(format)).dirName()};
+ if (!singleOutputSubdir.isEmpty())
+ subDirs << singleOutputSubdir;
+ }
+ }
+
+ if (!config.dependModules().empty()) {
+ if (!config.indexDirs().empty()) {
+ for (auto &dir : config.indexDirs()) {
+ if (dir.startsWith("..")) {
+ const QString prefix(QDir(config.currentDir())
+ .relativeFilePath(config.previousCurrentDir()));
+ if (!prefix.isEmpty())
+ dir.prepend(prefix + QLatin1Char('/'));
+ }
+ }
+ /*
+ Load all dependencies:
+ Either add all subdirectories of the indexdirs as dependModules,
+ when an asterisk is used in the 'depends' list, or
+ when <format>.nosubdirs is set, we need to look for all .index files
+ in the output subdirectory instead.
+ */
+ bool asteriskUsed = false;
+ if (config.dependModules().contains("*")) {
+ config.dependModules().removeOne("*");
+ asteriskUsed = true;
+ if (useNoSubDirs) {
+ std::for_each(formats.begin(), formats.end(), [&](const QString &format) {
+ QDir scanDir(config.getOutputDir(format));
+ QStringList foundModules =
+ scanDir.entryList(QStringList("*.index"), QDir::Files);
+ std::transform(
+ foundModules.begin(), foundModules.end(), foundModules.begin(),
+ [](const QString &index) { return QFileInfo(index).baseName(); });
+ config.dependModules() << foundModules;
+ });
+ } else {
+ for (const auto &indexDir : config.indexDirs()) {
+ QDir scanDir = QDir(indexDir);
+ scanDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
+ QFileInfoList dirList = scanDir.entryInfoList();
+ for (const auto &dir : dirList)
+ config.dependModules().append(dir.fileName());
+ }
+ }
+ // Remove self-dependencies and possible duplicates
+ QString project{config.get(CONFIG_PROJECT).asString()};
+ config.dependModules().removeAll(project.toLower());
+ config.dependModules().removeDuplicates();
+ qCCritical(lcQdoc) << "Configuration file for"
+ << project << "has depends = *; loading all"
+ << config.dependModules().size()
+ << "index files found";
+ }
+ for (const auto &module : config.dependModules()) {
+ QList<QFileInfo> foundIndices;
+ // Always look in module-specific subdir, even with *.nosubdirs config
+ bool useModuleSubDir = !subDirs.contains(module);
+ subDirs << module;
+
+ for (const auto &dir : config.indexDirs()) {
+ for (const auto &subDir : std::as_const(subDirs)) {
+ QString fileToLookFor = dir + QLatin1Char('/') + subDir + QLatin1Char('/')
+ + module + ".index";
+ if (QFile::exists(fileToLookFor)) {
+ QFileInfo tempFileInfo(fileToLookFor);
+ if (!foundIndices.contains(tempFileInfo))
+ foundIndices.append(tempFileInfo);
+ }
+ }
+ }
+ // Clear the temporary module-specific subdir
+ if (useModuleSubDir)
+ subDirs.remove(module);
+ std::sort(foundIndices.begin(), foundIndices.end(), creationTimeBefore);
+ QString indexToAdd;
+ if (foundIndices.size() > 1) {
+ /*
+ QDoc should always use the last entry in the multimap when there are
+ multiple index files for a module, since the last modified file has the
+ highest UNIX timestamp.
+ */
+ QStringList indexPaths;
+ indexPaths.reserve(foundIndices.size());
+ for (const auto &found : std::as_const(foundIndices))
+ indexPaths << found.absoluteFilePath();
+ if (warn) {
+ Location().warning(
+ QString("Multiple index files found for dependency \"%1\":\n%2")
+ .arg(module, indexPaths.join('\n')));
+ Location().warning(
+ QString("Using %1 as index file for dependency \"%2\"")
+ .arg(foundIndices[foundIndices.size() - 1].absoluteFilePath(),
+ module));
+ }
+ indexToAdd = foundIndices[foundIndices.size() - 1].absoluteFilePath();
+ } else if (foundIndices.size() == 1) {
+ indexToAdd = foundIndices[0].absoluteFilePath();
+ }
+ if (!indexToAdd.isEmpty()) {
+ if (!indexFiles.contains(indexToAdd))
+ indexFiles << indexToAdd;
+ } else if (!asteriskUsed && warn) {
+ Location().warning(
+ QString(R"("%1" Cannot locate index file for dependency "%2")")
+ .arg(config.get(CONFIG_PROJECT).asString(), module));
+ }
+ }
+ } else if (warn) {
+ Location().warning(
+ QLatin1String("Dependent modules specified, but no index directories were set. "
+ "There will probably be errors for missing links."));
+ }
+ }
+ qdb->readIndexes(indexFiles);
+}
+
+/*!
+ \internal
+ Prints to stderr the name of the project that QDoc is running for,
+ in which mode and which phase.
+
+ If QDoc is not running in debug mode or --log-progress command line
+ option is not set, do nothing.
+ */
+void logStartEndMessage(const QLatin1String &startStop, Config &config)
+{
+ if (!config.get(CONFIG_LOGPROGRESS).asBool())
+ return;
+
+ const QString runName = " qdoc for "
+ + config.get(CONFIG_PROJECT).asString()
+ + QLatin1String(" in ")
+ + QLatin1String(config.singleExec() ? "single" : "dual")
+ + QLatin1String(" process mode: ")
+ + QLatin1String(config.preparing() ? "prepare" : "generate")
+ + QLatin1String(" phase.");
+
+ const QString msg = startStop + runName;
+ qCInfo(lcQdoc) << msg.toUtf8().data();
+}
+
+/*!
+ Processes the qdoc config file \a fileName. This is the controller for all
+ of QDoc. The \a config instance represents the configuration data for QDoc.
+ All other classes are initialized with the same config.
+ */
+static void processQdocconfFile(const QString &fileName)
+{
+ Config &config = Config::instance();
+ config.setPreviousCurrentDir(QDir::currentPath());
+
+ /*
+ With the default configuration values in place, load
+ the qdoc configuration file. Note that the configuration
+ file may include other configuration files.
+
+ The Location class keeps track of the current location
+ in the file being processed, mainly for error reporting
+ purposes.
+ */
+ Location::initialize();
+ config.load(fileName);
+ QString project{config.get(CONFIG_PROJECT).asString()};
+ if (project.isEmpty()) {
+ qCCritical(lcQdoc) << QLatin1String("qdoc can't run; no project set in qdocconf file");
+ exit(1);
+ }
+ Location::terminate();
+
+ config.setCurrentDir(QFileInfo(fileName).path());
+ if (!config.currentDir().isEmpty())
+ QDir::setCurrent(config.currentDir());
+
+ logStartEndMessage(QLatin1String("Start"), config);
+
+ if (config.getDebug()) {
+ Utilities::startDebugging(QString("command line"));
+ qCDebug(lcQdoc).noquote() << "Arguments:" << QCoreApplication::arguments();
+ }
+
+ // <<TODO: [cleanup-temporary-kludges]
+ // The underlying validation should be performed at the
+ // configuration level during parsing.
+ // This cannot be done straightforwardly with how the Config class
+ // is implemented.
+ // When the Config class will be deprived of logic and
+ // restructured, the compiler will notify us of this kludge, but
+ // remember to reevaluate the code itself considering the new
+ // data-flow and the possibility for optimizations as this is not
+ // done for temporary code. Indeed some of the code is visibly wasteful.
+ // Similarly, ensure that the loose definition that we use here is
+ // not preserved.
+
+ QStringList search_directories{config.getCanonicalPathList(CONFIG_EXAMPLEDIRS)};
+ QStringList image_search_directories{config.getCanonicalPathList(CONFIG_IMAGEDIRS)};
+
+ const auto& [excludedDirs, excludedFiles] = config.getExcludedPaths();
+
+ qCDebug(lcQdoc, "Adding doc/image dirs found in exampledirs to imagedirs");
+ QSet<QString> exampleImageDirs;
+ QStringList exampleImageList = config.getExampleImageFiles(excludedDirs, excludedFiles);
+ for (const auto &image : exampleImageList) {
+ if (image.contains("doc/images")) {
+ QString t = image.left(image.lastIndexOf("doc/images") + 10);
+ if (!exampleImageDirs.contains(t))
+ exampleImageDirs.insert(t);
+ }
+ }
+
+ // REMARK: The previous system discerned between search directories based on the kind of file that was searched for.
+ // For example, an image search was bounded to some directories
+ // that may or may not be the same as the ones where examples are
+ // searched for.
+ // The current Qt documentation does not use this feature. That
+ // is, the output of QDoc when a unified search list is used is
+ // the same as the output for that of separated lists.
+ // For this reason, we currently simplify the process, albeit this
+ // may at some point change, by joining the various lists into a
+ // single search list and a unified interface.
+ // Do note that the configuration still allows for those
+ // parameters to be user defined in a split-way as this will not
+ // be able to be changed until Config itself is looked upon.
+ // Hence, we join the various directory sources into one list for the time being.
+ // Do note that this means that the amount of searched directories for a file is now increased.
+ // This shouldn't matter as the amount of directories is expected
+ // to be generally small and the search routine complexity is
+ // linear in the amount of directories.
+ // There are some complications that may arise in very specific
+ // cases by this choice (some of which where there before under
+ // possibly different circumstances), making some files
+ // unreachable.
+ // See the remarks in FileResolver for more infomration.
+ std::copy(image_search_directories.begin(), image_search_directories.end(), std::back_inserter(search_directories));
+ std::copy(exampleImageDirs.begin(), exampleImageDirs.end(), std::back_inserter(search_directories));
+
+ std::vector<DirectoryPath> validated_search_directories{};
+ for (const QString& path : search_directories) {
+ auto maybe_validated_path{DirectoryPath::refine(path)};
+ if (!maybe_validated_path)
+ // TODO: [uncentralized-admonition]
+ qCDebug(lcQdoc).noquote() << u"%1 is not a valid path, it will be ignored when resolving a file"_s.arg(path);
+ else validated_search_directories.push_back(*maybe_validated_path);
+ }
+
+ // TODO>>
+
+ FileResolver file_resolver{std::move(validated_search_directories)};
+
+ // REMARK: The constructor for generators doesn't actually perform
+ // initialization of their content.
+ // Indeed, Generators use the general antipattern of the static
+ // initialize-terminate non-scoped mutable state that we see in
+ // many parts of QDoc.
+ // In their constructor, Generators mainly register themselves into a static list.
+ // Previously, this was done at the start of main.
+ // To be able to pass a correct FileResolver or other systems, we
+ // need to construct them after the configuration has been read
+ // and has been destructured.
+ // For this reason, their construction was moved here.
+ // This function may be called more than once for some of QDoc's
+ // call, albeit this should not actually happen in Qt's
+ // documentation.
+ // Then, constructing the generators here might provide for some
+ // unexpected behavior as new generators are appended to the list
+ // and never used, considering that the list is searched in a
+ // linearly fashion and each generator of some type T, in the
+ // current codebase, will always be found if another instance of
+ // that same type would have been found.
+ // Furthermore, those instances would be destroyed by then, such
+ // that accessing them would be erroneous.
+ // To avoid this, the static list was made to be cleared in
+ // Generator::terminate, which, in theory, will be called before
+ // the generators will be constructed again.
+ // We could have used the initialize method for this, but this
+ // would force us into a limited and more complex semantic, see an
+ // example of this in DocParser, and would restrain us further to
+ // the initialize-terminate idiom which is expect to be purged in
+ // the future.
+ HtmlGenerator htmlGenerator{file_resolver};
+ WebXMLGenerator webXMLGenerator{file_resolver};
+ DocBookGenerator docBookGenerator{file_resolver};
+
+ Generator::initialize();
+
+ /*
+ Initialize the qdoc database, where all the parsed source files
+ will be stored. The database includes a tree of nodes, which gets
+ built as the source files are parsed. The documentation output is
+ generated by traversing that tree.
+
+ Note: qdocDB() allocates a new instance only if no instance exists.
+ So it is safe to call qdocDB() any time.
+ */
+ QDocDatabase *qdb = QDocDatabase::qdocDB();
+ qdb->setVersion(config.get(CONFIG_VERSION).asString());
+ /*
+ By default, the only output format is HTML.
+ */
+ const QSet<QString> outputFormats = config.getOutputFormats();
+
+ qdb->clearSearchOrder();
+ if (!config.singleExec()) {
+ if (!config.preparing()) {
+ qCDebug(lcQdoc, " loading index files");
+ loadIndexFiles(outputFormats);
+ qCDebug(lcQdoc, " done loading index files");
+ }
+ qdb->newPrimaryTree(project);
+ } else if (config.preparing())
+ qdb->newPrimaryTree(project);
+ else
+ qdb->setPrimaryTree(project);
+
+ // Retrieve the dependencies if loadIndexFiles() was not called
+ if (config.dependModules().isEmpty()) {
+ config.dependModules() = config.get(CONFIG_DEPENDS).asStringList();
+ config.dependModules().removeDuplicates();
+ }
+ qdb->setSearchOrder(config.dependModules());
+
+ // Store the title of the index (landing) page
+ NamespaceNode *root = qdb->primaryTreeRoot();
+ if (root) {
+ QString title{config.get(CONFIG_NAVIGATION + Config::dot + CONFIG_LANDINGPAGE).asString()};
+ root->tree()->setIndexTitle(
+ config.get(CONFIG_NAVIGATION + Config::dot + CONFIG_LANDINGTITLE).asString(title));
+ }
+
+
+ std::vector<QByteArray> include_paths{};
+ {
+ auto args = config.getCanonicalPathList(CONFIG_INCLUDEPATHS,
+ Config::IncludePaths);
+#ifdef Q_OS_MACOS
+ args.append(Utilities::getInternalIncludePaths(QStringLiteral("clang++")));
+#elif defined(Q_OS_LINUX)
+ args.append(Utilities::getInternalIncludePaths(QStringLiteral("g++")));
+#endif
+
+ for (const auto &path : std::as_const(args)) {
+ if (!path.isEmpty())
+ include_paths.push_back(path.toUtf8());
+ }
+
+ include_paths.erase(std::unique(include_paths.begin(), include_paths.end()),
+ include_paths.end());
+ }
+
+ QList<QByteArray> clang_defines{};
+ {
+ const QStringList config_defines{config.get(CONFIG_DEFINES).asStringList()};
+ for (const QString &def : config_defines) {
+ if (!def.contains(QChar('*'))) {
+ QByteArray tmp("-D");
+ tmp.append(def.toUtf8());
+ clang_defines.append(tmp.constData());
+ }
+ }
+ }
+
+ std::optional<PCHFile> pch = std::nullopt;
+ if (config.dualExec() || config.preparing()) {
+ const QString moduleHeader = config.get(CONFIG_MODULEHEADER).asString();
+ pch = buildPCH(
+ QDocDatabase::qdocDB(),
+ moduleHeader.isNull() ? project : moduleHeader,
+ Config::instance().getHeaderFiles(),
+ include_paths,
+ clang_defines
+ );
+ }
+
+ ClangCodeParser clangParser(QDocDatabase::qdocDB(), Config::instance(), include_paths, clang_defines, pch);
+ PureDocParser docParser{config.location()};
+
+ /*
+ Initialize all the classes and data structures with the
+ qdoc configuration. This is safe to do for each qdocconf
+ file processed, because all the data structures created
+ are either cleared after they have been used, or they
+ are cleared in the terminate() functions below.
+ */
+ Location::initialize();
+ Tokenizer::initialize();
+ CodeMarker::initialize();
+ CodeParser::initialize();
+ Doc::initialize(file_resolver);
+
+ if (config.dualExec() || config.preparing()) {
+ QStringList sourceList;
+
+ qCDebug(lcQdoc, "Reading sourcedirs");
+ sourceList =
+ config.getAllFiles(CONFIG_SOURCES, CONFIG_SOURCEDIRS, excludedDirs, excludedFiles);
+
+ std::vector<QString> sources{};
+ for (const auto &source : sourceList) {
+ if (source.contains(QLatin1String("doc/snippets")))
+ continue;
+ sources.emplace_back(source);
+ }
+ /*
+ Find all the qdoc files in the example dirs, and add
+ them to the source files to be parsed.
+ */
+ qCDebug(lcQdoc, "Reading exampledirs");
+ QStringList exampleQdocList = config.getExampleQdocFiles(excludedDirs, excludedFiles);
+ for (const auto &example : exampleQdocList) {
+ sources.emplace_back(example);
+ }
+
+ /*
+ Parse each source text file in the set using the appropriate parser and
+ add it to the big tree.
+ */
+ if (config.get(CONFIG_LOGPROGRESS).asBool())
+ qCInfo(lcQdoc) << "Parse source files for" << project;
+
+
+ auto headers = config.getHeaderFiles();
+ CppCodeParser cpp_code_parser(FnCommandParser(qdb, headers, clang_defines, pch));
+
+ SourceFileParser source_file_parser{clangParser, docParser};
+ parseSourceFiles(std::move(sources), source_file_parser, cpp_code_parser);
+
+ if (config.get(CONFIG_LOGPROGRESS).asBool())
+ qCInfo(lcQdoc) << "Source files parsed for" << project;
+ }
+ /*
+ Now the primary tree has been built from all the header and
+ source files. Resolve all the class names, function names,
+ targets, URLs, links, and other stuff that needs resolving.
+ */
+ qCDebug(lcQdoc, "Resolving stuff prior to generating docs");
+ qdb->resolveStuff();
+
+ /*
+ The primary tree is built and all the stuff that needed
+ resolving has been resolved. Now traverse the tree and
+ generate the documentation output. More than one output
+ format can be requested. The tree is traversed for each
+ one.
+ */
+ qCDebug(lcQdoc, "Generating docs");
+ for (const auto &format : outputFormats) {
+ auto *generator = Generator::generatorForFormat(format);
+ if (generator) {
+ generator->initializeFormat();
+ generator->generateDocs();
+ } else {
+ config.get(CONFIG_OUTPUTFORMATS)
+ .location()
+ .fatal(QStringLiteral("QDoc: Unknown output format '%1'").arg(format));
+ }
+ }
+
+ qCDebug(lcQdoc, "Terminating qdoc classes");
+ if (Utilities::debugging())
+ Utilities::stopDebugging(project);
+
+ logStartEndMessage(QLatin1String("End"), config);
+ QDocDatabase::qdocDB()->setVersion(QString());
+ Generator::terminate();
+ CodeParser::terminate();
+ CodeMarker::terminate();
+ Doc::terminate();
+ Tokenizer::terminate();
+ Location::terminate();
+ QDir::setCurrent(config.previousCurrentDir());
+
+ qCDebug(lcQdoc, "qdoc classes terminated");
+}
+
+/*!
+ \internal
+ For each file in \a qdocFiles, first clear the configured module
+ dependencies and then pass the file to processQdocconfFile().
+
+ \sa processQdocconfFile(), singleExecutionMode(), dualExecutionMode()
+*/
+static void clearModuleDependenciesAndProcessQdocconfFile(const QStringList &qdocFiles)
+{
+ for (const auto &file : std::as_const(qdocFiles)) {
+ Config::instance().dependModules().clear();
+ processQdocconfFile(file);
+ }
+}
+
+/*!
+ \internal
+
+ A single QDoc process for prepare and generate phases.
+ The purpose is to first generate all index files for all documentation
+ projects that combined make out the documentation set being generated.
+ This allows QDoc to link to all content contained in all projects, e.g.
+ user-defined types or overview documentation, regardless of the project
+ that content belongs to when generating the final output.
+*/
+static void singleExecutionMode()
+{
+ const QStringList qdocFiles = Config::loadMaster(Config::instance().qdocFiles().at(0));
+
+ Config::instance().setQDocPass(Config::Prepare);
+ clearModuleDependenciesAndProcessQdocconfFile(qdocFiles);
+
+ Config::instance().setQDocPass(Config::Generate);
+ QDocDatabase::qdocDB()->processForest();
+ clearModuleDependenciesAndProcessQdocconfFile(qdocFiles);
+}
+
+/*!
+ \internal
+
+ Process each .qdocconf-file passed as command line argument(s).
+*/
+static void dualExecutionMode()
+{
+ const QStringList qdocFiles = Config::instance().qdocFiles();
+ clearModuleDependenciesAndProcessQdocconfFile(qdocFiles);
+}
+
+QT_END_NAMESPACE
+
+int main(int argc, char **argv)
+{
+ QT_USE_NAMESPACE
+
+ // Initialize Qt:
+#ifndef QT_BOOTSTRAPPED
+ // use deterministic hash seed
+ QHashSeed::setDeterministicGlobalSeed();
+#endif
+ QCoreApplication app(argc, argv);
+ app.setApplicationVersion(QLatin1String(QT_VERSION_STR));
+
+ // Instantiate various singletons (used via static methods):
+ /*
+ Create code parsers for the languages to be parsed,
+ and create a tree for C++.
+ */
+ QmlCodeParser qmlParser;
+
+ /*
+ Create code markers for plain text, C++,
+ and QML.
+
+ The plain CodeMarker must be instantiated first because it is used as
+ fallback when the other markers cannot be used.
+
+ Each marker instance is prepended to the CodeMarker::markers list by the
+ base class constructor.
+ */
+ CodeMarker fallbackMarker;
+ CppCodeMarker cppMarker;
+ QmlCodeMarker qmlMarker;
+
+ Config::instance().init("QDoc", app.arguments());
+
+ if (Config::instance().qdocFiles().isEmpty())
+ Config::instance().showHelp();
+
+ if (Config::instance().singleExec()) {
+ singleExecutionMode();
+ } else {
+ dualExecutionMode();
+ }
+
+ // Tidy everything away:
+ QmlTypeNode::terminate();
+ QDocDatabase::destroyQdocDB();
+ return Location::exitCode();
+}
diff --git a/src/qdoc/qdoc/src/qdoc/manifestwriter.cpp b/src/qdoc/qdoc/src/qdoc/manifestwriter.cpp
new file mode 100644
index 000000000..97bf7f190
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/manifestwriter.cpp
@@ -0,0 +1,405 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "manifestwriter.h"
+
+#include "config.h"
+#include "examplenode.h"
+#include "generator.h"
+#include "qdocdatabase.h"
+
+#include <QtCore/qmap.h>
+#include <QtCore/qset.h>
+#include <QtCore/qxmlstream.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \internal
+
+ For each attribute in a map of attributes, checks if the attribute is
+ found in \a usedAttributes. If it is not found, issues a warning specific
+ to the attribute.
+ */
+void warnAboutUnusedAttributes(const QStringList &usedAttributes, const ExampleNode *example)
+{
+ QMap<QString, QString> attributesToWarnFor;
+ attributesToWarnFor.insert(QStringLiteral("imageUrl"),
+ QStringLiteral("Example documentation should have at least one '\\image'"));
+ attributesToWarnFor.insert(QStringLiteral("projectPath"),
+ QStringLiteral("Example has no project file"));
+
+ for (auto it = attributesToWarnFor.cbegin(); it != attributesToWarnFor.cend(); ++it) {
+ if (!usedAttributes.contains(it.key()))
+ example->doc().location().warning(example->name() + ": " + it.value());
+ }
+}
+
+/*!
+ \internal
+
+ Write the description element. The description for an example is set
+ with the \brief command. If no brief is available, the element is set
+ to "No description available".
+ */
+
+void writeDescription(QXmlStreamWriter *writer, const ExampleNode *example)
+{
+ Q_ASSERT(writer && example);
+ writer->writeStartElement("description");
+ const Text brief = example->doc().briefText();
+ if (!brief.isEmpty())
+ writer->writeCDATA(brief.toString());
+ else
+ writer->writeCDATA(QString("No description available"));
+ writer->writeEndElement(); // description
+}
+
+/*!
+ \internal
+
+ Returns a list of \a files that Qt Creator should open for the \a exampleName.
+ */
+QMap<int, QString> getFilesToOpen(const QStringList &files, const QString &exampleName)
+{
+ QMap<int, QString> filesToOpen;
+ for (const QString &file : files) {
+ QFileInfo fileInfo(file);
+ QString fileName = fileInfo.fileName().toLower();
+ // open .qml, .cpp and .h files with a
+ // basename matching the example (project) name
+ // QMap key indicates the priority -
+ // the lowest value will be the top-most file
+ if ((fileInfo.baseName().compare(exampleName, Qt::CaseInsensitive) == 0)) {
+ if (fileName.endsWith(".qml"))
+ filesToOpen.insert(0, file);
+ else if (fileName.endsWith(".cpp"))
+ filesToOpen.insert(1, file);
+ else if (fileName.endsWith(".h"))
+ filesToOpen.insert(2, file);
+ }
+ // main.qml takes precedence over main.cpp
+ else if (fileName.endsWith("main.qml")) {
+ filesToOpen.insert(3, file);
+ } else if (fileName.endsWith("main.cpp")) {
+ filesToOpen.insert(4, file);
+ }
+ }
+
+ return filesToOpen;
+}
+
+/*!
+ \internal
+ \brief Writes the lists of files to open for the example.
+
+ Writes out the \a filesToOpen and the full \a installPath through \a writer.
+ */
+void writeFilesToOpen(QXmlStreamWriter &writer, const QString &installPath,
+ const QMap<int, QString> &filesToOpen)
+{
+ for (auto it = filesToOpen.constEnd(); it != filesToOpen.constBegin();) {
+ writer.writeStartElement("fileToOpen");
+ if (--it == filesToOpen.constBegin()) {
+ writer.writeAttribute(QStringLiteral("mainFile"), QStringLiteral("true"));
+ }
+ writer.writeCharacters(installPath + it.value());
+ writer.writeEndElement();
+ }
+}
+
+/*!
+ \internal
+ \brief Writes example metadata into \a writer.
+
+ For instance,
+
+
+ \ meta category {Application Example}
+
+ becomes
+
+ <meta>
+ <entry name="category">Application Example</entry>
+ <meta>
+*/
+static void writeMetaInformation(QXmlStreamWriter &writer, const QStringMultiMap &map)
+{
+ if (map.isEmpty())
+ return;
+
+ writer.writeStartElement("meta");
+ for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
+ writer.writeStartElement("entry");
+ writer.writeAttribute(QStringLiteral("name"), it.key());
+ writer.writeCharacters(it.value());
+ writer.writeEndElement(); // tag
+ }
+ writer.writeEndElement(); // meta
+}
+
+/*!
+ \class ManifestWriter
+ \internal
+ \brief The ManifestWriter is responsible for writing manifest files.
+ */
+ManifestWriter::ManifestWriter()
+{
+ Config &config = Config::instance();
+ m_project = config.get(CONFIG_PROJECT).asString();
+ m_outputDirectory = config.getOutputDir();
+ m_qdb = QDocDatabase::qdocDB();
+
+ const QString prefix = CONFIG_QHP + Config::dot + m_project + Config::dot;
+ m_manifestDir =
+ QLatin1String("qthelp://") + config.get(prefix + QLatin1String("namespace")).asString();
+ m_manifestDir +=
+ QLatin1Char('/') + config.get(prefix + QLatin1String("virtualFolder")).asString()
+ + QLatin1Char('/');
+ readManifestMetaContent();
+ m_examplesPath = config.get(CONFIG_EXAMPLESINSTALLPATH).asString();
+ if (!m_examplesPath.isEmpty())
+ m_examplesPath += QLatin1Char('/');
+}
+
+template <typename F>
+void ManifestWriter::processManifestMetaContent(const QString &fullName, F matchFunc)
+{
+ for (const auto &index : m_manifestMetaContent) {
+ const auto &names = index.m_names;
+ for (const QString &name : names) {
+ bool match;
+ qsizetype wildcard = name.indexOf(QChar('*'));
+ switch (wildcard) {
+ case -1: // no wildcard used, exact match required
+ match = (fullName == name);
+ break;
+ case 0: // '*' matches all examples
+ match = true;
+ break;
+ default: // match with wildcard at the end
+ match = fullName.startsWith(name.left(wildcard));
+ }
+ if (match)
+ matchFunc(index);
+ }
+ }
+}
+
+/*!
+ This function outputs one or more manifest files in XML.
+ They are used by Creator.
+ */
+void ManifestWriter::generateManifestFiles()
+{
+ generateExampleManifestFile();
+ m_qdb->exampleNodeMap().clear();
+ m_manifestMetaContent.clear();
+}
+
+/*
+ Returns Qt module name as lower case tag, stripping Qt prefix:
+ QtQuickControls -> quickcontrols
+ QtOpenGL -> opengl
+ QtQuick3D -> quick3d
+ */
+static QString moduleNameAsTag(const QString &module)
+{
+ QString moduleName = module;
+ if (moduleName.startsWith("Qt"))
+ moduleName = moduleName.mid(2);
+ // Some examples are in QtDoc module, but 'doc' as tag makes little sense
+ if (moduleName == "Doc")
+ return QString();
+ return moduleName.toLower();
+}
+
+/*
+ Return tags that were added with
+ \ meta {tag} {tag1[,tag2,...]}
+ or
+ \ meta {tags} {tag1[,tag2,...]}
+ from example metadata
+ */
+static QSet<QString> tagsAddedWithMetaCommand(const ExampleNode *example)
+{
+ Q_ASSERT(example);
+
+ QSet<QString> tags;
+ const QStringMultiMap *metaTagMap = example->doc().metaTagMap();
+ if (metaTagMap) {
+ QStringList originalTags = metaTagMap->values("tag");
+ originalTags << metaTagMap->values("tags");
+ for (const auto &tag : originalTags) {
+ const auto &tagList = tag.toLower().split(QLatin1Char(','), Qt::SkipEmptyParts);
+ tags += QSet<QString>(tagList.constBegin(), tagList.constEnd());
+ }
+ }
+ return tags;
+}
+
+/*
+ Writes the contents of tags into writer, formatted as
+ <tags>tag1,tag2..</tags>
+ */
+static void writeTagsElement(QXmlStreamWriter *writer, const QSet<QString> &tags)
+{
+ Q_ASSERT(writer);
+ if (tags.isEmpty())
+ return;
+
+ writer->writeStartElement("tags");
+ QStringList sortedTags = tags.values();
+ sortedTags.sort();
+ writer->writeCharacters(sortedTags.join(","));
+ writer->writeEndElement(); // tags
+}
+
+/*!
+ This function is called by generateExampleManifestFiles(), once
+ for each manifest file to be generated.
+ */
+void ManifestWriter::generateExampleManifestFile()
+{
+ const ExampleNodeMap &exampleNodeMap = m_qdb->exampleNodeMap();
+ if (exampleNodeMap.isEmpty())
+ return;
+
+ const QString outputFileName = "examples-manifest.xml";
+ QFile outputFile(m_outputDirectory + QLatin1Char('/') + outputFileName);
+ if (!outputFile.open(QFile::WriteOnly | QFile::Text))
+ return;
+
+ QXmlStreamWriter writer(&outputFile);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument();
+ writer.writeStartElement("instructionals");
+ writer.writeAttribute("module", m_project);
+ writer.writeStartElement("examples");
+
+ for (const auto &example : exampleNodeMap.values()) {
+ QMap<QString, QString> usedAttributes;
+ QSet<QString> tags;
+ const QString installPath = retrieveExampleInstallationPath(example);
+ const QString fullName = m_project + QLatin1Char('/') + example->title();
+
+ processManifestMetaContent(
+ fullName, [&](const ManifestMetaFilter &filter) { tags += filter.m_tags; });
+ tags += tagsAddedWithMetaCommand(example);
+ // omit from the manifest if explicitly marked broken
+ if (tags.contains("broken"))
+ continue;
+
+ // attributes that are always written for the element
+ usedAttributes.insert("name", example->title());
+ usedAttributes.insert("docUrl", m_manifestDir + Generator::currentGenerator()->fileBase(example) + ".html");
+
+ if (!example->projectFile().isEmpty())
+ usedAttributes.insert("projectPath", installPath + example->projectFile());
+ if (!example->imageFileName().isEmpty())
+ usedAttributes.insert("imageUrl", m_manifestDir + example->imageFileName());
+
+ processManifestMetaContent(fullName, [&](const ManifestMetaFilter &filter) {
+ const auto attributes = filter.m_attributes;
+ for (const auto &attribute : attributes) {
+ const QLatin1Char div(':');
+ QStringList attrList = attribute.split(div);
+ if (attrList.size() == 1)
+ attrList.append(QStringLiteral("true"));
+ QString attrName = attrList.takeFirst();
+ if (!usedAttributes.contains(attrName))
+ usedAttributes.insert(attrName, attrList.join(div));
+ }
+ });
+
+ writer.writeStartElement("example");
+ for (auto it = usedAttributes.cbegin(); it != usedAttributes.cend(); ++it)
+ writer.writeAttribute(it.key(), it.value());
+
+ warnAboutUnusedAttributes(usedAttributes.keys(), example);
+ writeDescription(&writer, example);
+
+ const QString moduleNameTag = moduleNameAsTag(m_project);
+ if (!moduleNameTag.isEmpty())
+ tags << moduleNameTag;
+ writeTagsElement(&writer, tags);
+
+ const QString exampleName = example->name().mid(example->name().lastIndexOf('/') + 1);
+ const auto files = example->files();
+ const QMap<int, QString> filesToOpen = getFilesToOpen(files, exampleName);
+ writeFilesToOpen(writer, installPath, filesToOpen);
+
+ if (const QStringMultiMap *metaTagMapP = example->doc().metaTagMap()) {
+ // Write \meta elements into the XML, except for 'tag', 'installpath',
+ // as they are handled separately
+ QStringMultiMap map = *metaTagMapP;
+ erase_if(map, [](QStringMultiMap::iterator iter) {
+ return iter.key() == "tag" || iter.key() == "tags" || iter.key() == "installpath";
+ });
+ writeMetaInformation(writer, map);
+ }
+
+ writer.writeEndElement(); // example
+ }
+
+ writer.writeEndElement(); // examples
+
+ if (!m_exampleCategories.isEmpty()) {
+ writer.writeStartElement("categories");
+ for (const auto &examplecategory : m_exampleCategories) {
+ writer.writeStartElement("category");
+ writer.writeCharacters(examplecategory);
+ writer.writeEndElement();
+ }
+ writer.writeEndElement(); // categories
+ }
+
+ writer.writeEndElement(); // instructionals
+ writer.writeEndDocument();
+ outputFile.close();
+}
+
+/*!
+ Reads metacontent - additional attributes and tags to apply
+ when generating manifest files, read from config.
+
+ The manifest metacontent map is cleared immediately after
+ the manifest files have been generated.
+ */
+void ManifestWriter::readManifestMetaContent()
+{
+ Config &config = Config::instance();
+ const QStringList names{config.get(CONFIG_MANIFESTMETA
+ + Config::dot
+ + QStringLiteral("filters")).asStringList()};
+
+ for (const auto &manifest : names) {
+ ManifestMetaFilter filter;
+ QString prefix = CONFIG_MANIFESTMETA + Config::dot + manifest + Config::dot;
+ filter.m_names = config.get(prefix + QStringLiteral("names")).asStringSet();
+ filter.m_attributes = config.get(prefix + QStringLiteral("attributes")).asStringSet();
+ filter.m_tags = config.get(prefix + QStringLiteral("tags")).asStringSet();
+ m_manifestMetaContent.append(filter);
+ }
+
+ m_exampleCategories = config.get(CONFIG_MANIFESTMETA
+ + QStringLiteral(".examplecategories")).asStringList();
+}
+
+/*!
+ Retrieve the install path for the \a example as specified with
+ the \\meta command, or fall back to the one defined in .qdocconf.
+ */
+QString ManifestWriter::retrieveExampleInstallationPath(const ExampleNode *example) const
+{
+ QString installPath;
+ if (example->doc().metaTagMap())
+ installPath = example->doc().metaTagMap()->value(QLatin1String("installpath"));
+ if (installPath.isEmpty())
+ installPath = m_examplesPath;
+ if (!installPath.isEmpty() && !installPath.endsWith(QLatin1Char('/')))
+ installPath += QLatin1Char('/');
+
+ return installPath;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/manifestwriter.h b/src/qdoc/qdoc/src/qdoc/manifestwriter.h
new file mode 100644
index 000000000..730835b9e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/manifestwriter.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef MANIFESTWRITER_H
+#define MANIFESTWRITER_H
+
+#include <QtCore/qlist.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class ExampleNode;
+class QDocDatabase;
+class QXmlStreamWriter;
+class ManifestWriter
+{
+ struct ManifestMetaFilter
+ {
+ QSet<QString> m_names {};
+ QSet<QString> m_attributes {};
+ QSet<QString> m_tags {};
+ };
+
+public:
+ ManifestWriter();
+ void generateManifestFiles();
+ void generateExampleManifestFile();
+ void readManifestMetaContent();
+ QString retrieveExampleInstallationPath(const ExampleNode *example) const;
+
+private:
+ QString m_manifestDir {};
+ QString m_examplesPath {};
+ QString m_outputDirectory {};
+ QString m_project {};
+ QDocDatabase *m_qdb { nullptr };
+ QList<ManifestMetaFilter> m_manifestMetaContent {};
+ QStringList m_exampleCategories {};
+
+ template <typename F>
+ void processManifestMetaContent(const QString &fullName, F matchFunc);
+};
+
+QT_END_NAMESPACE
+
+#endif // MANIFESTWRITER_H
diff --git a/src/qdoc/qdoc/src/qdoc/namespacenode.cpp b/src/qdoc/qdoc/src/qdoc/namespacenode.cpp
new file mode 100644
index 000000000..22686c050
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/namespacenode.cpp
@@ -0,0 +1,190 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "namespacenode.h"
+
+#include "codeparser.h"
+#include "tree.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class NamespaceNode
+ \brief This class represents a C++ namespace.
+
+ A namespace can be used in multiple C++ modules, so there
+ can be a NamespaceNode for namespace Xxx in more than one
+ Node tree.
+ */
+
+/*! \fn NamespaceNode(Aggregate *parent, const QString &name)
+ Constructs a NamespaceNode with the specified \a parent and \a name.
+ The node type is Node::Namespace.
+ */
+
+/*!
+ Returns true if this namespace is to be documented in the
+ current module. There can be elements declared in this
+ namespace spread over multiple modules. Those elements are
+ documented in the modules where they are declared, but they
+ are linked to from the namespace page in the module where
+ the namespace itself is documented.
+ */
+bool NamespaceNode::isDocumentedHere() const
+{
+ return m_whereDocumented == tree()->camelCaseModuleName();
+}
+
+/*!
+ Returns true if this namespace node contains at least one
+ child that has documentation and is not private or internal.
+ */
+bool NamespaceNode::hasDocumentedChildren() const
+{
+ return std::any_of(m_children.cbegin(), m_children.cend(),
+ [](Node *child) { return child->isInAPI(); });
+}
+
+/*!
+ Report qdoc warning for each documented child in a namespace
+ that is not documented. This function should only be called
+ when the namespace is not documented.
+ */
+void NamespaceNode::reportDocumentedChildrenInUndocumentedNamespace() const
+{
+ for (const auto *node : std::as_const(m_children)) {
+ if (node->isInAPI()) {
+ QString msg1 = node->name();
+ if (node->isFunction())
+ msg1 += "()";
+ msg1 += QStringLiteral(
+ " is documented, but namespace %1 is not documented in any module.")
+ .arg(name());
+ QString msg2 =
+ QStringLiteral(
+ "Add /*! '\\%1 %2' ... */ or remove the qdoc comment marker (!) at "
+ "that line number.")
+ .arg(COMMAND_NAMESPACE, name());
+
+ node->doc().location().warning(msg1, msg2);
+ }
+ }
+}
+
+/*!
+ Returns true if this namespace node is not private and
+ contains at least one public child node with documentation.
+ */
+bool NamespaceNode::docMustBeGenerated() const
+{
+ if (isInAPI())
+ return true;
+ return hasDocumentedChildren();
+}
+
+/*!
+ Returns a const reference to the namespace node's list of
+ included children, which contains pointers to all the child
+ nodes of other namespace nodes that have the same name as
+ this namespace node. The list is built after the prepare
+ phase has been run but just before the generate phase. It
+ is buils by QDocDatabase::resolveNamespaces().
+
+ \sa QDocDatabase::resolveNamespaces()
+ */
+const NodeList &NamespaceNode::includedChildren() const
+{
+ return m_includedChildren;
+}
+
+/*!
+ This function is only called from QDocDatabase::resolveNamespaces().
+
+ \sa includedChildren(), QDocDatabase::resolveNamespaces()
+ */
+void NamespaceNode::includeChild(Node *child)
+{
+ m_includedChildren.append(child);
+}
+
+/*! \fn Tree* NamespaceNode::tree() const
+ Returns a pointer to the Tree that contains this NamespaceNode.
+ This requires traversing the parent() pointers to the root of
+ the Tree, which is the unnamed NamespaceNode.
+ */
+
+/*! \fn bool NamespaceNode::isFirstClassAggregate() const
+ Returns \c true.
+ */
+
+/*! \fn bool NamespaceNode::isRelatableType() const
+ Returns \c true.
+ */
+
+/*! \fn bool NamespaceNode::wasSeen() const
+ Returns \c true if the \c {\\namespace} command that this NamespaceNode
+ represents has been parsed by qdoc. When \c false is returned, it means
+ that only \c {\\relates} commands have been seen that relate elements to
+ this namespace.
+ */
+
+/*! \fn void NamespaceNode::markSeen()
+ Sets the data member that indicates that the \c {\\namespace} command this
+ NamespaceNode represents has been parsed by qdoc.
+ */
+
+/*! \fn void NamespaceNode::markNotSeen()
+ Clears the data member that indicates that the \c {\\namespace} command this
+ NamespaceNode represents has been parsed by qdoc.
+ */
+
+/*! \fn void NamespaceNode::setTree(Tree* t)
+ Sets the Tree pointer to \a t, which means this NamespaceNode is in the Tree \a t.
+ */
+
+/*! \fn QString NamespaceNode::whereDocumented() const
+ Returns the camel case name of the module where this namespace is documented.
+
+ \sa setWhereDocumented()
+ */
+
+/*! \fn void NamespaceNode::setWhereDocumented(const QString &t)
+ Sets the camel case name of the module where this namespace is documented to
+ the module named \a t.
+
+ This function is called when the \c {\\namespace} command is processed to let
+ qdoc know that this namespace is documented in the current module, so that
+ when something in another module is marked as related to this namespace, it
+ can be documented there with a ProxyNode for this namespace.
+
+ \sa whereDocumented()
+ */
+
+/*! \fn void NamespaceNode::setDocumented()
+ Sets the flag indicating that the \c {\\namespace} command for this
+ namespace was seen.
+ */
+
+/*! \fn bool NamespaceNode::wasDocumented() const
+ Returns \c true if a \c {\\namespace} command for this namespace was seen.
+ Otherwise returns \c false.
+ */
+
+/*! \fn void NamespaceNode::setDocNode(NamespaceNode *ns)
+ Called in QDocDatabase::resolveNamespaces() to set the pointer to the
+ NamespaceNode in which this namespace is documented.
+
+ \sa QDocDatabase::resolveNamespaces()
+ */
+
+/*! \fn NamespaceNode *NamespaceNode::docNode() const
+ Returns a pointer to the NamespaceNode that represents where the namespace
+ documentation is actually generated. API elements in many different modules
+ can be included in a single namespace. That namespace is only documented in
+ one module. The namespace is documented in the module where the \c {\\namespace}
+ command for the namespace appears.
+
+ \sa QDocDatabase::resolveNamespaces()
+ */
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/namespacenode.h b/src/qdoc/qdoc/src/qdoc/namespacenode.h
new file mode 100644
index 000000000..80d068838
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/namespacenode.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef NAMESPACENODE_H
+#define NAMESPACENODE_H
+
+#include "aggregate.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class Tree;
+
+class NamespaceNode : public Aggregate
+{
+public:
+ NamespaceNode(Aggregate *parent, const QString &name) : Aggregate(Namespace, parent, name) {}
+ ~NamespaceNode() override = default;
+ [[nodiscard]] Tree *tree() const override { return (parent() ? parent()->tree() : m_tree); }
+
+ [[nodiscard]] bool isFirstClassAggregate() const override { return true; }
+ [[nodiscard]] bool isRelatableType() const override { return true; }
+ [[nodiscard]] bool wasSeen() const override { return m_seen; }
+ void markSeen() { m_seen = true; }
+ void setTree(Tree *t) { m_tree = t; }
+ [[nodiscard]] const NodeList &includedChildren() const;
+ void includeChild(Node *child);
+ void setWhereDocumented(const QString &t) { m_whereDocumented = t; }
+ [[nodiscard]] bool isDocumentedHere() const;
+ [[nodiscard]] bool hasDocumentedChildren() const;
+ void reportDocumentedChildrenInUndocumentedNamespace() const;
+ [[nodiscard]] bool docMustBeGenerated() const override;
+ void setDocNode(NamespaceNode *ns) { m_docNode = ns; }
+ [[nodiscard]] NamespaceNode *docNode() const { return m_docNode; }
+
+private:
+ bool m_seen { false };
+ Tree *m_tree { nullptr };
+ QString m_whereDocumented {};
+ NamespaceNode *m_docNode { nullptr };
+ NodeList m_includedChildren {};
+};
+
+QT_END_NAMESPACE
+
+#endif // NAMESPACENODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/node.cpp b/src/qdoc/qdoc/src/qdoc/node.cpp
new file mode 100644
index 000000000..2857aecba
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/node.cpp
@@ -0,0 +1,1412 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "node.h"
+
+#include "aggregate.h"
+#include "codemarker.h"
+#include "config.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "generator.h"
+#include "qdocdatabase.h"
+#include "qmltypenode.h"
+#include "qmlpropertynode.h"
+#include "relatedclass.h"
+#include "sharedcommentnode.h"
+#include "tokenizer.h"
+#include "tree.h"
+
+#include <QtCore/quuid.h>
+#include <QtCore/qversionnumber.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+/*!
+ \class Node
+ \brief The Node class is the base class for all the nodes in QDoc's parse tree.
+
+ Class Node is the base class of all the node subclasses. There is a subclass of Node
+ for each type of entity that QDoc can document. The types of entities that QDoc can
+ document are listed in the enum type NodeType.
+
+ After ClangCodeParser has parsed all the header files to build its precompiled header,
+ it then visits the clang Abstract Syntax Tree (AST). For each node in the AST that it
+ determines is in the public API and must be documented, it creates an instance of one
+ of the Node subclasses and adds it to the QDoc Tree.
+
+ Each instance of a sublass of Node has a parent pointer to link it into the Tree. The
+ parent pointer is obtained by calling \l {parent()}, which returns a pointer to an
+ instance of the Node subclass, Aggregate, which is never instantiated directly, but
+ as the base class for certain subclasses of Node that can have children. For example,
+ ClassNode and QmlTypeNode can have children, so they both inherit Aggregate, while
+ PropertyNode and QmlPropertyNode can not have children, so they both inherit Node.
+
+ \sa Aggregate, ClassNode, PropertyNode
+ */
+
+/*!
+ Returns \c true if the node \a n1 is less than node \a n2. The
+ comparison is performed by comparing properties of the nodes
+ in order of increasing complexity.
+ */
+bool Node::nodeNameLessThan(const Node *n1, const Node *n2)
+{
+#define LT_RETURN_IF_NOT_EQUAL(a, b) \
+ if ((a) != (b)) \
+ return (a) < (b);
+
+ if (n1->isPageNode() && n2->isPageNode()) {
+ LT_RETURN_IF_NOT_EQUAL(n1->fullName(), n2->fullName());
+ LT_RETURN_IF_NOT_EQUAL(n1->fullTitle(), n2->fullTitle());
+ }
+
+ if (n1->isFunction() && n2->isFunction()) {
+ const auto *f1 = static_cast<const FunctionNode *>(n1);
+ const auto *f2 = static_cast<const FunctionNode *>(n2);
+
+ LT_RETURN_IF_NOT_EQUAL(f1->isConst(), f2->isConst());
+ LT_RETURN_IF_NOT_EQUAL(f1->signature(Node::SignatureReturnType),
+ f2->signature(Node::SignatureReturnType));
+ }
+
+ LT_RETURN_IF_NOT_EQUAL(n1->nodeType(), n2->nodeType());
+ LT_RETURN_IF_NOT_EQUAL(n1->name(), n2->name());
+ LT_RETURN_IF_NOT_EQUAL(n1->access(), n2->access());
+ LT_RETURN_IF_NOT_EQUAL(n1->location().filePath(), n2->location().filePath());
+
+ return false;
+}
+
+
+/*!
+ Returns \c true if node \a n1 is less than node \a n2 when comparing
+ the sort keys, defined with
+
+ \badcode
+ \meta sortkey {<value>}
+ \endcode
+
+ in the respective nodes' documentation. If the two sort keys are equal,
+ falls back to nodeNameLessThan(). If \a n1 defines a sort key and \a n2
+ does not, then n1 < n2.
+
+*/
+bool Node::nodeSortKeyOrNameLessThan(const Node *n1, const Node *n2)
+{
+ const QString default_sortkey{QChar{QChar::LastValidCodePoint}};
+ const auto *n1_metamap{n1->doc().metaTagMap()};
+ const auto *n2_metamap{n2->doc().metaTagMap()};
+ if (auto cmp = QString::compare(
+ n1_metamap ? n1_metamap->value(u"sortkey"_s, default_sortkey) : default_sortkey,
+ n2_metamap ? n2_metamap->value(u"sortkey"_s, default_sortkey) : default_sortkey); cmp != 0) {
+ return cmp < 0;
+ }
+ return nodeNameLessThan(n1, n2);
+}
+
+/*!
+ \enum Node::NodeType
+
+ An unsigned char value that identifies an object as a
+ particular subclass of Node.
+
+ \value NoType The node type has not been set yet.
+ \value Namespace The Node subclass is NamespaceNode, which represents a C++
+ namespace.
+ \value Class The Node subclass is ClassNode, which represents a C++ class.
+ \value Struct The Node subclass is ClassNode, which represents a C struct.
+ \value Union The Node subclass is ClassNode, which represents a C union
+ (currently no known uses).
+ \value HeaderFile The Node subclass is HeaderNode, which represents a header
+ file.
+ \value Page The Node subclass is PageNode, which represents a text page from
+ a .qdoc file.
+ \value Enum The Node subclass is EnumNode, which represents an enum type or
+ enum class.
+ \value Example The Node subclass is ExampleNode, which represents an example
+ subdirectory.
+ \value ExternalPage The Node subclass is ExternalPageNode, which is for
+ linking to an external page.
+ \value Function The Node subclass is FunctionNode, which can represent C++,
+ and QML functions.
+ \value Typedef The Node subclass is TypedefNode, which represents a C++
+ typedef.
+ \value Property The Node subclass is PropertyNode, which represents a use of
+ Q_Property.
+ \value Variable The Node subclass is VariableNode, which represents a global
+ variable or class data member.
+ \value Group The Node subclass is CollectionNode, which represents a group of
+ documents.
+ \value Module The Node subclass is CollectionNode, which represents a C++
+ module.
+ \value QmlType The Node subclass is QmlTypeNode, which represents a QML type.
+ \value QmlModule The Node subclass is CollectionNode, which represents a QML
+ module.
+ \value QmlProperty The Node subclass is QmlPropertyNode, which represents a
+ property in a QML type.
+ \value QmlBasicType The Node subclass is QmlTypeNode, which represents a
+ value type like int, etc.
+ \value SharedComment The Node subclass is SharedCommentNode, which represents
+ a collection of nodes that share the same documentation comment.
+ \omitvalue Collection
+ \value Proxy The Node subclass is ProxyNode, which represents one or more
+ entities that are documented in the current module but which actually
+ reside in a different module.
+ \omitvalue LastType
+*/
+
+/*!
+ \enum Node::Genus
+
+ An unsigned char value that specifies whether the Node represents a
+ C++ element, a QML element, or a text document.
+ The Genus values are also passed to search functions to specify the
+ Genus of Tree Node that can satisfy the search.
+
+ \value DontCare The Genus is not specified. Used when calling Tree search functions to indicate
+ the search can accept any Genus of Node.
+ \value CPP The Node represents a C++ element.
+ \value QML The Node represents a QML element.
+ \value DOC The Node represents a text document.
+*/
+
+/*!
+ \internal
+ \fn setComparisonCategory(const ComparisonCategory category)
+
+ Sets the comparison category of this node to \a category.
+
+ \sa ComparisonCategory, comparisonCategory()
+ */
+
+/*!
+ \internal
+ \fn ComparisonCategory comparisonCategory()
+
+ Returns the comparison category of this node.
+
+ \sa ComparisonCategory, setComparisonCategory()
+ */
+
+/*!
+ \enum Access
+
+ An unsigned char value that indicates the C++ access level.
+
+ \value Public The element has public access.
+ \value Protected The element has protected access.
+ \value Private The element has private access.
+*/
+
+/*!
+ \enum Node::Status
+
+ An unsigned char that specifies the status of the documentation element in
+ the documentation set.
+
+ \value Deprecated The element has been deprecated.
+ \value Preliminary The element is new; the documentation is preliminary.
+ \value Active The element is current.
+ \value Internal The element is for internal use only, not to be published.
+ \value DontDocument The element is not to be documented.
+*/
+
+/*!
+ \enum Node::ThreadSafeness
+
+ An unsigned char that specifies the degree of thread-safeness of the element.
+
+ \value UnspecifiedSafeness The thread-safeness is not specified.
+ \value NonReentrant The element is not reentrant.
+ \value Reentrant The element is reentrant.
+ \value ThreadSafe The element is threadsafe.
+*/
+
+/*!
+ \enum Node::LinkType
+
+ An unsigned char value that probably should be moved out of the Node base class.
+
+ \value StartLink
+ \value NextLink
+ \value PreviousLink
+ \value ContentsLink
+ */
+
+/*!
+ \enum Node::FlagValue
+
+ A value used in PropertyNode and QmlPropertyNode that can be -1, 0, or +1.
+ Properties and QML properties have flags, which can be 0 or 1, false or true,
+ or not set. FlagValueDefault is the not set value. In other words, if a flag
+ is set to FlagValueDefault, the meaning is the flag has not been set.
+
+ \value FlagValueDefault -1 Not set.
+ \value FlagValueFalse 0 False.
+ \value FlagValueTrue 1 True.
+*/
+
+/*!
+ \fn Node::~Node()
+
+ The default destructor is virtual so any subclass of Node can be
+ deleted by deleting a pointer to Node.
+ */
+
+/*! \fn bool Node::isActive() const
+ Returns true if this node's status is \c Active.
+ */
+
+/*! \fn bool Node::isClass() const
+ Returns true if the node type is \c Class.
+ */
+
+/*! \fn bool Node::isCppNode() const
+ Returns true if this node's Genus value is \c CPP.
+ */
+
+/*! \fn bool Node::isDeprecated() const
+ Returns true if this node's status is \c Deprecated.
+ */
+
+/*! \fn bool Node::isDontDocument() const
+ Returns true if this node's status is \c DontDocument.
+ */
+
+/*! \fn bool Node::isEnumType() const
+ Returns true if the node type is \c Enum.
+ */
+
+/*! \fn bool Node::isExample() const
+ Returns true if the node type is \c Example.
+ */
+
+/*! \fn bool Node::isExternalPage() const
+ Returns true if the node type is \c ExternalPage.
+ */
+
+/*! \fn bool Node::isFunction(Genus g = DontCare) const
+ Returns true if this is a FunctionNode and its Genus is set to \a g.
+ */
+
+/*! \fn bool Node::isGroup() const
+ Returns true if the node type is \c Group.
+ */
+
+/*! \fn bool Node::isHeader() const
+ Returns true if the node type is \c HeaderFile.
+ */
+
+/*! \fn bool Node::isIndexNode() const
+ Returns true if this node was created from something in an index file.
+ */
+
+/*! \fn bool Node::isModule() const
+ Returns true if the node type is \c Module.
+ */
+
+/*! \fn bool Node::isNamespace() const
+ Returns true if the node type is \c Namespace.
+ */
+
+/*! \fn bool Node::isPage() const
+ Returns true if the node type is \c Page.
+ */
+
+/*! \fn bool Node::isPreliminary() const
+ Returns true if this node's status is \c Preliminary.
+ */
+
+/*! \fn bool Node::isPrivate() const
+ Returns true if this node's access is \c Private.
+ */
+
+/*! \fn bool Node::isProperty() const
+ Returns true if the node type is \c Property.
+ */
+
+/*! \fn bool Node::isProxyNode() const
+ Returns true if the node type is \c Proxy.
+ */
+
+/*! \fn bool Node::isPublic() const
+ Returns true if this node's access is \c Public.
+ */
+
+/*! \fn bool Node::isProtected() const
+ Returns true if this node's access is \c Protected.
+ */
+
+/*! \fn bool Node::isQmlBasicType() const
+ Returns true if the node type is \c QmlBasicType.
+ */
+
+/*! \fn bool Node::isQmlModule() const
+ Returns true if the node type is \c QmlModule.
+ */
+
+/*! \fn bool Node::isQmlNode() const
+ Returns true if this node's Genus value is \c QML.
+ */
+
+/*! \fn bool Node::isQmlProperty() const
+ Returns true if the node type is \c QmlProperty.
+ */
+
+/*! \fn bool Node::isQmlType() const
+ Returns true if the node type is \c QmlType or \c QmlValueType.
+ */
+
+/*! \fn bool Node::isRelatedNonmember() const
+ Returns true if this is a related nonmember of something.
+ */
+
+/*! \fn bool Node::isStruct() const
+ Returns true if the node type is \c Struct.
+ */
+
+/*! \fn bool Node::isSharedCommentNode() const
+ Returns true if the node type is \c SharedComment.
+ */
+
+/*! \fn bool Node::isTypeAlias() const
+ Returns true if the node type is \c Typedef.
+ */
+
+/*! \fn bool Node::isTypedef() const
+ Returns true if the node type is \c Typedef.
+ */
+
+/*! \fn bool Node::isUnion() const
+ Returns true if the node type is \c Union.
+ */
+
+/*! \fn bool Node::isVariable() const
+ Returns true if the node type is \c Variable.
+ */
+
+/*! \fn bool Node::isGenericCollection() const
+ Returns true if the node type is \c Collection.
+ */
+
+/*! \fn bool Node::isAbstract() const
+ Returns true if the ClassNode or QmlTypeNode is marked abstract.
+*/
+
+/*! \fn bool Node::isAggregate() const
+ Returns true if this node is an aggregate, which means it
+ inherits Aggregate and can therefore have children.
+*/
+
+/*! \fn bool Node::isFirstClassAggregate() const
+ Returns true if this Node is an Aggregate but not a ProxyNode.
+*/
+
+/*! \fn bool Node::isAlias() const
+ Returns true if this QML property is marked as an alias.
+*/
+
+/*! \fn bool Node::isAttached() const
+ Returns true if the QML property or QML method node is marked as attached.
+*/
+
+/*! \fn bool Node::isClassNode() const
+ Returns true if this is an instance of ClassNode.
+*/
+
+/*! \fn bool Node::isCollectionNode() const
+ Returns true if this is an instance of CollectionNode.
+*/
+
+/*! \fn bool Node::isDefault() const
+ Returns true if the QML property node is marked as default.
+*/
+
+/*! \fn bool Node::isMacro() const
+ returns true if either FunctionNode::isMacroWithParams() or
+ FunctionNode::isMacroWithoutParams() returns true.
+*/
+
+/*! \fn bool Node::isPageNode() const
+ Returns true if this node represents something that generates a documentation
+ page. In other words, if this Node's subclass inherits PageNode, then this
+ function will return \e true.
+*/
+
+/*! \fn bool Node::isRelatableType() const
+ Returns true if this node is something you can relate things to with
+ the \e relates command. NamespaceNode, ClassNode, HeaderNode, and
+ ProxyNode are relatable types.
+*/
+
+/*! \fn bool Node::isMarkedReimp() const
+ Returns true if the FunctionNode is marked as a reimplemented function.
+ That means it is virtual in the base class and it is reimplemented in
+ the subclass.
+*/
+
+/*! \fn bool Node::isPropertyGroup() const
+ Returns true if the node is a SharedCommentNode for documenting
+ multiple C++ properties or multiple QML properties.
+*/
+
+/*! \fn bool Node::isStatic() const
+ Returns true if the FunctionNode represents a static function.
+*/
+
+/*! \fn bool Node::isTextPageNode() const
+ Returns true if the node is a PageNode but not an Aggregate.
+*/
+
+/*!
+ Returns this node's name member. Appends "()" to the returned
+ name if this node is a function node, but not if it is a macro
+ because macro names normally appear without parentheses.
+ */
+QString Node::plainName() const
+{
+ if (isFunction() && !isMacro())
+ return m_name + QLatin1String("()");
+ return m_name;
+}
+
+/*!
+ Constructs and returns the node's fully qualified name by
+ recursively ascending the parent links and prepending each
+ parent name + "::". Breaks out when reaching a HeaderNode,
+ or when the parent pointer is \a relative. Typically, calls
+ to this function pass \c nullptr for \a relative.
+ */
+QString Node::plainFullName(const Node *relative) const
+{
+ if (m_name.isEmpty())
+ return QLatin1String("global");
+ if (isHeader())
+ return plainName();
+
+ QStringList parts;
+ const Node *node = this;
+ while (node && !node->isHeader()) {
+ parts.prepend(node->plainName());
+ if (node->parent() == relative || node->parent()->name().isEmpty())
+ break;
+ node = node->parent();
+ }
+ return parts.join(QLatin1String("::"));
+}
+
+/*!
+ Constructs and returns the node's fully qualified signature
+ by recursively ascending the parent links and prepending each
+ parent name + "::" to the plain signature. The return type is
+ not included.
+ */
+QString Node::plainSignature() const
+{
+ if (m_name.isEmpty())
+ return QLatin1String("global");
+
+ QString fullName;
+ const Node *node = this;
+ while (node) {
+ fullName.prepend(node->signature(Node::SignaturePlain));
+ if (node->parent()->name().isEmpty())
+ break;
+ fullName.prepend(QLatin1String("::"));
+ node = node->parent();
+ }
+ return fullName;
+}
+
+/*!
+ Constructs and returns this node's full name. The full name is
+ often just the title(). When it is not the title, it is the
+ plainFullName().
+ */
+QString Node::fullName(const Node *relative) const
+{
+ if ((isTextPageNode() || isGroup()) && !title().isEmpty())
+ return title();
+ return plainFullName(relative);
+}
+
+/*!
+ Sets this Node's Doc to \a doc. If \a replace is false and
+ this Node already has a Doc, and if this doc is not marked
+ with the \\reimp command, a warning is reported that the
+ existing Doc is being overridden, and it reports where the
+ previous Doc was found. If \a replace is true, the Doc is
+ replaced silently.
+ */
+void Node::setDoc(const Doc &doc, bool replace)
+{
+ if (!m_doc.isEmpty() && !replace && !doc.isMarkedReimp()) {
+ doc.location().warning(QStringLiteral("Overrides a previous doc"),
+ QStringLiteral("from here: %1").arg(m_doc.location().toString()));
+ }
+ m_doc = doc;
+}
+
+/*!
+ Sets the node's status to \a t.
+
+ \sa Status
+*/
+void Node::setStatus(Status t)
+{
+ m_status = t;
+
+ // Set non-null, empty URL to nodes that are ignored as
+ // link targets
+ switch (t) {
+ case Internal:
+ if (Config::instance().showInternal())
+ break;
+ Q_FALLTHROUGH();
+ case DontDocument:
+ m_url = QStringLiteral("");
+ break;
+ default:
+ break;
+ }
+}
+
+/*!
+ Construct a node with the given \a type and having the
+ given \a parent and \a name. The new node is added to the
+ parent's child list.
+ */
+Node::Node(NodeType type, Aggregate *parent, QString name)
+ : m_nodeType(type),
+ m_indexNodeFlag(false),
+ m_relatedNonmember(false),
+ m_hadDoc(false),
+ m_parent(parent),
+ m_name(std::move(name))
+{
+ if (m_parent)
+ m_parent->addChild(this);
+
+ setGenus(getGenus(type));
+}
+
+/*!
+ Determines the appropriate Genus value for the NodeType
+ value \a t and returns that Genus value. Note that this
+ function is called in the Node() constructor. It always
+ returns Node::CPP when \a t is Node::Function, which
+ means the FunctionNode() constructor must determine its
+ own Genus value separately, because class FunctionNode
+ is a subclass of Node.
+ */
+Node::Genus Node::getGenus(Node::NodeType t)
+{
+ switch (t) {
+ case Node::Enum:
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ case Node::Module:
+ case Node::TypeAlias:
+ case Node::Typedef:
+ case Node::Property:
+ case Node::Variable:
+ case Node::Function:
+ case Node::Namespace:
+ case Node::HeaderFile:
+ return Node::CPP;
+ case Node::QmlType:
+ case Node::QmlModule:
+ case Node::QmlProperty:
+ case Node::QmlValueType:
+ return Node::QML;
+ case Node::Page:
+ case Node::Group:
+ case Node::Example:
+ case Node::ExternalPage:
+ return Node::DOC;
+ case Node::Collection:
+ case Node::SharedComment:
+ case Node::Proxy:
+ default:
+ return Node::DontCare;
+ }
+}
+
+/*! \fn QString Node::url() const
+ Returns the node's URL, which is the url of the documentation page
+ created for the node or the url of an external page if the node is
+ an ExternalPageNode. The url is used for generating a link to the
+ page the node represents.
+
+ \sa Node::setUrl()
+ */
+
+/*! \fn void Node::setUrl(const QString &url)
+ Sets the node's URL to \a url, which is the url to the page that the
+ node represents. This function is only called when an index file is
+ read. In other words, a node's url is set when qdoc decides where its
+ page will be and what its name will be, which happens when qdoc writes
+ the index file for the module being documented.
+
+ \sa QDocIndexFiles
+ */
+
+/*!
+ Returns this node's type as a string for use as an
+ attribute value in XML or HTML.
+ */
+QString Node::nodeTypeString() const
+{
+ if (isFunction()) {
+ const auto *fn = static_cast<const FunctionNode *>(this);
+ return fn->kindString();
+ }
+ return nodeTypeString(nodeType());
+}
+
+/*!
+ Returns the node type \a t as a string for use as an
+ attribute value in XML or HTML.
+ */
+QString Node::nodeTypeString(NodeType t)
+{
+ switch (t) {
+ case Namespace:
+ return QLatin1String("namespace");
+ case Class:
+ return QLatin1String("class");
+ case Struct:
+ return QLatin1String("struct");
+ case Union:
+ return QLatin1String("union");
+ case HeaderFile:
+ return QLatin1String("header");
+ case Page:
+ return QLatin1String("page");
+ case Enum:
+ return QLatin1String("enum");
+ case Example:
+ return QLatin1String("example");
+ case ExternalPage:
+ return QLatin1String("external page");
+ case TypeAlias:
+ case Typedef:
+ return QLatin1String("typedef");
+ case Function:
+ return QLatin1String("function");
+ case Property:
+ return QLatin1String("property");
+ case Proxy:
+ return QLatin1String("proxy");
+ case Variable:
+ return QLatin1String("variable");
+ case Group:
+ return QLatin1String("group");
+ case Module:
+ return QLatin1String("module");
+
+ case QmlType:
+ return QLatin1String("QML type");
+ case QmlValueType:
+ return QLatin1String("QML value type");
+ case QmlModule:
+ return QLatin1String("QML module");
+ case QmlProperty:
+ return QLatin1String("QML property");
+
+ case SharedComment:
+ return QLatin1String("shared comment");
+ case Collection:
+ return QLatin1String("collection");
+ default:
+ break;
+ }
+ return QString();
+}
+
+/*! Converts the boolean value \a b to an enum representation
+ of the boolean type, which includes an enum value for the
+ \e {default value} of the item, i.e. true, false, or default.
+ */
+Node::FlagValue Node::toFlagValue(bool b)
+{
+ return b ? FlagValueTrue : FlagValueFalse;
+}
+
+/*!
+ Converts the enum \a fv back to a boolean value.
+ If \a fv is neither the true enum value nor the
+ false enum value, the boolean value returned is
+ \a defaultValue.
+ */
+bool Node::fromFlagValue(FlagValue fv, bool defaultValue)
+{
+ switch (fv) {
+ case FlagValueTrue:
+ return true;
+ case FlagValueFalse:
+ return false;
+ default:
+ return defaultValue;
+ }
+}
+
+/*!
+ This function creates a pair that describes a link.
+ The pair is composed from \a link and \a desc. The
+ \a linkType is the map index the pair is filed under.
+ */
+void Node::setLink(LinkType linkType, const QString &link, const QString &desc)
+{
+ std::pair<QString, QString> linkPair;
+ linkPair.first = link;
+ linkPair.second = desc;
+ m_linkMap[linkType] = linkPair;
+}
+
+/*!
+ Sets the information about the project and version a node was introduced
+ in, unless the version is lower than the 'ignoresince.<project>'
+ configuration variable.
+ */
+void Node::setSince(const QString &since)
+{
+ QStringList parts = since.split(QLatin1Char(' '));
+ QString project;
+ if (parts.size() > 1)
+ project = Config::dot + parts.first();
+
+ QVersionNumber cutoff =
+ QVersionNumber::fromString(Config::instance().get(CONFIG_IGNORESINCE + project).asString())
+ .normalized();
+
+ if (!cutoff.isNull() && QVersionNumber::fromString(parts.last()).normalized() < cutoff)
+ return;
+
+ m_since = parts.join(QLatin1Char(' '));
+}
+
+/*!
+ Extract a class name from the type \a string and return it.
+ */
+QString Node::extractClassName(const QString &string) const
+{
+ QString result;
+ for (int i = 0; i <= string.size(); ++i) {
+ QChar ch;
+ if (i != string.size())
+ ch = string.at(i);
+
+ QChar lower = ch.toLower();
+ if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) || ch.digitValue() >= 0
+ || ch == QLatin1Char('_') || ch == QLatin1Char(':')) {
+ result += ch;
+ } else if (!result.isEmpty()) {
+ if (result != QLatin1String("const"))
+ return result;
+ result.clear();
+ }
+ }
+ return result;
+}
+
+/*!
+ Returns the thread safeness value for whatever this node
+ represents. But if this node has a parent and the thread
+ safeness value of the parent is the same as the thread
+ safeness value of this node, what is returned is the
+ value \c{UnspecifiedSafeness}. Why?
+ */
+Node::ThreadSafeness Node::threadSafeness() const
+{
+ if (m_parent && m_safeness == m_parent->inheritedThreadSafeness())
+ return UnspecifiedSafeness;
+ return m_safeness;
+}
+
+/*!
+ If this node has a parent, the parent's thread safeness
+ value is returned. Otherwise, this node's thread safeness
+ value is returned. Why?
+ */
+Node::ThreadSafeness Node::inheritedThreadSafeness() const
+{
+ if (m_parent && m_safeness == UnspecifiedSafeness)
+ return m_parent->inheritedThreadSafeness();
+ return m_safeness;
+}
+
+/*!
+ Returns \c true if the node's status is \c Internal, or if
+ its parent is a class with \c Internal status.
+ */
+bool Node::isInternal() const
+{
+ if (status() == Internal)
+ return true;
+ return parent() && parent()->status() == Internal && !parent()->isAbstract();
+}
+
+/*! \fn void Node::markInternal()
+ Sets the node's access to Private and its status to Internal.
+ */
+
+/*!
+ Returns a pointer to the root of the Tree this node is in.
+ */
+Aggregate *Node::root() const
+{
+ if (parent() == nullptr)
+ return (this->isAggregate() ? static_cast<Aggregate *>(const_cast<Node *>(this)) : nullptr);
+ Aggregate *t = parent();
+ while (t->parent() != nullptr)
+ t = t->parent();
+ return t;
+}
+
+/*!
+ Returns a pointer to the Tree this node is in.
+ */
+Tree *Node::tree() const
+{
+ return root()->tree();
+}
+
+/*!
+ Sets the node's declaration location, its definition
+ location, or both, depending on the suffix of the file
+ name from the file path in location \a t.
+ */
+void Node::setLocation(const Location &t)
+{
+ QString suffix = t.fileSuffix();
+ if (suffix == "h")
+ m_declLocation = t;
+ else if (suffix == "cpp")
+ m_defLocation = t;
+ else {
+ m_declLocation = t;
+ m_defLocation = t;
+ }
+}
+
+/*!
+ Returns \c true if this node is documented, or it represents
+ a documented node read from the index ('had doc'), or this
+ node is sharing a non-empty doc with other nodes.
+
+ \sa Doc
+ */
+bool Node::hasDoc() const
+{
+ if (m_hadDoc)
+ return true;
+
+ if (!m_doc.isEmpty())
+ return true;
+
+ return (m_sharedCommentNode && m_sharedCommentNode->hasDoc());
+}
+
+/*!
+ Returns the CPP node's qualified name by prepending the
+ namespaces name + "::" if there isw a namespace.
+ */
+QString Node::qualifyCppName()
+{
+ if (m_parent && m_parent->isNamespace() && !m_parent->name().isEmpty())
+ return m_parent->name() + "::" + m_name;
+ return m_name;
+}
+
+/*!
+ Return the name of this node qualified with the parent name
+ and "::" if there is a parent name.
+ */
+QString Node::qualifyWithParentName()
+{
+ if (m_parent && !m_parent->name().isEmpty())
+ return m_parent->name() + "::" + m_name;
+ return m_name;
+}
+
+/*!
+ Returns the QML node's qualified name by prepending the logical
+ module name.
+ */
+QString Node::qualifyQmlName()
+{
+ return logicalModuleName() + "::" + m_name;
+}
+
+/*!
+ Returns \c true if the node is a class node or a QML type node
+ that is marked as being a wrapper class or wrapper QML type,
+ or if it is a member of a wrapper class or type.
+ */
+bool Node::isWrapper() const
+{
+ return m_parent != nullptr && m_parent->isWrapper();
+}
+
+/*!
+ Construct the full document name for this node and return it.
+ */
+QString Node::fullDocumentName() const
+{
+ QStringList pieces;
+ const Node *n = this;
+
+ do {
+ if (!n->name().isEmpty())
+ pieces.insert(0, n->name());
+
+ if (n->isQmlType() && !n->logicalModuleName().isEmpty()) {
+ pieces.insert(0, n->logicalModuleName());
+ break;
+ }
+
+ if (n->isTextPageNode())
+ break;
+
+ // Examine the parent if the node is a member
+ if (!n->parent() || n->isRelatedNonmember())
+ break;
+
+ n = n->parent();
+ } while (true);
+
+ // Create a name based on the type of the ancestor node.
+ QString concatenator = "::";
+ if (n->isQmlType())
+ concatenator = QLatin1Char('.');
+
+ if (n->isTextPageNode())
+ concatenator = QLatin1Char('#');
+
+ return pieces.join(concatenator);
+}
+
+/*!
+ Sets the Node status to Node::Deprecated, unless \a sinceVersion represents
+ a future version.
+
+ Stores \a sinceVersion representing the version in which the deprecation
+ took (or will take) place.
+
+ Fetches the current version from the config ('version' variable) as a
+ string, and compared to \a sinceVersion. If both string represent a valid
+ version and \a sinceVersion is greater than the currect version, do not
+ mark the node as deprecated; leave it active.
+*/
+void Node::setDeprecated(const QString &sinceVersion)
+{
+
+ if (!m_deprecatedSince.isEmpty())
+ qCWarning(lcQdoc) << QStringLiteral(
+ "Setting deprecated since version for %1 to %2 even though it "
+ "was already set to %3. This is very unexpected.")
+ .arg(this->m_name, sinceVersion, this->m_deprecatedSince);
+ m_deprecatedSince = sinceVersion;
+
+ if (!sinceVersion.isEmpty()) {
+ QVersionNumber since = QVersionNumber::fromString(sinceVersion).normalized();
+ QVersionNumber current = QVersionNumber::fromString(
+ Config::instance().get(CONFIG_VERSION).asString())
+ .normalized();
+ if (!current.isNull() && !since.isNull()) {
+ if (current < since)
+ return;
+ }
+ }
+ setStatus(Deprecated);
+}
+
+/*! \fn Node *Node::clone(Aggregate *parent)
+
+ When reimplemented in a subclass, this function creates a
+ clone of this node on the heap and makes the clone a child
+ of \a parent. A pointer to the clone is returned.
+
+ Here in the base class, this function does nothing and returns
+ nullptr.
+ */
+
+/*! \fn NodeType Node::nodeType() const
+ Returns this node's type.
+
+ \sa NodeType
+*/
+
+/*! \fn Genus Node::genus() const
+ Returns this node's Genus.
+
+ \sa Genus
+*/
+
+/*! void Node::setGenus(Genus t)
+ Sets this node's Genus to \a t.
+*/
+
+/*! \fn QString Node::signature(Node::SignatureOptions options) const
+
+ Specific parts of the signature are included according to flags in
+ \a options.
+
+ If this node is not a FunctionNode, this function returns plainName().
+
+ \sa FunctionNode::signature()
+*/
+
+/*! \fn const QString &Node::fileNameBase() const
+ Returns the node's file name base string, which is built once, when
+ Generator::fileBase() is called and stored in the Node.
+*/
+
+/*! \fn bool Node::hasFileNameBase() const
+ Returns true if the node's file name base has been set.
+
+ \sa Node::fileNameBase()
+*/
+
+/*! \fn void Node::setFileNameBase(const QString &t)
+ Sets the node's file name base to \a t. Only called by
+ Generator::fileBase().
+*/
+
+/*! \fn void Node::setAccess(Access t)
+ Sets the node's access type to \a t.
+
+ \sa Access
+*/
+
+/*! \fn void Node::setThreadSafeness(ThreadSafeness t)
+ Sets the node's thread safeness to \a t.
+
+ \sa ThreadSafeness
+*/
+
+/*! \fn void Node::setPhysicalModuleName(const QString &name)
+ Sets the node's physical module \a name.
+*/
+
+/*! \fn void Node::setReconstitutedBrief(const QString &t)
+ When reading an index file, this function is called with the
+ reconstituted brief clause \a t to set the node's brief clause.
+ I think this is needed for linking to something in the brief clause.
+*/
+
+/*! \fn void Node::setParent(Aggregate *n)
+ Sets the node's parent pointer to \a n. Such a thing
+ is not lightly done. All the calls to this function
+ are in other member functions of Node subclasses. See
+ the code in the subclass implementations to understand
+ when this function can be called safely and why it is called.
+*/
+
+/*! \fn void Node::setIndexNodeFlag(bool isIndexNode = true)
+ Sets a flag in this Node that indicates the node was created
+ for something in an index file. This is important to know
+ because an index node is not to be documented in the current
+ module. When the index flag is set, it means the Node
+ represents something in another module, and it will be
+ documented in that module's documentation.
+*/
+
+/*! \fn void Node::setRelatedNonmember(bool b)
+ Sets a flag in the node indicating whether this node is a related nonmember
+ of something. This function is called when the \c relates command is seen.
+ */
+
+/*! \fn void Node::addMember(Node *node)
+ In a CollectionNode, this function adds \a node to the collection
+ node's members list. It does nothing if this node is not a CollectionNode.
+ */
+
+/*! \fn bool Node::hasNamespaces() const
+ Returns \c true if this is a CollectionNode and its members list
+ contains namespace nodes. Otherwise it returns \c false.
+ */
+
+/*! \fn bool Node::hasClasses() const
+ Returns \c true if this is a CollectionNode and its members list
+ contains class nodes. Otherwise it returns \c false.
+ */
+
+/*! \fn void Node::setAbstract(bool b)
+ If this node is a ClassNode or a QmlTypeNode, the node's abstract flag
+ data member is set to \a b.
+ */
+
+/*! \fn void Node::setWrapper()
+ If this node is a ClassNode or a QmlTypeNode, the node's wrapper flag
+ data member is set to \c true.
+ */
+
+/*! \fn void Node::setDataType(const QString &dataType)
+ If this node is a PropertyNode or a QmlPropertyNode, its
+ data type data member is set to \a dataType. Otherwise,
+ this function does nothing.
+ */
+
+/*! \fn bool Node::wasSeen() const
+ Returns the \c seen flag data member of this node if it is a NamespaceNode
+ or a CollectionNode. Otherwise it returns \c false. If \c true is returned,
+ it means that the location where the namespace or collection is to be
+ documented has been found.
+ */
+
+/*! \fn void appendGroupName(const QString &t)
+ If this node is a PageNode, the group name \a t is appended to the node's
+ list of group names. It is not clear to me what this list of group names
+ is used for, but it is written to the index file, and it is used in the
+ navigation bar.
+ */
+
+/*! \fn QString Node::element() const
+ If this node is a QmlPropertyNode or a FunctionNode, this function
+ returns the name of the parent node. Otherwise it returns an empty
+ string.
+ */
+
+/*! \fn bool Node::docMustBeGenerated() const
+ This function is called to perform a test to decide if the node must have
+ documentation generated. In the Node base class, it always returns \c false.
+
+ In the ProxyNode class it always returns \c true. There aren't many proxy
+ nodes, but when one appears, it must generate documentation. In the overrides
+ in NamespaceNode and ClassNode, a meaningful test is performed to decide if
+ documentation must be generated.
+ */
+
+/*! \fn QString Node::title() const
+ Returns a string that can be used to print a title in the documentation for
+ whatever this Node is. In the Node base class, the node's name() is returned.
+ In a PageNode, the function returns the title data member. In a HeaderNode,
+ if the title() is empty, the name() is returned.
+ */
+
+/*! \fn QString Node::subtitle() const { return QString(); }
+ Returns a string that can be used to print a subtitle in the documentation for
+ whatever this Node is. In the Node base class, the empty string is returned.
+ In a PageNode, the function returns the subtitle data member. In a HeaderNode,
+ the subtitle data member is returned.
+ */
+
+/*! \fn QString Node::fullTitle() const
+ Returns a string that can be used as the full title for the documentation of
+ this node. In this base class, the name() is returned. In a PageNode, title()
+ is returned. In a HeaderNode, if the title() is empty, the name() is returned.
+ If the title() is not empty then name-title is returned. In a CollectionNode,
+ the title() is returned.
+ */
+
+/*! \fn bool Node::setTitle(const QString &title)
+ Sets the node's \a title, which is used for the title of
+ the documentation page, if one is generated for this node.
+ Returns \c true if the title is set. In this base class,
+ there is no title string stored, so in the base class,
+ nothing happens and \c false is returned. The override in
+ the PageNode class is where the title is set.
+ */
+
+/*! \fn bool Node::setSubtitle(const QString &subtitle)
+ Sets the node's \a subtitle, which is used for the subtitle
+ of the documentation page, if one is generated for this node.
+ Returns \c true if the subtitle is set. In this base class,
+ there is no subtitle string stored, so in the base class,
+ nothing happens and \c false is returned. The override in
+ the PageNode and HeaderNode classes is where the subtitle is
+ set.
+ */
+
+/*! \fn void Node::markDefault()
+ If this node is a QmlPropertyNode, it is marked as the default property.
+ Otherwise the function does nothing.
+ */
+
+/*! \fn void Node::markReadOnly(bool flag)
+ If this node is a QmlPropertyNode, then the property's read-only
+ flag is set to \a flag.
+ */
+
+/*! \fn Aggregate *Node::parent() const
+ Returns the node's parent pointer.
+*/
+
+/*! \fn const QString &Node::name() const
+ Returns the node's name data member.
+*/
+
+/*! \fn void Node::setQtVariable(const QString &v)
+ If this node is a CollectionNode, its QT variable is set to \a v.
+ Otherwise the function does nothing. I don't know what the QT variable
+ is used for.
+ */
+
+/*! \fn QString Node::qtVariable() const
+ If this node is a CollectionNode, its QT variable is returned.
+ Otherwise an empty string is returned. I don't know what the QT
+ variable is used for.
+ */
+
+/*! \fn bool Node::hasTag(const QString &t) const
+ If this node is a FunctionNode, the function returns \c true if
+ the function has the tag \a t. Otherwise the function returns
+ \c false. I don't know what the tag is used for.
+ */
+
+/*! \fn const QMap<LinkType, std::pair<QString, QString> > &Node::links() const
+ Returns a reference to this node's link map. The link map should
+ probably be moved to the PageNode, because it contains links to the
+ start page, next page, previous page, and contents page, and these
+ are only used in PageNode, I think.
+ */
+
+/*! \fn Access Node::access() const
+ Returns the node's Access setting, which can be \c Public,
+ \c Protected, or \c Private.
+ */
+
+/*! \fn const Location& Node::declLocation() const
+ Returns the Location where this node's declaration was seen.
+ Normally the declaration location is in an \e include file.
+ The declaration location is used in qdoc error/warning messages
+ about the declaration.
+ */
+
+/*! \fn const Location& Node::defLocation() const
+ Returns the Location where this node's dedefinition was seen.
+ Normally the definition location is in a \e .cpp file.
+ The definition location is used in qdoc error/warning messages
+ when the error is discovered at the location of the definition,
+ although the way to correct the problem often requires changing
+ the declaration.
+ */
+
+/*! \fn const Location& Node::location() const
+ If this node's definition location is empty, this function
+ returns this node's declaration location. Otherwise it
+ returns the definition location.
+
+ \sa Location
+ */
+
+/*! \fn const Doc &Node::doc() const
+ Returns a reference to the node's Doc data member.
+
+ \sa Doc
+ */
+
+/*! \fn Status Node::status() const
+ Returns the node's status value.
+
+ \sa Status
+ */
+
+/*! \fn QString Node::since() const
+ Returns the node's since string, which can be empty.
+ */
+
+/*! \fn QString Node::templateStuff() const
+ Returns the node's template parameters string, if this node
+ represents a templated element.
+ */
+
+/*! \fn bool Node::isSharingComment() const
+ This function returns \c true if the node is sharing a comment
+ with other nodes. For example, multiple functions can be documented
+ with a single qdoc comment by listing the \c {\\fn} signatures for
+ all the functions in the single qdoc comment.
+ */
+
+/*! \fn QString Node::qmlTypeName() const
+ If this is a QmlPropertyNode or a FunctionNode representing a QML
+ method, this function returns the qmlTypeName() of
+ the parent() node. Otherwise it returns the name data member.
+ */
+
+/*! \fn QString Node::qmlFullBaseName() const
+ If this is a QmlTypeNode, this function returns the QML full
+ base name. Otherwise it returns an empty string.
+ */
+
+/*! \fn QString Node::logicalModuleName() const
+ If this is a CollectionNode, this function returns the logical
+ module name. Otherwise it returns an empty string.
+ */
+
+/*! \fn QString Node::logicalModuleVersion() const
+ If this is a CollectionNode, this function returns the logical
+ module version number. Otherwise it returns an empty string.
+ */
+
+/*! \fn QString Node::logicalModuleIdentifier() const
+ If this is a CollectionNode, this function returns the logical
+ module identifier. Otherwise it returns an empty string.
+ */
+
+/*! \fn void Node::setLogicalModuleInfo(const QString &arg)
+ If this node is a CollectionNode, this function splits \a arg
+ on the blank character to get a logical module name and version
+ number. If the version number is present, it splits the version
+ number on the '.' character to get a major version number and a
+ minor version number. If the version number is present, both the
+ major and minor version numbers should be there, but the minor
+ version number is not absolutely necessary.
+
+ The strings are stored in the appropriate data members for use
+ when the QML module page is generated.
+ */
+
+/*! \fn void Node::setLogicalModuleInfo(const QStringList &info)
+ If this node is a CollectionNode, this function accepts the
+ logical module \a info as a string list. If the logical module
+ info contains the version number, it splits the version number
+ on the '.' character to get the major and minor version numbers.
+ Both major and minor version numbers should be provided, but
+ the minor version number is not strictly necessary.
+
+ The strings are stored in the appropriate data members for use
+ when the QML module page is generated. This overload
+ of the function is called when qdoc is reading an index file.
+ */
+
+/*! \fn CollectionNode *Node::logicalModule() const
+ If this is a QmlTypeNode, a pointer to its QML module is returned,
+ which is a pointer to a CollectionNode. Otherwise the \c nullptr
+ is returned.
+ */
+
+/*! \fn void Node::setQmlModule(CollectionNode *t)
+ If this is a QmlTypeNode, this function sets the QML type's QML module
+ pointer to the CollectionNode \a t. Otherwise the function does nothing.
+ */
+
+/*! \fn ClassNode *Node::classNode()
+ If this is a QmlTypeNode, this function returns the pointer to
+ the C++ ClassNode that this QML type represents. Otherwise the
+ \c nullptr is returned.
+ */
+
+/*! \fn void Node::setClassNode(ClassNode *cn)
+ If this is a QmlTypeNode, this function sets the C++ class node
+ to \a cn. The C++ ClassNode is the C++ implementation of the QML
+ type.
+ */
+
+/*! \fn NodeType Node::goal(const QString &t)
+ When a square-bracket parameter is used in a qdoc command, this
+ function might be called to convert the text string \a t obtained
+ from inside the square brackets to be a Goal value, which is returned.
+
+ \sa Goal
+ */
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/node.h b/src/qdoc/qdoc/src/qdoc/node.h
new file mode 100644
index 000000000..faf06d51c
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/node.h
@@ -0,0 +1,344 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef NODE_H
+#define NODE_H
+
+#include "access.h"
+#include "comparisoncategory.h"
+#include "doc.h"
+#include "enumitem.h"
+#include "importrec.h"
+#include "parameters.h"
+#include "relatedclass.h"
+#include "template_declaration.h"
+
+#include <QtCore/qdir.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qstringlist.h>
+
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+class ClassNode;
+class CollectionNode;
+class EnumNode;
+class ExampleNode;
+class FunctionNode;
+class Node;
+class QDocDatabase;
+class QmlTypeNode;
+class PageNode;
+class PropertyNode;
+class QmlPropertyNode;
+class SharedCommentNode;
+class Tree;
+class TypedefNode;
+
+typedef QList<Node *> NodeList;
+typedef QList<ClassNode *> ClassList;
+typedef QList<Node *> NodeVector;
+typedef QMap<QString, Node *> NodeMap;
+typedef QMap<QString, NodeMap> NodeMapMap;
+typedef QMultiMap<QString, Node *> NodeMultiMap;
+typedef QMap<QString, NodeMultiMap> NodeMultiMapMap;
+typedef QMap<QString, CollectionNode *> CNMap;
+typedef QMultiMap<QString, CollectionNode *> CNMultiMap;
+
+class Node
+{
+public:
+ enum NodeType : unsigned char {
+ NoType,
+ Namespace,
+ Class,
+ Struct,
+ Union,
+ HeaderFile,
+ Page,
+ Enum,
+ Example,
+ ExternalPage,
+ Function,
+ Typedef,
+ TypeAlias,
+ Property,
+ Variable,
+ Group,
+ Module,
+ QmlType,
+ QmlModule,
+ QmlProperty,
+ QmlValueType,
+ SharedComment,
+ Collection,
+ Proxy
+ };
+
+ enum Genus : unsigned char {
+ DontCare = 0x0,
+ CPP = 0x1,
+ QML = 0x4,
+ DOC = 0x8,
+ API = CPP | QML
+ };
+
+ enum Status : unsigned char {
+ Deprecated,
+ Preliminary,
+ Active,
+ Internal,
+ DontDocument
+ }; // don't reorder this enum
+
+ enum ThreadSafeness : unsigned char {
+ UnspecifiedSafeness,
+ NonReentrant,
+ Reentrant,
+ ThreadSafe
+ };
+
+ enum SignatureOption : unsigned char {
+ SignaturePlain = 0x0,
+ SignatureDefaultValues = 0x1,
+ SignatureReturnType = 0x2,
+ SignatureTemplateParams = 0x4
+ };
+ Q_DECLARE_FLAGS(SignatureOptions, SignatureOption)
+
+ enum LinkType : unsigned char { StartLink, NextLink, PreviousLink, ContentsLink };
+
+ enum FlagValue { FlagValueDefault = -1, FlagValueFalse = 0, FlagValueTrue = 1 };
+
+ virtual ~Node() = default;
+ virtual Node *clone(Aggregate *) { return nullptr; } // currently only FunctionNode
+ [[nodiscard]] virtual Tree *tree() const;
+ [[nodiscard]] Aggregate *root() const;
+
+ [[nodiscard]] NodeType nodeType() const { return m_nodeType; }
+ [[nodiscard]] QString nodeTypeString() const;
+
+ [[nodiscard]] Genus genus() const { return m_genus; }
+ void setGenus(Genus t) { m_genus = t; }
+ static Genus getGenus(NodeType t);
+
+ [[nodiscard]] bool isActive() const { return m_status == Active; }
+ [[nodiscard]] bool isClass() const { return m_nodeType == Class; }
+ [[nodiscard]] bool isCppNode() const { return genus() == CPP; }
+ [[nodiscard]] bool isDontDocument() const { return (m_status == DontDocument); }
+ [[nodiscard]] bool isEnumType() const { return m_nodeType == Enum; }
+ [[nodiscard]] bool isExample() const { return m_nodeType == Example; }
+ [[nodiscard]] bool isExternalPage() const { return m_nodeType == ExternalPage; }
+ [[nodiscard]] bool isFunction(Genus g = DontCare) const
+ {
+ return m_nodeType == Function && (genus() == g || g == DontCare);
+ }
+ [[nodiscard]] bool isGroup() const { return m_nodeType == Group; }
+ [[nodiscard]] bool isHeader() const { return m_nodeType == HeaderFile; }
+ [[nodiscard]] bool isIndexNode() const { return m_indexNodeFlag; }
+ [[nodiscard]] bool isModule() const { return m_nodeType == Module; }
+ [[nodiscard]] bool isNamespace() const { return m_nodeType == Namespace; }
+ [[nodiscard]] bool isPage() const { return m_nodeType == Page; }
+ [[nodiscard]] bool isPreliminary() const { return (m_status == Preliminary); }
+ [[nodiscard]] bool isPrivate() const { return m_access == Access::Private; }
+ [[nodiscard]] bool isProperty() const { return m_nodeType == Property; }
+ [[nodiscard]] bool isProxyNode() const { return m_nodeType == Proxy; }
+ [[nodiscard]] bool isPublic() const { return m_access == Access::Public; }
+ [[nodiscard]] bool isProtected() const { return m_access == Access::Protected; }
+ [[nodiscard]] bool isQmlBasicType() const { return m_nodeType == QmlValueType; }
+ [[nodiscard]] bool isQmlModule() const { return m_nodeType == QmlModule; }
+ [[nodiscard]] bool isQmlNode() const { return genus() == QML; }
+ [[nodiscard]] bool isQmlProperty() const { return m_nodeType == QmlProperty; }
+ [[nodiscard]] bool isQmlType() const { return m_nodeType == QmlType || m_nodeType == QmlValueType; }
+ [[nodiscard]] bool isRelatedNonmember() const { return m_relatedNonmember; }
+ [[nodiscard]] bool isStruct() const { return m_nodeType == Struct; }
+ [[nodiscard]] bool isSharedCommentNode() const { return m_nodeType == SharedComment; }
+ [[nodiscard]] bool isTypeAlias() const { return m_nodeType == TypeAlias; }
+ [[nodiscard]] bool isTypedef() const
+ {
+ return m_nodeType == Typedef || m_nodeType == TypeAlias;
+ }
+ [[nodiscard]] bool isUnion() const { return m_nodeType == Union; }
+ [[nodiscard]] bool isVariable() const { return m_nodeType == Variable; }
+ [[nodiscard]] bool isGenericCollection() const { return (m_nodeType == Node::Collection); }
+
+ [[nodiscard]] virtual bool isDeprecated() const { return (m_status == Deprecated); }
+ [[nodiscard]] virtual bool isAbstract() const { return false; }
+ [[nodiscard]] virtual bool isAggregate() const { return false; } // means "can have children"
+ [[nodiscard]] virtual bool isFirstClassAggregate() const
+ {
+ return false;
+ } // Aggregate but not proxy or prop group"
+ [[nodiscard]] virtual bool isAlias() const { return false; }
+ [[nodiscard]] virtual bool isAttached() const { return false; }
+ [[nodiscard]] virtual bool isClassNode() const { return false; }
+ [[nodiscard]] virtual bool isCollectionNode() const { return false; }
+ [[nodiscard]] virtual bool isDefault() const { return false; }
+ [[nodiscard]] virtual bool isInternal() const;
+ [[nodiscard]] virtual bool isMacro() const { return false; }
+ [[nodiscard]] virtual bool isPageNode() const { return false; } // means "generates a doc page"
+ [[nodiscard]] virtual bool isRelatableType() const { return false; }
+ [[nodiscard]] virtual bool isMarkedReimp() const { return false; }
+ [[nodiscard]] virtual bool isPropertyGroup() const { return false; }
+ [[nodiscard]] virtual bool isStatic() const { return false; }
+ [[nodiscard]] virtual bool isTextPageNode() const
+ {
+ return false;
+ } // means PageNode but not Aggregate
+ [[nodiscard]] virtual bool isWrapper() const;
+
+ [[nodiscard]] QString plainName() const;
+ QString plainFullName(const Node *relative = nullptr) const;
+ [[nodiscard]] QString plainSignature() const;
+ QString fullName(const Node *relative = nullptr) const;
+ [[nodiscard]] virtual QString signature(Node::SignatureOptions) const { return plainName(); }
+
+ [[nodiscard]] const QString &fileNameBase() const { return m_fileNameBase; }
+ [[nodiscard]] bool hasFileNameBase() const { return !m_fileNameBase.isEmpty(); }
+ void setFileNameBase(const QString &t) { m_fileNameBase = t; }
+
+ void setAccess(Access t) { m_access = t; }
+ void setLocation(const Location &t);
+ void setDoc(const Doc &doc, bool replace = false);
+ void setStatus(Status t);
+ void setThreadSafeness(ThreadSafeness t) { m_safeness = t; }
+ void setSince(const QString &since);
+ void setPhysicalModuleName(const QString &name) { m_physicalModuleName = name; }
+ void setUrl(const QString &url) { m_url = url; }
+ void setTemplateDecl(std::optional<RelaxedTemplateDeclaration> t) { m_templateDecl = t; }
+ void setReconstitutedBrief(const QString &t) { m_reconstitutedBrief = t; }
+ void setParent(Aggregate *n) { m_parent = n; }
+ void setIndexNodeFlag(bool isIndexNode = true) { m_indexNodeFlag = isIndexNode; }
+ void setHadDoc() { m_hadDoc = true; }
+ void setComparisonCategory(const ComparisonCategory &category) { m_comparisonCategory = category; }
+ [[nodiscard]] ComparisonCategory comparisonCategory() const { return m_comparisonCategory; }
+ virtual void setRelatedNonmember(bool b) { m_relatedNonmember = b; }
+ virtual void addMember(Node *) {}
+ [[nodiscard]] virtual bool hasNamespaces() const { return false; }
+ [[nodiscard]] virtual bool hasClasses() const { return false; }
+ virtual void setAbstract(bool) {}
+ virtual void setWrapper() {}
+ virtual void setDataType(const QString &) {}
+ [[nodiscard]] virtual bool wasSeen() const { return false; }
+ virtual void appendGroupName(const QString &) {}
+ [[nodiscard]] virtual QString element() const { return QString(); }
+ [[nodiscard]] virtual bool docMustBeGenerated() const { return false; }
+
+ [[nodiscard]] virtual QString title() const { return name(); }
+ [[nodiscard]] virtual QString subtitle() const { return QString(); }
+ [[nodiscard]] virtual QString fullTitle() const { return name(); }
+ virtual bool setTitle(const QString &) { return false; }
+ virtual bool setSubtitle(const QString &) { return false; }
+
+ void markInternal()
+ {
+ setAccess(Access::Private);
+ setStatus(Internal);
+ }
+ virtual void markDefault() {}
+ virtual void markReadOnly(bool) {}
+
+ [[nodiscard]] Aggregate *parent() const { return m_parent; }
+ [[nodiscard]] const QString &name() const { return m_name; }
+ [[nodiscard]] QString physicalModuleName() const { return m_physicalModuleName; }
+ [[nodiscard]] QString url() const { return m_url; }
+ virtual void setQtVariable(const QString &) {}
+ [[nodiscard]] virtual QString qtVariable() const { return QString(); }
+ virtual void setQtCMakeComponent(const QString &) {}
+ virtual void setQtCMakeTargetItem(const QString &) {}
+ [[nodiscard]] virtual QString qtCMakeComponent() const { return QString(); }
+ [[nodiscard]] virtual QString qtCMakeTargetItem() const { return QString(); }
+ [[nodiscard]] virtual bool hasTag(const QString &) const { return false; }
+
+ void setDeprecated(const QString &sinceVersion);
+ [[nodiscard]] const QString &deprecatedSince() const { return m_deprecatedSince; }
+
+ [[nodiscard]] const QMap<LinkType, std::pair<QString, QString>> &links() const { return m_linkMap; }
+ void setLink(LinkType linkType, const QString &link, const QString &desc);
+
+ [[nodiscard]] Access access() const { return m_access; }
+ [[nodiscard]] const Location &declLocation() const { return m_declLocation; }
+ [[nodiscard]] const Location &defLocation() const { return m_defLocation; }
+ [[nodiscard]] const Location &location() const
+ {
+ return (m_defLocation.isEmpty() ? m_declLocation : m_defLocation);
+ }
+ [[nodiscard]] const Doc &doc() const { return m_doc; }
+ [[nodiscard]] bool isInAPI() const
+ {
+ return !isPrivate() && !isInternal() && !isDontDocument() && hasDoc();
+ }
+ [[nodiscard]] bool hasDoc() const;
+ [[nodiscard]] bool hadDoc() const { return m_hadDoc; }
+ [[nodiscard]] Status status() const { return m_status; }
+ [[nodiscard]] ThreadSafeness threadSafeness() const;
+ [[nodiscard]] ThreadSafeness inheritedThreadSafeness() const;
+ [[nodiscard]] QString since() const { return m_since; }
+ [[nodiscard]] const std::optional<RelaxedTemplateDeclaration>& templateDecl() const { return m_templateDecl; }
+ [[nodiscard]] const QString &reconstitutedBrief() const { return m_reconstitutedBrief; }
+
+ [[nodiscard]] bool isSharingComment() const { return (m_sharedCommentNode != nullptr); }
+ void setSharedCommentNode(SharedCommentNode *t) { m_sharedCommentNode = t; }
+ SharedCommentNode *sharedCommentNode() { return m_sharedCommentNode; }
+
+ [[nodiscard]] QString extractClassName(const QString &string) const;
+ [[nodiscard]] virtual QString qmlTypeName() const { return m_name; }
+ [[nodiscard]] virtual QString qmlFullBaseName() const { return QString(); }
+ [[nodiscard]] virtual QString logicalModuleName() const { return QString(); }
+ [[nodiscard]] virtual QString logicalModuleVersion() const { return QString(); }
+ [[nodiscard]] virtual QString logicalModuleIdentifier() const { return QString(); }
+
+ virtual void setLogicalModuleInfo(const QStringList &) {}
+ [[nodiscard]] virtual CollectionNode *logicalModule() const { return nullptr; }
+ virtual void setQmlModule(CollectionNode *) {}
+ virtual ClassNode *classNode() { return nullptr; }
+ virtual void setClassNode(ClassNode *) {}
+ [[nodiscard]] QString fullDocumentName() const;
+ QString qualifyCppName();
+ QString qualifyQmlName();
+ QString qualifyWithParentName();
+
+ static FlagValue toFlagValue(bool b);
+ static bool fromFlagValue(FlagValue fv, bool defaultValue);
+ static QString nodeTypeString(NodeType t);
+ [[nodiscard]] static bool nodeNameLessThan(const Node *first, const Node *second);
+ [[nodiscard]] static bool nodeSortKeyOrNameLessThan(const Node *n1, const Node *n2);
+
+protected:
+ Node(NodeType type, Aggregate *parent, QString name);
+
+private:
+ NodeType m_nodeType {};
+ Genus m_genus {};
+ Access m_access { Access::Public };
+ ThreadSafeness m_safeness { UnspecifiedSafeness };
+ Status m_status { Active };
+ ComparisonCategory m_comparisonCategory { ComparisonCategory::None };
+ bool m_indexNodeFlag : 1;
+ bool m_relatedNonmember : 1;
+ bool m_hadDoc : 1;
+
+ Aggregate *m_parent { nullptr };
+ SharedCommentNode *m_sharedCommentNode { nullptr };
+ QString m_name {};
+ Location m_declLocation {};
+ Location m_defLocation {};
+ Doc m_doc {};
+ QMap<LinkType, std::pair<QString, QString>> m_linkMap {};
+ QString m_fileNameBase {};
+ QString m_physicalModuleName {};
+ QString m_url {};
+ QString m_since {};
+ std::optional<RelaxedTemplateDeclaration> m_templateDecl{std::nullopt};
+ QString m_reconstitutedBrief {};
+ QString m_deprecatedSince {};
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(Node::SignatureOptions)
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/openedlist.cpp b/src/qdoc/qdoc/src/qdoc/openedlist.cpp
new file mode 100644
index 000000000..a85e45ec4
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/openedlist.cpp
@@ -0,0 +1,172 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "openedlist.h"
+
+#include "atom.h"
+
+#include <QtCore/qregularexpression.h>
+
+QT_BEGIN_NAMESPACE
+
+static const char roman[] = "m\2d\5c\2l\5x\2v\5i";
+
+OpenedList::OpenedList(ListStyle style) : sty(style), ini(1), nex(0) {}
+
+OpenedList::OpenedList(const Location &location, const QString &hint) : sty(Bullet), ini(1)
+{
+ static const QRegularExpression hintSyntax("^(\\W*)([0-9]+|[A-Z]+|[a-z]+)(\\W*)$");
+
+ auto match = hintSyntax.match(hint);
+ if (match.hasMatch()) {
+ bool ok;
+ int asNumeric = hint.toInt(&ok);
+ int asRoman = fromRoman(match.captured(2));
+ int asAlpha = fromAlpha(match.captured(2));
+
+ if (ok) {
+ sty = Numeric;
+ ini = asNumeric;
+ } else if (asRoman > 0 && asRoman != 100 && asRoman != 500) {
+ sty = (hint == hint.toLower()) ? LowerRoman : UpperRoman;
+ ini = asRoman;
+ } else {
+ sty = (hint == hint.toLower()) ? LowerAlpha : UpperAlpha;
+ ini = asAlpha;
+ }
+ pref = match.captured(1);
+ suff = match.captured(3);
+ } else if (!hint.isEmpty()) {
+ location.warning(QStringLiteral("Unrecognized list style '%1'").arg(hint));
+ }
+ nex = ini - 1;
+}
+
+QString OpenedList::styleString() const
+{
+ switch (style()) {
+ case Bullet:
+ default:
+ return ATOM_LIST_BULLET;
+ case Tag:
+ return ATOM_LIST_TAG;
+ case Value:
+ return ATOM_LIST_VALUE;
+ case Numeric:
+ return ATOM_LIST_NUMERIC;
+ case UpperAlpha:
+ return ATOM_LIST_UPPERALPHA;
+ case LowerAlpha:
+ return ATOM_LIST_LOWERALPHA;
+ case UpperRoman:
+ return ATOM_LIST_UPPERROMAN;
+ case LowerRoman:
+ return ATOM_LIST_LOWERROMAN;
+ }
+}
+
+QString OpenedList::numberString() const
+{
+ return QString::number(number());
+ /*
+ switch ( style() ) {
+ case Numeric:
+ return QString::number( number() );
+ case UpperAlpha:
+ return toAlpha( number() ).toUpper();
+ case LowerAlpha:
+ return toAlpha( number() );
+ case UpperRoman:
+ return toRoman( number() ).toUpper();
+ case LowerRoman:
+ return toRoman( number() );
+ case Bullet:
+ default:
+ return "*";
+ }*/
+}
+
+int OpenedList::fromAlpha(const QString &str)
+{
+ int n = 0;
+ int u;
+
+ for (const QChar &character : str) {
+ u = character.toLower().unicode();
+ if (u >= 'a' && u <= 'z') {
+ n *= 26;
+ n += u - 'a' + 1;
+ } else {
+ return 0;
+ }
+ }
+ return n;
+}
+
+QString OpenedList::toRoman(int n)
+{
+ /*
+ See p. 30 of Donald E. Knuth's "TeX: The Program".
+ */
+ QString str;
+ int j = 0;
+ int k;
+ int u;
+ int v = 1000;
+
+ for (;;) {
+ while (n >= v) {
+ str += roman[j];
+ n -= v;
+ }
+
+ if (n <= 0)
+ break;
+
+ k = j + 2;
+ u = v / roman[k - 1];
+ if (roman[k - 1] == 2) {
+ k += 2;
+ u /= 5;
+ }
+ if (n + u >= v) {
+ str += roman[k];
+ n += u;
+ } else {
+ j += 2;
+ v /= roman[j - 1];
+ }
+ }
+ return str;
+}
+
+int OpenedList::fromRoman(const QString &str)
+{
+ int n = 0;
+ int j;
+ int u;
+ int v = 0;
+
+ for (const QChar &character : str) {
+ j = 0;
+ u = 1000;
+ while (roman[j] != 'i' && roman[j] != character.toLower()) {
+ j += 2;
+ u /= roman[j - 1];
+ }
+ if (u < v) {
+ n -= u;
+ } else {
+ n += u;
+ }
+ v = u;
+ }
+
+ if (str.toLower() == toRoman(n)) {
+ return n;
+ } else {
+ return 0;
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/openedlist.h b/src/qdoc/qdoc/src/qdoc/openedlist.h
new file mode 100644
index 000000000..cd0c54e40
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/openedlist.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef OPENEDLIST_H
+#define OPENEDLIST_H
+
+#include "location.h"
+
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class OpenedList
+{
+public:
+ enum ListStyle { Bullet, Tag, Value, Numeric, UpperAlpha, LowerAlpha, UpperRoman, LowerRoman };
+
+ OpenedList() : sty(Bullet), ini(1), nex(0) {}
+ explicit OpenedList(ListStyle style);
+ OpenedList(const Location &location, const QString &hint);
+
+ void next() { nex++; }
+
+ [[nodiscard]] bool isStarted() const { return nex >= ini; }
+ [[nodiscard]] ListStyle style() const { return sty; }
+ [[nodiscard]] QString styleString() const;
+ [[nodiscard]] int number() const { return nex; }
+ [[nodiscard]] QString numberString() const;
+ [[nodiscard]] QString prefix() const { return pref; }
+ [[nodiscard]] QString suffix() const { return suff; }
+
+private:
+ static int fromAlpha(const QString &str);
+ static QString toRoman(int n);
+ static int fromRoman(const QString &str);
+
+ ListStyle sty;
+ int ini;
+ int nex;
+ QString pref;
+ QString suff;
+};
+Q_DECLARE_TYPEINFO(OpenedList, Q_RELOCATABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/pagenode.cpp b/src/qdoc/qdoc/src/qdoc/pagenode.cpp
new file mode 100644
index 000000000..5f01bc24f
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/pagenode.cpp
@@ -0,0 +1,117 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "pagenode.h"
+
+#include "aggregate.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class PageNode
+ \brief A PageNode is a Node that generates a documentation page.
+
+ Not all subclasses of Node produce documentation pages. FunctionNode,
+ PropertyNode, and EnumNode are all examples of subclasses of Node that
+ don't produce documentation pages but add documentation to a page.
+ They are always child nodes of an Aggregate, and Aggregate inherits
+ PageNode.
+
+ Not every subclass of PageNode inherits Aggregate. ExternalPageNode,
+ ExampleNode, and CollectionNode are subclasses of PageNode that are
+ not subclasses of Aggregate. Because they are not subclasses of
+ Aggregate, they can't have children. But they still generate, or
+ link to, a documentation page.
+ */
+
+/*! \fn QString PageNode::title() const
+ Returns the node's title, which is used for the page title.
+ */
+
+/*! \fn QString PageNode::subtitle() const
+ Returns the node's subtitle, which may be empty.
+ */
+
+/*!
+ Returns the node's full title.
+ */
+QString PageNode::fullTitle() const
+{
+ return title();
+}
+
+/*!
+ Sets the node's \a title, which is used for the page title.
+ Returns true. Adds the node to the parent() nonfunction map
+ using the \a title as the key.
+ */
+bool PageNode::setTitle(const QString &title)
+{
+ m_title = title;
+ parent()->addChildByTitle(this, title);
+ return true;
+}
+
+/*!
+ \fn bool PageNode::setSubtitle(const QString &subtitle)
+ Sets the node's \a subtitle. Returns true;
+ */
+
+/*! \fn PageNode::PageNode(Aggregate *parent, const QString &name)
+ This constructor sets the PageNode's \a parent and the \a name is the
+ argument of the \c {\\page} command. The node type is set to Node::Page.
+ */
+
+/*! \fn PageNode::PageNode(NodeType type, Aggregate *parent, const QString &name)
+ This constructor is not called directly. It is called by the constructors of
+ subclasses of PageNode, usually Aggregate. The node type is set to \a type,
+ and the parent pointer is set to \a parent. \a name is the argument of the topic
+ command that resulted in the PageNode being created. This could be \c {\\class}
+ or \c {\\namespace}, for example.
+ */
+
+/*! \fn PageNode::~PageNode()
+ The destructor is virtual, and it does nothing.
+ */
+
+/*! \fn bool PageNode::isPageNode() const
+ Always returns \c true because this is a PageNode.
+ */
+
+/*! \fn bool PageNode::isTextPageNode() const
+ Returns \c true if this instance of PageNode is not an Aggregate.
+ The significance of a \c true return value is that this PageNode
+ doesn't have children, because it is not an Aggregate.
+
+ \sa Aggregate.
+ */
+
+/*! \fn QString PageNode::imageFileName() const
+ If this PageNode is an ExampleNode, the image file name
+ data member is returned. Otherwise an empty string is
+ returned.
+ */
+
+/*! \fn void PageNode::setImageFileName(const QString &ifn)
+ If this PageNode is an ExampleNode, the image file name
+ data member is set to \a ifn. Otherwise the function does
+ nothing.
+ */
+
+/*! \fn bool PageNode::noAutoList() const
+ Returns the value of the no auto-list flag.
+ */
+
+/*! \fn void PageNode::setNoAutoList(bool b)
+ Sets the no auto-list flag to \a b.
+ */
+
+/*! \fn const QStringList &PageNode::groupNames() const
+ Returns a const reference to the string list containing all the group names.
+ */
+
+/*! \fn void PageNode::appendGroupName(const QString &t)
+ Appends \a t to the list of group names.
+ */
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/pagenode.h b/src/qdoc/qdoc/src/qdoc/pagenode.h
new file mode 100644
index 000000000..14231bccd
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/pagenode.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef PAGENODE_H
+#define PAGENODE_H
+
+#include "node.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+class PageNode : public Node
+{
+public:
+ PageNode(Aggregate *parent, const QString &name) : Node(Page, parent, name) {}
+ PageNode(NodeType type, Aggregate *parent, const QString &name) : Node(type, parent, name) {}
+
+ [[nodiscard]] bool isPageNode() const override { return true; }
+ [[nodiscard]] bool isTextPageNode() const override
+ {
+ return !isAggregate();
+ } // PageNode but not Aggregate
+
+ [[nodiscard]] QString title() const override { return m_title; }
+ [[nodiscard]] QString subtitle() const override { return m_subtitle; }
+ [[nodiscard]] QString fullTitle() const override;
+ bool setTitle(const QString &title) override;
+ bool setSubtitle(const QString &subtitle) override
+ {
+ m_subtitle = subtitle;
+ return true;
+ }
+ [[nodiscard]] virtual QString imageFileName() const { return QString(); }
+ virtual void setImageFileName(const QString &) {}
+
+ [[nodiscard]] bool noAutoList() const { return m_noAutoList; }
+ void setNoAutoList(bool b) { m_noAutoList = b; }
+ [[nodiscard]] const QStringList &groupNames() const { return m_groupNames; }
+ void appendGroupName(const QString &t) override { m_groupNames.append(t); }
+
+ [[nodiscard]] const PageNode *navigationParent() const { return m_navParent; }
+ void setNavigationParent(const PageNode *parent) { m_navParent = parent; }
+
+ void markAttribution() { is_attribution = true; }
+ [[nodiscard]] bool isAttribution() const { return is_attribution; }
+
+protected:
+ friend class Node;
+
+protected:
+ bool m_noAutoList { false };
+ QString m_title {};
+ QString m_subtitle {};
+ QStringList m_groupNames {};
+
+ // Marks the PageNode as being or not being an attribution.
+ // A PageNode that is an attribution represents a page that serves
+ // to present the third party software that a project uses,
+ // together with its license, link to the website of the project
+ // and so on.
+ // PageNode that are attribution are expected to be generate only
+ // for the Qt project by the QAttributionScanner, as part of the
+ // built of Qt's documentation.
+ //
+ // PageNodes that are attribution are marked primarily so that
+ // QDoc is able to generate a specialized list of attributions for
+ // a specific module through the use of the "\generatedlist"
+ // command, and behave like any other PageNode otherwise.
+ bool is_attribution{ false };
+
+private:
+ const PageNode *m_navParent { nullptr };
+};
+
+QT_END_NAMESPACE
+
+#endif // PAGENODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/parameters.cpp b/src/qdoc/qdoc/src/qdoc/parameters.cpp
new file mode 100644
index 000000000..39f88b48f
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/parameters.cpp
@@ -0,0 +1,542 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "parameters.h"
+
+#include "codechunk.h"
+#include "generator.h"
+#include "tokenizer.h"
+
+QT_BEGIN_NAMESPACE
+
+QRegularExpression Parameters::s_varComment(R"(^/\*\s*([a-zA-Z_0-9]+)\s*\*/$)");
+
+/*!
+ \class Parameter
+ \brief The Parameter class describes one function parameter.
+
+ A parameter can be a function parameter or a macro parameter.
+ It has a name, a data type, and an optional default value.
+ These are all stored as strings so they can be compared with
+ a parameter in a function signature to find a match.
+ */
+
+/*!
+ \fn Parameter::Parameter(const QString &type, const QString &name, const QString &defaultValue)
+
+ Constructs the parameter from the \a type, the optional \a name,
+ and the optional \a defaultValue.
+ */
+
+/*!
+ Reconstructs the text signature for the parameter and returns
+ it. If \a includeValue is true and there is a default value,
+ the default value is appended with '='.
+ */
+QString Parameter::signature(bool includeValue) const
+{
+ QString p = m_type;
+ if (!p.isEmpty() && !p.endsWith(QChar('*')) && !p.endsWith(QChar('&')) &&
+ !p.endsWith(QChar(' ')) && !m_name.isEmpty()) {
+ p += QLatin1Char(' ');
+ }
+ p += m_name;
+ if (includeValue && !m_defaultValue.isEmpty())
+ p += " = " + m_defaultValue;
+ return p;
+}
+
+/*!
+ \class Parameters
+
+ \brief A class for parsing and managing a function parameter list
+
+ The constructor is passed a string that is the text inside the
+ parentheses of a function declaration. The constructor parses
+ the parameter list into a vector of class Parameter.
+
+ The Parameters object is then used in function searches to find
+ the correct function node given the function name and the signature
+ of its parameters.
+ */
+
+Parameters::Parameters() : m_valid(true), m_privateSignal(false), m_tok(0), m_tokenizer(nullptr)
+{
+ // nothing.
+}
+
+Parameters::Parameters(const QString &signature)
+ : m_valid(true), m_privateSignal(false), m_tok(0), m_tokenizer(nullptr)
+{
+ if (!signature.isEmpty()) {
+ if (!parse(signature)) {
+ m_parameters.clear();
+ m_valid = false;
+ }
+ }
+}
+
+/*!
+ Get the next token from the string being parsed and store
+ it in the token variable.
+ */
+void Parameters::readToken()
+{
+ m_tok = m_tokenizer->getToken();
+}
+
+/*!
+ Return the current lexeme from the string being parsed.
+ */
+QString Parameters::lexeme()
+{
+ return m_tokenizer->lexeme();
+}
+
+/*!
+ Return the previous lexeme read from the string being parsed.
+ */
+QString Parameters::previousLexeme()
+{
+ return m_tokenizer->previousLexeme();
+}
+
+/*!
+ If the current token is \a target, read the next token and
+ return \c true. Otherwise, return false without reading the
+ next token.
+ */
+bool Parameters::match(int target)
+{
+ if (m_tok == target) {
+ readToken();
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Match a template clause in angle brackets, append it to the
+ \a type, and return \c true. If there is no template clause,
+ or if an error is detected, return \c false.
+ */
+void Parameters::matchTemplateAngles(CodeChunk &type)
+{
+ if (m_tok == Tok_LeftAngle) {
+ int leftAngleDepth = 0;
+ int parenAndBraceDepth = 0;
+ do {
+ if (m_tok == Tok_LeftAngle) {
+ leftAngleDepth++;
+ } else if (m_tok == Tok_RightAngle) {
+ leftAngleDepth--;
+ } else if (m_tok == Tok_LeftParen || m_tok == Tok_LeftBrace) {
+ ++parenAndBraceDepth;
+ } else if (m_tok == Tok_RightParen || m_tok == Tok_RightBrace) {
+ if (--parenAndBraceDepth < 0)
+ return;
+ }
+ type.append(lexeme());
+ readToken();
+ } while (leftAngleDepth > 0 && m_tok != Tok_Eoi);
+ }
+}
+
+/*!
+ Uses the current tokenizer to parse the \a name and \a type
+ of the parameter.
+ */
+bool Parameters::matchTypeAndName(CodeChunk &type, QString &name)
+{
+ /*
+ This code is really hard to follow... sorry. The loop is there to match
+ Alpha::Beta::Gamma::...::Omega.
+ */
+ for (;;) {
+ bool virgin = true;
+
+ if (m_tok != Tok_Ident) {
+ /*
+ There is special processing for 'Foo::operator int()'
+ and such elsewhere. This is the only case where we
+ return something with a trailing gulbrandsen ('Foo::').
+ */
+ if (m_tok == Tok_operator)
+ return true;
+
+ /*
+ People may write 'const unsigned short' or
+ 'short unsigned const' or any other permutation.
+ */
+ while (match(Tok_const) || match(Tok_volatile))
+ type.append(previousLexeme());
+ QString pending;
+ while (m_tok == Tok_signed || m_tok == Tok_int || m_tok == Tok_unsigned
+ || m_tok == Tok_short || m_tok == Tok_long || m_tok == Tok_int64) {
+ if (m_tok == Tok_signed)
+ pending = lexeme();
+ else {
+ if (m_tok == Tok_unsigned && !pending.isEmpty())
+ type.append(pending);
+ pending.clear();
+ type.append(lexeme());
+ }
+ readToken();
+ virgin = false;
+ }
+ if (!pending.isEmpty()) {
+ type.append(pending);
+ pending.clear();
+ }
+ while (match(Tok_const) || match(Tok_volatile))
+ type.append(previousLexeme());
+
+ if (match(Tok_Tilde))
+ type.append(previousLexeme());
+ }
+
+ if (virgin) {
+ if (match(Tok_Ident)) {
+ /*
+ This is a hack until we replace this "parser"
+ with the real one used in Qt Creator.
+ Is it still needed? mws 11/12/2018
+ */
+ if (lexeme() == "("
+ && ((previousLexeme() == "QT_PREPEND_NAMESPACE")
+ || (previousLexeme() == "NS"))) {
+ readToken();
+ readToken();
+ type.append(previousLexeme());
+ readToken();
+ } else
+ type.append(previousLexeme());
+ } else if (match(Tok_void) || match(Tok_int) || match(Tok_char) || match(Tok_double)
+ || match(Tok_Ellipsis)) {
+ type.append(previousLexeme());
+ } else {
+ return false;
+ }
+ } else if (match(Tok_int) || match(Tok_char) || match(Tok_double)) {
+ type.append(previousLexeme());
+ }
+
+ matchTemplateAngles(type);
+
+ while (match(Tok_const) || match(Tok_volatile))
+ type.append(previousLexeme());
+
+ if (match(Tok_Gulbrandsen))
+ type.append(previousLexeme());
+ else
+ break;
+ }
+
+ while (match(Tok_Ampersand) || match(Tok_Aster) || match(Tok_const) || match(Tok_Caret)
+ || match(Tok_Ellipsis))
+ type.append(previousLexeme());
+
+ if (match(Tok_LeftParenAster)) {
+ /*
+ A function pointer. This would be rather hard to handle without a
+ tokenizer hack, because a type can be followed with a left parenthesis
+ in some cases (e.g., 'operator int()'). The tokenizer recognizes '(*'
+ as a single token.
+ */
+ type.append(" "); // force a space after the type
+ type.append(previousLexeme());
+ type.appendHotspot();
+ if (match(Tok_Ident))
+ name = previousLexeme();
+ if (!match(Tok_RightParen))
+ return false;
+ type.append(previousLexeme());
+ if (!match(Tok_LeftParen))
+ return false;
+ type.append(previousLexeme());
+
+ /* parse the parameters. Ignore the parameter name from the type */
+ while (m_tok != Tok_RightParen && m_tok != Tok_Eoi) {
+ QString dummy;
+ if (!matchTypeAndName(type, dummy))
+ return false;
+ if (match(Tok_Comma))
+ type.append(previousLexeme());
+ }
+ if (!match(Tok_RightParen))
+ return false;
+ type.append(previousLexeme());
+ } else {
+ /*
+ The common case: Look for an optional identifier, then for
+ some array brackets.
+ */
+ type.appendHotspot();
+
+ if (match(Tok_Ident)) {
+ name = previousLexeme();
+ } else if (match(Tok_Comment)) {
+ /*
+ A neat hack: Commented-out parameter names are
+ recognized by qdoc. It's impossible to illustrate
+ here inside a C-style comment, because it requires
+ an asterslash. It's also impossible to illustrate
+ inside a C++-style comment, because the explanation
+ does not fit on one line.
+ */
+ auto match = s_varComment.match(previousLexeme());
+ if (match.hasMatch())
+ name = match.captured(1);
+ } else if (match(Tok_LeftParen)) {
+ name = "(";
+ while (m_tok != Tok_RightParen && m_tok != Tok_Eoi) {
+ name.append(lexeme());
+ readToken();
+ }
+ name.append(")");
+ readToken();
+ if (match(Tok_LeftBracket)) {
+ name.append("[");
+ while (m_tok != Tok_RightBracket && m_tok != Tok_Eoi) {
+ name.append(lexeme());
+ readToken();
+ }
+ name.append("]");
+ readToken();
+ }
+ }
+
+ if (m_tok == Tok_LeftBracket) {
+ int bracketDepth0 = m_tokenizer->bracketDepth();
+ while ((m_tokenizer->bracketDepth() >= bracketDepth0 && m_tok != Tok_Eoi)
+ || m_tok == Tok_RightBracket) {
+ type.append(lexeme());
+ readToken();
+ }
+ }
+ }
+ return true;
+}
+
+/*!
+ Parse the next function parameter, if there is one, and
+ append it to the internal parameter vector. Return true
+ if a parameter is parsed correctly. Otherwise return false.
+ */
+bool Parameters::matchParameter()
+{
+ if (match(Tok_QPrivateSignal)) {
+ m_privateSignal = true;
+ return true;
+ }
+
+ CodeChunk chunk;
+ QString name;
+ if (!matchTypeAndName(chunk, name))
+ return false;
+ QString type = chunk.toString();
+ QString defaultValue;
+ match(Tok_Comment);
+ if (match(Tok_Equal)) {
+ chunk.clear();
+ int pdepth = m_tokenizer->parenDepth();
+ while (m_tokenizer->parenDepth() >= pdepth
+ && (m_tok != Tok_Comma || (m_tokenizer->parenDepth() > pdepth))
+ && m_tok != Tok_Eoi) {
+ chunk.append(lexeme());
+ readToken();
+ }
+ defaultValue = chunk.toString();
+ }
+ append(type, name, defaultValue);
+ return true;
+}
+
+/*!
+ This function uses a Tokenizer to parse the \a signature,
+ which is a comma-separated list of parameter declarations.
+ If an error is detected, the Parameters object is cleared
+ and \c false is returned. Otherwise \c true is returned.
+ */
+bool Parameters::parse(const QString &signature)
+{
+ Tokenizer *outerTokenizer = m_tokenizer;
+ int outerTok = m_tok;
+
+ QByteArray latin1 = signature.toLatin1();
+ Tokenizer stringTokenizer(Location(), latin1);
+ stringTokenizer.setParsingFnOrMacro(true);
+ m_tokenizer = &stringTokenizer;
+
+ readToken();
+ do {
+ if (!matchParameter()) {
+ m_parameters.clear();
+ m_valid = false;
+ break;
+ }
+ } while (match(Tok_Comma));
+
+ m_tokenizer = outerTokenizer;
+ m_tok = outerTok;
+ return m_valid;
+}
+
+/*!
+ Append a Parameter constructed from \a type, \a name, and \a value
+ to the parameter vector.
+ */
+void Parameters::append(const QString &type, const QString &name, const QString &value)
+{
+ m_parameters.append(Parameter(type, name, value));
+}
+
+/*!
+ Returns the list of reconstructed parameters. If \a includeValues
+ is true, the default values are included, if any are present.
+ */
+QString Parameters::signature(bool includeValues) const
+{
+ QString result;
+ if (!m_parameters.empty()) {
+ for (int i = 0; i < m_parameters.size(); i++) {
+ if (i > 0)
+ result += ", ";
+ result += m_parameters.at(i).signature(includeValues);
+ }
+ }
+ return result;
+}
+
+/*!
+ Returns the signature of all the parameters with all the
+ spaces and commas removed. It is unintelligible, but that
+ is what the caller wants.
+
+ If \a names is true, the parameter names are included. If
+ \a values is true, the default values are included.
+ */
+QString Parameters::rawSignature(bool names, bool values) const
+{
+ QString raw;
+ const auto params = m_parameters;
+ for (const auto &parameter : params) {
+ raw += parameter.type();
+ if (names)
+ raw += parameter.name();
+ if (values)
+ raw += parameter.defaultValue();
+ }
+ return raw;
+}
+
+/*!
+ Parse the parameter \a signature by splitting the string,
+ and store the individual parameters in the parameter vector.
+
+ This method of parsing is naive but sufficient for QML methods
+ and macros.
+ */
+void Parameters::set(const QString &signature)
+{
+ clear();
+ if (!signature.isEmpty()) {
+ QStringList commaSplit = signature.split(',');
+ m_parameters.resize(commaSplit.size());
+ int i = 0;
+ for (const auto &item : std::as_const(commaSplit)) {
+ QStringList blankSplit = item.split(' ', Qt::SkipEmptyParts);
+ QString pDefault;
+ qsizetype defaultIdx = blankSplit.indexOf(QStringLiteral("="));
+ if (defaultIdx != -1) {
+ if (++defaultIdx < blankSplit.size())
+ pDefault = blankSplit.mid(defaultIdx).join(' ');
+ blankSplit = blankSplit.mid(0, defaultIdx - 1);
+ }
+ QString pName = blankSplit.takeLast();
+ QString pType = blankSplit.join(' ');
+ if (pType.isEmpty() && pName == QLatin1String("..."))
+ qSwap(pType, pName);
+ else {
+ int j = 0;
+ while (j < pName.size() && !pName.at(j).isLetter())
+ j++;
+ if (j > 0) {
+ pType += QChar(' ') + pName.left(j);
+ pName = pName.mid(j);
+ }
+ }
+ m_parameters[i++].set(pType, pName, pDefault);
+ }
+ }
+}
+
+/*!
+ Insert all the parameter names into names.
+ */
+QSet<QString> Parameters::getNames() const
+{
+ QSet<QString> names;
+ const auto params = m_parameters;
+ for (const auto &parameter : params) {
+ if (!parameter.name().isEmpty())
+ names.insert(parameter.name());
+ }
+ return names;
+}
+
+/*!
+ Construct a list of the parameter types and return it.
+ */
+QString Parameters::generateTypeList() const
+{
+ QString out;
+ if (count() > 0) {
+ for (int i = 0; i < count(); ++i) {
+ if (i > 0)
+ out += ", ";
+ out += m_parameters.at(i).type();
+ }
+ }
+ return out;
+}
+
+/*!
+ Construct a list of the parameter type/name pairs and
+ return it.
+*/
+QString Parameters::generateTypeAndNameList() const
+{
+ QString out;
+ if (count() > 0) {
+ for (int i = 0; i < count(); ++i) {
+ if (i != 0)
+ out += ", ";
+ const Parameter &p = m_parameters.at(i);
+ out += p.type();
+ if (out[out.size() - 1].isLetterOrNumber())
+ out += QLatin1Char(' ');
+ out += p.name();
+ }
+ }
+ return out;
+}
+
+/*!
+ Returns true if \a parameters contains the same parameter
+ signature as this.
+ */
+bool Parameters::match(const Parameters &parameters) const
+{
+ if (count() != parameters.count())
+ return false;
+ if (count() == 0)
+ return true;
+ for (int i = 0; i < count(); i++) {
+ if (parameters.at(i).type() != m_parameters.at(i).type())
+ return false;
+ }
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/parameters.h b/src/qdoc/qdoc/src/qdoc/parameters.h
new file mode 100644
index 000000000..1417b0958
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/parameters.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef PARAMETERS_H
+#define PARAMETERS_H
+
+#include <QtCore/qlist.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qset.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+class Location;
+class Tokenizer;
+class CodeChunk;
+
+class Parameter
+{
+public:
+ Parameter() = default;
+ explicit Parameter(QString type, QString name = QString(), QString defaultValue = QString())
+ : m_type(std::move(type)), m_name(std::move(name)), m_defaultValue(std::move(defaultValue))
+ {
+ }
+
+ void setName(const QString &name) { m_name = name; }
+ [[nodiscard]] bool hasType() const { return !m_type.isEmpty(); }
+ [[nodiscard]] const QString &type() const { return m_type; }
+ [[nodiscard]] const QString &name() const { return m_name; }
+ [[nodiscard]] const QString &defaultValue() const { return m_defaultValue; }
+ void setDefaultValue(const QString &t) { m_defaultValue = t; }
+
+ void set(const QString &type, const QString &name, const QString &defaultValue = QString())
+ {
+ m_type = type;
+ m_name = name;
+ m_defaultValue = defaultValue;
+ }
+
+ [[nodiscard]] QString signature(bool includeValue = false) const;
+
+ [[nodiscard]] const QString &canonicalType() const { return m_canonicalType; }
+ void setCanonicalType(const QString &t) { m_canonicalType = t; }
+
+public:
+ QString m_canonicalType {};
+ QString m_type {};
+ QString m_name {};
+ QString m_defaultValue {};
+};
+
+typedef QList<Parameter> ParameterVector;
+
+class Parameters
+{
+public:
+ Parameters();
+ Parameters(const QString &signature); // TODO: Making this explicit breaks QDoc
+
+ void clear()
+ {
+ m_parameters.clear();
+ m_privateSignal = false;
+ m_valid = true;
+ }
+ [[nodiscard]] const ParameterVector &parameters() const { return m_parameters; }
+ [[nodiscard]] bool isPrivateSignal() const { return m_privateSignal; }
+ [[nodiscard]] bool isEmpty() const { return m_parameters.isEmpty(); }
+ [[nodiscard]] bool isValid() const { return m_valid; }
+ [[nodiscard]] int count() const { return m_parameters.size(); }
+ void reserve(int count) { m_parameters.reserve(count); }
+ [[nodiscard]] const Parameter &at(int i) const { return m_parameters.at(i); }
+ Parameter &last() { return m_parameters.last(); }
+ [[nodiscard]] const Parameter &last() const { return m_parameters.last(); }
+ inline Parameter &operator[](int index) { return m_parameters[index]; }
+ void append(const QString &type, const QString &name, const QString &value);
+ void append(const QString &type, const QString &name) { append(type, name, QString()); }
+ void append(const QString &type) { append(type, QString(), QString()); }
+ void pop_back() { m_parameters.pop_back(); }
+ void setPrivateSignal() { m_privateSignal = true; }
+ [[nodiscard]] QString signature(bool includeValues = false) const;
+ [[nodiscard]] QString rawSignature(bool names = false, bool values = false) const;
+ void set(const QString &signature);
+ [[nodiscard]] QSet<QString> getNames() const;
+ [[nodiscard]] QString generateTypeList() const;
+ [[nodiscard]] QString generateTypeAndNameList() const;
+ [[nodiscard]] bool match(const Parameters &parameters) const;
+
+private:
+ void readToken();
+ QString lexeme();
+ QString previousLexeme();
+ bool match(int target);
+ void matchTemplateAngles(CodeChunk &type);
+ bool matchTypeAndName(CodeChunk &type, QString &name);
+ bool matchParameter();
+ bool parse(const QString &signature);
+
+private:
+ static QRegularExpression s_varComment;
+
+ bool m_valid {};
+ bool m_privateSignal {};
+ int m_tok {};
+ Tokenizer *m_tokenizer { nullptr };
+ ParameterVector m_parameters;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/parsererror.cpp b/src/qdoc/qdoc/src/qdoc/parsererror.cpp
new file mode 100644
index 000000000..b55660572
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/parsererror.cpp
@@ -0,0 +1,90 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "parsererror.h"
+#include "node.h"
+#include "qdocdatabase.h"
+#include "config.h"
+#include "utilities.h"
+
+#include <QtCore/qregularexpression.h>
+
+using namespace Qt::Literals::StringLiterals;
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class FnMatchError
+ \brief Encapsulates information about \fn match error during parsing.
+*/
+
+/*!
+ \variable FnMatchError::signature
+
+ Signature for the \fn topic that failed to match.
+*/
+
+/*!
+ \relates FnMatchError
+
+ Returns \c true if any parent of a C++ function represented by
+ \a signature is documented as \\internal.
+*/
+bool isParentInternal(const QString &signature)
+{
+ const QRegularExpression scoped_fn{R"((?:\w+(?:<[^>]+>)?::)+~?\w\S*\()"};
+ auto match = scoped_fn.match(signature);
+ if (!match.isValid())
+ return false;
+
+ auto scope = match.captured().split("::"_L1);
+ scope.removeLast(); // Drop function name
+
+ for (auto &s : scope)
+ if (qsizetype pos = s.indexOf('<'); pos >= 0)
+ s.truncate(pos);
+
+ auto parent = QDocDatabase::qdocDB()->findNodeByNameAndType(scope, &Node::isCppNode);
+ if (parent && !(parent->isClassNode() || parent->isNamespace())) {
+ qCDebug(lcQdoc).noquote()
+ << "Invalid scope:" << qPrintable(parent->nodeTypeString())
+ << qPrintable(parent->fullName())
+ << "for \\fn" << qPrintable(signature);
+ return false;
+ }
+
+ while (parent) {
+ if (parent->isInternal())
+ return true;
+ parent = parent->parent();
+ }
+
+ return false;
+}
+
+/*!
+ \class ParserErrorHandler
+ \brief Processes parser errors and outputs warnings for them.
+*/
+
+/*!
+ Generates a warning specific to FnMatchError.
+
+ Warnings for internal documentation are omitted. Specifically, this
+ (omission) happens if:
+
+ \list
+ \li \c {--showinternal} command line option is \b not
+ used, and
+ \li The warning is for an \\fn that is declared
+ under a namespace/class that is documented as
+ \\internal.
+ \endlist
+*/
+void ParserErrorHandler::operator()(const FnMatchError &e) const
+{
+ if (Config::instance().showInternal() || !isParentInternal(e.signature))
+ e.location.warning("Failed to find function when parsing \\fn %1"_L1.arg(e.signature));
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/parsererror.h b/src/qdoc/qdoc/src/qdoc/parsererror.h
new file mode 100644
index 000000000..85435366f
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/parsererror.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef PARSERERROR_H
+#define PARSERERROR_H
+
+#include "location.h"
+
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+struct FnMatchError {
+ QString signature {};
+ Location location {};
+
+};
+
+struct ParserErrorHandler
+{
+ void operator()(const FnMatchError &e) const;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/propertynode.cpp b/src/qdoc/qdoc/src/qdoc/propertynode.cpp
new file mode 100644
index 000000000..6607af5bd
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/propertynode.cpp
@@ -0,0 +1,135 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "propertynode.h"
+
+#include "aggregate.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class PropertyNode
+
+ This class describes one instance of using the Q_PROPERTY macro.
+ */
+
+/*!
+ The constructor sets the \a parent and the \a name, but
+ everything else is left to default values.
+ */
+PropertyNode::PropertyNode(Aggregate *parent, const QString &name) : Node(Property, parent, name)
+{
+ // nothing
+}
+
+
+/*!
+ Returns a string representing an access function \a role.
+*/
+QString PropertyNode::roleName(FunctionRole role)
+{
+ switch (role) {
+ case FunctionRole::Getter:
+ return "getter";
+ case FunctionRole::Setter:
+ return "setter";
+ case FunctionRole::Resetter:
+ return "resetter";
+ case FunctionRole::Notifier:
+ return "notifier";
+ case FunctionRole::Bindable:
+ return "bindable";
+ default:
+ break;
+ }
+ return QString();
+}
+
+/*!
+ Sets this property's \e {overridden from} property to
+ \a baseProperty, which indicates that this property
+ overrides \a baseProperty. To begin with, all the values
+ in this property are set to the corresponding values in
+ \a baseProperty.
+
+ We probably should ensure that the constant and final
+ attributes are not being overridden improperly.
+ */
+void PropertyNode::setOverriddenFrom(const PropertyNode *baseProperty)
+{
+ for (qsizetype i{0}; i < (qsizetype)FunctionRole::NumFunctionRoles; ++i) {
+ if (m_functions[i].isEmpty())
+ m_functions[i] = baseProperty->m_functions[i];
+ }
+ if (m_stored == FlagValueDefault)
+ m_stored = baseProperty->m_stored;
+ if (m_writable == FlagValueDefault)
+ m_writable = baseProperty->m_writable;
+ if (m_user == FlagValueDefault)
+ m_user = baseProperty->m_user;
+ m_overrides = baseProperty;
+}
+
+/*!
+ Returns a string containing the data type qualified with "const" either
+ prepended to the data type or appended to it, or without the const
+ qualification, depending circumstances in the PropertyNode internal state.
+ */
+QString PropertyNode::qualifiedDataType() const
+{
+ if (m_propertyType != PropertyType::StandardProperty || m_type.startsWith(QLatin1String("const ")))
+ return m_type;
+
+ if (setters().isEmpty() && resetters().isEmpty()) {
+ if (m_type.contains(QLatin1Char('*')) || m_type.contains(QLatin1Char('&'))) {
+ // 'QWidget *' becomes 'QWidget *' const
+ return m_type + " const";
+ } else {
+ /*
+ 'int' becomes 'const int' ('int const' is
+ correct C++, but looks wrong)
+ */
+ return "const " + m_type;
+ }
+ } else {
+ return m_type;
+ }
+}
+
+/*!
+ Returns true if this property has an access function named \a name.
+ */
+bool PropertyNode::hasAccessFunction(const QString &name) const
+{
+ for (const auto &getter : getters()) {
+ if (getter->name() == name)
+ return true;
+ }
+ for (const auto &setter : setters()) {
+ if (setter->name() == name)
+ return true;
+ }
+ for (const auto &resetter : resetters()) {
+ if (resetter->name() == name)
+ return true;
+ }
+ for (const auto &notifier : notifiers()) {
+ if (notifier->name() == name)
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Returns the role of \a functionNode for this property.
+ */
+PropertyNode::FunctionRole PropertyNode::role(const FunctionNode *functionNode) const
+{
+ for (qsizetype i{0}; i < (qsizetype)FunctionRole::NumFunctionRoles; i++) {
+ if (m_functions[i].contains(const_cast<FunctionNode *>(functionNode)))
+ return (FunctionRole)i;
+ }
+ return FunctionRole::Notifier; // TODO: Figure out a better way to handle 'not found'.
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/propertynode.h b/src/qdoc/qdoc/src/qdoc/propertynode.h
new file mode 100644
index 000000000..9ae59932b
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/propertynode.h
@@ -0,0 +1,93 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef PROPERTYNODE_H
+#define PROPERTYNODE_H
+
+#include "functionnode.h"
+#include "node.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+class PropertyNode : public Node
+{
+public:
+ enum class PropertyType { StandardProperty, BindableProperty };
+ enum class FunctionRole { Getter, Setter, Resetter, Notifier, Bindable, NumFunctionRoles };
+ static QString roleName(FunctionRole role);
+
+ PropertyNode(Aggregate *parent, const QString &name);
+
+ void setDataType(const QString &dataType) override { m_type = dataType; }
+ void addFunction(FunctionNode *function, FunctionRole role);
+ void addSignal(FunctionNode *function, FunctionRole role);
+ void setStored(bool stored) { m_stored = toFlagValue(stored); }
+ void setWritable(bool writable) { m_writable = toFlagValue(writable); }
+ void setOverriddenFrom(const PropertyNode *baseProperty);
+ void setConstant() { m_const = true; }
+ void setRequired() { m_required = true; }
+ void setPropertyType(PropertyType type) { m_propertyType = type; }
+
+ [[nodiscard]] const QString &dataType() const { return m_type; }
+ [[nodiscard]] QString qualifiedDataType() const;
+ [[nodiscard]] NodeList functions() const;
+ [[nodiscard]] const NodeList &functions(FunctionRole role) const
+ {
+ return m_functions[(int)role];
+ }
+ [[nodiscard]] const NodeList &getters() const { return functions(FunctionRole::Getter); }
+ [[nodiscard]] const NodeList &setters() const { return functions(FunctionRole::Setter); }
+ [[nodiscard]] const NodeList &resetters() const { return functions(FunctionRole::Resetter); }
+ [[nodiscard]] const NodeList &notifiers() const { return functions(FunctionRole::Notifier); }
+ [[nodiscard]] bool hasAccessFunction(const QString &name) const;
+ FunctionRole role(const FunctionNode *functionNode) const;
+ [[nodiscard]] bool isStored() const { return fromFlagValue(m_stored, storedDefault()); }
+ [[nodiscard]] bool isWritable() const { return fromFlagValue(m_writable, writableDefault()); }
+ [[nodiscard]] bool isConstant() const { return m_const; }
+ [[nodiscard]] bool isRequired() const { return m_required; }
+ [[nodiscard]] PropertyType propertyType() const { return m_propertyType; }
+ [[nodiscard]] const PropertyNode *overriddenFrom() const { return m_overrides; }
+
+ [[nodiscard]] bool storedDefault() const { return true; }
+ [[nodiscard]] bool writableDefault() const { return !setters().isEmpty(); }
+
+private:
+ QString m_type {};
+ PropertyType m_propertyType { PropertyType::StandardProperty };
+ NodeList m_functions[(qsizetype)FunctionRole::NumFunctionRoles] {};
+ FlagValue m_stored { FlagValueDefault };
+ FlagValue m_writable { FlagValueDefault };
+ FlagValue m_user { FlagValueDefault };
+ bool m_const { false };
+ bool m_required { false };
+ const PropertyNode *m_overrides { nullptr };
+};
+
+inline void PropertyNode::addFunction(FunctionNode *function, FunctionRole role)
+{
+ m_functions[(int)role].append(function);
+ function->addAssociatedProperty(this);
+}
+
+inline void PropertyNode::addSignal(FunctionNode *function, FunctionRole role)
+{
+ m_functions[(int)role].append(function);
+ function->addAssociatedProperty(this);
+}
+
+inline NodeList PropertyNode::functions() const
+{
+ NodeList list;
+ for (qsizetype i{0}; i < (qsizetype)FunctionRole::NumFunctionRoles; ++i)
+ list += m_functions[i];
+ return list;
+}
+
+QT_END_NAMESPACE
+
+#endif // PROPERTYNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/proxynode.cpp b/src/qdoc/qdoc/src/qdoc/proxynode.cpp
new file mode 100644
index 000000000..49e4be34e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/proxynode.cpp
@@ -0,0 +1,54 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "proxynode.h"
+
+#include "tree.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class ProxyNode
+ \brief A class for representing an Aggregate that is documented in a different module.
+
+ This class is used to represent an Aggregate (usually a class)
+ that is located and documented in a different module. In the
+ current module, a ProxyNode holds child nodes that are related
+ to the class in the other module.
+
+ For example, class QHash is located and documented in QtCore.
+ There are many global functions named qHash() in QtCore that
+ are all related to class QHash using the \c relates command.
+ There are also a few qHash() function in QtNetwork that are
+ related to QHash. These functions must be documented when the
+ documentation for QtNetwork is generated, but the reference
+ page for QHash must link to that documentation in its related
+ nonmembers list.
+
+ The ProxyNode allows qdoc to construct links to the related
+ functions (or other things?) in QtNetwork from the reference
+ page in QtCore.
+ */
+
+/*!
+ Constructs the ProxyNode, which at this point looks like any
+ other Aggregate, and then finds the Tree this node is in and
+ appends this node to that Tree's proxy list so it will be
+ easy to find later.
+ */
+ProxyNode::ProxyNode(Aggregate *parent, const QString &name) : Aggregate(Node::Proxy, parent, name)
+{
+ tree()->appendProxy(this);
+}
+
+/*! \fn bool ProxyNode::docMustBeGenerated() const
+ Returns true because a ProxyNode always means some documentation
+ must be generated.
+*/
+
+/*! \fn bool ProxyNode::isRelatableType() const
+ Returns true because the ProxyNode exists so that elements
+ can be related to it with the \c {\\relates} command.
+*/
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/proxynode.h b/src/qdoc/qdoc/src/qdoc/proxynode.h
new file mode 100644
index 000000000..cced34892
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/proxynode.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef PROXYNODE_H
+#define PROXYNODE_H
+
+#include "aggregate.h"
+
+#include <QtCore/qglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class ProxyNode : public Aggregate
+{
+public:
+ ProxyNode(Aggregate *parent, const QString &name);
+ [[nodiscard]] bool docMustBeGenerated() const override { return true; }
+ [[nodiscard]] bool isRelatableType() const override { return true; }
+};
+
+QT_END_NAMESPACE
+
+#endif // PROXYNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/puredocparser.cpp b/src/qdoc/qdoc/src/qdoc/puredocparser.cpp
new file mode 100644
index 000000000..c6de06a9c
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/puredocparser.cpp
@@ -0,0 +1,74 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "puredocparser.h"
+
+#include "qdocdatabase.h"
+#include "tokenizer.h"
+
+#include <cerrno>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Parses the source file identified by \a filePath and adds its
+ parsed contents to the database. The \a location is used for
+ reporting errors.
+ */
+std::vector<UntiedDocumentation> PureDocParser::parse_qdoc_file(const QString &filePath)
+{
+ QFile in(filePath);
+ if (!in.open(QIODevice::ReadOnly)) {
+ location.error(
+ QStringLiteral("Can't open source file '%1' (%2)").arg(filePath, strerror(errno)));
+ return {};
+ }
+
+ return processQdocComments(in);
+}
+
+/*!
+ This is called by parseSourceFile() to do the actual parsing
+ and tree building. It only processes qdoc comments. It skips
+ everything else.
+ */
+std::vector<UntiedDocumentation> PureDocParser::processQdocComments(QFile& input_file)
+{
+ std::vector<UntiedDocumentation> untied{};
+
+ Tokenizer tokenizer(Location{input_file.fileName()}, input_file);
+
+ const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
+
+ int token = tokenizer.getToken();
+ while (token != Tok_Eoi) {
+ if (token != Tok_Doc) {
+ token = tokenizer.getToken();
+ continue;
+ }
+ QString comment = tokenizer.lexeme(); // returns an entire qdoc comment.
+ Location start_loc(tokenizer.location());
+ token = tokenizer.getToken();
+
+ Doc::trimCStyleComment(start_loc, comment);
+ Location end_loc(tokenizer.location());
+
+ // Doc constructor parses the comment.
+ Doc doc(start_loc, end_loc, comment, commands, CppCodeParser::topic_commands);
+ if (doc.topicsUsed().isEmpty()) {
+ doc.location().warning(QStringLiteral("This qdoc comment contains no topic command "
+ "(e.g., '\\%1', '\\%2').")
+ .arg(COMMAND_MODULE, COMMAND_PAGE));
+ continue;
+ }
+
+ if (hasTooManyTopics(doc))
+ continue;
+
+ untied.emplace_back(UntiedDocumentation{doc, QStringList()});
+ }
+
+ return untied;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/puredocparser.h b/src/qdoc/qdoc/src/qdoc/puredocparser.h
new file mode 100644
index 000000000..d3467ed92
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/puredocparser.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef PUREDOCPARSER_H
+#define PUREDOCPARSER_H
+
+#include "cppcodeparser.h"
+
+#include <QtCore/QFile>
+
+QT_BEGIN_NAMESPACE
+
+class Location;
+
+class PureDocParser
+{
+public:
+ PureDocParser(const Location& location) : location{location} {}
+
+ std::vector<UntiedDocumentation> parse_qdoc_file(const QString& filePath);
+
+private:
+ std::vector<UntiedDocumentation> processQdocComments(QFile& input_file);
+
+private:
+ const Location& location;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.cpp b/src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.cpp
new file mode 100644
index 000000000..6586056a4
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.cpp
@@ -0,0 +1,177 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qdoccommandlineparser.h"
+
+#include "utilities.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qfile.h>
+
+QDocCommandLineParser::QDocCommandLineParser()
+ : QCommandLineParser(),
+ defineOption(QStringList() << QStringLiteral("D")),
+ dependsOption(QStringList() << QStringLiteral("depends")),
+ highlightingOption(QStringList() << QStringLiteral("highlighting")),
+ showInternalOption(QStringList() << QStringLiteral("showinternal")),
+ redirectDocumentationToDevNullOption(QStringList()
+ << QStringLiteral("redirect-documentation-to-dev-null")),
+ noExamplesOption(QStringList() << QStringLiteral("no-examples")),
+ indexDirOption(QStringList() << QStringLiteral("indexdir")),
+ installDirOption(QStringList() << QStringLiteral("installdir")),
+ outputDirOption(QStringList() << QStringLiteral("outputdir")),
+ outputFormatOption(QStringList() << QStringLiteral("outputformat")),
+ noLinkErrorsOption(QStringList() << QStringLiteral("no-link-errors")),
+ autoLinkErrorsOption(QStringList() << QStringLiteral("autolink-errors")),
+ debugOption(QStringList() << QStringLiteral("debug")),
+ atomsDumpOption("atoms-dump"),
+ prepareOption(QStringList() << QStringLiteral("prepare")),
+ generateOption(QStringList() << QStringLiteral("generate")),
+ logProgressOption(QStringList() << QStringLiteral("log-progress")),
+ singleExecOption(QStringList() << QStringLiteral("single-exec")),
+ includePathOption("I", "Add dir to the include path for header files.", "path"),
+ includePathSystemOption("isystem", "Add dir to the system include path for header files.",
+ "path"),
+ frameworkOption("F", "Add macOS framework to the include path for header files.",
+ "framework"),
+ timestampsOption(QStringList() << QStringLiteral("timestamps")),
+ useDocBookExtensions(QStringList() << QStringLiteral("docbook-extensions"))
+{
+ setApplicationDescription(QStringLiteral("Qt documentation generator"));
+ addHelpOption();
+ addVersionOption();
+
+ setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
+
+ addPositionalArgument("file1.qdocconf ...", QStringLiteral("Input files"));
+
+ defineOption.setDescription(
+ QStringLiteral("Define the argument as a macro while parsing sources"));
+ defineOption.setValueName(QStringLiteral("macro[=def]"));
+ addOption(defineOption);
+
+ dependsOption.setDescription(QStringLiteral("Specify dependent modules"));
+ dependsOption.setValueName(QStringLiteral("module"));
+ addOption(dependsOption);
+
+ highlightingOption.setDescription(
+ QStringLiteral("Turn on syntax highlighting (makes qdoc run slower)"));
+ addOption(highlightingOption);
+
+ showInternalOption.setDescription(QStringLiteral("Include content marked internal"));
+ addOption(showInternalOption);
+
+ redirectDocumentationToDevNullOption.setDescription(
+ QStringLiteral("Save all documentation content to /dev/null. "
+ " Useful if someone is interested in qdoc errors only."));
+ addOption(redirectDocumentationToDevNullOption);
+
+ noExamplesOption.setDescription(QStringLiteral("Do not generate documentation for examples"));
+ addOption(noExamplesOption);
+
+ indexDirOption.setDescription(
+ QStringLiteral("Specify a directory where QDoc should search for index files to load"));
+ indexDirOption.setValueName(QStringLiteral("dir"));
+ addOption(indexDirOption);
+
+ installDirOption.setDescription(QStringLiteral(
+ "Specify the directory where the output will be after running \"make install\""));
+ installDirOption.setValueName(QStringLiteral("dir"));
+ addOption(installDirOption);
+
+ outputDirOption.setDescription(
+ QStringLiteral("Specify output directory, overrides setting in qdocconf file"));
+ outputDirOption.setValueName(QStringLiteral("dir"));
+ addOption(outputDirOption);
+
+ outputFormatOption.setDescription(
+ QStringLiteral("Specify output format, overrides setting in qdocconf file"));
+ outputFormatOption.setValueName(QStringLiteral("format"));
+ addOption(outputFormatOption);
+
+ noLinkErrorsOption.setDescription(
+ QStringLiteral("Do not print link errors (i.e. missing targets)"));
+ addOption(noLinkErrorsOption);
+
+ autoLinkErrorsOption.setDescription(QStringLiteral("Show errors when automatic linking fails"));
+ addOption(autoLinkErrorsOption);
+
+ debugOption.setDescription(QStringLiteral("Enable debug output"));
+ addOption(debugOption);
+
+ atomsDumpOption.setDescription(QStringLiteral(
+ "Shows a human-readable form of the intermediate result of parsing a block-comment."));
+ addOption(atomsDumpOption);
+
+ prepareOption.setDescription(
+ QStringLiteral("Run qdoc only to generate an index file, not the docs"));
+ addOption(prepareOption);
+
+ generateOption.setDescription(
+ QStringLiteral("Run qdoc to read the index files and generate the docs"));
+ addOption(generateOption);
+
+ logProgressOption.setDescription(QStringLiteral("Log progress on stderr."));
+ addOption(logProgressOption);
+
+ singleExecOption.setDescription(QStringLiteral("Run qdoc once over all the qdoc conf files."));
+ addOption(singleExecOption);
+
+ includePathOption.setFlags(QCommandLineOption::ShortOptionStyle);
+ addOption(includePathOption);
+
+ addOption(includePathSystemOption);
+
+ frameworkOption.setFlags(QCommandLineOption::ShortOptionStyle);
+ addOption(frameworkOption);
+
+ timestampsOption.setDescription(QStringLiteral("Timestamp each qdoc log line."));
+ addOption(timestampsOption);
+
+ useDocBookExtensions.setDescription(
+ QStringLiteral("Use the DocBook Library extensions for metadata."));
+ addOption(useDocBookExtensions);
+}
+
+/*!
+ * \internal
+ *
+ * Create a list of arguments from the command line and/or file(s).
+ * This lets QDoc accept arguments contained in a file provided as a
+ * command-line argument prepended by '@'.
+ */
+static QStringList argumentsFromCommandLineAndFile(const QStringList &arguments)
+{
+ QStringList allArguments;
+ allArguments.reserve(arguments.size());
+ for (const QString &argument : arguments) {
+ // "@file" doesn't start with a '-' so we can't use QCommandLineParser for it
+ if (argument.startsWith(QLatin1Char('@'))) {
+ QString optionsFile = argument;
+ optionsFile.remove(0, 1);
+ if (optionsFile.isEmpty())
+ qFatal("The @ option requires an input file");
+ QFile f(optionsFile);
+ if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
+ qFatal("Cannot open options file specified with @: %ls",
+ qUtf16Printable(optionsFile));
+ while (!f.atEnd()) {
+ QString line = QString::fromLocal8Bit(f.readLine().trimmed());
+ if (!line.isEmpty())
+ allArguments << line;
+ }
+ } else {
+ allArguments << argument;
+ }
+ }
+ return allArguments;
+}
+
+void QDocCommandLineParser::process(const QStringList &arguments)
+{
+ auto allArguments = argumentsFromCommandLineAndFile(arguments);
+ QCommandLineParser::process(allArguments);
+
+ if (isSet(singleExecOption) && isSet(indexDirOption))
+ qCWarning(lcQdoc) << "Warning: -indexdir option ignored: Index files are not used in single-exec mode.";
+}
diff --git a/src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.h b/src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.h
new file mode 100644
index 000000000..57b36b582
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QDOCCOMMANDLINEPARSER_H
+#define QDOCCOMMANDLINEPARSER_H
+
+#include <QtCore/qcommandlineparser.h>
+
+QT_BEGIN_NAMESPACE
+
+struct QDocCommandLineParser : public QCommandLineParser
+{
+ QDocCommandLineParser();
+ void process(const QStringList &arguments);
+
+ QCommandLineOption defineOption, dependsOption, highlightingOption;
+ QCommandLineOption showInternalOption, redirectDocumentationToDevNullOption;
+ QCommandLineOption noExamplesOption, indexDirOption, installDirOption;
+ QCommandLineOption outputDirOption, outputFormatOption;
+ QCommandLineOption noLinkErrorsOption, autoLinkErrorsOption, debugOption, atomsDumpOption;
+ QCommandLineOption prepareOption, generateOption, logProgressOption, singleExecOption;
+ QCommandLineOption includePathOption, includePathSystemOption, frameworkOption;
+ QCommandLineOption timestampsOption, useDocBookExtensions;
+};
+
+QT_END_NAMESPACE
+
+#endif // QDOCCOMMANDLINEPARSER_H
diff --git a/src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp b/src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp
new file mode 100644
index 000000000..57e88fbde
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp
@@ -0,0 +1,1685 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qdocdatabase.h"
+
+#include "atom.h"
+#include "collectionnode.h"
+#include "functionnode.h"
+#include "generator.h"
+#include "qdocindexfiles.h"
+#include "tree.h"
+
+#include <QtCore/qregularexpression.h>
+#include <stack>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+static NodeMultiMap emptyNodeMultiMap_;
+
+/*!
+ \class QDocForest
+
+ A class representing a forest of Tree objects.
+
+ This private class manages a collection of Tree objects (a
+ forest) for the singleton QDocDatabase object. It is only
+ accessed by that singleton QDocDatabase object, which is a
+ friend. Each tree in the forest is an instance of class
+ Tree, which is a mostly private class. Both QDocForest and
+ QDocDatabase are friends of Tree and have full access.
+
+ There are two kinds of trees in the forest, differing not
+ in structure but in use. One Tree is the primary tree. It
+ is the tree representing the module being documented. All
+ the other trees in the forest are called index trees. Each
+ one represents the contents of the index file for one of
+ the modules the current module must be able to link to.
+
+ The instances of subclasses of Node in the primary tree
+ will contain documentation in an instance of Doc. The
+ index trees contain no documentation, and each Node in
+ an index tree is marked as an index node.
+
+ Each tree is named with the name of its module.
+
+ The search order is created by searchOrder(), if it has
+ not already been created. The search order and module
+ names arrays have parallel structure, i.e. modulNames_[i]
+ is the module name of the Tree at searchOrder_[i].
+
+ The primary tree is always the first tree in the search
+ order. i.e., when the database is searched, the primary
+ tree is always searched first, unless a specific tree is
+ being searched.
+ */
+
+/*!
+ Destroys the qdoc forest. This requires deleting
+ each Tree in the forest. Note that the forest has
+ been transferred into the search order array, so
+ what is really being used to destroy the forest
+ is the search order array.
+ */
+QDocForest::~QDocForest()
+{
+ for (auto *entry : m_searchOrder)
+ delete entry;
+ m_forest.clear();
+ m_searchOrder.clear();
+ m_indexSearchOrder.clear();
+ m_moduleNames.clear();
+ m_primaryTree = nullptr;
+}
+
+/*!
+ Initializes the forest prior to a traversal and
+ returns a pointer to the primary tree. If the
+ forest is empty, it returns \nullptr.
+ */
+Tree *QDocForest::firstTree()
+{
+ m_currentIndex = 0;
+ return (!searchOrder().isEmpty() ? searchOrder()[0] : nullptr);
+}
+
+/*!
+ Increments the forest's current tree index. If the current
+ tree index is still within the forest, the function returns
+ the pointer to the current tree. Otherwise it returns \nullptr.
+ */
+Tree *QDocForest::nextTree()
+{
+ ++m_currentIndex;
+ return (m_currentIndex < searchOrder().size() ? searchOrder()[m_currentIndex] : nullptr);
+}
+
+/*!
+ \fn Tree *QDocForest::primaryTree()
+
+ Returns the pointer to the primary tree.
+ */
+
+/*!
+ Finds the tree for module \a t in the forest and
+ sets the primary tree to be that tree. After the
+ primary tree is set, that tree is removed from the
+ forest.
+
+ \node It gets re-inserted into the forest after the
+ search order is built.
+ */
+void QDocForest::setPrimaryTree(const QString &t)
+{
+ QString T = t.toLower();
+ m_primaryTree = findTree(T);
+ m_forest.remove(T);
+ if (m_primaryTree == nullptr)
+ qCCritical(lcQdoc) << "Error: Could not set primary tree to" << t;
+}
+
+/*!
+ If the search order array is empty, create the search order.
+ If the search order array is not empty, do nothing.
+ */
+void QDocForest::setSearchOrder(const QStringList &t)
+{
+ if (!m_searchOrder.isEmpty())
+ return;
+
+ /* Allocate space for the search order. */
+ m_searchOrder.reserve(m_forest.size() + 1);
+ m_searchOrder.clear();
+ m_moduleNames.reserve(m_forest.size() + 1);
+ m_moduleNames.clear();
+
+ /* The primary tree is always first in the search order. */
+ QString primaryName = primaryTree()->physicalModuleName();
+ m_searchOrder.append(m_primaryTree);
+ m_moduleNames.append(primaryName);
+ m_forest.remove(primaryName);
+
+ for (const QString &m : t) {
+ if (primaryName != m) {
+ auto it = m_forest.find(m);
+ if (it != m_forest.end()) {
+ m_searchOrder.append(it.value());
+ m_moduleNames.append(m);
+ m_forest.remove(m);
+ }
+ }
+ }
+ /*
+ If any trees remain in the forest, just add them
+ to the search order sequentially, because we don't
+ know any better at this point.
+ */
+ if (!m_forest.isEmpty()) {
+ for (auto it = m_forest.begin(); it != m_forest.end(); ++it) {
+ m_searchOrder.append(it.value());
+ m_moduleNames.append(it.key());
+ }
+ m_forest.clear();
+ }
+
+ /*
+ Rebuild the forest after constructing the search order.
+ It was destroyed during construction of the search order,
+ but it is needed for module-specific searches.
+
+ Note that this loop also inserts the primary tree into the
+ forrest. That is a requirement.
+ */
+ for (int i = 0; i < m_searchOrder.size(); ++i) {
+ if (!m_forest.contains(m_moduleNames.at(i))) {
+ m_forest.insert(m_moduleNames.at(i), m_searchOrder.at(i));
+ }
+ }
+}
+
+/*!
+ Returns an ordered array of Tree pointers that represents
+ the order in which the trees should be searched. The first
+ Tree in the array is the tree for the current module, i.e.
+ the module for which qdoc is generating documentation.
+
+ The other Tree pointers in the array represent the index
+ files that were loaded in preparation for generating this
+ module's documentation. Each Tree pointer represents one
+ index file. The index file Tree points have been ordered
+ heuristically to, hopefully, minimize searching. Thr order
+ will probably be changed.
+
+ If the search order array is empty, this function calls
+ indexSearchOrder(). The search order array is empty while
+ the index files are being loaded, but some searches must
+ be performed during this time, notably searches for base
+ class nodes. These searches require a temporary search
+ order. The temporary order changes throughout the loading
+ of the index files, but it is always the tree for the
+ current index file first, followed by the trees for the
+ index files that have already been loaded. The only
+ ordering required in this temporary search order is that
+ the current tree must be searched first.
+ */
+const QList<Tree *> &QDocForest::searchOrder()
+{
+ if (m_searchOrder.isEmpty())
+ return indexSearchOrder();
+ return m_searchOrder;
+}
+
+/*!
+ There are two search orders used by qdoc when searching for
+ things. The normal search order is returned by searchOrder(),
+ but this normal search order is not known until all the index
+ files have been read. At that point, setSearchOrder() is
+ called.
+
+ During the reading of the index files, the vector holding
+ the normal search order remains empty. Whenever the search
+ order is requested, if that vector is empty, this function
+ is called to return a temporary search order, which includes
+ all the index files that have been read so far, plus the
+ one being read now. That one is prepended to the front of
+ the vector.
+ */
+const QList<Tree *> &QDocForest::indexSearchOrder()
+{
+ if (m_forest.size() > m_indexSearchOrder.size())
+ m_indexSearchOrder.prepend(m_primaryTree);
+ return m_indexSearchOrder;
+}
+
+/*!
+ Create a new Tree for the index file for the specified
+ \a module and add it to the forest. Return the pointer
+ to its root.
+ */
+NamespaceNode *QDocForest::newIndexTree(const QString &module)
+{
+ m_primaryTree = new Tree(module, m_qdb);
+ m_forest.insert(module.toLower(), m_primaryTree);
+ return m_primaryTree->root();
+}
+
+/*!
+ Create a new Tree for use as the primary tree. This tree
+ will represent the primary module. \a module is camel case.
+ */
+void QDocForest::newPrimaryTree(const QString &module)
+{
+ m_primaryTree = new Tree(module, m_qdb);
+}
+
+/*!
+ Searches through the forest for a node named \a targetPath
+ and returns a pointer to it if found. The \a relative node
+ is the starting point. It only makes sense for the primary
+ tree, which is searched first. After the primary tree has
+ been searched, \a relative is set to 0 for searching the
+ other trees, which are all index trees. With relative set
+ to 0, the starting point for each index tree is the root
+ of the index tree.
+
+ If \a targetPath is resolved successfully but it refers to
+ a \\section title, continue the search, keeping the section
+ title as a fallback if no higher-priority targets are found.
+ */
+const Node *QDocForest::findNodeForTarget(QStringList &targetPath, const Node *relative,
+ Node::Genus genus, QString &ref)
+{
+ int flags = SearchBaseClasses | SearchEnumValues;
+
+ QString entity = targetPath.takeFirst();
+ QStringList entityPath = entity.split("::");
+
+ QString target;
+ if (!targetPath.isEmpty())
+ target = targetPath.takeFirst();
+
+ TargetRec::TargetType type = TargetRec::Unknown;
+ const Node *tocNode = nullptr;
+ for (const auto *tree : searchOrder()) {
+ const Node *n = tree->findNodeForTarget(entityPath, target, relative, flags, genus, ref, &type);
+ if (n) {
+ // Targets referring to non-section titles are returned immediately
+ if (type != TargetRec::Contents)
+ return n;
+ if (!tocNode)
+ tocNode = n;
+ }
+ relative = nullptr;
+ }
+ return tocNode;
+}
+
+/*!
+ Finds the FunctionNode for the qualified function name
+ in \a path, that also has the specified \a parameters.
+ Returns a pointer to the first matching function.
+
+ \a relative is a node in the primary tree where the search
+ should begin. It is only used when searching the primary
+ tree. \a genus can be used to force the search to find a
+ C++ function or a QML function.
+ */
+const FunctionNode *QDocForest::findFunctionNode(const QStringList &path,
+ const Parameters &parameters, const Node *relative,
+ Node::Genus genus)
+{
+ for (const auto *tree : searchOrder()) {
+ const FunctionNode *fn = tree->findFunctionNode(path, parameters, relative, genus);
+ if (fn)
+ return fn;
+ relative = nullptr;
+ }
+ return nullptr;
+}
+
+/*! \class QDocDatabase
+ This class provides exclusive access to the qdoc database,
+ which consists of a forrest of trees and a lot of maps and
+ other useful data structures.
+ */
+
+QDocDatabase *QDocDatabase::s_qdocDB = nullptr;
+NodeMap QDocDatabase::s_typeNodeMap;
+NodeMultiMap QDocDatabase::s_obsoleteClasses;
+NodeMultiMap QDocDatabase::s_classesWithObsoleteMembers;
+NodeMultiMap QDocDatabase::s_obsoleteQmlTypes;
+NodeMultiMap QDocDatabase::s_qmlTypesWithObsoleteMembers;
+NodeMultiMap QDocDatabase::s_cppClasses;
+NodeMultiMap QDocDatabase::s_qmlBasicTypes;
+NodeMultiMap QDocDatabase::s_qmlTypes;
+NodeMultiMap QDocDatabase::s_examples;
+NodeMultiMapMap QDocDatabase::s_newClassMaps;
+NodeMultiMapMap QDocDatabase::s_newQmlTypeMaps;
+NodeMultiMapMap QDocDatabase::s_newEnumValueMaps;
+NodeMultiMapMap QDocDatabase::s_newSinceMaps;
+
+/*!
+ Constructs the singleton qdoc database object. The singleton
+ constructs the \a forest_ object, which is also a singleton.
+ \a m_showInternal is normally false. If it is true, qdoc will
+ write documentation for nodes marked \c internal.
+
+ \a singleExec_ is false when qdoc is being used in the standard
+ way of running qdoc twices for each module, first with -prepare
+ and then with -generate. First the -prepare phase is run for
+ each module, then the -generate phase is run for each module.
+
+ When \a singleExec_ is true, qdoc is run only once. During the
+ single execution, qdoc processes the qdocconf files for all the
+ modules sequentially in a loop. Each source file for each module
+ is read exactly once.
+ */
+QDocDatabase::QDocDatabase() : m_forest(this)
+{
+ // nothing
+}
+
+/*!
+ Creates the singleton. Allows only one instance of the class
+ to be created. Returns a pointer to the singleton.
+*/
+QDocDatabase *QDocDatabase::qdocDB()
+{
+ if (s_qdocDB == nullptr) {
+ s_qdocDB = new QDocDatabase;
+ initializeDB();
+ }
+ return s_qdocDB;
+}
+
+/*!
+ Destroys the singleton.
+ */
+void QDocDatabase::destroyQdocDB()
+{
+ if (s_qdocDB != nullptr) {
+ delete s_qdocDB;
+ s_qdocDB = nullptr;
+ }
+}
+
+/*!
+ Initialize data structures in the singleton qdoc database.
+
+ In particular, the type node map is initialized with a lot
+ type names that don't refer to documented types. For example,
+ many C++ standard types are included. These might be documented
+ here at some point, but for now they are not. Other examples
+ include \c array and \c data, which are just generic names
+ used as place holders in function signatures that appear in
+ the documentation.
+
+ \note Do not add QML basic types into this list as it will
+ break linking to those types.
+ */
+void QDocDatabase::initializeDB()
+{
+ s_typeNodeMap.insert("accepted", nullptr);
+ s_typeNodeMap.insert("actionPerformed", nullptr);
+ s_typeNodeMap.insert("activated", nullptr);
+ s_typeNodeMap.insert("alias", nullptr);
+ s_typeNodeMap.insert("anchors", nullptr);
+ s_typeNodeMap.insert("any", nullptr);
+ s_typeNodeMap.insert("array", nullptr);
+ s_typeNodeMap.insert("autoSearch", nullptr);
+ s_typeNodeMap.insert("axis", nullptr);
+ s_typeNodeMap.insert("backClicked", nullptr);
+ s_typeNodeMap.insert("boomTime", nullptr);
+ s_typeNodeMap.insert("border", nullptr);
+ s_typeNodeMap.insert("buttonClicked", nullptr);
+ s_typeNodeMap.insert("callback", nullptr);
+ s_typeNodeMap.insert("char", nullptr);
+ s_typeNodeMap.insert("clicked", nullptr);
+ s_typeNodeMap.insert("close", nullptr);
+ s_typeNodeMap.insert("closed", nullptr);
+ s_typeNodeMap.insert("cond", nullptr);
+ s_typeNodeMap.insert("data", nullptr);
+ s_typeNodeMap.insert("dataReady", nullptr);
+ s_typeNodeMap.insert("dateString", nullptr);
+ s_typeNodeMap.insert("dateTimeString", nullptr);
+ s_typeNodeMap.insert("datetime", nullptr);
+ s_typeNodeMap.insert("day", nullptr);
+ s_typeNodeMap.insert("deactivated", nullptr);
+ s_typeNodeMap.insert("drag", nullptr);
+ s_typeNodeMap.insert("easing", nullptr);
+ s_typeNodeMap.insert("error", nullptr);
+ s_typeNodeMap.insert("exposure", nullptr);
+ s_typeNodeMap.insert("fatalError", nullptr);
+ s_typeNodeMap.insert("fileSelected", nullptr);
+ s_typeNodeMap.insert("flags", nullptr);
+ s_typeNodeMap.insert("float", nullptr);
+ s_typeNodeMap.insert("focus", nullptr);
+ s_typeNodeMap.insert("focusZone", nullptr);
+ s_typeNodeMap.insert("format", nullptr);
+ s_typeNodeMap.insert("framePainted", nullptr);
+ s_typeNodeMap.insert("from", nullptr);
+ s_typeNodeMap.insert("frontClicked", nullptr);
+ s_typeNodeMap.insert("function", nullptr);
+ s_typeNodeMap.insert("hasOpened", nullptr);
+ s_typeNodeMap.insert("hovered", nullptr);
+ s_typeNodeMap.insert("hoveredTitle", nullptr);
+ s_typeNodeMap.insert("hoveredUrl", nullptr);
+ s_typeNodeMap.insert("imageCapture", nullptr);
+ s_typeNodeMap.insert("imageProcessing", nullptr);
+ s_typeNodeMap.insert("index", nullptr);
+ s_typeNodeMap.insert("initialized", nullptr);
+ s_typeNodeMap.insert("isLoaded", nullptr);
+ s_typeNodeMap.insert("item", nullptr);
+ s_typeNodeMap.insert("key", nullptr);
+ s_typeNodeMap.insert("keysequence", nullptr);
+ s_typeNodeMap.insert("listViewClicked", nullptr);
+ s_typeNodeMap.insert("loadRequest", nullptr);
+ s_typeNodeMap.insert("locale", nullptr);
+ s_typeNodeMap.insert("location", nullptr);
+ s_typeNodeMap.insert("long", nullptr);
+ s_typeNodeMap.insert("message", nullptr);
+ s_typeNodeMap.insert("messageReceived", nullptr);
+ s_typeNodeMap.insert("mode", nullptr);
+ s_typeNodeMap.insert("month", nullptr);
+ s_typeNodeMap.insert("name", nullptr);
+ s_typeNodeMap.insert("number", nullptr);
+ s_typeNodeMap.insert("object", nullptr);
+ s_typeNodeMap.insert("offset", nullptr);
+ s_typeNodeMap.insert("ok", nullptr);
+ s_typeNodeMap.insert("openCamera", nullptr);
+ s_typeNodeMap.insert("openImage", nullptr);
+ s_typeNodeMap.insert("openVideo", nullptr);
+ s_typeNodeMap.insert("padding", nullptr);
+ s_typeNodeMap.insert("parent", nullptr);
+ s_typeNodeMap.insert("path", nullptr);
+ s_typeNodeMap.insert("photoModeSelected", nullptr);
+ s_typeNodeMap.insert("position", nullptr);
+ s_typeNodeMap.insert("precision", nullptr);
+ s_typeNodeMap.insert("presetClicked", nullptr);
+ s_typeNodeMap.insert("preview", nullptr);
+ s_typeNodeMap.insert("previewSelected", nullptr);
+ s_typeNodeMap.insert("progress", nullptr);
+ s_typeNodeMap.insert("puzzleLost", nullptr);
+ s_typeNodeMap.insert("qmlSignal", nullptr);
+ s_typeNodeMap.insert("rectangle", nullptr);
+ s_typeNodeMap.insert("request", nullptr);
+ s_typeNodeMap.insert("requestId", nullptr);
+ s_typeNodeMap.insert("section", nullptr);
+ s_typeNodeMap.insert("selected", nullptr);
+ s_typeNodeMap.insert("send", nullptr);
+ s_typeNodeMap.insert("settingsClicked", nullptr);
+ s_typeNodeMap.insert("shoe", nullptr);
+ s_typeNodeMap.insert("short", nullptr);
+ s_typeNodeMap.insert("signed", nullptr);
+ s_typeNodeMap.insert("sizeChanged", nullptr);
+ s_typeNodeMap.insert("size_t", nullptr);
+ s_typeNodeMap.insert("sockaddr", nullptr);
+ s_typeNodeMap.insert("someOtherSignal", nullptr);
+ s_typeNodeMap.insert("sourceSize", nullptr);
+ s_typeNodeMap.insert("startButtonClicked", nullptr);
+ s_typeNodeMap.insert("state", nullptr);
+ s_typeNodeMap.insert("std::initializer_list", nullptr);
+ s_typeNodeMap.insert("std::list", nullptr);
+ s_typeNodeMap.insert("std::map", nullptr);
+ s_typeNodeMap.insert("std::pair", nullptr);
+ s_typeNodeMap.insert("std::string", nullptr);
+ s_typeNodeMap.insert("std::vector", nullptr);
+ s_typeNodeMap.insert("stringlist", nullptr);
+ s_typeNodeMap.insert("swapPlayers", nullptr);
+ s_typeNodeMap.insert("symbol", nullptr);
+ s_typeNodeMap.insert("t", nullptr);
+ s_typeNodeMap.insert("T", nullptr);
+ s_typeNodeMap.insert("tagChanged", nullptr);
+ s_typeNodeMap.insert("timeString", nullptr);
+ s_typeNodeMap.insert("timeout", nullptr);
+ s_typeNodeMap.insert("to", nullptr);
+ s_typeNodeMap.insert("toggled", nullptr);
+ s_typeNodeMap.insert("type", nullptr);
+ s_typeNodeMap.insert("unsigned", nullptr);
+ s_typeNodeMap.insert("urllist", nullptr);
+ s_typeNodeMap.insert("va_list", nullptr);
+ s_typeNodeMap.insert("value", nullptr);
+ s_typeNodeMap.insert("valueEmitted", nullptr);
+ s_typeNodeMap.insert("videoFramePainted", nullptr);
+ s_typeNodeMap.insert("videoModeSelected", nullptr);
+ s_typeNodeMap.insert("videoRecorder", nullptr);
+ s_typeNodeMap.insert("void", nullptr);
+ s_typeNodeMap.insert("volatile", nullptr);
+ s_typeNodeMap.insert("wchar_t", nullptr);
+ s_typeNodeMap.insert("x", nullptr);
+ s_typeNodeMap.insert("y", nullptr);
+ s_typeNodeMap.insert("zoom", nullptr);
+ s_typeNodeMap.insert("zoomTo", nullptr);
+}
+
+/*! \fn NamespaceNode *QDocDatabase::primaryTreeRoot()
+ Returns a pointer to the root node of the primary tree.
+ */
+
+/*!
+ \fn const CNMap &QDocDatabase::groups()
+ Returns a const reference to the collection of all
+ group nodes in the primary tree.
+*/
+
+/*!
+ \fn const CNMap &QDocDatabase::modules()
+ Returns a const reference to the collection of all
+ module nodes in the primary tree.
+*/
+
+/*!
+ \fn const CNMap &QDocDatabase::qmlModules()
+ Returns a const reference to the collection of all
+ QML module nodes in the primary tree.
+*/
+
+/*! \fn CollectionNode *QDocDatabase::findGroup(const QString &name)
+ Find the group node named \a name and return a pointer
+ to it. If a matching node is not found, add a new group
+ node named \a name and return a pointer to that one.
+
+ If a new group node is added, its parent is the tree root,
+ and the new group node is marked \e{not seen}.
+ */
+
+/*! \fn CollectionNode *QDocDatabase::findModule(const QString &name)
+ Find the module node named \a name and return a pointer
+ to it. If a matching node is not found, add a new module
+ node named \a name and return a pointer to that one.
+
+ If a new module node is added, its parent is the tree root,
+ and the new module node is marked \e{not seen}.
+ */
+
+/*! \fn CollectionNode *QDocDatabase::addGroup(const QString &name)
+ Looks up the group named \a name in the primary tree. If
+ a match is found, a pointer to the node is returned.
+ Otherwise, a new group node named \a name is created and
+ inserted into the collection, and the pointer to that node
+ is returned.
+ */
+
+/*! \fn CollectionNode *QDocDatabase::addModule(const QString &name)
+ Looks up the module named \a name in the primary tree. If
+ a match is found, a pointer to the node is returned.
+ Otherwise, a new module node named \a name is created and
+ inserted into the collection, and the pointer to that node
+ is returned.
+ */
+
+/*! \fn CollectionNode *QDocDatabase::addQmlModule(const QString &name)
+ Looks up the QML module named \a name in the primary tree.
+ If a match is found, a pointer to the node is returned.
+ Otherwise, a new QML module node named \a name is created
+ and inserted into the collection, and the pointer to that
+ node is returned.
+ */
+
+/*! \fn CollectionNode *QDocDatabase::addToGroup(const QString &name, Node *node)
+ Looks up the group node named \a name in the collection
+ of all group nodes. If a match is not found, a new group
+ node named \a name is created and inserted into the collection.
+ Then append \a node to the group's members list, and append the
+ group node to the member list of the \a node. The parent of the
+ \a node is not changed by this function. Returns a pointer to
+ the group node.
+ */
+
+/*! \fn CollectionNode *QDocDatabase::addToModule(const QString &name, Node *node)
+ Looks up the module node named \a name in the collection
+ of all module nodes. If a match is not found, a new module
+ node named \a name is created and inserted into the collection.
+ Then append \a node to the module's members list. The parent of
+ \a node is not changed by this function. Returns the module node.
+ */
+
+/*! \fn Collection *QDocDatabase::addToQmlModule(const QString &name, Node *node)
+ Looks up the QML module named \a name. If it isn't there,
+ create it. Then append \a node to the QML module's member
+ list. The parent of \a node is not changed by this function.
+ */
+
+/*! \fn QmlTypeNode *QDocDatabase::findQmlType(const QString &name)
+ Returns the QML type node identified by the qualified
+ QML type \a name, or \c nullptr if no type was found.
+ */
+
+/*!
+ Returns the QML type node identified by the QML module id
+ \a qmid and QML type \a name, or \c nullptr if no type
+ was found.
+
+ If the QML module id is empty, looks up the QML type by
+ \a name only.
+ */
+QmlTypeNode *QDocDatabase::findQmlType(const QString &qmid, const QString &name)
+{
+ if (!qmid.isEmpty()) {
+ if (auto *qcn = m_forest.lookupQmlType(qmid + u"::"_s + name); qcn)
+ return qcn;
+ }
+
+ QStringList path(name);
+ return static_cast<QmlTypeNode *>(m_forest.findNodeByNameAndType(path, &Node::isQmlType));
+}
+
+/*!
+ Returns the QML type node identified by the QML module id
+ constructed from the strings in the import \a record and the
+ QML type \a name. Returns \c nullptr if no type was not found.
+ */
+QmlTypeNode *QDocDatabase::findQmlType(const ImportRec &record, const QString &name)
+{
+ if (!record.isEmpty()) {
+ const QString qmName = record.m_importUri.isEmpty() ?
+ record.m_moduleName : record.m_importUri;
+ const QStringList dotSplit{name.split(QLatin1Char('.'))};
+ for (const auto &namePart : dotSplit) {
+ if (auto *qcn = m_forest.lookupQmlType(qmName + u"::"_s + namePart); qcn)
+ return qcn;
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Returns the QML node identified by the QML module id \a qmid
+ and \a name, searching in the primary tree only. If \a qmid
+ is an empty string, searches for the node using name only.
+
+ Returns \c nullptr if no node was found.
+*/
+QmlTypeNode *QDocDatabase::findQmlTypeInPrimaryTree(const QString &qmid, const QString &name)
+{
+ if (!qmid.isEmpty())
+ return primaryTree()->lookupQmlType(qmid + u"::"_s + name);
+ return static_cast<QmlTypeNode *>(primaryTreeRoot()->findChildNode(name, Node::QML, TypesOnly));
+}
+
+/*!
+ This function calls a set of functions for each tree in the
+ forest that has not already been analyzed. In this way, when
+ running qdoc in \e singleExec mode, each tree is analyzed in
+ turn, and its classes and types are added to the appropriate
+ node maps.
+ */
+void QDocDatabase::processForest()
+{
+ processForest(&QDocDatabase::findAllClasses);
+ processForest(&QDocDatabase::findAllFunctions);
+ processForest(&QDocDatabase::findAllObsoleteThings);
+ processForest(&QDocDatabase::findAllLegaleseTexts);
+ processForest(&QDocDatabase::findAllSince);
+ processForest(&QDocDatabase::findAllAttributions);
+ resolveNamespaces();
+}
+
+/*!
+ This function calls \a func for each tree in the forest,
+ ensuring that \a func is called only once per tree.
+
+ \sa processForest()
+ */
+void QDocDatabase::processForest(FindFunctionPtr func)
+{
+ Tree *t = m_forest.firstTree();
+ while (t) {
+ if (!m_completedFindFunctions.values(t).contains(func)) {
+ (this->*(func))(t->root());
+ m_completedFindFunctions.insert(t, func);
+ }
+ t = m_forest.nextTree();
+ }
+}
+
+/*!
+ Returns a reference to the collection of legalese texts.
+ */
+TextToNodeMap &QDocDatabase::getLegaleseTexts()
+{
+ processForest(&QDocDatabase::findAllLegaleseTexts);
+ return m_legaleseTexts;
+}
+
+/*!
+ Returns a reference to the map of C++ classes with obsolete members.
+ */
+NodeMultiMap &QDocDatabase::getClassesWithObsoleteMembers()
+{
+ processForest(&QDocDatabase::findAllObsoleteThings);
+ return s_classesWithObsoleteMembers;
+}
+
+/*!
+ Returns a reference to the map of obsolete QML types.
+ */
+NodeMultiMap &QDocDatabase::getObsoleteQmlTypes()
+{
+ processForest(&QDocDatabase::findAllObsoleteThings);
+ return s_obsoleteQmlTypes;
+}
+
+/*!
+ Returns a reference to the map of QML types with obsolete members.
+ */
+NodeMultiMap &QDocDatabase::getQmlTypesWithObsoleteMembers()
+{
+ processForest(&QDocDatabase::findAllObsoleteThings);
+ return s_qmlTypesWithObsoleteMembers;
+}
+
+/*!
+ Returns a reference to the map of QML basic types.
+ */
+NodeMultiMap &QDocDatabase::getQmlValueTypes()
+{
+ processForest(&QDocDatabase::findAllClasses);
+ return s_qmlBasicTypes;
+}
+
+/*!
+ Returns a reference to the multimap of QML types.
+ */
+NodeMultiMap &QDocDatabase::getQmlTypes()
+{
+ processForest(&QDocDatabase::findAllClasses);
+ return s_qmlTypes;
+}
+
+/*!
+ Returns a reference to the multimap of example nodes.
+ */
+NodeMultiMap &QDocDatabase::getExamples()
+{
+ processForest(&QDocDatabase::findAllClasses);
+ return s_examples;
+}
+
+/*!
+ Returns a reference to the multimap of attribution nodes.
+ */
+NodeMultiMap &QDocDatabase::getAttributions()
+{
+ processForest(&QDocDatabase::findAllAttributions);
+ return m_attributions;
+}
+
+/*!
+ Returns a reference to the map of obsolete C++ clases.
+ */
+NodeMultiMap &QDocDatabase::getObsoleteClasses()
+{
+ processForest(&QDocDatabase::findAllObsoleteThings);
+ return s_obsoleteClasses;
+}
+
+/*!
+ Returns a reference to the map of all C++ classes.
+ */
+NodeMultiMap &QDocDatabase::getCppClasses()
+{
+ processForest(&QDocDatabase::findAllClasses);
+ return s_cppClasses;
+}
+
+/*!
+ Returns the function index. This data structure is used to
+ output the function index page.
+ */
+NodeMapMap &QDocDatabase::getFunctionIndex()
+{
+ processForest(&QDocDatabase::findAllFunctions);
+ return m_functionIndex;
+}
+
+/*!
+ Finds all the nodes containing legalese text and puts them
+ in a map.
+ */
+void QDocDatabase::findAllLegaleseTexts(Aggregate *node)
+{
+ for (const auto &childNode : node->childNodes()) {
+ if (childNode->isPrivate())
+ continue;
+ if (!childNode->doc().legaleseText().isEmpty())
+ m_legaleseTexts.insert(childNode->doc().legaleseText(), childNode);
+ if (childNode->isAggregate())
+ findAllLegaleseTexts(static_cast<Aggregate *>(childNode));
+ }
+}
+
+/*!
+ \fn void QDocDatabase::findAllObsoleteThings(Aggregate *node)
+
+ Finds all nodes with status = Deprecated and sorts them into
+ maps. They can be C++ classes, QML types, or they can be
+ functions, enum types, typedefs, methods, etc.
+ */
+
+/*!
+ \fn void QDocDatabase::findAllSince(Aggregate *node)
+
+ Finds all the nodes in \a node where a \e{since} command appeared
+ in the qdoc comment and sorts them into maps according to the kind
+ of node.
+
+ This function is used for generating the "New Classes... in x.y"
+ section on the \e{What's New in Qt x.y} page.
+ */
+
+/*!
+ Find the \a key in the map of new class maps, and return a
+ reference to the value, which is a NodeMap. If \a key is not
+ found, return a reference to an empty NodeMap.
+ */
+const NodeMultiMap &QDocDatabase::getClassMap(const QString &key)
+{
+ processForest(&QDocDatabase::findAllSince);
+ auto it = s_newClassMaps.constFind(key);
+ return (it != s_newClassMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
+}
+
+/*!
+ Find the \a key in the map of new QML type maps, and return a
+ reference to the value, which is a NodeMap. If the \a key is not
+ found, return a reference to an empty NodeMap.
+ */
+const NodeMultiMap &QDocDatabase::getQmlTypeMap(const QString &key)
+{
+ processForest(&QDocDatabase::findAllSince);
+ auto it = s_newQmlTypeMaps.constFind(key);
+ return (it != s_newQmlTypeMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
+}
+
+/*!
+ Find the \a key in the map of new \e {since} maps, and return
+ a reference to the value, which is a NodeMultiMap. If \a key
+ is not found, return a reference to an empty NodeMultiMap.
+ */
+const NodeMultiMap &QDocDatabase::getSinceMap(const QString &key)
+{
+ processForest(&QDocDatabase::findAllSince);
+ auto it = s_newSinceMaps.constFind(key);
+ return (it != s_newSinceMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
+}
+
+/*!
+ Performs several housekeeping tasks prior to generating the
+ documentation. These tasks create required data structures
+ and resolve links.
+ */
+void QDocDatabase::resolveStuff()
+{
+ const auto &config = Config::instance();
+ if (config.dualExec() || config.preparing()) {
+ // order matters
+ primaryTree()->resolveBaseClasses(primaryTreeRoot());
+ primaryTree()->resolvePropertyOverriddenFromPtrs(primaryTreeRoot());
+ primaryTreeRoot()->resolveRelates();
+ primaryTreeRoot()->normalizeOverloads();
+ primaryTree()->markDontDocumentNodes();
+ primaryTree()->removePrivateAndInternalBases(primaryTreeRoot());
+ primaryTree()->resolveProperties();
+ primaryTreeRoot()->markUndocumentedChildrenInternal();
+ primaryTreeRoot()->resolveQmlInheritance();
+ primaryTree()->resolveTargets(primaryTreeRoot());
+ primaryTree()->resolveCppToQmlLinks();
+ primaryTree()->resolveSince(*primaryTreeRoot());
+ }
+ if (config.singleExec() && config.generating()) {
+ primaryTree()->resolveBaseClasses(primaryTreeRoot());
+ primaryTree()->resolvePropertyOverriddenFromPtrs(primaryTreeRoot());
+ primaryTreeRoot()->resolveQmlInheritance();
+ primaryTree()->resolveCppToQmlLinks();
+ primaryTree()->resolveSince(*primaryTreeRoot());
+ }
+ if (!config.preparing()) {
+ resolveNamespaces();
+ resolveProxies();
+ resolveBaseClasses();
+ updateNavigation();
+ }
+ if (config.dualExec())
+ QDocIndexFiles::destroyQDocIndexFiles();
+}
+
+void QDocDatabase::resolveBaseClasses()
+{
+ Tree *t = m_forest.firstTree();
+ while (t) {
+ t->resolveBaseClasses(t->root());
+ t = m_forest.nextTree();
+ }
+}
+
+/*!
+ Returns a reference to the namespace map. Constructs the
+ namespace map if it hasn't been constructed yet.
+
+ \note This function must not be called in the prepare phase.
+ */
+NodeMultiMap &QDocDatabase::getNamespaces()
+{
+ resolveNamespaces();
+ return m_namespaceIndex;
+}
+
+/*!
+ Multiple namespace nodes for namespace X can exist in the
+ qdoc database in different trees. This function first finds
+ all namespace nodes in all the trees and inserts them into
+ a multimap. Then it combines all the namespace nodes that
+ have the same name into a single namespace node of that
+ name and inserts that combined namespace node into an index.
+ */
+void QDocDatabase::resolveNamespaces()
+{
+ if (!m_namespaceIndex.isEmpty())
+ return;
+
+ bool linkErrors = !Config::instance().get(CONFIG_NOLINKERRORS).asBool();
+ NodeMultiMap namespaceMultimap;
+ Tree *t = m_forest.firstTree();
+ while (t) {
+ t->root()->findAllNamespaces(namespaceMultimap);
+ t = m_forest.nextTree();
+ }
+ const QList<QString> keys = namespaceMultimap.uniqueKeys();
+ for (const QString &key : keys) {
+ NamespaceNode *ns = nullptr;
+ NamespaceNode *indexNamespace = nullptr;
+ const NodeList namespaces = namespaceMultimap.values(key);
+ qsizetype count = namespaceMultimap.remove(key);
+ if (count > 0) {
+ for (auto *node : namespaces) {
+ ns = static_cast<NamespaceNode *>(node);
+ if (ns->isDocumentedHere())
+ break;
+ else if (ns->hadDoc())
+ indexNamespace = ns; // namespace was documented but in another tree
+ ns = nullptr;
+ }
+ if (ns) {
+ for (auto *node : namespaces) {
+ auto *nsNode = static_cast<NamespaceNode *>(node);
+ if (nsNode->hadDoc() && nsNode != ns) {
+ ns->doc().location().warning(
+ QStringLiteral("Namespace %1 documented more than once")
+ .arg(nsNode->name()), QStringLiteral("also seen here: %1")
+ .arg(nsNode->doc().location().toString()));
+ }
+ }
+ } else if (!indexNamespace) {
+ // Warn about documented children in undocumented namespaces.
+ // As the namespace can be documented outside this project,
+ // skip the warning if -no-link-errors is set
+ if (linkErrors) {
+ for (auto *node : namespaces) {
+ if (!node->isIndexNode())
+ static_cast<NamespaceNode *>(node)->reportDocumentedChildrenInUndocumentedNamespace();
+ }
+ }
+ } else {
+ for (auto *node : namespaces) {
+ auto *nsNode = static_cast<NamespaceNode *>(node);
+ if (nsNode != indexNamespace)
+ nsNode->setDocNode(indexNamespace);
+ }
+ }
+ }
+ /*
+ If there are multiple namespace nodes with the same
+ name where one of them will be the main reference page
+ for the namespace, include all nodes in the public
+ API of the namespace.
+ */
+ if (ns && count > 1) {
+ for (auto *node : namespaces) {
+ auto *nameSpaceNode = static_cast<NamespaceNode *>(node);
+ if (nameSpaceNode != ns) {
+ for (auto it = nameSpaceNode->constBegin(); it != nameSpaceNode->constEnd();
+ ++it) {
+ Node *anotherNs = *it;
+ if (anotherNs && anotherNs->isPublic() && !anotherNs->isInternal())
+ ns->includeChild(anotherNs);
+ }
+ }
+ }
+ }
+ /*
+ Add the main namespace reference node to index, or the last seen
+ namespace if the main one was not found.
+ */
+ if (!ns)
+ ns = indexNamespace ? indexNamespace : static_cast<NamespaceNode *>(namespaces.last());
+ m_namespaceIndex.insert(ns->name(), ns);
+ }
+}
+
+/*!
+ Each instance of class Tree that represents an index file
+ must be traversed to find all instances of class ProxyNode.
+ For each ProxyNode found, look up the ProxyNode's name in
+ the primary Tree. If it is found, it means that the proxy
+ node contains elements (normally just functions) that are
+ documented in the module represented by the Tree containing
+ the proxy node but that are related to the node we found in
+ the primary tree.
+ */
+void QDocDatabase::resolveProxies()
+{
+ // The first tree is the primary tree.
+ // Skip the primary tree.
+ Tree *t = m_forest.firstTree();
+ t = m_forest.nextTree();
+ while (t) {
+ const NodeList &proxies = t->proxies();
+ if (!proxies.isEmpty()) {
+ for (auto *node : proxies) {
+ const auto *pn = static_cast<ProxyNode *>(node);
+ if (pn->count() > 0) {
+ Aggregate *aggregate = primaryTree()->findAggregate(pn->name());
+ if (aggregate != nullptr)
+ aggregate->appendToRelatedByProxy(pn->childNodes());
+ }
+ }
+ }
+ t = m_forest.nextTree();
+ }
+}
+
+/*!
+ Finds the function node for the qualified function path in
+ \a target and returns a pointer to it. The \a target is a
+ function signature with or without parameters but without
+ the return type.
+
+ \a relative is the node in the primary tree where the search
+ begins. It is not used in the other trees, if the node is not
+ found in the primary tree. \a genus can be used to force the
+ search to find a C++ function or a QML function.
+
+ The entire forest is searched, but the first match is accepted.
+ */
+const FunctionNode *QDocDatabase::findFunctionNode(const QString &target, const Node *relative,
+ Node::Genus genus)
+{
+ QString signature;
+ QString function = target;
+ qsizetype length = target.size();
+ if (function.endsWith("()"))
+ function.chop(2);
+ if (function.endsWith(QChar(')'))) {
+ qsizetype position = function.lastIndexOf(QChar('('));
+ signature = function.mid(position + 1, length - position - 2);
+ function = function.left(position);
+ }
+ QStringList path = function.split("::");
+ return m_forest.findFunctionNode(path, Parameters(signature), relative, genus);
+}
+
+/*!
+ This function is called for autolinking to a \a type,
+ which could be a function return type or a parameter
+ type. The tree node that represents the \a type is
+ returned. All the trees are searched until a match is
+ found. When searching the primary tree, the search
+ begins at \a relative and proceeds up the parent chain.
+ When searching the index trees, the search begins at the
+ root.
+ */
+const Node *QDocDatabase::findTypeNode(const QString &type, const Node *relative, Node::Genus genus)
+{
+ QStringList path = type.split("::");
+ if ((path.size() == 1) && (path.at(0)[0].isLower() || path.at(0) == QString("T"))) {
+ auto it = s_typeNodeMap.find(path.at(0));
+ if (it != s_typeNodeMap.end())
+ return it.value();
+ }
+ return m_forest.findTypeNode(path, relative, genus);
+}
+
+/*!
+ Finds the node that will generate the documentation that
+ contains the \a target and returns a pointer to it.
+
+ Can this be improved by using the target map in Tree?
+ */
+const Node *QDocDatabase::findNodeForTarget(const QString &target, const Node *relative)
+{
+ const Node *node = nullptr;
+ if (target.isEmpty())
+ node = relative;
+ else if (target.endsWith(".html"))
+ node = findNodeByNameAndType(QStringList(target), &Node::isPageNode);
+ else {
+ QStringList path = target.split("::");
+ int flags = SearchBaseClasses | SearchEnumValues;
+ for (const auto *tree : searchOrder()) {
+ const Node *n = tree->findNode(path, relative, flags, Node::DontCare);
+ if (n)
+ return n;
+ relative = nullptr;
+ }
+ node = findPageNodeByTitle(target);
+ }
+ return node;
+}
+
+QStringList QDocDatabase::groupNamesForNode(Node *node)
+{
+ QStringList result;
+ CNMap *m = primaryTree()->getCollectionMap(Node::Group);
+
+ if (!m)
+ return result;
+
+ for (auto it = m->cbegin(); it != m->cend(); ++it)
+ if (it.value()->members().contains(node))
+ result << it.key();
+
+ return result;
+}
+
+/*!
+ Reads and parses the qdoc index files listed in \a indexFiles.
+ */
+void QDocDatabase::readIndexes(const QStringList &indexFiles)
+{
+ QStringList filesToRead;
+ for (const QString &file : indexFiles) {
+ QString fn = file.mid(file.lastIndexOf(QChar('/')) + 1);
+ if (!isLoaded(fn))
+ filesToRead << file;
+ else
+ qCCritical(lcQdoc) << "Index file" << file << "is already in memory.";
+ }
+ QDocIndexFiles::qdocIndexFiles()->readIndexes(filesToRead);
+}
+
+/*!
+ Generates a qdoc index file and write it to \a fileName. The
+ index file is generated with the parameters \a url and \a title,
+ using the generator \a g.
+ */
+void QDocDatabase::generateIndex(const QString &fileName, const QString &url, const QString &title,
+ Generator *g)
+{
+ QString t = fileName.mid(fileName.lastIndexOf(QChar('/')) + 1);
+ primaryTree()->setIndexFileName(t);
+ QDocIndexFiles::qdocIndexFiles()->generateIndex(fileName, url, title, g);
+ QDocIndexFiles::destroyQDocIndexFiles();
+}
+
+/*!
+ Returns the collection node representing the module that \a relative
+ node belongs to, or \c nullptr if there is no such module in the
+ primary tree.
+*/
+const CollectionNode *QDocDatabase::getModuleNode(const Node *relative)
+{
+ Node::NodeType moduleType{Node::Module};
+ QString moduleName;
+ switch (relative->genus())
+ {
+ case Node::CPP:
+ moduleType = Node::Module;
+ moduleName = relative->physicalModuleName();
+ break;
+ case Node::QML:
+ moduleType = Node::QmlModule;
+ moduleName = relative->logicalModuleName();
+ break;
+ default:
+ return nullptr;
+ }
+ if (moduleName.isEmpty())
+ return nullptr;
+
+ return primaryTree()->getCollection(moduleName, moduleType);
+}
+
+/*!
+ Finds all the collection nodes of the specified \a type
+ and merges them into the collection node map \a cnm. Nodes
+ that match the \a relative node are not included.
+ */
+void QDocDatabase::mergeCollections(Node::NodeType type, CNMap &cnm, const Node *relative)
+{
+ cnm.clear();
+ CNMultiMap cnmm;
+ for (auto *tree : searchOrder()) {
+ CNMap *m = tree->getCollectionMap(type);
+ if (m && !m->isEmpty()) {
+ for (auto it = m->cbegin(); it != m->cend(); ++it) {
+ if (!it.value()->isInternal())
+ cnmm.insert(it.key(), it.value());
+ }
+ }
+ }
+ if (cnmm.isEmpty())
+ return;
+ static const QRegularExpression singleDigit("\\b([0-9])\\b");
+ const QStringList keys = cnmm.uniqueKeys();
+ for (const auto &key : keys) {
+ const QList<CollectionNode *> values = cnmm.values(key);
+ CollectionNode *n = nullptr;
+ for (auto *value : values) {
+ if (value && value->wasSeen() && value != relative) {
+ n = value;
+ break;
+ }
+ }
+ if (n) {
+ if (values.size() > 1) {
+ for (CollectionNode *value : values) {
+ if (value != n) {
+ // Allow multiple (major) versions of QML modules
+ if ((n->isQmlModule())
+ && n->logicalModuleIdentifier() != value->logicalModuleIdentifier()) {
+ if (value->wasSeen() && value != relative
+ && !value->members().isEmpty())
+ cnm.insert(value->fullTitle().toLower(), value);
+ continue;
+ }
+ for (Node *t : value->members())
+ n->addMember(t);
+ }
+ }
+ }
+ QString sortKey = n->fullTitle().toLower();
+ if (sortKey.startsWith("the "))
+ sortKey.remove(0, 4);
+ sortKey.replace(singleDigit, "0\\1");
+ cnm.insert(sortKey, n);
+ }
+ }
+}
+
+/*!
+ Finds all the collection nodes with the same name
+ and type as \a c and merges their members into the
+ members list of \a c.
+
+ For QML modules, only nodes with matching
+ module identifiers are merged to avoid merging
+ modules with different (major) versions.
+ */
+void QDocDatabase::mergeCollections(CollectionNode *c)
+{
+ if (c == nullptr)
+ return;
+
+ // REMARK: This form of merging is usually called during the
+ // generation phase om-the-fly when a source-of-truth collection
+ // is required.
+ // In practice, this means a collection could be merged many, many
+ // times during the lifetime of a generation.
+ // To avoid repeating the merging process each time, which could
+ // be time consuming, we use a small flag that is set directly on
+ // the collection to bail-out early.
+ //
+ // The merging process is only meaningful for collections when the
+ // collection references are spread troughout multiple projects.
+ // The part of information that exists in other project is read
+ // before the generation phase, such that when the generation
+ // phase comes, we already have all the information we need for
+ // merging such that we can consider all version of a certain
+ // collection node immutable, making the caching inherently
+ // correct at any point of the generation.
+ //
+ // This implies that this operation is unsafe if it is performed
+ // before all the index files are loaded.
+ // Indeed, this is a prerequisite, with the current structure, to
+ // perform this optmization.
+ //
+ // At the current time, this is true and is expected not to
+ // change.
+ //
+ // Do note that this is not applied to the other overload of
+ // mergeCollections as we cannot as safely ensure its consistency
+ // and, as the result of the merging depends on multiple
+ // parameters, it would require an actual memoization of the call.
+ //
+ // Note that this is a defensive optimization and we are assuming
+ // that it is effective based on heuristical data. As this is
+ // expected to disappear, at least in its current form, in the
+ // future, a more thorough analysis was not performed.
+ if (c->isMerged()) {
+ return;
+ }
+
+ for (auto *tree : searchOrder()) {
+ CollectionNode *cn = tree->getCollection(c->name(), c->nodeType());
+ if (cn && cn != c) {
+ if ((cn->isQmlModule())
+ && cn->logicalModuleIdentifier() != c->logicalModuleIdentifier())
+ continue;
+
+ for (auto *node : cn->members())
+ c->addMember(node);
+
+ // REMARK: The merging process is performed to ensure that
+ // references to the collection in external projects are
+ // taken into account before consuming the collection.
+ //
+ // This works by having QDoc construct empty collections
+ // as soon as a reference to a collection is encountered
+ // and filling details later on when its definition is
+ // found.
+ //
+ // This initially-empty collection is always saved to the
+ // primaryTree and it is the collection that is directly
+ // accessible to consumers during the generation process.
+ //
+ // Nonetheless, when the definition for the collection is
+ // not in the same project as the one that is being
+ // compiled, its details will never be filled in.
+ //
+ // Indeed, the details will live in the index file for the
+ // project where the collection is defined, if any, and
+ // the node for it, which has complete information, will
+ // live in some non-primaryTree.
+ //
+ // The merging process itself is used by consumers during
+ // the generation process because they access the
+ // primaryTree version of the collection expecting a
+ // source-of-truth.
+ // To ensure that this is the case for usages that
+ // requires linking, we need to merge not only the members
+ // of the collection that reside in external versions of
+ // the collection; but some of the data that reside in the
+ // definition of the collection intself, namely the title
+ // and the url.
+ //
+ // A collection that contains the data of a definition is
+ // always marked as seen, hence we use that to discern
+ // whether we are working with a placeholder node or not,
+ // and fill in the data if we encounter a node that
+ // represents a definition.
+ //
+ // The way in which QDoc works implies that collection are
+ // globally scoped between projects.
+ // The repetition of the definition for the same
+ // collection is warned for as a duplicate documentation,
+ // such that we can expect a single valid source of truth
+ // for a given collection in each project.
+ // It is currently unknown if this warning is applicable
+ // when the repeated collection is defined in two
+ // different projects.
+ //
+ // As QDoc implicitly would not correctly support this
+ // case, we assume that only one declaration exists for
+ // each collection, such that the first encoutered one
+ // must be the source of truth and that there is no need
+ // to copy any data after the first copy is performed.
+ // KLUDGE: Note that this process is done as a hackish
+ // solution to QTBUG-104237 and should not be considered
+ // final or dependable.
+ if (!c->wasSeen() && cn->wasSeen()) {
+ c->markSeen();
+ c->setTitle(cn->title());
+ c->setUrl(cn->url());
+ }
+ }
+ }
+
+ c->markMerged();
+}
+
+/*!
+ Searches for the node that matches the path in \a atom and the
+ specified \a genus. The \a relative node is used if the first
+ leg of the path is empty, i.e. if the path begins with '#'.
+ The function also sets \a ref if there remains an unused leg
+ in the path after the node is found. The node is returned as
+ well as the \a ref. If the returned node pointer is null,
+ \a ref is also not valid.
+ */
+const Node *QDocDatabase::findNodeForAtom(const Atom *a, const Node *relative, QString &ref,
+ Node::Genus genus)
+{
+ const Node *node = nullptr;
+
+ Atom *atom = const_cast<Atom *>(a);
+ QStringList targetPath = atom->string().split(QLatin1Char('#'));
+ QString first = targetPath.first().trimmed();
+
+ Tree *domain = nullptr;
+
+ if (atom->isLinkAtom()) {
+ domain = atom->domain();
+ genus = atom->genus();
+ }
+
+ if (first.isEmpty())
+ node = relative; // search for a target on the current page.
+ else if (domain) {
+ if (first.endsWith(".html"))
+ node = domain->findNodeByNameAndType(QStringList(first), &Node::isPageNode);
+ else if (first.endsWith(QChar(')'))) {
+ QString signature;
+ QString function = first;
+ qsizetype length = first.size();
+ if (function.endsWith("()"))
+ function.chop(2);
+ if (function.endsWith(QChar(')'))) {
+ qsizetype position = function.lastIndexOf(QChar('('));
+ signature = function.mid(position + 1, length - position - 2);
+ function = function.left(position);
+ }
+ QStringList path = function.split("::");
+ node = domain->findFunctionNode(path, Parameters(signature), nullptr, genus);
+ }
+ if (node == nullptr) {
+ int flags = SearchBaseClasses | SearchEnumValues;
+ QStringList nodePath = first.split("::");
+ QString target;
+ targetPath.removeFirst();
+ if (!targetPath.isEmpty())
+ target = targetPath.takeFirst();
+ if (relative && relative->tree()->physicalModuleName() != domain->physicalModuleName())
+ relative = nullptr;
+ return domain->findNodeForTarget(nodePath, target, relative, flags, genus, ref);
+ }
+ } else {
+ if (first.endsWith(".html"))
+ node = findNodeByNameAndType(QStringList(first), &Node::isPageNode);
+ else if (first.endsWith(QChar(')')))
+ node = findFunctionNode(first, relative, genus);
+ if (node == nullptr)
+ return findNodeForTarget(targetPath, relative, genus, ref);
+ }
+
+ if (node != nullptr && ref.isEmpty()) {
+ if (!node->url().isEmpty())
+ return node;
+ targetPath.removeFirst();
+ if (!targetPath.isEmpty()) {
+ ref = node->root()->tree()->getRef(targetPath.first(), node);
+ if (ref.isEmpty())
+ node = nullptr;
+ }
+ }
+ return node;
+}
+
+/*!
+ Updates navigation (previous/next page links and the navigation parent)
+ for pages listed in the TOC, specified by the \c navigation.toctitles
+ configuration variable.
+
+ if \c navigation.toctitles.inclusive is \c true, include also the TOC
+ page(s) themselves as a 'root' item in the navigation bar (breadcrumbs)
+ that are generated for HTML output.
+*/
+void QDocDatabase::updateNavigation()
+{
+ // Restrict searching only to the local (primary) tree
+ QList<Tree *> searchOrder = this->searchOrder();
+ setLocalSearch();
+
+ const QString configVar = CONFIG_NAVIGATION +
+ Config::dot +
+ CONFIG_TOCTITLES;
+
+ // TODO: [direct-configuration-access]
+ // The configuration is currently a singleton with some generally
+ // global mutable state.
+ //
+ // Accessing the data in this form complicates testing and
+ // requires tests that inhibit any test parallelization, as the
+ // tests are not self contained.
+ //
+ // This should be generally avoived. Possibly, we should strive
+ // for Config to be a POD type that generally is scoped to
+ // main and whose data is destructured into dependencies when
+ // the dependencies are constructed.
+ bool inclusive{Config::instance().get(
+ configVar + Config::dot + CONFIG_INCLUSIVE).asBool()};
+
+ // TODO: [direct-configuration-access]
+ const auto tocTitles{Config::instance().get(configVar).asStringList()};
+
+ for (const auto &tocTitle : tocTitles) {
+ if (const auto candidateTarget = findNodeForTarget(tocTitle, nullptr); candidateTarget && candidateTarget->isPageNode()) {
+ auto tocPage{static_cast<const PageNode*>(candidateTarget)};
+
+ Text body = tocPage->doc().body();
+
+ auto *atom = body.firstAtom();
+
+ std::pair<PageNode *, Atom *> prev { nullptr, nullptr };
+
+ std::stack<const PageNode *> tocStack;
+ tocStack.push(inclusive ? tocPage : nullptr);
+
+ bool inItem = false;
+
+ // TODO: Understand how much we use this form of looping over atoms.
+ // If it is used a few times we might consider providing
+ // an iterator for Text to make use of a simpler
+ // range-for loop.
+ while (atom) {
+ switch (atom->type()) {
+ case Atom::ListItemLeft:
+ // Not known if we're going to have a link, push a temporary
+ tocStack.push(nullptr);
+ inItem = true;
+ break;
+ case Atom::ListItemRight:
+ tocStack.pop();
+ inItem = false;
+ break;
+ case Atom::Link: {
+ if (!inItem)
+ break;
+
+ // TODO: [unnecessary-output-parameter]
+ // We currently need an lvalue string to
+ // pass to findNodeForAtom, as the
+ // outparameter ref.
+ //
+ // Apart from the general problems with output
+ // parameters, we shouldn't be forced to
+ // instanciate an unnecessary object at call
+ // site.
+ //
+ // Understand what the correct way to avoid this is.
+ // This requires changes to findNodeForAtom
+ // and should be addressed in the context of
+ // revising that method.
+ QString unused{};
+ // TODO: Having to const cast is really a code
+ // smell and could result in undefined
+ // behavior in some specific cases (e.g point
+ // to something that is actually const).
+ //
+ // We should understand how to sequence the
+ // code so that we have access to mutable data
+ // when we need it and "freeze" the data
+ // afterwards.
+ //
+ // If it we expect this form of mutability at
+ // this point we should expose a non-const API
+ // for the database, possibly limited to a
+ // very specific scope of execution.
+ //
+ // Understand what the correct sequencing for
+ // this processing is and revise this part.
+ auto candidatePage = const_cast<Node *>(findNodeForAtom(atom, nullptr, unused));
+ if (!candidatePage || !candidatePage->isPageNode()) break;
+
+ auto page{static_cast<PageNode*>(candidatePage)};
+
+ // ignore self-references
+ if (page == prev.first) break;
+
+ if (prev.first) {
+ prev.first->setLink(
+ Node::NextLink,
+ page->title(),
+ // TODO: [possible-assertion-failure][imprecise-types][atoms-link]
+ // As with other structures in QDoc we
+ // are able to call methods that are
+ // valid only on very specific states.
+ //
+ // For some of those calls we have
+ // some defensive programming measures
+ // that allow us to at least identify
+ // the error during debugging, while
+ // for others this may currently hide
+ // some logic error.
+ //
+ // To avoid those cases, we should
+ // strive to move those cases to a
+ // compilation error, requiring a
+ // statically analyzable state that
+ // represents the current model.
+ //
+ // This would ensure that those
+ // lingering class of bugs are
+ // eliminated completely, forces a
+ // more explicit codebase where the
+ // current capabilities do not depend
+ // on runtime values might not be
+ // generally visible, and does not
+ // require us to incur into the
+ // required state, which may be rare,
+ // simplifying our abilities to
+ // evaluate all possible states.
+ //
+ // For linking atoms, LinkAtom is
+ // available and might be a good
+ // enough solution to move linkText
+ // to.
+ atom->linkText()
+ );
+ page->setLink(
+ Node::PreviousLink,
+ prev.first->title(),
+ // TODO: [possible-assertion-failure][imprecise-types][atoms-link]
+ prev.second->linkText()
+ );
+ }
+
+ if (page == tocPage)
+ break;
+
+ // Find the navigation parent from the stack; we may have null pointers
+ // for non-link list items, so skip those.
+ qsizetype popped = 0;
+ while (tocStack.size() > 1 && !tocStack.top()) {
+ tocStack.pop();
+ ++popped;
+ }
+
+ page->setNavigationParent(tocStack.empty() ? nullptr : tocStack.top());
+
+ while (--popped > 0)
+ tocStack.push(nullptr);
+
+ tocStack.push(page);
+ prev = { page, atom };
+ }
+ break;
+ default:
+ break;
+ }
+
+ atom = atom->next();
+ }
+ } else {
+ Config::instance().get(configVar).location()
+ .warning(QStringLiteral("Failed to find table of contents with title '%1'")
+ .arg(tocTitle));
+ }
+ }
+
+ // Restore search order
+ setSearchOrder(searchOrder);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qdocdatabase.h b/src/qdoc/qdoc/src/qdoc/qdocdatabase.h
new file mode 100644
index 000000000..df2b4135c
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qdocdatabase.h
@@ -0,0 +1,395 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QDOCDATABASE_H
+#define QDOCDATABASE_H
+
+#include "config.h"
+#include "examplenode.h"
+#include "propertynode.h"
+#include "text.h"
+#include "tree.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+typedef QMultiMap<Text, const Node *> TextToNodeMap;
+
+class Atom;
+class FunctionNode;
+class Generator;
+class QDocDatabase;
+
+enum FindFlag {
+ SearchBaseClasses = 0x1,
+ SearchEnumValues = 0x2,
+ TypesOnly = 0x4,
+ IgnoreModules = 0x8
+};
+
+class QDocForest
+{
+private:
+ friend class QDocDatabase;
+ explicit QDocForest(QDocDatabase *qdb) : m_qdb(qdb), m_primaryTree(nullptr), m_currentIndex(0)
+ {
+ }
+ ~QDocForest();
+
+ Tree *firstTree();
+ Tree *nextTree();
+ Tree *primaryTree() { return m_primaryTree; }
+ Tree *findTree(const QString &t) { return m_forest.value(t); }
+ QStringList keys() { return m_forest.keys(); }
+ NamespaceNode *primaryTreeRoot() { return (m_primaryTree ? m_primaryTree->root() : nullptr); }
+ bool isEmpty() { return searchOrder().isEmpty(); }
+ bool done() { return (m_currentIndex >= searchOrder().size()); }
+ const QList<Tree *> &searchOrder();
+ const QList<Tree *> &indexSearchOrder();
+ void setSearchOrder(const QStringList &t);
+ bool isLoaded(const QString &fn)
+ {
+ return std::any_of(searchOrder().constBegin(), searchOrder().constEnd(),
+ [fn](Tree *tree) { return fn == tree->indexFileName(); });
+ }
+
+ const Node *findNode(const QStringList &path, const Node *relative, int findFlags,
+ Node::Genus genus)
+ {
+ for (const auto *tree : searchOrder()) {
+ const Node *n = tree->findNode(path, relative, findFlags, genus);
+ if (n)
+ return n;
+ relative = nullptr;
+ }
+ return nullptr;
+ }
+
+ Node *findNodeByNameAndType(const QStringList &path, bool (Node::*isMatch)() const)
+ {
+ for (const auto *tree : searchOrder()) {
+ Node *n = tree->findNodeByNameAndType(path, isMatch);
+ if (n)
+ return n;
+ }
+ return nullptr;
+ }
+
+ ClassNode *findClassNode(const QStringList &path)
+ {
+ for (const auto *tree : searchOrder()) {
+ ClassNode *n = tree->findClassNode(path);
+ if (n)
+ return n;
+ }
+ return nullptr;
+ }
+
+ Node *findNodeForInclude(const QStringList &path)
+ {
+ for (const auto *tree : searchOrder()) {
+ Node *n = tree->findNodeForInclude(path);
+ if (n)
+ return n;
+ }
+ return nullptr;
+ }
+
+ const FunctionNode *findFunctionNode(const QStringList &path, const Parameters &parameters,
+ const Node *relative, Node::Genus genus);
+ const Node *findNodeForTarget(QStringList &targetPath, const Node *relative, Node::Genus genus,
+ QString &ref);
+
+ const Node *findTypeNode(const QStringList &path, const Node *relative, Node::Genus genus)
+ {
+ int flags = SearchBaseClasses | SearchEnumValues | TypesOnly;
+ if (relative && genus == Node::DontCare && relative->genus() != Node::DOC)
+ genus = relative->genus();
+ for (const auto *tree : searchOrder()) {
+ const Node *n = tree->findNode(path, relative, flags, genus);
+ if (n)
+ return n;
+ relative = nullptr;
+ }
+ return nullptr;
+ }
+
+ const PageNode *findPageNodeByTitle(const QString &title)
+ {
+ for (const auto *tree : searchOrder()) {
+ const PageNode *n = tree->findPageNodeByTitle(title);
+ if (n)
+ return n;
+ }
+ return nullptr;
+ }
+
+ const CollectionNode *getCollectionNode(const QString &name, Node::NodeType type)
+ {
+ for (auto *tree : searchOrder()) {
+ const CollectionNode *cn = tree->getCollection(name, type);
+ if (cn)
+ return cn;
+ }
+ return nullptr;
+ }
+
+ QmlTypeNode *lookupQmlType(const QString &name)
+ {
+ for (const auto *tree : searchOrder()) {
+ QmlTypeNode *qcn = tree->lookupQmlType(name);
+ if (qcn)
+ return qcn;
+ }
+ return nullptr;
+ }
+
+ void clearSearchOrder() { m_searchOrder.clear(); }
+ void newPrimaryTree(const QString &module);
+ void setPrimaryTree(const QString &t);
+ NamespaceNode *newIndexTree(const QString &module);
+
+private:
+ QDocDatabase *m_qdb;
+ Tree *m_primaryTree;
+ int m_currentIndex;
+ QMap<QString, Tree *> m_forest;
+ QList<Tree *> m_searchOrder;
+ QList<Tree *> m_indexSearchOrder;
+ QList<QString> m_moduleNames;
+};
+
+class QDocDatabase
+{
+public:
+ static QDocDatabase *qdocDB();
+ static void destroyQdocDB();
+ ~QDocDatabase() = default;
+
+ using FindFunctionPtr = void (QDocDatabase::*)(Aggregate *);
+
+ Tree *findTree(const QString &t) { return m_forest.findTree(t); }
+
+ const CNMap &groups() { return primaryTree()->groups(); }
+ const CNMap &modules() { return primaryTree()->modules(); }
+ const CNMap &qmlModules() { return primaryTree()->qmlModules(); }
+
+ CollectionNode *addGroup(const QString &name) { return primaryTree()->addGroup(name); }
+ CollectionNode *addModule(const QString &name) { return primaryTree()->addModule(name); }
+ CollectionNode *addQmlModule(const QString &name) { return primaryTree()->addQmlModule(name); }
+
+ CollectionNode *addToGroup(const QString &name, Node *node)
+ {
+ return primaryTree()->addToGroup(name, node);
+ }
+ CollectionNode *addToModule(const QString &name, Node *node)
+ {
+ return primaryTree()->addToModule(name, node);
+ }
+ CollectionNode *addToQmlModule(const QString &name, Node *node)
+ {
+ return primaryTree()->addToQmlModule(name, node);
+ }
+
+ void addExampleNode(ExampleNode *n) { primaryTree()->addExampleNode(n); }
+ ExampleNodeMap &exampleNodeMap() { return primaryTree()->exampleNodeMap(); }
+
+ QmlTypeNode *findQmlType(const QString &name)
+ {
+ return m_forest.lookupQmlType(name);
+ }
+ QmlTypeNode *findQmlType(const QString &qmid, const QString &name);
+ QmlTypeNode *findQmlType(const ImportRec &import, const QString &name);
+ QmlTypeNode *findQmlTypeInPrimaryTree(const QString &qmid, const QString &name);
+
+ static NodeMultiMap &obsoleteClasses() { return s_obsoleteClasses; }
+ static NodeMultiMap &obsoleteQmlTypes() { return s_obsoleteQmlTypes; }
+ static NodeMultiMap &classesWithObsoleteMembers() { return s_classesWithObsoleteMembers; }
+ static NodeMultiMap &qmlTypesWithObsoleteMembers() { return s_qmlTypesWithObsoleteMembers; }
+ static NodeMultiMap &cppClasses() { return s_cppClasses; }
+ static NodeMultiMap &qmlBasicTypes() { return s_qmlBasicTypes; }
+ static NodeMultiMap &qmlTypes() { return s_qmlTypes; }
+ static NodeMultiMap &examples() { return s_examples; }
+ static NodeMultiMapMap &newClassMaps() { return s_newClassMaps; }
+ static NodeMultiMapMap &newQmlTypeMaps() { return s_newQmlTypeMaps; }
+ static NodeMultiMapMap &newEnumValueMaps() { return s_newEnumValueMaps; }
+ static NodeMultiMapMap &newSinceMaps() { return s_newSinceMaps; }
+
+private:
+ void findAllClasses(Aggregate *node) { node->findAllClasses(); }
+ void findAllFunctions(Aggregate *node) { node->findAllFunctions(m_functionIndex); }
+ void findAllAttributions(Aggregate *node) { node->findAllAttributions(m_attributions); }
+ void findAllLegaleseTexts(Aggregate *node);
+ void findAllObsoleteThings(Aggregate *node) { node->findAllObsoleteThings(); }
+ void findAllSince(Aggregate *node) { node->findAllSince(); }
+
+public:
+ /*******************************************************************
+ special collection access functions
+ ********************************************************************/
+ NodeMultiMap &getCppClasses();
+ NodeMultiMap &getObsoleteClasses();
+ NodeMultiMap &getClassesWithObsoleteMembers();
+ NodeMultiMap &getObsoleteQmlTypes();
+ NodeMultiMap &getQmlTypesWithObsoleteMembers();
+ NodeMultiMap &getNamespaces();
+ NodeMultiMap &getQmlValueTypes();
+ NodeMultiMap &getQmlTypes();
+ NodeMultiMap &getExamples();
+ NodeMultiMap &getAttributions();
+ NodeMapMap &getFunctionIndex();
+ TextToNodeMap &getLegaleseTexts();
+ const NodeMultiMap &getClassMap(const QString &key);
+ const NodeMultiMap &getQmlTypeMap(const QString &key);
+ const NodeMultiMap &getSinceMap(const QString &key);
+
+ /*******************************************************************
+ Many of these will be either eliminated or replaced.
+ ********************************************************************/
+ void resolveStuff();
+ void insertTarget(const QString &name, const QString &title, TargetRec::TargetType type,
+ Node *node, int priority)
+ {
+ primaryTree()->insertTarget(name, title, type, node, priority);
+ }
+
+ /*******************************************************************
+ The functions declared below are called for the current tree only.
+ ********************************************************************/
+ Aggregate *findRelatesNode(const QStringList &path)
+ {
+ return primaryTree()->findRelatesNode(path);
+ }
+ /*******************************************************************/
+
+ /*****************************************************************************
+ This function can handle parameters enclosed in '[' ']' (domain and genus).
+ ******************************************************************************/
+ const Node *findNodeForAtom(const Atom *atom, const Node *relative, QString &ref,
+ Node::Genus genus = Node::DontCare);
+ /*******************************************************************/
+
+ /*******************************************************************
+ The functions declared below are called for all trees.
+ ********************************************************************/
+ ClassNode *findClassNode(const QStringList &path) { return m_forest.findClassNode(path); }
+ Node *findNodeForInclude(const QStringList &path) { return m_forest.findNodeForInclude(path); }
+ const FunctionNode *findFunctionNode(const QString &target, const Node *relative,
+ Node::Genus genus);
+ const Node *findTypeNode(const QString &type, const Node *relative, Node::Genus genus);
+ const Node *findNodeForTarget(const QString &target, const Node *relative);
+ const PageNode *findPageNodeByTitle(const QString &title)
+ {
+ return m_forest.findPageNodeByTitle(title);
+ }
+ Node *findNodeByNameAndType(const QStringList &path, bool (Node::*isMatch)() const)
+ {
+ return m_forest.findNodeByNameAndType(path, isMatch);
+ }
+ const CollectionNode *getCollectionNode(const QString &name, Node::NodeType type)
+ {
+ return m_forest.getCollectionNode(name, type);
+ }
+ const CollectionNode *getModuleNode(const Node *relative);
+
+ FunctionNode *findFunctionNodeForTag(const QString &tag)
+ {
+ return primaryTree()->findFunctionNodeForTag(tag);
+ }
+ FunctionNode *findMacroNode(const QString &t) { return primaryTree()->findMacroNode(t); }
+
+ QStringList groupNamesForNode(Node *node);
+
+private:
+ const Node *findNodeForTarget(QStringList &targetPath, const Node *relative, Node::Genus genus,
+ QString &ref)
+ {
+ return m_forest.findNodeForTarget(targetPath, relative, genus, ref);
+ }
+ const FunctionNode *findFunctionNode(const QStringList &path, const Parameters &parameters,
+ const Node *relative, Node::Genus genus)
+ {
+ return m_forest.findFunctionNode(path, parameters, relative, genus);
+ }
+
+ /*******************************************************************/
+public:
+ void addPropertyFunction(PropertyNode *property, const QString &funcName,
+ PropertyNode::FunctionRole funcRole)
+ {
+ primaryTree()->addPropertyFunction(property, funcName, funcRole);
+ }
+
+ void setVersion(const QString &v) { m_version = v; }
+ [[nodiscard]] QString version() const { return m_version; }
+
+ void readIndexes(const QStringList &indexFiles);
+ void generateIndex(const QString &fileName, const QString &url, const QString &title,
+ Generator *g);
+
+ void processForest();
+
+ NamespaceNode *primaryTreeRoot() { return m_forest.primaryTreeRoot(); }
+ void newPrimaryTree(const QString &module) { m_forest.newPrimaryTree(module); }
+ void setPrimaryTree(const QString &t) { m_forest.setPrimaryTree(t); }
+ NamespaceNode *newIndexTree(const QString &module) { return m_forest.newIndexTree(module); }
+ const QList<Tree *> &searchOrder() { return m_forest.searchOrder(); }
+ void setLocalSearch() { m_forest.m_searchOrder = QList<Tree *>(1, primaryTree()); }
+ void setSearchOrder(const QList<Tree *> &searchOrder) { m_forest.m_searchOrder = searchOrder; }
+ void setSearchOrder(QStringList &t) { m_forest.setSearchOrder(t); }
+ void mergeCollections(Node::NodeType type, CNMap &cnm, const Node *relative);
+ void mergeCollections(CollectionNode *c);
+ void clearSearchOrder() { m_forest.clearSearchOrder(); }
+ QStringList keys() { return m_forest.keys(); }
+ void resolveNamespaces();
+ void resolveProxies();
+ void resolveBaseClasses();
+ void updateNavigation();
+
+private:
+ friend class Tree;
+
+ void processForest(FindFunctionPtr func);
+ bool isLoaded(const QString &t) { return m_forest.isLoaded(t); }
+ static void initializeDB();
+
+private:
+ QDocDatabase();
+ QDocDatabase(QDocDatabase const &) : m_forest(this) { }
+ QDocDatabase &operator=(QDocDatabase const &);
+
+public:
+ Tree *primaryTree() { return m_forest.primaryTree(); }
+
+private:
+ static QDocDatabase *s_qdocDB;
+ static NodeMap s_typeNodeMap;
+ static NodeMultiMap s_obsoleteClasses;
+ static NodeMultiMap s_classesWithObsoleteMembers;
+ static NodeMultiMap s_obsoleteQmlTypes;
+ static NodeMultiMap s_qmlTypesWithObsoleteMembers;
+ static NodeMultiMap s_cppClasses;
+ static NodeMultiMap s_qmlBasicTypes;
+ static NodeMultiMap s_qmlTypes;
+ static NodeMultiMap s_examples;
+ static NodeMultiMapMap s_newClassMaps;
+ static NodeMultiMapMap s_newQmlTypeMaps;
+ static NodeMultiMapMap s_newEnumValueMaps;
+ static NodeMultiMapMap s_newSinceMaps;
+
+ QString m_version {};
+ QDocForest m_forest;
+
+ NodeMultiMap m_namespaceIndex {};
+ NodeMultiMap m_attributions {};
+ NodeMapMap m_functionIndex {};
+ TextToNodeMap m_legaleseTexts {};
+ QMultiHash<Tree*, FindFunctionPtr> m_completedFindFunctions {};
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/qdocindexfiles.cpp b/src/qdoc/qdoc/src/qdoc/qdocindexfiles.cpp
new file mode 100644
index 000000000..f2f0d017e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qdocindexfiles.cpp
@@ -0,0 +1,1458 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qdocindexfiles.h"
+
+#include "access.h"
+#include "atom.h"
+#include "classnode.h"
+#include "collectionnode.h"
+#include "comparisoncategory.h"
+#include "config.h"
+#include "enumnode.h"
+#include "examplenode.h"
+#include "externalpagenode.h"
+#include "functionnode.h"
+#include "generator.h"
+#include "headernode.h"
+#include "location.h"
+#include "utilities.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "qmlpropertynode.h"
+#include "typedefnode.h"
+#include "variablenode.h"
+
+#include <QtCore/qxmlstream.h>
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+enum QDocAttr {
+ QDocAttrNone,
+ QDocAttrExample,
+ QDocAttrFile,
+ QDocAttrImage,
+ QDocAttrDocument,
+ QDocAttrExternalPage,
+ QDocAttrAttribution
+};
+
+static Node *root_ = nullptr;
+static IndexSectionWriter *post_ = nullptr;
+
+/*!
+ \class QDocIndexFiles
+
+ This class handles qdoc index files.
+ */
+
+QDocIndexFiles *QDocIndexFiles::s_qdocIndexFiles = nullptr;
+
+/*!
+ Constructs the singleton QDocIndexFiles.
+ */
+QDocIndexFiles::QDocIndexFiles() : m_gen(nullptr)
+{
+ m_qdb = QDocDatabase::qdocDB();
+ m_storeLocationInfo = Config::instance().get(CONFIG_LOCATIONINFO).asBool();
+}
+
+/*!
+ Destroys the singleton QDocIndexFiles.
+ */
+QDocIndexFiles::~QDocIndexFiles()
+{
+ m_qdb = nullptr;
+ m_gen = nullptr;
+}
+
+/*!
+ Creates the singleton. Allows only one instance of the class
+ to be created. Returns a pointer to the singleton.
+ */
+QDocIndexFiles *QDocIndexFiles::qdocIndexFiles()
+{
+ if (s_qdocIndexFiles == nullptr)
+ s_qdocIndexFiles = new QDocIndexFiles;
+ return s_qdocIndexFiles;
+}
+
+/*!
+ Destroys the singleton.
+ */
+void QDocIndexFiles::destroyQDocIndexFiles()
+{
+ if (s_qdocIndexFiles != nullptr) {
+ delete s_qdocIndexFiles;
+ s_qdocIndexFiles = nullptr;
+ }
+}
+
+/*!
+ Reads and parses the list of index files in \a indexFiles.
+ */
+void QDocIndexFiles::readIndexes(const QStringList &indexFiles)
+{
+ for (const QString &file : indexFiles) {
+ qCDebug(lcQdoc) << "Loading index file: " << file;
+ readIndexFile(file);
+ }
+}
+
+/*!
+ Reads and parses the index file at \a path.
+ */
+void QDocIndexFiles::readIndexFile(const QString &path)
+{
+ QFile file(path);
+ if (!file.open(QFile::ReadOnly)) {
+ qWarning() << "Could not read index file" << path;
+ return;
+ }
+
+ QXmlStreamReader reader(&file);
+ reader.setNamespaceProcessing(false);
+
+ if (!reader.readNextStartElement())
+ return;
+
+ if (reader.name() != QLatin1String("INDEX"))
+ return;
+
+ QXmlStreamAttributes attrs = reader.attributes();
+
+ QString indexUrl {attrs.value(QLatin1String("url")).toString()};
+
+ // Decide how we link to nodes loaded from this index file:
+ // If building a set that will be installed AND the URL of
+ // the dependency is identical to ours, assume that also
+ // the dependent html files are available under the same
+ // directory tree. Otherwise, link using the full index URL.
+ if (!Config::installDir.isEmpty() && indexUrl == Config::instance().get(CONFIG_URL).asString()) {
+ // Generate a relative URL between the install dir and the index file
+ // when the -installdir command line option is set.
+ QDir installDir(path.section('/', 0, -3) + '/' + Generator::outputSubdir());
+ indexUrl = installDir.relativeFilePath(path).section('/', 0, -2);
+ }
+ m_project = attrs.value(QLatin1String("project")).toString();
+ QString indexTitle = attrs.value(QLatin1String("indexTitle")).toString();
+ m_basesList.clear();
+ m_relatedNodes.clear();
+
+ NamespaceNode *root = m_qdb->newIndexTree(m_project);
+ if (!root) {
+ qWarning() << "Issue parsing index tree" << path;
+ return;
+ }
+
+ root->tree()->setIndexTitle(indexTitle);
+
+ // Scan all elements in the XML file, constructing a map that contains
+ // base classes for each class found.
+ while (reader.readNextStartElement()) {
+ readIndexSection(reader, root, indexUrl);
+ }
+
+ // Now that all the base classes have been found for this index,
+ // arrange them into an inheritance hierarchy.
+ resolveIndex();
+}
+
+/*!
+ Read a <section> element from the index file and create the
+ appropriate node(s).
+ */
+void QDocIndexFiles::readIndexSection(QXmlStreamReader &reader, Node *current,
+ const QString &indexUrl)
+{
+ QXmlStreamAttributes attributes = reader.attributes();
+ QStringView elementName = reader.name();
+
+ QString name = attributes.value(QLatin1String("name")).toString();
+ QString href = attributes.value(QLatin1String("href")).toString();
+ Node *node;
+ Location location;
+ Aggregate *parent = nullptr;
+ bool hasReadChildren = false;
+
+ if (current->isAggregate())
+ parent = static_cast<Aggregate *>(current);
+
+ if (attributes.hasAttribute(QLatin1String("related"))) {
+ bool isIntTypeRelatedValue = false;
+ int relatedIndex = attributes.value(QLatin1String("related")).toInt(&isIntTypeRelatedValue);
+ if (isIntTypeRelatedValue) {
+ if (adoptRelatedNode(parent, relatedIndex)) {
+ reader.skipCurrentElement();
+ return;
+ }
+ } else {
+ QList<Node *>::iterator nodeIterator =
+ std::find_if(m_relatedNodes.begin(), m_relatedNodes.end(), [&](const Node *relatedNode) {
+ return (name == relatedNode->name() && href == relatedNode->url().section(QLatin1Char('/'), -1));
+ });
+
+ if (nodeIterator != m_relatedNodes.end() && parent) {
+ parent->adoptChild(*nodeIterator);
+ reader.skipCurrentElement();
+ return;
+ }
+ }
+ }
+
+ QString filePath;
+ int lineNo = 0;
+ if (attributes.hasAttribute(QLatin1String("filepath"))) {
+ filePath = attributes.value(QLatin1String("filepath")).toString();
+ lineNo = attributes.value("lineno").toInt();
+ }
+ if (elementName == QLatin1String("namespace")) {
+ auto *namespaceNode = new NamespaceNode(parent, name);
+ node = namespaceNode;
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + name.toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(name.toLower() + ".html");
+ } else if (elementName == QLatin1String("class") || elementName == QLatin1String("struct")
+ || elementName == QLatin1String("union")) {
+ Node::NodeType type = Node::Class;
+ if (elementName == QLatin1String("class"))
+ type = Node::Class;
+ else if (elementName == QLatin1String("struct"))
+ type = Node::Struct;
+ else if (elementName == QLatin1String("union"))
+ type = Node::Union;
+ node = new ClassNode(type, parent, name);
+ if (attributes.hasAttribute(QLatin1String("bases"))) {
+ QString bases = attributes.value(QLatin1String("bases")).toString();
+ if (!bases.isEmpty())
+ m_basesList.append(
+ std::pair<ClassNode *, QString>(static_cast<ClassNode *>(node), bases));
+ }
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + name.toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(name.toLower() + ".html");
+ bool abstract = false;
+ if (attributes.value(QLatin1String("abstract")) == QLatin1String("true"))
+ abstract = true;
+ node->setAbstract(abstract);
+ } else if (elementName == QLatin1String("header")) {
+ node = new HeaderNode(parent, name);
+
+ if (attributes.hasAttribute(QLatin1String("location")))
+ name = attributes.value(QLatin1String("location")).toString();
+
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + name);
+ else if (!indexUrl.isNull())
+ location = Location(name);
+ } else if (elementName == QLatin1String("qmlclass") || elementName == QLatin1String("qmlvaluetype")
+ || elementName == QLatin1String("qmlbasictype")) {
+ auto *qmlTypeNode = new QmlTypeNode(parent, name,
+ elementName == QLatin1String("qmlclass") ? Node::QmlType : Node::QmlValueType);
+ qmlTypeNode->setTitle(attributes.value(QLatin1String("title")).toString());
+ QString logicalModuleName = attributes.value(QLatin1String("qml-module-name")).toString();
+ if (!logicalModuleName.isEmpty())
+ m_qdb->addToQmlModule(logicalModuleName, qmlTypeNode);
+ bool abstract = false;
+ if (attributes.value(QLatin1String("abstract")) == QLatin1String("true"))
+ abstract = true;
+ qmlTypeNode->setAbstract(abstract);
+ QString qmlFullBaseName = attributes.value(QLatin1String("qml-base-type")).toString();
+ if (!qmlFullBaseName.isEmpty()) {
+ qmlTypeNode->setQmlBaseName(qmlFullBaseName);
+ }
+ if (attributes.hasAttribute(QLatin1String("location")))
+ name = attributes.value("location").toString();
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + name);
+ else if (!indexUrl.isNull())
+ location = Location(name);
+ node = qmlTypeNode;
+ } else if (elementName == QLatin1String("qmlproperty")) {
+ QString type = attributes.value(QLatin1String("type")).toString();
+ bool attached = false;
+ if (attributes.value(QLatin1String("attached")) == QLatin1String("true"))
+ attached = true;
+ bool readonly = false;
+ if (attributes.value(QLatin1String("writable")) == QLatin1String("false"))
+ readonly = true;
+ auto *qmlPropertyNode = new QmlPropertyNode(parent, name, type, attached);
+ qmlPropertyNode->markReadOnly(readonly);
+ if (attributes.value(QLatin1String("required")) == QLatin1String("true"))
+ qmlPropertyNode->setRequired();
+ node = qmlPropertyNode;
+ } else if (elementName == QLatin1String("group")) {
+ auto *collectionNode = m_qdb->addGroup(name);
+ collectionNode->setTitle(attributes.value(QLatin1String("title")).toString());
+ collectionNode->setSubtitle(attributes.value(QLatin1String("subtitle")).toString());
+ if (attributes.value(QLatin1String("seen")) == QLatin1String("true"))
+ collectionNode->markSeen();
+ node = collectionNode;
+ } else if (elementName == QLatin1String("module")) {
+ auto *collectionNode = m_qdb->addModule(name);
+ collectionNode->setTitle(attributes.value(QLatin1String("title")).toString());
+ collectionNode->setSubtitle(attributes.value(QLatin1String("subtitle")).toString());
+ if (attributes.value(QLatin1String("seen")) == QLatin1String("true"))
+ collectionNode->markSeen();
+ node = collectionNode;
+ } else if (elementName == QLatin1String("qmlmodule")) {
+ auto *collectionNode = m_qdb->addQmlModule(name);
+ const QStringList info = QStringList()
+ << name
+ << QString(attributes.value(QLatin1String("qml-module-version")).toString());
+ collectionNode->setLogicalModuleInfo(info);
+ collectionNode->setTitle(attributes.value(QLatin1String("title")).toString());
+ collectionNode->setSubtitle(attributes.value(QLatin1String("subtitle")).toString());
+ if (attributes.value(QLatin1String("seen")) == QLatin1String("true"))
+ collectionNode->markSeen();
+ node = collectionNode;
+ } else if (elementName == QLatin1String("page")) {
+ QDocAttr subtype = QDocAttrNone;
+ QString attr = attributes.value(QLatin1String("subtype")).toString();
+ if (attr == QLatin1String("attribution")) {
+ subtype = QDocAttrAttribution;
+ } else if (attr == QLatin1String("example")) {
+ subtype = QDocAttrExample;
+ } else if (attr == QLatin1String("file")) {
+ subtype = QDocAttrFile;
+ } else if (attr == QLatin1String("image")) {
+ subtype = QDocAttrImage;
+ } else if (attr == QLatin1String("page")) {
+ subtype = QDocAttrDocument;
+ } else if (attr == QLatin1String("externalpage")) {
+ subtype = QDocAttrExternalPage;
+ } else
+ goto done;
+
+ if (current->isExample()) {
+ auto *exampleNode = static_cast<ExampleNode *>(current);
+ if (subtype == QDocAttrFile) {
+ exampleNode->appendFile(name);
+ goto done;
+ } else if (subtype == QDocAttrImage) {
+ exampleNode->appendImage(name);
+ goto done;
+ }
+ }
+ PageNode *pageNode = nullptr;
+ if (subtype == QDocAttrExample)
+ pageNode = new ExampleNode(parent, name);
+ else if (subtype == QDocAttrExternalPage)
+ pageNode = new ExternalPageNode(parent, name);
+ else {
+ pageNode = new PageNode(parent, name);
+ if (subtype == QDocAttrAttribution) pageNode->markAttribution();
+ }
+
+ pageNode->setTitle(attributes.value(QLatin1String("title")).toString());
+
+ if (attributes.hasAttribute(QLatin1String("location")))
+ name = attributes.value(QLatin1String("location")).toString();
+
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + name);
+ else if (!indexUrl.isNull())
+ location = Location(name);
+
+ node = pageNode;
+
+ } else if (elementName == QLatin1String("enum")) {
+ auto *enumNode = new EnumNode(parent, name, attributes.hasAttribute("scoped"));
+
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(parent->name().toLower() + ".html");
+
+ while (reader.readNextStartElement()) {
+ QXmlStreamAttributes childAttributes = reader.attributes();
+ if (reader.name() == QLatin1String("value")) {
+ EnumItem item(childAttributes.value(QLatin1String("name")).toString(),
+ childAttributes.value(QLatin1String("value")).toString(),
+ childAttributes.value(QLatin1String("since")).toString()
+ );
+ enumNode->addItem(item);
+ } else if (reader.name() == QLatin1String("keyword")) {
+ insertTarget(TargetRec::Keyword, childAttributes, enumNode);
+ } else if (reader.name() == QLatin1String("target")) {
+ insertTarget(TargetRec::Target, childAttributes, enumNode);
+ }
+ reader.skipCurrentElement();
+ }
+
+ node = enumNode;
+
+ hasReadChildren = true;
+ } else if (elementName == QLatin1String("typedef")) {
+ if (attributes.hasAttribute("aliasedtype"))
+ node = new TypeAliasNode(parent, name, attributes.value(QLatin1String("aliasedtype")).toString());
+ else
+ node = new TypedefNode(parent, name);
+
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(parent->name().toLower() + ".html");
+ } else if (elementName == QLatin1String("property")) {
+ auto *propNode = new PropertyNode(parent, name);
+ node = propNode;
+ if (attributes.value(QLatin1String("bindable")) == QLatin1String("true"))
+ propNode->setPropertyType(PropertyNode::PropertyType::BindableProperty);
+
+ propNode->setWritable(attributes.value(QLatin1String("writable")) != QLatin1String("false"));
+
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(parent->name().toLower() + ".html");
+
+ } else if (elementName == QLatin1String("function")) {
+ QString t = attributes.value(QLatin1String("meta")).toString();
+ bool attached = false;
+ FunctionNode::Metaness metaness = FunctionNode::Plain;
+ if (!t.isEmpty())
+ metaness = FunctionNode::getMetaness(t);
+ if (attributes.value(QLatin1String("attached")) == QLatin1String("true"))
+ attached = true;
+ auto *fn = new FunctionNode(metaness, parent, name, attached);
+
+ fn->setReturnType(attributes.value(QLatin1String("type")).toString());
+
+ if (fn->isCppNode()) {
+ fn->setVirtualness(attributes.value(QLatin1String("virtual")).toString());
+ fn->setConst(attributes.value(QLatin1String("const")) == QLatin1String("true"));
+ fn->setStatic(attributes.value(QLatin1String("static")) == QLatin1String("true"));
+ fn->setFinal(attributes.value(QLatin1String("final")) == QLatin1String("true"));
+ fn->setOverride(attributes.value(QLatin1String("override")) == QLatin1String("true"));
+
+ if (attributes.value(QLatin1String("explicit")) == QLatin1String("true"))
+ fn->markExplicit();
+
+ if (attributes.value(QLatin1String("constexpr")) == QLatin1String("true"))
+ fn->markConstexpr();
+
+ if (attributes.value(QLatin1String("noexcept")) == QLatin1String("true")) {
+ fn->markNoexcept(attributes.value("noexcept_expression").toString());
+ }
+
+ qsizetype refness = attributes.value(QLatin1String("refness")).toUInt();
+ if (refness == 1)
+ fn->setRef(true);
+ else if (refness == 2)
+ fn->setRefRef(true);
+ /*
+ Theoretically, this should ensure that each function
+ node receives the same overload number and overload
+ flag it was written with, and it should be unnecessary
+ to call normalizeOverloads() for index nodes.
+ */
+ if (attributes.value(QLatin1String("overload")) == QLatin1String("true"))
+ fn->setOverloadNumber(attributes.value(QLatin1String("overload-number")).toUInt());
+ else
+ fn->setOverloadNumber(0);
+ }
+
+ /*
+ Note: The "signature" attribute was written to the
+ index file, but it is not read back in. That is ok
+ because we reconstruct the parameter list and the
+ return type, from which the signature was built in
+ the first place and from which it can be rebuilt.
+ */
+ while (reader.readNextStartElement()) {
+ QXmlStreamAttributes childAttributes = reader.attributes();
+ if (reader.name() == QLatin1String("parameter")) {
+ // Do not use the default value for the parameter; it is not
+ // required, and has been known to cause problems.
+ QString type = childAttributes.value(QLatin1String("type")).toString();
+ QString name = childAttributes.value(QLatin1String("name")).toString();
+ fn->parameters().append(type, name);
+ } else if (reader.name() == QLatin1String("keyword")) {
+ insertTarget(TargetRec::Keyword, childAttributes, fn);
+ } else if (reader.name() == QLatin1String("target")) {
+ insertTarget(TargetRec::Target, childAttributes, fn);
+ }
+ reader.skipCurrentElement();
+ }
+
+ node = fn;
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(parent->name().toLower() + ".html");
+
+ hasReadChildren = true;
+ } else if (elementName == QLatin1String("variable")) {
+ node = new VariableNode(parent, name);
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(parent->name().toLower() + ".html");
+ } else if (elementName == QLatin1String("keyword")) {
+ insertTarget(TargetRec::Keyword, attributes, current);
+ goto done;
+ } else if (elementName == QLatin1String("target")) {
+ insertTarget(TargetRec::Target, attributes, current);
+ goto done;
+ } else if (elementName == QLatin1String("contents")) {
+ insertTarget(TargetRec::Contents, attributes, current);
+ goto done;
+ } else if (elementName == QLatin1String("proxy")) {
+ node = new ProxyNode(parent, name);
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + name.toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(name.toLower() + ".html");
+ } else {
+ goto done;
+ }
+
+ {
+ if (!href.isEmpty()) {
+ node->setUrl(href);
+ // Include the index URL if it exists
+ if (!node->isExternalPage() && !indexUrl.isEmpty())
+ node->setUrl(indexUrl + QLatin1Char('/') + href);
+ }
+
+ const QString access = attributes.value(QLatin1String("access")).toString();
+ if (access == "protected")
+ node->setAccess(Access::Protected);
+ else if ((access == "private") || (access == "internal"))
+ node->setAccess(Access::Private);
+ else
+ node->setAccess(Access::Public);
+
+ if (attributes.hasAttribute(QLatin1String("related"))) {
+ node->setRelatedNonmember(true);
+ m_relatedNodes << node;
+ }
+
+ if (attributes.hasAttribute(QLatin1String("threadsafety"))) {
+ QString threadSafety = attributes.value(QLatin1String("threadsafety")).toString();
+ if (threadSafety == QLatin1String("non-reentrant"))
+ node->setThreadSafeness(Node::NonReentrant);
+ else if (threadSafety == QLatin1String("reentrant"))
+ node->setThreadSafeness(Node::Reentrant);
+ else if (threadSafety == QLatin1String("thread safe"))
+ node->setThreadSafeness(Node::ThreadSafe);
+ else
+ node->setThreadSafeness(Node::UnspecifiedSafeness);
+ } else
+ node->setThreadSafeness(Node::UnspecifiedSafeness);
+
+ const QString category = attributes.value(QLatin1String("comparison_category")).toString();
+ node->setComparisonCategory(comparisonCategoryFromString(category.toStdString()));
+
+ QString status = attributes.value(QLatin1String("status")).toString();
+ // TODO: "obsolete" is kept for backward compatibility, remove in the near future
+ if (status == QLatin1String("obsolete") || status == QLatin1String("deprecated"))
+ node->setStatus(Node::Deprecated);
+ else if (status == QLatin1String("preliminary"))
+ node->setStatus(Node::Preliminary);
+ else if (status == QLatin1String("internal"))
+ node->setStatus(Node::Internal);
+ else if (status == QLatin1String("ignored"))
+ node->setStatus(Node::DontDocument);
+ else
+ node->setStatus(Node::Active);
+
+ QString physicalModuleName = attributes.value(QLatin1String("module")).toString();
+ if (!physicalModuleName.isEmpty())
+ m_qdb->addToModule(physicalModuleName, node);
+
+ QString since = attributes.value(QLatin1String("since")).toString();
+ if (!since.isEmpty()) {
+ node->setSince(since);
+ }
+
+ if (attributes.hasAttribute(QLatin1String("documented"))) {
+ if (attributes.value(QLatin1String("documented")) == QLatin1String("true"))
+ node->setHadDoc();
+ }
+
+ QString groupsAttr = attributes.value(QLatin1String("groups")).toString();
+ if (!groupsAttr.isEmpty()) {
+ const QStringList groupNames = groupsAttr.split(QLatin1Char(','));
+ for (const auto &group : groupNames) {
+ m_qdb->addToGroup(group, node);
+ }
+ }
+
+ // Create some content for the node.
+ QSet<QString> emptySet;
+ Location t(filePath);
+ if (!filePath.isEmpty()) {
+ t.setLineNo(lineNo);
+ node->setLocation(t);
+ location = t;
+ }
+ Doc doc(location, location, QString(), emptySet, emptySet); // placeholder
+ node->setDoc(doc);
+ node->setIndexNodeFlag(); // Important: This node came from an index file.
+ QString briefAttr = attributes.value(QLatin1String("brief")).toString();
+ if (!briefAttr.isEmpty()) {
+ node->setReconstitutedBrief(briefAttr);
+ }
+
+ if (const auto sortKey = attributes.value(QLatin1String("sortkey")).toString(); !sortKey.isEmpty()) {
+ node->doc().constructExtra();
+ if (auto *metaMap = node->doc().metaTagMap())
+ metaMap->insert("sortkey", sortKey);
+ }
+ if (!hasReadChildren) {
+ bool useParent = (elementName == QLatin1String("namespace") && name.isEmpty());
+ while (reader.readNextStartElement()) {
+ if (useParent)
+ readIndexSection(reader, parent, indexUrl);
+ else
+ readIndexSection(reader, node, indexUrl);
+ }
+ }
+ }
+
+done:
+ while (!reader.isEndElement()) {
+ if (reader.readNext() == QXmlStreamReader::Invalid) {
+ break;
+ }
+ }
+}
+
+void QDocIndexFiles::insertTarget(TargetRec::TargetType type,
+ const QXmlStreamAttributes &attributes, Node *node)
+{
+ int priority;
+ switch (type) {
+ case TargetRec::Keyword:
+ priority = 1;
+ break;
+ case TargetRec::Target:
+ priority = 2;
+ break;
+ case TargetRec::Contents:
+ priority = 3;
+ break;
+ default:
+ return;
+ }
+
+ QString name = attributes.value(QLatin1String("name")).toString();
+ QString title = attributes.value(QLatin1String("title")).toString();
+ m_qdb->insertTarget(name, title, type, node, priority);
+}
+
+/*!
+ This function tries to resolve class inheritance immediately
+ after the index file is read. It is not always possible to
+ resolve a class inheritance at this point, because the base
+ class might be in an index file that hasn't been read yet, or
+ it might be in one of the header files that will be read for
+ the current module. These cases will be resolved after all
+ the index files and header and source files have been read,
+ just prior to beginning the generate phase for the current
+ module.
+
+ I don't think this is completely correct because it always
+ sets the access to public.
+ */
+void QDocIndexFiles::resolveIndex()
+{
+ for (const auto &pair : std::as_const(m_basesList)) {
+ const QStringList bases = pair.second.split(QLatin1Char(','));
+ for (const auto &base : bases) {
+ QStringList basePath = base.split(QString("::"));
+ Node *n = m_qdb->findClassNode(basePath);
+ if (n)
+ pair.first->addResolvedBaseClass(Access::Public, static_cast<ClassNode *>(n));
+ else
+ pair.first->addUnresolvedBaseClass(Access::Public, basePath);
+ }
+ }
+ // No longer needed.
+ m_basesList.clear();
+}
+
+static QString getAccessString(Access t)
+{
+
+ switch (t) {
+ case Access::Public:
+ return QLatin1String("public");
+ case Access::Protected:
+ return QLatin1String("protected");
+ case Access::Private:
+ return QLatin1String("private");
+ default:
+ break;
+ }
+ return QLatin1String("public");
+}
+
+static QString getStatusString(Node::Status t)
+{
+ switch (t) {
+ case Node::Deprecated:
+ return QLatin1String("deprecated");
+ case Node::Preliminary:
+ return QLatin1String("preliminary");
+ case Node::Active:
+ return QLatin1String("active");
+ case Node::Internal:
+ return QLatin1String("internal");
+ case Node::DontDocument:
+ return QLatin1String("ignored");
+ default:
+ break;
+ }
+ return QLatin1String("active");
+}
+
+static QString getThreadSafenessString(Node::ThreadSafeness t)
+{
+ switch (t) {
+ case Node::NonReentrant:
+ return QLatin1String("non-reentrant");
+ case Node::Reentrant:
+ return QLatin1String("reentrant");
+ case Node::ThreadSafe:
+ return QLatin1String("thread safe");
+ case Node::UnspecifiedSafeness:
+ default:
+ break;
+ }
+ return QLatin1String("unspecified");
+}
+
+/*!
+ Returns the index of \a node in the list of related non-member nodes.
+*/
+int QDocIndexFiles::indexForNode(Node *node)
+{
+ qsizetype i = m_relatedNodes.indexOf(node);
+ if (i == -1) {
+ i = m_relatedNodes.size();
+ m_relatedNodes << node;
+ }
+ return i;
+}
+
+/*!
+ Adopts the related non-member node identified by \a index to the
+ parent \a adoptiveParent. Returns \c true if successful.
+*/
+bool QDocIndexFiles::adoptRelatedNode(Aggregate *adoptiveParent, int index)
+{
+ Node *related = m_relatedNodes.value(index);
+
+ if (adoptiveParent && related) {
+ adoptiveParent->adoptChild(related);
+ return true;
+ }
+
+ return false;
+}
+
+/*!
+ Write canonicalized versions of \\target and \\keyword identifiers
+ that appear in the documentation of \a node into the index using
+ \a writer, so that they can be used as link targets in external
+ documentation sets.
+*/
+void QDocIndexFiles::writeTargets(QXmlStreamWriter &writer, Node *node)
+{
+ if (node->doc().hasTargets()) {
+ for (const Atom *target : std::as_const(node->doc().targets())) {
+ const QString &title = target->string();
+ const QString &name{Utilities::asAsciiPrintable(title)};
+ writer.writeStartElement("target");
+ writer.writeAttribute("name", node->isExternalPage() ? title : name);
+ if (name != title)
+ writer.writeAttribute("title", title);
+ writer.writeEndElement(); // target
+ }
+ }
+ if (node->doc().hasKeywords()) {
+ for (const Atom *keyword : std::as_const(node->doc().keywords())) {
+ const QString &title = keyword->string();
+ const QString &name{Utilities::asAsciiPrintable(title)};
+ writer.writeStartElement("keyword");
+ writer.writeAttribute("name", name);
+ if (name != title)
+ writer.writeAttribute("title", title);
+ writer.writeEndElement(); // keyword
+ }
+ }
+}
+
+/*!
+ Generate the index section with the given \a writer for the \a node
+ specified, returning true if an element was written, and returning
+ false if an element is not written.
+
+ \note Function nodes are processed in generateFunctionSection()
+ */
+bool QDocIndexFiles::generateIndexSection(QXmlStreamWriter &writer, Node *node,
+ IndexSectionWriter *post)
+{
+ if (m_gen == nullptr)
+ m_gen = Generator::currentGenerator();
+
+ Q_ASSERT(m_gen);
+
+ post_ = nullptr;
+ /*
+ Don't include index nodes in a new index file.
+ */
+ if (node->isIndexNode())
+ return false;
+
+ QString nodeName;
+ QString logicalModuleName;
+ QString logicalModuleVersion;
+ QString qmlFullBaseName;
+ QString baseNameAttr;
+ QString moduleNameAttr;
+ QString moduleVerAttr;
+
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ nodeName = "namespace";
+ break;
+ case Node::Class:
+ nodeName = "class";
+ break;
+ case Node::Struct:
+ nodeName = "struct";
+ break;
+ case Node::Union:
+ nodeName = "union";
+ break;
+ case Node::HeaderFile:
+ nodeName = "header";
+ break;
+ case Node::QmlType:
+ case Node::QmlValueType:
+ nodeName = (node->nodeType() == Node::QmlType) ? "qmlclass" : "qmlvaluetype";
+ logicalModuleName = node->logicalModuleName();
+ baseNameAttr = "qml-base-type";
+ moduleNameAttr = "qml-module-name";
+ moduleVerAttr = "qml-module-version";
+ qmlFullBaseName = node->qmlFullBaseName();
+ break;
+ case Node::Page:
+ case Node::Example:
+ case Node::ExternalPage:
+ nodeName = "page";
+ break;
+ case Node::Group:
+ nodeName = "group";
+ break;
+ case Node::Module:
+ nodeName = "module";
+ break;
+ case Node::QmlModule:
+ nodeName = "qmlmodule";
+ moduleNameAttr = "qml-module-name";
+ moduleVerAttr = "qml-module-version";
+ logicalModuleName = node->logicalModuleName();
+ logicalModuleVersion = node->logicalModuleVersion();
+ break;
+ case Node::Enum:
+ nodeName = "enum";
+ break;
+ case Node::TypeAlias:
+ case Node::Typedef:
+ nodeName = "typedef";
+ break;
+ case Node::Property:
+ nodeName = "property";
+ break;
+ case Node::Variable:
+ nodeName = "variable";
+ break;
+ case Node::SharedComment:
+ if (!node->isPropertyGroup())
+ return false;
+ // Add an entry for property groups so that they can be linked to
+ nodeName = "qmlproperty";
+ break;
+ case Node::QmlProperty:
+ nodeName = "qmlproperty";
+ break;
+ case Node::Proxy:
+ nodeName = "proxy";
+ break;
+ case Node::Function: // Now processed in generateFunctionSection()
+ default:
+ return false;
+ }
+
+ QString objName = node->name();
+ // Special case: only the root node should have an empty name.
+ if (objName.isEmpty() && node != m_qdb->primaryTreeRoot())
+ return false;
+
+ writer.writeStartElement(nodeName);
+
+ if (!node->isTextPageNode() && !node->isCollectionNode() && !node->isHeader()) {
+ if (node->threadSafeness() != Node::UnspecifiedSafeness)
+ writer.writeAttribute("threadsafety", getThreadSafenessString(node->threadSafeness()));
+ }
+
+ writer.writeAttribute("name", objName);
+
+ // Write module and base type info for QML types
+ if (!moduleNameAttr.isEmpty()) {
+ if (!logicalModuleName.isEmpty())
+ writer.writeAttribute(moduleNameAttr, logicalModuleName);
+ if (!logicalModuleVersion.isEmpty())
+ writer.writeAttribute(moduleVerAttr, logicalModuleVersion);
+ }
+ if (!baseNameAttr.isEmpty() && !qmlFullBaseName.isEmpty())
+ writer.writeAttribute(baseNameAttr, qmlFullBaseName);
+
+ QString href;
+ if (!node->isExternalPage()) {
+ QString fullName = node->fullDocumentName();
+ if (fullName != objName)
+ writer.writeAttribute("fullname", fullName);
+ href = m_gen->fullDocumentLocation(node);
+ } else
+ href = node->name();
+ if (node->isQmlNode()) {
+ Aggregate *p = node->parent();
+ if (p && p->isQmlType() && p->isAbstract())
+ href.clear();
+ }
+ if (!href.isEmpty())
+ writer.writeAttribute("href", href);
+
+ writer.writeAttribute("status", getStatusString(node->status()));
+ if (!node->isTextPageNode() && !node->isCollectionNode() && !node->isHeader()) {
+ writer.writeAttribute("access", getAccessString(node->access()));
+ if (node->isAbstract())
+ writer.writeAttribute("abstract", "true");
+ }
+ const Location &declLocation = node->declLocation();
+ if (!declLocation.fileName().isEmpty())
+ writer.writeAttribute("location", declLocation.fileName());
+ if (m_storeLocationInfo && !declLocation.filePath().isEmpty()) {
+ writer.writeAttribute("filepath", declLocation.filePath());
+ writer.writeAttribute("lineno", QString("%1").arg(declLocation.lineNo()));
+ }
+
+ if (node->isRelatedNonmember())
+ writer.writeAttribute("related", QString::number(indexForNode(node)));
+
+ if (!node->since().isEmpty())
+ writer.writeAttribute("since", node->since());
+
+ if (node->hasDoc())
+ writer.writeAttribute("documented", "true");
+
+ QStringList groups = m_qdb->groupNamesForNode(node);
+ if (!groups.isEmpty())
+ writer.writeAttribute("groups", groups.join(QLatin1Char(',')));
+
+ if (const auto *metamap = node->doc().metaTagMap(); metamap)
+ if (const auto sortKey = metamap->value("sortkey"); !sortKey.isEmpty())
+ writer.writeAttribute("sortkey", sortKey);
+
+ QString brief = node->doc().trimmedBriefText(node->name()).toString();
+ switch (node->nodeType()) {
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union: {
+ // Classes contain information about their base classes.
+ const auto *classNode = static_cast<const ClassNode *>(node);
+ const QList<RelatedClass> &bases = classNode->baseClasses();
+ QSet<QString> baseStrings;
+ for (const auto &related : bases) {
+ ClassNode *n = related.m_node;
+ if (n)
+ baseStrings.insert(n->fullName());
+ else if (!related.m_path.isEmpty())
+ baseStrings.insert(related.m_path.join(QLatin1String("::")));
+ }
+ if (!baseStrings.isEmpty()) {
+ QStringList baseStringsAsList = baseStrings.values();
+ baseStringsAsList.sort();
+ writer.writeAttribute("bases", baseStringsAsList.join(QLatin1Char(',')));
+ }
+ if (!node->physicalModuleName().isEmpty())
+ writer.writeAttribute("module", node->physicalModuleName());
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ if (auto category = node->comparisonCategory(); category != ComparisonCategory::None)
+ writer.writeAttribute("comparison_category", comparisonCategoryAsString(category));
+ } break;
+ case Node::HeaderFile: {
+ const auto *headerNode = static_cast<const HeaderNode *>(node);
+ if (!headerNode->physicalModuleName().isEmpty())
+ writer.writeAttribute("module", headerNode->physicalModuleName());
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ writer.writeAttribute("title", headerNode->title());
+ writer.writeAttribute("fulltitle", headerNode->fullTitle());
+ writer.writeAttribute("subtitle", headerNode->subtitle());
+ } break;
+ case Node::Namespace: {
+ const auto *namespaceNode = static_cast<const NamespaceNode *>(node);
+ if (!namespaceNode->physicalModuleName().isEmpty())
+ writer.writeAttribute("module", namespaceNode->physicalModuleName());
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ } break;
+ case Node::QmlValueType:
+ case Node::QmlType: {
+ const auto *qmlTypeNode = static_cast<const QmlTypeNode *>(node);
+ writer.writeAttribute("title", qmlTypeNode->title());
+ writer.writeAttribute("fulltitle", qmlTypeNode->fullTitle());
+ writer.writeAttribute("subtitle", qmlTypeNode->subtitle());
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ } break;
+ case Node::Page:
+ case Node::Example:
+ case Node::ExternalPage: {
+ if (node->isExample())
+ writer.writeAttribute("subtype", "example");
+ else if (node->isExternalPage())
+ writer.writeAttribute("subtype", "externalpage");
+ else
+ writer.writeAttribute("subtype", (static_cast<PageNode*>(node)->isAttribution() ? "attribution" : "page"));
+
+ const auto *pageNode = static_cast<const PageNode *>(node);
+ writer.writeAttribute("title", pageNode->title());
+ writer.writeAttribute("fulltitle", pageNode->fullTitle());
+ writer.writeAttribute("subtitle", pageNode->subtitle());
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ } break;
+ case Node::Group:
+ case Node::Module:
+ case Node::QmlModule: {
+ const auto *collectionNode = static_cast<const CollectionNode *>(node);
+ writer.writeAttribute("seen", collectionNode->wasSeen() ? "true" : "false");
+ writer.writeAttribute("title", collectionNode->title());
+ if (!collectionNode->subtitle().isEmpty())
+ writer.writeAttribute("subtitle", collectionNode->subtitle());
+ if (!collectionNode->physicalModuleName().isEmpty())
+ writer.writeAttribute("module", collectionNode->physicalModuleName());
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ } break;
+ case Node::QmlProperty: {
+ auto *qmlPropertyNode = static_cast<QmlPropertyNode *>(node);
+ writer.writeAttribute("type", qmlPropertyNode->dataType());
+ writer.writeAttribute("attached", qmlPropertyNode->isAttached() ? "true" : "false");
+ writer.writeAttribute("writable", qmlPropertyNode->isReadOnly() ? "false" : "true");
+ if (qmlPropertyNode->isRequired())
+ writer.writeAttribute("required", "true");
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ } break;
+ case Node::Property: {
+ const auto *propertyNode = static_cast<const PropertyNode *>(node);
+
+ if (propertyNode->propertyType() == PropertyNode::PropertyType::BindableProperty)
+ writer.writeAttribute("bindable", "true");
+
+ if (!propertyNode->isWritable())
+ writer.writeAttribute("writable", "false");
+
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ // Property access function names
+ for (qsizetype i{0}; i < (qsizetype)PropertyNode::FunctionRole::NumFunctionRoles; ++i) {
+ auto role{(PropertyNode::FunctionRole)i};
+ for (const auto *fnNode : propertyNode->functions(role)) {
+ writer.writeStartElement(PropertyNode::roleName(role));
+ writer.writeAttribute("name", fnNode->name());
+ writer.writeEndElement();
+ }
+ }
+ } break;
+ case Node::Variable: {
+ const auto *variableNode = static_cast<const VariableNode *>(node);
+ writer.writeAttribute("type", variableNode->dataType());
+ writer.writeAttribute("static", variableNode->isStatic() ? "true" : "false");
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ } break;
+ case Node::Enum: {
+ const auto *enumNode = static_cast<const EnumNode *>(node);
+ if (enumNode->isScoped())
+ writer.writeAttribute("scoped", "true");
+ if (enumNode->flagsType())
+ writer.writeAttribute("typedef", enumNode->flagsType()->fullDocumentName());
+ const auto &items = enumNode->items();
+ for (const auto &item : items) {
+ writer.writeStartElement("value");
+ writer.writeAttribute("name", item.name());
+ writer.writeAttribute("value", item.value());
+ if (!item.since().isEmpty())
+ writer.writeAttribute("since", item.since());
+ writer.writeEndElement(); // value
+ }
+ } break;
+ case Node::Typedef: {
+ const auto *typedefNode = static_cast<const TypedefNode *>(node);
+ if (typedefNode->associatedEnum())
+ writer.writeAttribute("enum", typedefNode->associatedEnum()->fullDocumentName());
+ } break;
+ case Node::TypeAlias:
+ writer.writeAttribute("aliasedtype", static_cast<const TypeAliasNode *>(node)->aliasedType());
+ break;
+ case Node::Function: // Now processed in generateFunctionSection()
+ default:
+ break;
+ }
+
+ writeTargets(writer, node);
+
+ /*
+ Some nodes have a table of contents. For these, we close
+ the opening tag, create sub-elements for the items in the
+ table of contents, and then add a closing tag for the
+ element. Elements for all other nodes are closed in the
+ opening tag.
+ */
+ if (node->isPageNode() || node->isCollectionNode()) {
+ if (node->doc().hasTableOfContents()) {
+ for (int i = 0; i < node->doc().tableOfContents().size(); ++i) {
+ Atom *item = node->doc().tableOfContents()[i];
+ int level = node->doc().tableOfContentsLevels()[i];
+ QString title = Text::sectionHeading(item).toString();
+ writer.writeStartElement("contents");
+ writer.writeAttribute("name", Tree::refForAtom(item));
+ writer.writeAttribute("title", title);
+ writer.writeAttribute("level", QString::number(level));
+ writer.writeEndElement(); // contents
+ }
+ }
+ }
+ // WebXMLGenerator - skip the nested <page> elements for example
+ // files/images, as the generator produces them separately
+ if (node->isExample() && m_gen->format() != QLatin1String("WebXML")) {
+ const auto *exampleNode = static_cast<const ExampleNode *>(node);
+ const auto &files = exampleNode->files();
+ for (const QString &file : files) {
+ writer.writeStartElement("page");
+ writer.writeAttribute("name", file);
+ QString href = m_gen->linkForExampleFile(file);
+ writer.writeAttribute("href", href);
+ writer.writeAttribute("status", "active");
+ writer.writeAttribute("subtype", "file");
+ writer.writeAttribute("title", "");
+ writer.writeAttribute("fulltitle", Generator::exampleFileTitle(exampleNode, file));
+ writer.writeAttribute("subtitle", file);
+ writer.writeEndElement(); // page
+ }
+ const auto &images = exampleNode->images();
+ for (const QString &file : images) {
+ writer.writeStartElement("page");
+ writer.writeAttribute("name", file);
+ QString href = m_gen->linkForExampleFile(file);
+ writer.writeAttribute("href", href);
+ writer.writeAttribute("status", "active");
+ writer.writeAttribute("subtype", "image");
+ writer.writeAttribute("title", "");
+ writer.writeAttribute("fulltitle", Generator::exampleFileTitle(exampleNode, file));
+ writer.writeAttribute("subtitle", file);
+ writer.writeEndElement(); // page
+ }
+ }
+ // Append to the section if the callback object was set
+ if (post)
+ post->append(writer, node);
+
+ post_ = post;
+ return true;
+}
+
+/*!
+ This function writes a <function> element for \a fn to the
+ index file using \a writer.
+ */
+void QDocIndexFiles::generateFunctionSection(QXmlStreamWriter &writer, FunctionNode *fn)
+{
+ if (fn->isInternal() && !Config::instance().showInternal())
+ return;
+
+ const QString objName = fn->name();
+ writer.writeStartElement("function");
+ writer.writeAttribute("name", objName);
+
+ const QString fullName = fn->fullDocumentName();
+ if (fullName != objName)
+ writer.writeAttribute("fullname", fullName);
+ const QString href = m_gen->fullDocumentLocation(fn);
+ if (!href.isEmpty())
+ writer.writeAttribute("href", href);
+ if (fn->threadSafeness() != Node::UnspecifiedSafeness)
+ writer.writeAttribute("threadsafety", getThreadSafenessString(fn->threadSafeness()));
+ writer.writeAttribute("status", getStatusString(fn->status()));
+ writer.writeAttribute("access", getAccessString(fn->access()));
+
+ const Location &declLocation = fn->declLocation();
+ if (!declLocation.fileName().isEmpty())
+ writer.writeAttribute("location", declLocation.fileName());
+ if (m_storeLocationInfo && !declLocation.filePath().isEmpty()) {
+ writer.writeAttribute("filepath", declLocation.filePath());
+ writer.writeAttribute("lineno", QString("%1").arg(declLocation.lineNo()));
+ }
+
+ if (fn->hasDoc())
+ writer.writeAttribute("documented", "true");
+ if (fn->isRelatedNonmember())
+ writer.writeAttribute("related", QString::number(indexForNode(fn)));
+ if (!fn->since().isEmpty())
+ writer.writeAttribute("since", fn->since());
+
+ const QString brief = fn->doc().trimmedBriefText(fn->name()).toString();
+ writer.writeAttribute("meta", fn->metanessString());
+ if (fn->isCppNode()) {
+ if (!fn->isNonvirtual())
+ writer.writeAttribute("virtual", fn->virtualness());
+
+ if (fn->isConst())
+ writer.writeAttribute("const", "true");
+ if (fn->isStatic())
+ writer.writeAttribute("static", "true");
+ if (fn->isFinal())
+ writer.writeAttribute("final", "true");
+ if (fn->isOverride())
+ writer.writeAttribute("override", "true");
+ if (fn->isExplicit())
+ writer.writeAttribute("explicit", "true");
+ if (fn->isConstexpr())
+ writer.writeAttribute("constexpr", "true");
+
+ if (auto noexcept_info = fn->getNoexcept()) {
+ writer.writeAttribute("noexcept", "true");
+ if (!(*noexcept_info).isEmpty()) writer.writeAttribute("noexcept_expression", *noexcept_info);
+ }
+
+ /*
+ This ensures that for functions that have overloads,
+ the first function written is the one that is not an
+ overload, and the overloads follow it immediately in
+ the index file numbered from 1 to n.
+ */
+ if (fn->isOverload() && (fn->overloadNumber() > 0)) {
+ writer.writeAttribute("overload", "true");
+ writer.writeAttribute("overload-number", QString::number(fn->overloadNumber()));
+ }
+ if (fn->isRef())
+ writer.writeAttribute("refness", QString::number(1));
+ else if (fn->isRefRef())
+ writer.writeAttribute("refness", QString::number(2));
+ if (fn->hasAssociatedProperties()) {
+ QStringList associatedProperties;
+ for (const auto *node : fn->associatedProperties()) {
+ associatedProperties << node->name();
+ }
+ associatedProperties.sort();
+ writer.writeAttribute("associated-property",
+ associatedProperties.join(QLatin1Char(',')));
+ }
+ }
+
+ const auto return_type = fn->returnType();
+ if (!return_type.isEmpty())
+ writer.writeAttribute("type", return_type);
+
+ if (fn->isCppNode()) {
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+
+ /*
+ Note: The "signature" attribute is written to the
+ index file, but it is not read back in by qdoc. However,
+ we need it for the webxml generator.
+ */
+ const QString signature = appendAttributesToSignature(fn);
+ writer.writeAttribute("signature", signature);
+
+ QStringList groups = m_qdb->groupNamesForNode(fn);
+ if (!groups.isEmpty())
+ writer.writeAttribute("groups", groups.join(QLatin1Char(',')));
+ }
+
+ for (int i = 0; i < fn->parameters().count(); ++i) {
+ const Parameter &parameter = fn->parameters().at(i);
+ writer.writeStartElement("parameter");
+ writer.writeAttribute("type", parameter.type());
+ writer.writeAttribute("name", parameter.name());
+ writer.writeAttribute("default", parameter.defaultValue());
+ writer.writeEndElement(); // parameter
+ }
+
+ writeTargets(writer, fn);
+
+ // Append to the section if the callback object was set
+ if (post_)
+ post_->append(writer, fn);
+
+ writer.writeEndElement(); // function
+}
+
+/*!
+ \internal
+
+ Constructs the signature to be written to an index file for the function
+ represented by FunctionNode \a fn.
+
+ 'const' is already part of FunctionNode::signature(), which forms the basis
+ for the signature returned by this method. The method adds, where
+ applicable, the C++ keywords "final", "override", or "= 0", to the
+ signature carried by the FunctionNode itself.
+ */
+QString QDocIndexFiles::appendAttributesToSignature(const FunctionNode *fn) const noexcept
+{
+ QString signature = fn->signature(Node::SignatureReturnType);
+
+ if (fn->isFinal())
+ signature += " final";
+ if (fn->isOverride())
+ signature += " override";
+ if (fn->isPureVirtual())
+ signature += " = 0";
+
+ return signature;
+}
+
+/*!
+ Outputs a <function> element to the index for each FunctionNode in
+ an \a aggregate, using \a writer.
+ The \a aggregate has a function map that contains all the
+ function nodes (a vector of overloads) indexed by function
+ name.
+
+ If a function element represents an overload, it has an
+ \c overload attribute set to \c true and an \c {overload-number}
+ attribute set to the function's overload number.
+ */
+void QDocIndexFiles::generateFunctionSections(QXmlStreamWriter &writer, Aggregate *aggregate)
+{
+ for (auto functions : std::as_const(aggregate->functionMap())) {
+ std::for_each(functions.begin(), functions.end(),
+ [this,&writer](FunctionNode *fn) {
+ generateFunctionSection(writer, fn);
+ }
+ );
+ }
+}
+
+/*!
+ Generate index sections for the child nodes of the given \a node
+ using the \a writer specified.
+*/
+void QDocIndexFiles::generateIndexSections(QXmlStreamWriter &writer, Node *node,
+ IndexSectionWriter *post)
+{
+ /*
+ Note that groups, modules, and QML modules are written
+ after all the other nodes.
+ */
+ if (node->isCollectionNode() || node->isGroup() || node->isModule() || node->isQmlModule())
+ return;
+
+ if (node->isInternal() && !Config::instance().showInternal())
+ return;
+
+ if (generateIndexSection(writer, node, post)) {
+ if (node->isAggregate()) {
+ auto *aggregate = static_cast<Aggregate *>(node);
+ // First write the function children, then write the nonfunction children.
+ generateFunctionSections(writer, aggregate);
+ const auto &nonFunctionList = aggregate->nonfunctionList();
+ for (auto *node : nonFunctionList)
+ generateIndexSections(writer, node, post);
+ }
+
+ if (node == root_) {
+ /*
+ We wait until the end of the index file to output the group, module,
+ and QML module elements. By outputting them at the end, when we read
+ the index file back in, all the group, module, and QML module member
+ elements will have already been created. It is then only necessary to
+ create the group, module, or QML module element and add each member to
+ its member list.
+ */
+ const CNMap &groups = m_qdb->groups();
+ if (!groups.isEmpty()) {
+ for (auto it = groups.constBegin(); it != groups.constEnd(); ++it) {
+ if (generateIndexSection(writer, it.value(), post))
+ writer.writeEndElement();
+ }
+ }
+
+ const CNMap &modules = m_qdb->modules();
+ if (!modules.isEmpty()) {
+ for (auto it = modules.constBegin(); it != modules.constEnd(); ++it) {
+ if (generateIndexSection(writer, it.value(), post))
+ writer.writeEndElement();
+ }
+ }
+
+ const CNMap &qmlModules = m_qdb->qmlModules();
+ if (!qmlModules.isEmpty()) {
+ for (auto it = qmlModules.constBegin(); it != qmlModules.constEnd(); ++it) {
+ if (generateIndexSection(writer, it.value(), post))
+ writer.writeEndElement();
+ }
+ }
+ }
+
+ writer.writeEndElement();
+ }
+}
+
+/*!
+ Writes a qdoc module index in XML to a file named \a fileName.
+ \a url is the \c url attribute of the <INDEX> element.
+ \a title is the \c title attribute of the <INDEX> element.
+ \a g is a pointer to the current Generator in use, stored for later use.
+ */
+void QDocIndexFiles::generateIndex(const QString &fileName, const QString &url,
+ const QString &title, Generator *g)
+{
+ QFile file(fileName);
+ if (!file.open(QFile::WriteOnly | QFile::Text))
+ return;
+
+ qCDebug(lcQdoc) << "Writing index file:" << fileName;
+
+ m_gen = g;
+ m_relatedNodes.clear();
+ QXmlStreamWriter writer(&file);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument();
+ writer.writeDTD("<!DOCTYPE QDOCINDEX>");
+
+ writer.writeStartElement("INDEX");
+ writer.writeAttribute("url", url);
+ writer.writeAttribute("title", title);
+ writer.writeAttribute("version", m_qdb->version());
+ writer.writeAttribute("project", Config::instance().get(CONFIG_PROJECT).asString());
+
+ root_ = m_qdb->primaryTreeRoot();
+ if (!root_->tree()->indexTitle().isEmpty())
+ writer.writeAttribute("indexTitle", root_->tree()->indexTitle());
+
+ generateIndexSections(writer, root_, nullptr);
+
+ writer.writeEndElement(); // INDEX
+ writer.writeEndElement(); // QDOCINDEX
+ writer.writeEndDocument();
+ file.close();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qdocindexfiles.h b/src/qdoc/qdoc/src/qdoc/qdocindexfiles.h
new file mode 100644
index 000000000..2225aa576
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qdocindexfiles.h
@@ -0,0 +1,73 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QDOCINDEXFILES_H
+#define QDOCINDEXFILES_H
+
+#include "node.h"
+#include "tree.h"
+
+QT_BEGIN_NAMESPACE
+
+class Atom;
+class FunctionNode;
+class Generator;
+class QDocDatabase;
+class WebXMLGenerator;
+class QXmlStreamReader;
+class QXmlStreamWriter;
+class QXmlStreamAttributes;
+
+// A callback interface for extending index sections
+class IndexSectionWriter
+{
+public:
+ virtual ~IndexSectionWriter() = default;
+ virtual void append(QXmlStreamWriter &writer, Node *node) = 0;
+};
+
+class QDocIndexFiles
+{
+ friend class QDocDatabase;
+ friend class WebXMLGenerator; // for using generateIndexSections()
+
+private:
+ static QDocIndexFiles *qdocIndexFiles();
+ static void destroyQDocIndexFiles();
+
+ QDocIndexFiles();
+ ~QDocIndexFiles();
+
+ void readIndexes(const QStringList &indexFiles);
+ void readIndexFile(const QString &path);
+ void readIndexSection(QXmlStreamReader &reader, Node *current, const QString &indexUrl);
+ void insertTarget(TargetRec::TargetType type, const QXmlStreamAttributes &attributes,
+ Node *node);
+ void resolveIndex();
+ int indexForNode(Node *node);
+ bool adoptRelatedNode(Aggregate *adoptiveParent, int index);
+ void writeTargets(QXmlStreamWriter &writer, Node *node);
+
+ void generateIndex(const QString &fileName, const QString &url, const QString &title,
+ Generator *g);
+ void generateFunctionSection(QXmlStreamWriter &writer, FunctionNode *fn);
+ void generateFunctionSections(QXmlStreamWriter &writer, Aggregate *aggregate);
+ bool generateIndexSection(QXmlStreamWriter &writer, Node *node,
+ IndexSectionWriter *post = nullptr);
+ void generateIndexSections(QXmlStreamWriter &writer, Node *node,
+ IndexSectionWriter *post = nullptr);
+ QString appendAttributesToSignature(const FunctionNode *fn) const noexcept;
+
+private:
+ static QDocIndexFiles *s_qdocIndexFiles;
+ QDocDatabase *m_qdb {};
+ Generator *m_gen {};
+ QString m_project;
+ QList<std::pair<ClassNode *, QString>> m_basesList;
+ NodeList m_relatedNodes;
+ bool m_storeLocationInfo;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/qmlcodemarker.cpp b/src/qdoc/qdoc/src/qdoc/qmlcodemarker.cpp
new file mode 100644
index 000000000..30dec979e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlcodemarker.cpp
@@ -0,0 +1,175 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qmlcodemarker.h"
+
+#include <QtCore/qregularexpression.h>
+
+#include "atom.h"
+#include "node.h"
+#include "qmlmarkupvisitor.h"
+#include "text.h"
+
+#include <private/qqmljsast_p.h>
+#include <private/qqmljsastfwd_p.h>
+#include <private/qqmljsengine_p.h>
+#include <private/qqmljslexer_p.h>
+#include <private/qqmljsparser_p.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Returns \c true if the \a code is recognized by the parser.
+ */
+bool QmlCodeMarker::recognizeCode(const QString &code)
+{
+ // Naive pre-check; starts with an import statement or 'CamelCase {'
+ static const QRegularExpression regExp(QStringLiteral("^\\s*(import |([A-Z][a-z0-9]*)+\\s?{)"));
+ if (!regExp.match(code).hasMatch())
+ return false;
+
+ QQmlJS::Engine engine;
+ QQmlJS::Lexer lexer(&engine);
+ QQmlJS::Parser parser(&engine);
+
+ QString newCode = code;
+ extractPragmas(newCode);
+ lexer.setCode(newCode, 1);
+
+ return parser.parse();
+}
+
+/*!
+ Returns \c true if \a ext is any of a list of file extensions
+ for the QML language.
+ */
+bool QmlCodeMarker::recognizeExtension(const QString &ext)
+{
+ return ext == "qml";
+}
+
+/*!
+ Returns \c true if the \a language is recognized. Only "QML" is
+ recognized by this marker.
+ */
+bool QmlCodeMarker::recognizeLanguage(const QString &language)
+{
+ return language == "QML";
+}
+
+/*!
+ Returns the type of atom used to represent QML code in the documentation.
+*/
+Atom::AtomType QmlCodeMarker::atomType() const
+{
+ return Atom::Qml;
+}
+
+QString QmlCodeMarker::markedUpCode(const QString &code, const Node *relative,
+ const Location &location)
+{
+ return addMarkUp(code, relative, location);
+}
+
+/*!
+ Constructs and returns the marked up name for the \a node.
+ If the node is any kind of QML function (a method,
+ signal, or handler), "()" is appended to the marked up name.
+ */
+QString QmlCodeMarker::markedUpName(const Node *node)
+{
+ QString name = linkTag(node, taggedNode(node));
+ if (node->isFunction())
+ name += "()";
+ return name;
+}
+
+QString QmlCodeMarker::markedUpInclude(const QString &include)
+{
+ return addMarkUp("import " + include, nullptr, Location{});
+}
+
+QString QmlCodeMarker::addMarkUp(const QString &code, const Node * /* relative */,
+ const Location &location)
+{
+ QQmlJS::Engine engine;
+ QQmlJS::Lexer lexer(&engine);
+
+ QString newCode = code;
+ QList<QQmlJS::SourceLocation> pragmas = extractPragmas(newCode);
+ lexer.setCode(newCode, 1);
+
+ QQmlJS::Parser parser(&engine);
+ QString output;
+
+ if (parser.parse()) {
+ QQmlJS::AST::UiProgram *ast = parser.ast();
+ // Pass the unmodified code to the visitor so that pragmas and other
+ // unhandled source text can be output.
+ QmlMarkupVisitor visitor(code, pragmas, &engine);
+ QQmlJS::AST::Node::accept(ast, &visitor);
+ if (visitor.hasError()) {
+ location.warning(
+ location.fileName()
+ + QStringLiteral("Unable to analyze QML snippet. The output is incomplete."));
+ }
+ output = visitor.markedUpCode();
+ } else {
+ location.warning(QStringLiteral("Unable to parse QML snippet: \"%1\" at line %2, column %3")
+ .arg(parser.errorMessage())
+ .arg(parser.errorLineNumber())
+ .arg(parser.errorColumnNumber()));
+ output = protect(code);
+ }
+
+ return output;
+}
+
+/*
+ Copied and pasted from
+ src/declarative/qml/qqmlscriptparser.cpp.
+*/
+void replaceWithSpace(QString &str, int idx, int n); // qmlcodeparser.cpp
+
+/*
+ Copied and pasted from
+ src/declarative/qml/qqmlscriptparser.cpp then modified to
+ return a list of removed pragmas.
+
+ Searches for ".pragma <value>" or ".import <stuff>" declarations
+ in \a script. Currently supported pragmas are: library
+*/
+QList<QQmlJS::SourceLocation> QmlCodeMarker::extractPragmas(QString &script)
+{
+ QList<QQmlJS::SourceLocation> removed;
+
+ QQmlJS::Lexer l(nullptr);
+ l.setCode(script, 0);
+
+ int token = l.lex();
+
+ while (true) {
+ if (token != QQmlJSGrammar::T_DOT)
+ break;
+
+ int startOffset = l.tokenOffset();
+ int startLine = l.tokenStartLine();
+ int startColumn = l.tokenStartColumn();
+
+ token = l.lex();
+
+ if (token != QQmlJSGrammar::T_PRAGMA && token != QQmlJSGrammar::T_IMPORT)
+ break;
+ int endOffset = 0;
+ while (startLine == l.tokenStartLine()) {
+ endOffset = l.tokenLength() + l.tokenOffset();
+ token = l.lex();
+ }
+ replaceWithSpace(script, startOffset, endOffset - startOffset);
+ removed.append(QQmlJS::SourceLocation(startOffset, endOffset - startOffset, startLine,
+ startColumn));
+ }
+ return removed;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qmlcodemarker.h b/src/qdoc/qdoc/src/qdoc/qmlcodemarker.h
new file mode 100644
index 000000000..64a1f7c9f
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlcodemarker.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QMLCODEMARKER_H
+#define QMLCODEMARKER_H
+
+#include "cppcodemarker.h"
+
+#include <private/qqmljsastfwd_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QmlCodeMarker : public CppCodeMarker
+{
+public:
+ QmlCodeMarker() = default;
+ ~QmlCodeMarker() override = default;
+
+ bool recognizeCode(const QString &code) override;
+ bool recognizeExtension(const QString &ext) override;
+ bool recognizeLanguage(const QString &language) override;
+ [[nodiscard]] Atom::AtomType atomType() const override;
+ QString markedUpCode(const QString &code, const Node *relative,
+ const Location &location) override;
+
+ QString markedUpName(const Node *node) override;
+ QString markedUpInclude(const QString &include) override;
+
+ /* Copied from src/declarative/qml/qdeclarativescriptparser.cpp */
+ QList<QQmlJS::SourceLocation> extractPragmas(QString &script);
+
+private:
+ QString addMarkUp(const QString &code, const Node *relative, const Location &location);
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/qmlcodeparser.cpp b/src/qdoc/qdoc/src/qdoc/qmlcodeparser.cpp
new file mode 100644
index 000000000..fadd7c307
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlcodeparser.cpp
@@ -0,0 +1,143 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qmlcodeparser.h"
+
+#include "node.h"
+#include "qmlvisitor.h"
+#include "utilities.h"
+
+#include <private/qqmljsast_p.h>
+
+#include <qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Returns "QML".
+ */
+QString QmlCodeParser::language()
+{
+ return "QML";
+}
+
+/*!
+ Returns a string list containing "*.qml". This is the only
+ file type parsed by the QMLN parser.
+ */
+QStringList QmlCodeParser::sourceFileNameFilter()
+{
+ return QStringList() << "*.qml";
+}
+
+/*!
+ Parses the source file at \a filePath and inserts the contents
+ into the database. The \a location is used for error reporting.
+
+ If it can't open the file at \a filePath, it reports an error
+ and returns without doing anything.
+ */
+void QmlCodeParser::parseSourceFile(const Location &location, const QString &filePath, CppCodeParser&)
+{
+ static const QSet<QString> topic_commands{
+ COMMAND_VARIABLE, COMMAND_QMLCLASS, COMMAND_QMLTYPE, COMMAND_QMLPROPERTY,
+ COMMAND_QMLPROPERTYGROUP, COMMAND_QMLATTACHEDPROPERTY, COMMAND_QMLSIGNAL,
+ COMMAND_QMLATTACHEDSIGNAL, COMMAND_QMLMETHOD, COMMAND_QMLATTACHEDMETHOD,
+ COMMAND_QMLVALUETYPE, COMMAND_QMLBASICTYPE,
+ };
+
+ QFile in(filePath);
+ if (!in.open(QIODevice::ReadOnly)) {
+ location.error(QStringLiteral("Cannot open QML file '%1'").arg(filePath));
+ return;
+ }
+
+ QString document = in.readAll();
+ in.close();
+
+ QString newCode = document;
+ extractPragmas(newCode);
+
+ QQmlJS::Engine engine{};
+ QQmlJS::Lexer lexer{&engine};
+ lexer.setCode(newCode, 1);
+
+ QQmlJS::Parser parser{&engine};
+
+ if (parser.parse()) {
+ QQmlJS::AST::UiProgram *ast = parser.ast();
+ QmlDocVisitor visitor(filePath, newCode, &engine, topic_commands + CodeParser::common_meta_commands,
+ topic_commands);
+ QQmlJS::AST::Node::accept(ast, &visitor);
+ if (visitor.hasError())
+ Location(filePath).warning("Could not analyze QML file, output is incomplete.");
+ }
+ const auto &messages = parser.diagnosticMessages();
+ for (const auto &msg : messages) {
+ qCDebug(lcQdoc, "%s: %d: %d: QML syntax error: %s", qUtf8Printable(filePath),
+ msg.loc.startLine, msg.loc.startColumn, qUtf8Printable(msg.message));
+ }
+}
+
+/*!
+ Copy and paste from src/declarative/qml/qdeclarativescriptparser.cpp.
+ This function blanks out the section of the \a str beginning at \a idx
+ and running for \a n characters.
+*/
+void replaceWithSpace(QString &str, int idx, int n) // Also used in qmlcodemarker.cpp.
+{
+ QChar *data = str.data() + idx;
+ const QChar space(QLatin1Char(' '));
+ for (int ii = 0; ii < n; ++ii)
+ *data++ = space;
+}
+
+/*!
+ Copy & paste from src/declarative/qml/qdeclarativescriptparser.cpp,
+ then modified to return no values.
+
+ Searches for ".pragma <value>" declarations within \a script.
+ Currently supported pragmas are: library
+*/
+void QmlCodeParser::extractPragmas(QString &script)
+{
+ const QString pragma(QLatin1String("pragma"));
+
+ QQmlJS::Lexer l(nullptr);
+ l.setCode(script, 0);
+
+ int token = l.lex();
+
+ while (true) {
+ if (token != QQmlJSGrammar::T_DOT)
+ return;
+
+ int startOffset = l.tokenOffset();
+ int startLine = l.tokenStartLine();
+
+ token = l.lex();
+
+ if (token != QQmlJSGrammar::T_IDENTIFIER || l.tokenStartLine() != startLine
+ || script.mid(l.tokenOffset(), l.tokenLength()) != pragma)
+ return;
+
+ token = l.lex();
+
+ if (token != QQmlJSGrammar::T_IDENTIFIER || l.tokenStartLine() != startLine)
+ return;
+
+ QString pragmaValue = script.mid(l.tokenOffset(), l.tokenLength());
+ int endOffset = l.tokenLength() + l.tokenOffset();
+
+ token = l.lex();
+ if (l.tokenStartLine() == startLine)
+ return;
+
+ if (pragmaValue == QLatin1String("library"))
+ replaceWithSpace(script, startOffset, endOffset - startOffset);
+ else
+ return;
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qmlcodeparser.h b/src/qdoc/qdoc/src/qdoc/qmlcodeparser.h
new file mode 100644
index 000000000..dff493be4
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlcodeparser.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QMLCODEPARSER_H
+#define QMLCODEPARSER_H
+
+#include "codeparser.h"
+
+#include <QtCore/qset.h>
+
+#include <private/qqmljsengine_p.h>
+#include <private/qqmljslexer_p.h>
+#include <private/qqmljsparser_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Node;
+class QString;
+
+class QmlCodeParser : public CodeParser
+{
+public:
+ QmlCodeParser() = default;
+ ~QmlCodeParser() override = default;
+
+ void initializeParser() override {}
+ void terminateParser() override {}
+ QString language() override;
+ QStringList sourceFileNameFilter() override;
+ void parseSourceFile(const Location &location, const QString &filePath, CppCodeParser&) override;
+
+ /* Copied from src/declarative/qml/qdeclarativescriptparser.cpp */
+ void extractPragmas(QString &script);
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.cpp b/src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.cpp
new file mode 100644
index 000000000..31adb838d
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.cpp
@@ -0,0 +1,794 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qmlmarkupvisitor.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstringlist.h>
+
+#include <private/qqmljsast_p.h>
+#include <private/qqmljsengine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QmlMarkupVisitor::QmlMarkupVisitor(const QString &source,
+ const QList<QQmlJS::SourceLocation> &pragmas,
+ QQmlJS::Engine *engine)
+{
+ this->m_source = source;
+ this->m_engine = engine;
+
+ m_cursor = 0;
+ m_extraIndex = 0;
+
+ // Merge the lists of locations of pragmas and comments in the source code.
+ int i = 0;
+ int j = 0;
+ const QList<QQmlJS::SourceLocation> comments = engine->comments();
+ while (i < comments.size() && j < pragmas.size()) {
+ if (comments[i].offset < pragmas[j].offset) {
+ m_extraTypes.append(Comment);
+ m_extraLocations.append(comments[i]);
+ ++i;
+ } else {
+ m_extraTypes.append(Pragma);
+ m_extraLocations.append(comments[j]);
+ ++j;
+ }
+ }
+
+ while (i < comments.size()) {
+ m_extraTypes.append(Comment);
+ m_extraLocations.append(comments[i]);
+ ++i;
+ }
+
+ while (j < pragmas.size()) {
+ m_extraTypes.append(Pragma);
+ m_extraLocations.append(pragmas[j]);
+ ++j;
+ }
+}
+
+// The protect() function is a copy of the one from CppCodeMarker.
+
+static const QString samp = QLatin1String("&amp;");
+static const QString slt = QLatin1String("&lt;");
+static const QString sgt = QLatin1String("&gt;");
+static const QString squot = QLatin1String("&quot;");
+
+QString QmlMarkupVisitor::protect(const QString &str)
+{
+ qsizetype n = str.size();
+ QString marked;
+ marked.reserve(n * 2 + 30);
+ const QChar *data = str.constData();
+ for (int i = 0; i != n; ++i) {
+ switch (data[i].unicode()) {
+ case '&':
+ marked += samp;
+ break;
+ case '<':
+ marked += slt;
+ break;
+ case '>':
+ marked += sgt;
+ break;
+ case '"':
+ marked += squot;
+ break;
+ default:
+ marked += data[i];
+ }
+ }
+ return marked;
+}
+
+QString QmlMarkupVisitor::markedUpCode()
+{
+ if (int(m_cursor) < m_source.size())
+ addExtra(m_cursor, m_source.size());
+
+ return m_output;
+}
+
+bool QmlMarkupVisitor::hasError() const
+{
+ return m_hasRecursionDepthError;
+}
+
+void QmlMarkupVisitor::addExtra(quint32 start, quint32 finish)
+{
+ if (m_extraIndex >= m_extraLocations.size()) {
+ QString extra = m_source.mid(start, finish - start);
+ if (extra.trimmed().isEmpty())
+ m_output += extra;
+ else
+ m_output += protect(extra); // text that should probably have been caught by the parser
+
+ m_cursor = finish;
+ return;
+ }
+
+ while (m_extraIndex < m_extraLocations.size()) {
+ if (m_extraTypes[m_extraIndex] == Comment) {
+ if (m_extraLocations[m_extraIndex].offset - 2 >= start)
+ break;
+ } else {
+ if (m_extraLocations[m_extraIndex].offset >= start)
+ break;
+ }
+ m_extraIndex++;
+ }
+
+ quint32 i = start;
+ while (i < finish && m_extraIndex < m_extraLocations.size()) {
+ quint32 j = m_extraLocations[m_extraIndex].offset - 2;
+ if (i <= j && j < finish) {
+ if (i < j)
+ m_output += protect(m_source.mid(i, j - i));
+
+ quint32 l = m_extraLocations[m_extraIndex].length;
+ if (m_extraTypes[m_extraIndex] == Comment) {
+ if (m_source.mid(j, 2) == QLatin1String("/*"))
+ l += 4;
+ else
+ l += 2;
+ m_output += QLatin1String("<@comment>");
+ m_output += protect(m_source.mid(j, l));
+ m_output += QLatin1String("</@comment>");
+ } else
+ m_output += protect(m_source.mid(j, l));
+
+ m_extraIndex++;
+ i = j + l;
+ } else
+ break;
+ }
+
+ QString extra = m_source.mid(i, finish - i);
+ if (extra.trimmed().isEmpty())
+ m_output += extra;
+ else
+ m_output += protect(extra); // text that should probably have been caught by the parser
+
+ m_cursor = finish;
+}
+
+void QmlMarkupVisitor::addMarkedUpToken(QQmlJS::SourceLocation &location,
+ const QString &tagName,
+ const QHash<QString, QString> &attributes)
+{
+ if (!location.isValid())
+ return;
+
+ if (m_cursor < location.offset)
+ addExtra(m_cursor, location.offset);
+ else if (m_cursor > location.offset)
+ return;
+
+ m_output += QString(QLatin1String("<@%1")).arg(tagName);
+ for (const auto &key : attributes)
+ m_output += QString(QLatin1String(" %1=\"%2\"")).arg(key, attributes[key]);
+ m_output += QString(QLatin1String(">%2</@%3>")).arg(protect(sourceText(location)), tagName);
+ m_cursor += location.length;
+}
+
+QString QmlMarkupVisitor::sourceText(QQmlJS::SourceLocation &location)
+{
+ return m_source.mid(location.offset, location.length);
+}
+
+void QmlMarkupVisitor::addVerbatim(QQmlJS::SourceLocation first,
+ QQmlJS::SourceLocation last)
+{
+ if (!first.isValid())
+ return;
+
+ quint32 start = first.begin();
+ quint32 finish;
+ if (last.isValid())
+ finish = last.end();
+ else
+ finish = first.end();
+
+ if (m_cursor < start)
+ addExtra(m_cursor, start);
+ else if (m_cursor > start)
+ return;
+
+ QString text = m_source.mid(start, finish - start);
+ m_output += protect(text);
+ m_cursor = finish;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiImport *uiimport)
+{
+ addVerbatim(uiimport->importToken);
+ if (!uiimport->importUri)
+ addMarkedUpToken(uiimport->fileNameToken, QLatin1String("headerfile"));
+ return false;
+}
+
+void QmlMarkupVisitor::endVisit(QQmlJS::AST::UiImport *uiimport)
+{
+ if (uiimport->version)
+ addVerbatim(uiimport->version->firstSourceLocation(),
+ uiimport->version->lastSourceLocation());
+ addVerbatim(uiimport->asToken);
+ addMarkedUpToken(uiimport->importIdToken, QLatin1String("headerfile"));
+ addVerbatim(uiimport->semicolonToken);
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiPublicMember *member)
+{
+ if (member->type == QQmlJS::AST::UiPublicMember::Property) {
+ addVerbatim(member->defaultToken());
+ addVerbatim(member->readonlyToken());
+ addVerbatim(member->propertyToken());
+ addVerbatim(member->typeModifierToken);
+ addMarkedUpToken(member->typeToken, QLatin1String("type"));
+ addMarkedUpToken(member->identifierToken, QLatin1String("name"));
+ addVerbatim(member->colonToken);
+ if (member->binding)
+ QQmlJS::AST::Node::accept(member->binding, this);
+ else if (member->statement)
+ QQmlJS::AST::Node::accept(member->statement, this);
+ } else {
+ addVerbatim(member->propertyToken());
+ addVerbatim(member->typeModifierToken);
+ addMarkedUpToken(member->typeToken, QLatin1String("type"));
+ // addVerbatim(member->identifierToken());
+ QQmlJS::AST::Node::accept(member->parameters, this);
+ }
+ addVerbatim(member->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiObjectInitializer *initializer)
+{
+ addVerbatim(initializer->lbraceToken, initializer->lbraceToken);
+ return true;
+}
+
+void QmlMarkupVisitor::endVisit(QQmlJS::AST::UiObjectInitializer *initializer)
+{
+ addVerbatim(initializer->rbraceToken, initializer->rbraceToken);
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiObjectBinding *binding)
+{
+ QQmlJS::AST::Node::accept(binding->qualifiedId, this);
+ addVerbatim(binding->colonToken);
+ QQmlJS::AST::Node::accept(binding->qualifiedTypeNameId, this);
+ QQmlJS::AST::Node::accept(binding->initializer, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiScriptBinding *binding)
+{
+ QQmlJS::AST::Node::accept(binding->qualifiedId, this);
+ addVerbatim(binding->colonToken);
+ QQmlJS::AST::Node::accept(binding->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiArrayBinding *binding)
+{
+ QQmlJS::AST::Node::accept(binding->qualifiedId, this);
+ addVerbatim(binding->colonToken);
+ addVerbatim(binding->lbracketToken);
+ QQmlJS::AST::Node::accept(binding->members, this);
+ addVerbatim(binding->rbracketToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiArrayMemberList *list)
+{
+ for (QQmlJS::AST::UiArrayMemberList *it = list; it; it = it->next) {
+ QQmlJS::AST::Node::accept(it->member, this);
+ // addVerbatim(it->commaToken);
+ }
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiQualifiedId *id)
+{
+ addMarkedUpToken(id->identifierToken, QLatin1String("name"));
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ThisExpression *expression)
+{
+ addVerbatim(expression->thisToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::IdentifierExpression *identifier)
+{
+ addMarkedUpToken(identifier->identifierToken, QLatin1String("name"));
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::NullExpression *null)
+{
+ addMarkedUpToken(null->nullToken, QLatin1String("number"));
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::TrueLiteral *literal)
+{
+ addMarkedUpToken(literal->trueToken, QLatin1String("number"));
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::FalseLiteral *literal)
+{
+ addMarkedUpToken(literal->falseToken, QLatin1String("number"));
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::NumericLiteral *literal)
+{
+ addMarkedUpToken(literal->literalToken, QLatin1String("number"));
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::StringLiteral *literal)
+{
+ addMarkedUpToken(literal->literalToken, QLatin1String("string"));
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::RegExpLiteral *literal)
+{
+ addVerbatim(literal->literalToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ArrayPattern *literal)
+{
+ addVerbatim(literal->lbracketToken);
+ QQmlJS::AST::Node::accept(literal->elements, this);
+ addVerbatim(literal->rbracketToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ObjectPattern *literal)
+{
+ addVerbatim(literal->lbraceToken);
+ return true;
+}
+
+void QmlMarkupVisitor::endVisit(QQmlJS::AST::ObjectPattern *literal)
+{
+ addVerbatim(literal->rbraceToken);
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::PatternElementList *list)
+{
+ for (QQmlJS::AST::PatternElementList *it = list; it; it = it->next) {
+ QQmlJS::AST::Node::accept(it->element, this);
+ // addVerbatim(it->commaToken);
+ }
+ QQmlJS::AST::Node::accept(list->elision, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::Elision *elision)
+{
+ addVerbatim(elision->commaToken, elision->commaToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::PatternProperty *list)
+{
+ QQmlJS::AST::Node::accept(list->name, this);
+ addVerbatim(list->colonToken, list->colonToken);
+ QQmlJS::AST::Node::accept(list->initializer, this);
+ // addVerbatim(list->commaToken, list->commaToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ArrayMemberExpression *expression)
+{
+ QQmlJS::AST::Node::accept(expression->base, this);
+ addVerbatim(expression->lbracketToken);
+ QQmlJS::AST::Node::accept(expression->expression, this);
+ addVerbatim(expression->rbracketToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::FieldMemberExpression *expression)
+{
+ QQmlJS::AST::Node::accept(expression->base, this);
+ addVerbatim(expression->dotToken);
+ addMarkedUpToken(expression->identifierToken, QLatin1String("name"));
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::NewMemberExpression *expression)
+{
+ addVerbatim(expression->newToken);
+ QQmlJS::AST::Node::accept(expression->base, this);
+ addVerbatim(expression->lparenToken);
+ QQmlJS::AST::Node::accept(expression->arguments, this);
+ addVerbatim(expression->rparenToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::NewExpression *expression)
+{
+ addVerbatim(expression->newToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ArgumentList *list)
+{
+ addVerbatim(list->commaToken, list->commaToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::PostIncrementExpression *expression)
+{
+ addVerbatim(expression->incrementToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::PostDecrementExpression *expression)
+{
+ addVerbatim(expression->decrementToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::DeleteExpression *expression)
+{
+ addVerbatim(expression->deleteToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::VoidExpression *expression)
+{
+ addVerbatim(expression->voidToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::TypeOfExpression *expression)
+{
+ addVerbatim(expression->typeofToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::PreIncrementExpression *expression)
+{
+ addVerbatim(expression->incrementToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::PreDecrementExpression *expression)
+{
+ addVerbatim(expression->decrementToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UnaryPlusExpression *expression)
+{
+ addVerbatim(expression->plusToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UnaryMinusExpression *expression)
+{
+ addVerbatim(expression->minusToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::TildeExpression *expression)
+{
+ addVerbatim(expression->tildeToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::NotExpression *expression)
+{
+ addVerbatim(expression->notToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::BinaryExpression *expression)
+{
+ QQmlJS::AST::Node::accept(expression->left, this);
+ addMarkedUpToken(expression->operatorToken, QLatin1String("op"));
+ QQmlJS::AST::Node::accept(expression->right, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ConditionalExpression *expression)
+{
+ QQmlJS::AST::Node::accept(expression->expression, this);
+ addVerbatim(expression->questionToken);
+ QQmlJS::AST::Node::accept(expression->ok, this);
+ addVerbatim(expression->colonToken);
+ QQmlJS::AST::Node::accept(expression->ko, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::Expression *expression)
+{
+ QQmlJS::AST::Node::accept(expression->left, this);
+ addVerbatim(expression->commaToken);
+ QQmlJS::AST::Node::accept(expression->right, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::Block *block)
+{
+ addVerbatim(block->lbraceToken);
+ return true;
+}
+
+void QmlMarkupVisitor::endVisit(QQmlJS::AST::Block *block)
+{
+ addVerbatim(block->rbraceToken);
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::VariableStatement *statement)
+{
+ addVerbatim(statement->declarationKindToken);
+ QQmlJS::AST::Node::accept(statement->declarations, this);
+ // addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::VariableDeclarationList *list)
+{
+ for (QQmlJS::AST::VariableDeclarationList *it = list; it; it = it->next) {
+ QQmlJS::AST::Node::accept(it->declaration, this);
+ addVerbatim(it->commaToken);
+ }
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::EmptyStatement *statement)
+{
+ addVerbatim(statement->semicolonToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ExpressionStatement *statement)
+{
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::IfStatement *statement)
+{
+ addMarkedUpToken(statement->ifToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ QQmlJS::AST::Node::accept(statement->ok, this);
+ if (statement->ko) {
+ addMarkedUpToken(statement->elseToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(statement->ko, this);
+ }
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::DoWhileStatement *statement)
+{
+ addMarkedUpToken(statement->doToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ addMarkedUpToken(statement->whileToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::WhileStatement *statement)
+{
+ addMarkedUpToken(statement->whileToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ForStatement *statement)
+{
+ addMarkedUpToken(statement->forToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->initialiser, this);
+ addVerbatim(statement->firstSemicolonToken);
+ QQmlJS::AST::Node::accept(statement->condition, this);
+ addVerbatim(statement->secondSemicolonToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ForEachStatement *statement)
+{
+ addMarkedUpToken(statement->forToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->lhs, this);
+ addVerbatim(statement->inOfToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ContinueStatement *statement)
+{
+ addMarkedUpToken(statement->continueToken, QLatin1String("keyword"));
+ addMarkedUpToken(statement->identifierToken, QLatin1String("name"));
+ addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::BreakStatement *statement)
+{
+ addMarkedUpToken(statement->breakToken, QLatin1String("keyword"));
+ addMarkedUpToken(statement->identifierToken, QLatin1String("name"));
+ addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ReturnStatement *statement)
+{
+ addMarkedUpToken(statement->returnToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::WithStatement *statement)
+{
+ addMarkedUpToken(statement->withToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::CaseBlock *block)
+{
+ addVerbatim(block->lbraceToken);
+ return true;
+}
+
+void QmlMarkupVisitor::endVisit(QQmlJS::AST::CaseBlock *block)
+{
+ addVerbatim(block->rbraceToken, block->rbraceToken);
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::SwitchStatement *statement)
+{
+ addMarkedUpToken(statement->switchToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ QQmlJS::AST::Node::accept(statement->block, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::CaseClause *clause)
+{
+ addMarkedUpToken(clause->caseToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(clause->expression, this);
+ addVerbatim(clause->colonToken);
+ QQmlJS::AST::Node::accept(clause->statements, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::DefaultClause *clause)
+{
+ addMarkedUpToken(clause->defaultToken, QLatin1String("keyword"));
+ addVerbatim(clause->colonToken, clause->colonToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::LabelledStatement *statement)
+{
+ addMarkedUpToken(statement->identifierToken, QLatin1String("name"));
+ addVerbatim(statement->colonToken);
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ThrowStatement *statement)
+{
+ addMarkedUpToken(statement->throwToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::Catch *c)
+{
+ addMarkedUpToken(c->catchToken, QLatin1String("keyword"));
+ addVerbatim(c->lparenToken);
+ addMarkedUpToken(c->identifierToken, QLatin1String("name"));
+ addVerbatim(c->rparenToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::Finally *f)
+{
+ addMarkedUpToken(f->finallyToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(f->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::TryStatement *statement)
+{
+ addMarkedUpToken(statement->tryToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ QQmlJS::AST::Node::accept(statement->catchExpression, this);
+ QQmlJS::AST::Node::accept(statement->finallyExpression, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::FunctionExpression *expression)
+{
+ addMarkedUpToken(expression->functionToken, QLatin1String("keyword"));
+ addMarkedUpToken(expression->identifierToken, QLatin1String("name"));
+ addVerbatim(expression->lparenToken);
+ QQmlJS::AST::Node::accept(expression->formals, this);
+ addVerbatim(expression->rparenToken);
+ addVerbatim(expression->lbraceToken);
+ QQmlJS::AST::Node::accept(expression->body, this);
+ addVerbatim(expression->rbraceToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::FunctionDeclaration *declaration)
+{
+ addMarkedUpToken(declaration->functionToken, QLatin1String("keyword"));
+ addMarkedUpToken(declaration->identifierToken, QLatin1String("name"));
+ addVerbatim(declaration->lparenToken);
+ QQmlJS::AST::Node::accept(declaration->formals, this);
+ addVerbatim(declaration->rparenToken);
+ addVerbatim(declaration->lbraceToken);
+ QQmlJS::AST::Node::accept(declaration->body, this);
+ addVerbatim(declaration->rbraceToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::FormalParameterList *list)
+{
+ // addVerbatim(list->commaToken);
+ QQmlJS::AST::Node::accept(list->element, this);
+ // addMarkedUpToken(list->identifierToken, QLatin1String("name"));
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::DebuggerStatement *statement)
+{
+ addVerbatim(statement->debuggerToken);
+ addVerbatim(statement->semicolonToken);
+ return true;
+}
+
+// Elements and items are represented by UiObjectDefinition nodes.
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiObjectDefinition *definition)
+{
+ addMarkedUpToken(definition->qualifiedTypeNameId->identifierToken, QLatin1String("type"));
+ QQmlJS::AST::Node::accept(definition->initializer, this);
+ return false;
+}
+
+void QmlMarkupVisitor::throwRecursionDepthError()
+{
+ m_hasRecursionDepthError = true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.h b/src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.h
new file mode 100644
index 000000000..a19636a67
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.h
@@ -0,0 +1,139 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QMLMARKUPVISITOR_H
+#define QMLMARKUPVISITOR_H
+
+#include "node.h"
+#include "tree.h"
+
+#include <QtCore/qstring.h>
+
+#include <private/qqmljsastvisitor_p.h>
+#include <private/qqmljsengine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QmlMarkupVisitor : public QQmlJS::AST::Visitor
+{
+public:
+ enum ExtraType { Comment, Pragma };
+
+ QmlMarkupVisitor(const QString &code, const QList<QQmlJS::SourceLocation> &pragmas,
+ QQmlJS::Engine *engine);
+ ~QmlMarkupVisitor() override = default;
+
+ QString markedUpCode();
+ [[nodiscard]] bool hasError() const;
+
+ bool visit(QQmlJS::AST::UiImport *) override;
+ void endVisit(QQmlJS::AST::UiImport *) override;
+
+ bool visit(QQmlJS::AST::UiPublicMember *) override;
+ bool visit(QQmlJS::AST::UiObjectDefinition *) override;
+
+ bool visit(QQmlJS::AST::UiObjectInitializer *) override;
+ void endVisit(QQmlJS::AST::UiObjectInitializer *) override;
+
+ bool visit(QQmlJS::AST::UiObjectBinding *) override;
+ bool visit(QQmlJS::AST::UiScriptBinding *) override;
+ bool visit(QQmlJS::AST::UiArrayBinding *) override;
+ bool visit(QQmlJS::AST::UiArrayMemberList *) override;
+ bool visit(QQmlJS::AST::UiQualifiedId *) override;
+
+ bool visit(QQmlJS::AST::ThisExpression *) override;
+ bool visit(QQmlJS::AST::IdentifierExpression *) override;
+ bool visit(QQmlJS::AST::NullExpression *) override;
+ bool visit(QQmlJS::AST::TrueLiteral *) override;
+ bool visit(QQmlJS::AST::FalseLiteral *) override;
+ bool visit(QQmlJS::AST::NumericLiteral *) override;
+ bool visit(QQmlJS::AST::StringLiteral *) override;
+ bool visit(QQmlJS::AST::RegExpLiteral *) override;
+ bool visit(QQmlJS::AST::ArrayPattern *) override;
+
+ bool visit(QQmlJS::AST::ObjectPattern *) override;
+ void endVisit(QQmlJS::AST::ObjectPattern *) override;
+
+ bool visit(QQmlJS::AST::PatternElementList *) override;
+ bool visit(QQmlJS::AST::Elision *) override;
+ bool visit(QQmlJS::AST::PatternProperty *) override;
+ bool visit(QQmlJS::AST::ArrayMemberExpression *) override;
+ bool visit(QQmlJS::AST::FieldMemberExpression *) override;
+ bool visit(QQmlJS::AST::NewMemberExpression *) override;
+ bool visit(QQmlJS::AST::NewExpression *) override;
+ bool visit(QQmlJS::AST::ArgumentList *) override;
+ bool visit(QQmlJS::AST::PostIncrementExpression *) override;
+ bool visit(QQmlJS::AST::PostDecrementExpression *) override;
+ bool visit(QQmlJS::AST::DeleteExpression *) override;
+ bool visit(QQmlJS::AST::VoidExpression *) override;
+ bool visit(QQmlJS::AST::TypeOfExpression *) override;
+ bool visit(QQmlJS::AST::PreIncrementExpression *) override;
+ bool visit(QQmlJS::AST::PreDecrementExpression *) override;
+ bool visit(QQmlJS::AST::UnaryPlusExpression *) override;
+ bool visit(QQmlJS::AST::UnaryMinusExpression *) override;
+ bool visit(QQmlJS::AST::TildeExpression *) override;
+ bool visit(QQmlJS::AST::NotExpression *) override;
+ bool visit(QQmlJS::AST::BinaryExpression *) override;
+ bool visit(QQmlJS::AST::ConditionalExpression *) override;
+ bool visit(QQmlJS::AST::Expression *) override;
+
+ bool visit(QQmlJS::AST::Block *) override;
+ void endVisit(QQmlJS::AST::Block *) override;
+
+ bool visit(QQmlJS::AST::VariableStatement *) override;
+ bool visit(QQmlJS::AST::VariableDeclarationList *) override;
+ bool visit(QQmlJS::AST::EmptyStatement *) override;
+ bool visit(QQmlJS::AST::ExpressionStatement *) override;
+ bool visit(QQmlJS::AST::IfStatement *) override;
+ bool visit(QQmlJS::AST::DoWhileStatement *) override;
+ bool visit(QQmlJS::AST::WhileStatement *) override;
+ bool visit(QQmlJS::AST::ForStatement *) override;
+ bool visit(QQmlJS::AST::ForEachStatement *) override;
+ bool visit(QQmlJS::AST::ContinueStatement *) override;
+ bool visit(QQmlJS::AST::BreakStatement *) override;
+ bool visit(QQmlJS::AST::ReturnStatement *) override;
+ bool visit(QQmlJS::AST::WithStatement *) override;
+
+ bool visit(QQmlJS::AST::CaseBlock *) override;
+ void endVisit(QQmlJS::AST::CaseBlock *) override;
+
+ bool visit(QQmlJS::AST::SwitchStatement *) override;
+ bool visit(QQmlJS::AST::CaseClause *) override;
+ bool visit(QQmlJS::AST::DefaultClause *) override;
+ bool visit(QQmlJS::AST::LabelledStatement *) override;
+ bool visit(QQmlJS::AST::ThrowStatement *) override;
+ bool visit(QQmlJS::AST::TryStatement *) override;
+ bool visit(QQmlJS::AST::Catch *) override;
+ bool visit(QQmlJS::AST::Finally *) override;
+ bool visit(QQmlJS::AST::FunctionDeclaration *) override;
+ bool visit(QQmlJS::AST::FunctionExpression *) override;
+ bool visit(QQmlJS::AST::FormalParameterList *) override;
+ bool visit(QQmlJS::AST::DebuggerStatement *) override;
+
+protected:
+ QString protect(const QString &string);
+
+private:
+ typedef QHash<QString, QString> StringHash;
+ void addExtra(quint32 start, quint32 finish);
+ void addMarkedUpToken(QQmlJS::SourceLocation &location, const QString &text,
+ const StringHash &attributes = StringHash());
+ void addVerbatim(QQmlJS::SourceLocation first,
+ QQmlJS::SourceLocation last = QQmlJS::SourceLocation());
+ QString sourceText(QQmlJS::SourceLocation &location);
+ void throwRecursionDepthError() final;
+
+ QQmlJS::Engine *m_engine { nullptr };
+ QList<ExtraType> m_extraTypes {};
+ QList<QQmlJS::SourceLocation> m_extraLocations {};
+ QString m_source {};
+ QString m_output {};
+ quint32 m_cursor {};
+ int m_extraIndex {};
+ bool m_hasRecursionDepthError { false };
+};
+Q_DECLARE_TYPEINFO(QmlMarkupVisitor::ExtraType, Q_PRIMITIVE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/qmlpropertynode.cpp b/src/qdoc/qdoc/src/qdoc/qmlpropertynode.cpp
new file mode 100644
index 000000000..335b7d870
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlpropertynode.cpp
@@ -0,0 +1,175 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qmlpropertynode.h"
+
+#include "classnode.h"
+#include "propertynode.h"
+#include "enumnode.h"
+
+#include <utility>
+#include "qdocdatabase.h"
+#include "utilities.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Constructor for the QML property node.
+ */
+QmlPropertyNode::QmlPropertyNode(Aggregate *parent, const QString &name, QString type,
+ bool attached)
+ : Node(QmlProperty, parent, name),
+ m_type(std::move(type)),
+ m_attached(attached)
+{
+ if (m_type == "alias")
+ m_isAlias = true;
+ if (name.startsWith("__"))
+ setStatus(Internal);
+}
+
+/*!
+ \fn bool QmlPropertyNode::isReadOnly() const
+
+ Returns \c true if this QML property node is marked as a
+ read-only property.
+*/
+
+/*!
+ \fn const EnumNode *QmlPropertyNode::enumNode() const
+
+ Returns the node representing the C++ enumeration associated
+ with this property, or \nullptr.
+*/
+
+/*!
+ Returns the prefix to use for documentated enumerators from
+ the associated C++ enum for this property.
+*/
+const QString &QmlPropertyNode::enumPrefix() const
+{
+ return !m_enumNode.second.isEmpty() ?
+ m_enumNode.second : parent()->name();
+}
+
+/*!
+ Locates the node specified by \a path and sets it as the C++ enumeration
+ associated with this property.
+
+ \a registeredQmlName is used as the prefix in the generated enum value
+ documentation.
+
+ \note The target EnumNode is searched under the primary tree only.
+
+ Returns \c true on success.
+*/
+bool QmlPropertyNode::setEnumNode(const QString &path, const QString &registeredQmlName)
+{
+ m_enumNode.first = static_cast<EnumNode*>(
+ QDocDatabase::qdocDB()->primaryTree()->findNodeByNameAndType(path.split("::"), &Node::isEnumType)
+ );
+ m_enumNode.second = registeredQmlName;
+ return m_enumNode.first != nullptr;
+}
+
+/*!
+ Returns \c true if this QML property or attached property is
+ read-only. If the read-only status is not set explicitly
+ using the \\readonly command, QDoc attempts to resolve it
+ from the associated C++ class instantiated by the QML type
+ that this property belongs to.
+
+ \note Depending on how the QML type is implemented, this
+ information may not be available to QDoc. If so, add a debug
+ line but do not treat it as a warning.
+ */
+bool QmlPropertyNode::isReadOnly()
+{
+ if (m_readOnly != FlagValueDefault)
+ return fromFlagValue(m_readOnly, false);
+
+ // Find the parent QML type node
+ auto *parent{this->parent()};
+ while (parent && !(parent->isQmlType()))
+ parent = parent->parent();
+
+ bool readonly{false};
+ if (auto qcn = static_cast<QmlTypeNode *>(parent); qcn && qcn->classNode()) {
+ if (auto propertyNode = findCorrespondingCppProperty(); propertyNode)
+ readonly = !propertyNode->isWritable();
+ else
+ qCDebug(lcQdoc).nospace()
+ << qPrintable(defLocation().toString())
+ << ": Automatic resolution of QML property attributes failed for "
+ << name()
+ << " (Q_PROPERTY not found in the C++ class hierarchy known to QDoc. "
+ << "Likely, the type is replaced with a private implementation.)";
+ }
+ markReadOnly(readonly);
+ return readonly;
+}
+
+/*!
+ Returns \c true if this QML property is marked with \required or the
+ corresponding C++ property uses the REQUIRED keyword.
+*/
+bool QmlPropertyNode::isRequired()
+{
+ if (m_required != FlagValueDefault)
+ return fromFlagValue(m_required, false);
+
+ PropertyNode *pn = findCorrespondingCppProperty();
+ return pn != nullptr && pn->isRequired();
+}
+
+/*!
+ Returns a pointer this QML property's corresponding C++
+ property, if it has one.
+ */
+PropertyNode *QmlPropertyNode::findCorrespondingCppProperty()
+{
+ PropertyNode *pn;
+ Node *n = parent();
+ while (n && !(n->isQmlType()))
+ n = n->parent();
+ if (n) {
+ auto *qcn = static_cast<QmlTypeNode *>(n);
+ ClassNode *cn = qcn->classNode();
+ if (cn) {
+ /*
+ If there is a dot in the property name, first
+ find the C++ property corresponding to the QML
+ property group.
+ */
+ QStringList dotSplit = name().split(QChar('.'));
+ pn = cn->findPropertyNode(dotSplit[0]);
+ if (pn) {
+ /*
+ Now find the C++ property corresponding to
+ the QML property in the QML property group,
+ <group>.<property>.
+ */
+ if (dotSplit.size() > 1) {
+ QStringList path(extractClassName(pn->qualifiedDataType()));
+ Node *nn = QDocDatabase::qdocDB()->findClassNode(path);
+ if (nn) {
+ auto *cn = static_cast<ClassNode *>(nn);
+ PropertyNode *pn2 = cn->findPropertyNode(dotSplit[1]);
+ /*
+ If found, return the C++ property
+ corresponding to the QML property.
+ Otherwise, return the C++ property
+ corresponding to the QML property
+ group.
+ */
+ return (pn2 ? pn2 : pn);
+ }
+ } else
+ return pn;
+ }
+ }
+ }
+ return nullptr;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qmlpropertynode.h b/src/qdoc/qdoc/src/qdoc/qmlpropertynode.h
new file mode 100644
index 000000000..f966949c1
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlpropertynode.h
@@ -0,0 +1,72 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QMLPROPERTYNODE_H
+#define QMLPROPERTYNODE_H
+
+#include "aggregate.h"
+#include "node.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class QmlPropertyNode : public Node
+{
+public:
+ QmlPropertyNode(Aggregate *parent, const QString &name, QString type, bool attached);
+
+ void setDataType(const QString &dataType) override { m_type = dataType; }
+ void setStored(bool stored) { m_stored = toFlagValue(stored); }
+ void setDefaultValue(const QString &value) { m_defaultValue = value; }
+ void setRequired() { m_required = toFlagValue(true); }
+ bool setEnumNode(const QString &path, const QString &registeredQmlName);
+
+ [[nodiscard]] const QString &dataType() const { return m_type; }
+ [[nodiscard]] const QString &defaultValue() const { return m_defaultValue; }
+ [[nodiscard]] bool isStored() const { return fromFlagValue(m_stored, true); }
+ bool isRequired();
+ [[nodiscard]] bool isDefault() const override { return m_isDefault; }
+ [[nodiscard]] bool isReadOnly() const { return fromFlagValue(m_readOnly, false); }
+ [[nodiscard]] bool isReadOnly();
+ [[nodiscard]] bool isAlias() const override { return m_isAlias; }
+ [[nodiscard]] bool isAttached() const override { return m_attached; }
+ [[nodiscard]] QString qmlTypeName() const override { return parent()->qmlTypeName(); }
+ [[nodiscard]] QString logicalModuleName() const override
+ {
+ return parent()->logicalModuleName();
+ }
+ [[nodiscard]] QString logicalModuleVersion() const override
+ {
+ return parent()->logicalModuleVersion();
+ }
+ [[nodiscard]] QString logicalModuleIdentifier() const override
+ {
+ return parent()->logicalModuleIdentifier();
+ }
+ [[nodiscard]] QString element() const override { return parent()->name(); }
+ [[nodiscard]] const EnumNode *enumNode() const { return m_enumNode.first; }
+ [[nodiscard]] const QString &enumPrefix() const;
+
+ void markDefault() override { m_isDefault = true; }
+ void markReadOnly(bool flag) override { m_readOnly = toFlagValue(flag); }
+
+private:
+ PropertyNode *findCorrespondingCppProperty();
+
+private:
+ QString m_type {};
+ QString m_defaultValue {};
+ FlagValue m_stored { FlagValueDefault };
+ bool m_isAlias { false };
+ bool m_isDefault { false };
+ bool m_attached {};
+ FlagValue m_readOnly { FlagValueDefault };
+ FlagValue m_required { FlagValueDefault };
+ std::pair<EnumNode *, QString> m_enumNode { nullptr, {} };
+};
+
+QT_END_NAMESPACE
+
+#endif // QMLPROPERTYNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/qmltypenode.cpp b/src/qdoc/qdoc/src/qdoc/qmltypenode.cpp
new file mode 100644
index 000000000..4285f9b6e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmltypenode.cpp
@@ -0,0 +1,153 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qmltypenode.h"
+#include "collectionnode.h"
+#include "qdocdatabase.h"
+
+#include <QtCore/qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+QMultiMap<const Node *, Node *> QmlTypeNode::s_inheritedBy;
+
+/*!
+ Constructs a Qml type.
+
+ The new node has the given \a parent, name \a name, and a specific node
+ \a type. Valid types are Node::QmlType and Node::QmlValueType.
+ */
+QmlTypeNode::QmlTypeNode(Aggregate *parent, const QString &name, Node::NodeType type)
+ : Aggregate(type, parent, name)
+{
+ Q_ASSERT(type == Node::QmlType || type == Node::QmlValueType);
+ setTitle(name);
+}
+
+/*!
+ Clear the static maps so that subsequent runs don't try to use
+ contents from a previous run.
+ */
+void QmlTypeNode::terminate()
+{
+ s_inheritedBy.clear();
+}
+
+/*!
+ Record the fact that QML class \a base is inherited by
+ QML class \a sub.
+ */
+void QmlTypeNode::addInheritedBy(const Node *base, Node *sub)
+{
+ if (sub->isInternal())
+ return;
+ if (!s_inheritedBy.contains(base, sub))
+ s_inheritedBy.insert(base, sub);
+}
+
+/*!
+ Loads the list \a subs with the nodes of all the subclasses of \a base.
+ */
+void QmlTypeNode::subclasses(const Node *base, NodeList &subs)
+{
+ subs.clear();
+ if (s_inheritedBy.count(base) > 0) {
+ subs = s_inheritedBy.values(base);
+ }
+}
+
+/*!
+ If this QML type node has a base type node,
+ return the fully qualified name of that QML
+ type, i.e. <QML-module-name>::<QML-type-name>.
+ */
+QString QmlTypeNode::qmlFullBaseName() const
+{
+ QString result;
+ if (m_qmlBaseNode) {
+ result = m_qmlBaseNode->logicalModuleName() + "::" + m_qmlBaseNode->name();
+ }
+ return result;
+}
+
+/*!
+ If the QML type's QML module pointer is set, return the QML
+ module name from the QML module node. Otherwise, return the
+ empty string.
+ */
+QString QmlTypeNode::logicalModuleName() const
+{
+ return (m_logicalModule ? m_logicalModule->logicalModuleName() : QString());
+}
+
+/*!
+ If the QML type's QML module pointer is set, return the QML
+ module version from the QML module node. Otherwise, return
+ the empty string.
+ */
+QString QmlTypeNode::logicalModuleVersion() const
+{
+ return (m_logicalModule ? m_logicalModule->logicalModuleVersion() : QString());
+}
+
+/*!
+ If the QML type's QML module pointer is set, return the QML
+ module identifier from the QML module node. Otherwise, return
+ the empty string.
+ */
+QString QmlTypeNode::logicalModuleIdentifier() const
+{
+ return (m_logicalModule ? m_logicalModule->logicalModuleIdentifier() : QString());
+}
+
+/*!
+ Returns true if this QML type inherits \a type.
+ */
+bool QmlTypeNode::inherits(Aggregate *type)
+{
+ QmlTypeNode *qtn = qmlBaseNode();
+ while (qtn != nullptr) {
+ if (qtn == type)
+ return true;
+ qtn = qtn->qmlBaseNode();
+ }
+ return false;
+}
+
+/*!
+ Recursively resolves the base node for this QML type when only the name of
+ the base type is known.
+
+ \a previousSearches is used for speeding up the process.
+*/
+void QmlTypeNode::resolveInheritance(NodeMap &previousSearches)
+{
+ if (m_qmlBaseNode || m_qmlBaseName.isEmpty())
+ return;
+
+ auto *base = static_cast<QmlTypeNode *>(previousSearches.value(m_qmlBaseName));
+ if (!previousSearches.contains(m_qmlBaseName)) {
+ for (const auto &imp : std::as_const(m_importList)) {
+ base = QDocDatabase::qdocDB()->findQmlType(imp, m_qmlBaseName);
+ if (base)
+ break;
+ }
+ if (!base) {
+ if (m_qmlBaseName.contains(':'))
+ base = QDocDatabase::qdocDB()->findQmlType(m_qmlBaseName);
+ else
+ base = QDocDatabase::qdocDB()->findQmlType(QString(), m_qmlBaseName);
+ }
+ previousSearches.insert(m_qmlBaseName, base);
+ }
+
+ if (base && base != this) {
+ m_qmlBaseNode = base;
+ QmlTypeNode::addInheritedBy(base, this);
+ // Base types read from the index need resolving as they only have the name set
+ if (base->isIndexNode())
+ base->resolveInheritance(previousSearches);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qmltypenode.h b/src/qdoc/qdoc/src/qdoc/qmltypenode.h
new file mode 100644
index 000000000..d7cd5ff2b
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmltypenode.h
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QMLTYPENODE_H
+#define QMLTYPENODE_H
+
+#include "importrec.h"
+#include "aggregate.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class ClassNode;
+class CollectionNode;
+
+typedef QList<ImportRec> ImportList;
+
+class QmlTypeNode : public Aggregate
+{
+public:
+ QmlTypeNode(Aggregate *parent, const QString &name, Node::NodeType type);
+ [[nodiscard]] bool isFirstClassAggregate() const override { return true; }
+ ClassNode *classNode() override { return m_classNode; }
+ void setClassNode(ClassNode *cn) override { m_classNode = cn; }
+ [[nodiscard]] bool isAbstract() const override { return m_abstract; }
+ [[nodiscard]] bool isWrapper() const override { return m_wrapper; }
+ void setAbstract(bool b) override { m_abstract = b; }
+ void setWrapper() override { m_wrapper = true; }
+ [[nodiscard]] bool isInternal() const override { return (status() == Internal); }
+ [[nodiscard]] QString qmlFullBaseName() const override;
+ [[nodiscard]] QString logicalModuleName() const override;
+ [[nodiscard]] QString logicalModuleVersion() const override;
+ [[nodiscard]] QString logicalModuleIdentifier() const override;
+ [[nodiscard]] CollectionNode *logicalModule() const override { return m_logicalModule; }
+ void setQmlModule(CollectionNode *t) override { m_logicalModule = t; }
+
+ void setImportList(const ImportList &il) { m_importList = il; }
+ [[nodiscard]] const QString &qmlBaseName() const { return m_qmlBaseName; }
+ void setQmlBaseName(const QString &name) { m_qmlBaseName = name; }
+ [[nodiscard]] QmlTypeNode *qmlBaseNode() const override { return m_qmlBaseNode; }
+ void resolveInheritance(NodeMap &previousSearches);
+ static void addInheritedBy(const Node *base, Node *sub);
+ static void subclasses(const Node *base, NodeList &subs);
+ static void terminate();
+ bool inherits(Aggregate *type);
+
+public:
+ static QMultiMap<const Node *, Node *> s_inheritedBy;
+
+private:
+ bool m_abstract { false };
+ bool m_wrapper { false };
+ ClassNode *m_classNode { nullptr };
+ QString m_qmlBaseName {};
+ CollectionNode *m_logicalModule { nullptr };
+ QmlTypeNode *m_qmlBaseNode { nullptr };
+ ImportList m_importList {};
+};
+
+QT_END_NAMESPACE
+
+#endif // QMLTYPENODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/qmlvisitor.cpp b/src/qdoc/qdoc/src/qdoc/qmlvisitor.cpp
new file mode 100644
index 000000000..d6ecf1986
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlvisitor.cpp
@@ -0,0 +1,729 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qmlvisitor.h"
+
+#include "aggregate.h"
+#include "codechunk.h"
+#include "codeparser.h"
+#include "functionnode.h"
+#include "node.h"
+#include "qdocdatabase.h"
+#include "qmlpropertynode.h"
+#include "tokenizer.h"
+#include "utilities.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qglobal.h>
+
+#include <private/qqmljsast_p.h>
+#include <private/qqmljsengine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+/*!
+ The constructor stores all the parameters in local data members.
+ */
+QmlDocVisitor::QmlDocVisitor(const QString &filePath, const QString &code, QQmlJS::Engine *engine,
+ const QSet<QString> &commands, const QSet<QString> &topics)
+ : m_nestingLevel(0)
+{
+ m_lastEndOffset = 0;
+ this->m_filePath = filePath;
+ this->m_name = QFileInfo(filePath).baseName();
+ m_document = code;
+ this->m_engine = engine;
+ this->m_commands = commands;
+ this->m_topics = topics;
+ m_current = QDocDatabase::qdocDB()->primaryTreeRoot();
+}
+
+/*!
+ Returns the location of the nearest comment above the \a offset.
+ */
+QQmlJS::SourceLocation QmlDocVisitor::precedingComment(quint32 offset) const
+{
+ const auto comments = m_engine->comments();
+ for (auto it = comments.rbegin(); it != comments.rend(); ++it) {
+ QQmlJS::SourceLocation loc = *it;
+
+ if (loc.begin() <= m_lastEndOffset) {
+ // Return if we reach the end of the preceding structure.
+ break;
+ } else if (m_usedComments.contains(loc.begin())) {
+ // Return if we encounter a previously used comment.
+ break;
+ } else if (loc.begin() > m_lastEndOffset && loc.end() < offset) {
+ // Only examine multiline comments in order to avoid snippet markers.
+ if (m_document.at(loc.offset - 1) == QLatin1Char('*')) {
+ QString comment = m_document.mid(loc.offset, loc.length);
+ if (comment.startsWith(QLatin1Char('!')) || comment.startsWith(QLatin1Char('*'))) {
+ return loc;
+ }
+ }
+ }
+ }
+
+ return QQmlJS::SourceLocation();
+}
+
+class QmlSignatureParser
+{
+public:
+ QmlSignatureParser(FunctionNode *func, const QString &signature, const Location &loc);
+ void readToken() { tok_ = tokenizer_->getToken(); }
+ QString lexeme() { return tokenizer_->lexeme(); }
+ QString previousLexeme() { return tokenizer_->previousLexeme(); }
+
+ bool match(int target);
+ bool matchTypeAndName(CodeChunk *type, QString *var);
+ bool matchParameter();
+ bool matchFunctionDecl();
+
+private:
+ QString signature_;
+ QStringList names_;
+ Tokenizer *tokenizer_;
+ int tok_;
+ FunctionNode *func_;
+ const Location &location_;
+};
+
+/*!
+ Finds the nearest unused qdoc comment above the QML entity
+ represented by a \a node and processes the qdoc commands
+ in that comment. The processed documentation is stored in
+ \a node.
+
+ If \a node is a \c nullptr and there is a valid comment block,
+ the QML module identifier (\inqmlmodule argument) is used
+ for searching an existing QML type node. If an existing node
+ is not found, constructs a new QmlTypeNode instance.
+
+ Returns a pointer to the QmlTypeNode instance if one was
+ found or constructed. Otherwise, returns a pointer to the \a
+ node that was passed as an argument.
+ */
+Node *QmlDocVisitor::applyDocumentation(QQmlJS::SourceLocation location, Node *node)
+{
+ QQmlJS::SourceLocation loc = precedingComment(location.begin());
+ Location comment_loc(m_filePath);
+
+ // No preceding comment; construct a new QML type if
+ // needed.
+ if (!loc.isValid()) {
+ if (!node)
+ node = new QmlTypeNode(m_current, m_name, Node::QmlType);
+ comment_loc.setLineNo(location.startLine);
+ node->setLocation(comment_loc);
+ return node;
+ }
+
+ QString source = m_document.mid(loc.offset + 1, loc.length - 1);
+ comment_loc.setLineNo(loc.startLine);
+ comment_loc.setColumnNo(loc.startColumn);
+
+ Doc doc(comment_loc, comment_loc, source, m_commands, m_topics);
+ const TopicList &topicsUsed = doc.topicsUsed();
+ NodeList nodes;
+ if (!node) {
+ QString qmid;
+ if (auto args = doc.metaCommandArgs(COMMAND_INQMLMODULE); !args.isEmpty())
+ qmid = args.first().first;
+ node = QDocDatabase::qdocDB()->findQmlTypeInPrimaryTree(qmid, m_name);
+ if (!node) {
+ node = new QmlTypeNode(m_current, m_name, Node::QmlType);
+ node->setLocation(comment_loc);
+ }
+ }
+
+ auto *parent{node->parent()};
+ node->setDoc(doc);
+ nodes.append(node);
+ if (!topicsUsed.empty()) {
+ for (int i = 0; i < topicsUsed.size(); ++i) {
+ QString topic = topicsUsed.at(i).m_topic;
+ QString args = topicsUsed.at(i).m_args;
+ if (topic.endsWith(QLatin1String("property"))) {
+ auto *qmlProperty = static_cast<QmlPropertyNode *>(node);
+ QmlPropArgs qpa;
+ if (splitQmlPropertyArg(doc, args, qpa)) {
+ if (qpa.m_name == node->name()) {
+ if (qmlProperty->isAlias())
+ qmlProperty->setDataType(qpa.m_type);
+ } else {
+ bool isAttached = topic.contains(QLatin1String("attached"));
+ QmlPropertyNode *n = parent->hasQmlProperty(qpa.m_name, isAttached);
+ if (n == nullptr)
+ n = new QmlPropertyNode(parent, qpa.m_name, qpa.m_type, isAttached);
+ n->setLocation(doc.location());
+ n->setDoc(doc);
+ // Use the const-overload of QmlPropertyNode::isReadOnly() as there's
+ // no associated C++ property to resolve the read-only status from
+ n->markReadOnly(const_cast<const QmlPropertyNode *>(qmlProperty)->isReadOnly()
+ && !isAttached);
+ if (qmlProperty->isDefault())
+ n->markDefault();
+ nodes.append(n);
+ }
+ } else
+ qCDebug(lcQdoc) << "Failed to parse QML property:" << topic << args;
+ } else if (topic.endsWith(QLatin1String("method")) || topic == COMMAND_QMLSIGNAL) {
+ if (node->isFunction()) {
+ auto *fn = static_cast<FunctionNode *>(node);
+ QmlSignatureParser qsp(fn, args, doc.location());
+ }
+ }
+ }
+ }
+ for (const auto &node : nodes)
+ applyMetacommands(loc, node, doc);
+
+ m_usedComments.insert(loc.offset);
+ return node;
+}
+
+QmlSignatureParser::QmlSignatureParser(FunctionNode *func, const QString &signature,
+ const Location &loc)
+ : signature_(signature), func_(func), location_(loc)
+{
+ QByteArray latin1 = signature.toLatin1();
+ Tokenizer stringTokenizer(location_, latin1);
+ stringTokenizer.setParsingFnOrMacro(true);
+ tokenizer_ = &stringTokenizer;
+ readToken();
+ matchFunctionDecl();
+}
+
+/*!
+ If the current token matches \a target, read the next
+ token and return true. Otherwise, don't read the next
+ token, and return false.
+ */
+bool QmlSignatureParser::match(int target)
+{
+ if (tok_ == target) {
+ readToken();
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Parse a QML data type into \a type and an optional
+ variable name into \a var.
+ */
+bool QmlSignatureParser::matchTypeAndName(CodeChunk *type, QString *var)
+{
+ /*
+ This code is really hard to follow... sorry. The loop is there to match
+ Alpha::Beta::Gamma::...::Omega.
+ */
+ for (;;) {
+ bool virgin = true;
+
+ if (tok_ != Tok_Ident) {
+ while (match(Tok_signed) || match(Tok_unsigned) || match(Tok_short) || match(Tok_long)
+ || match(Tok_int64)) {
+ type->append(previousLexeme());
+ virgin = false;
+ }
+ }
+
+ if (virgin) {
+ if (match(Tok_Ident)) {
+ type->append(previousLexeme());
+ } else if (match(Tok_void) || match(Tok_int) || match(Tok_char) || match(Tok_double)
+ || match(Tok_Ellipsis))
+ type->append(previousLexeme());
+ else
+ return false;
+ } else if (match(Tok_int) || match(Tok_char) || match(Tok_double)) {
+ type->append(previousLexeme());
+ }
+
+ if (match(Tok_Gulbrandsen))
+ type->append(previousLexeme());
+ else
+ break;
+ }
+
+ while (match(Tok_Ampersand) || match(Tok_Aster) || match(Tok_const) || match(Tok_Caret))
+ type->append(previousLexeme());
+
+ /*
+ The usual case: Look for an optional identifier, then for
+ some array brackets.
+ */
+ type->appendHotspot();
+
+ if ((var != nullptr) && match(Tok_Ident))
+ *var = previousLexeme();
+
+ if (tok_ == Tok_LeftBracket) {
+ int bracketDepth0 = tokenizer_->bracketDepth();
+ while ((tokenizer_->bracketDepth() >= bracketDepth0 && tok_ != Tok_Eoi)
+ || tok_ == Tok_RightBracket) {
+ type->append(lexeme());
+ readToken();
+ }
+ }
+ return true;
+}
+
+bool QmlSignatureParser::matchParameter()
+{
+ QString name;
+ CodeChunk type;
+ CodeChunk defaultValue;
+
+ bool result = matchTypeAndName(&type, &name);
+ if (name.isEmpty()) {
+ name = type.toString();
+ type.clear();
+ }
+
+ if (!result)
+ return false;
+ if (match(Tok_Equal)) {
+ int parenDepth0 = tokenizer_->parenDepth();
+ while (tokenizer_->parenDepth() >= parenDepth0
+ && (tok_ != Tok_Comma || tokenizer_->parenDepth() > parenDepth0)
+ && tok_ != Tok_Eoi) {
+ defaultValue.append(lexeme());
+ readToken();
+ }
+ }
+ func_->parameters().append(type.toString(), name, defaultValue.toString());
+ return true;
+}
+
+bool QmlSignatureParser::matchFunctionDecl()
+{
+ CodeChunk returnType;
+
+ qsizetype firstBlank = signature_.indexOf(QChar(' '));
+ qsizetype leftParen = signature_.indexOf(QChar('('));
+ if ((firstBlank > 0) && (leftParen - firstBlank) > 1) {
+ if (!matchTypeAndName(&returnType, nullptr))
+ return false;
+ }
+
+ while (match(Tok_Ident)) {
+ names_.append(previousLexeme());
+ if (!match(Tok_Gulbrandsen)) {
+ previousLexeme();
+ names_.pop_back();
+ break;
+ }
+ }
+
+ if (tok_ != Tok_LeftParen)
+ return false;
+ /*
+ Parsing the parameters should be moved into class Parameters,
+ but it can wait. mws 14/12/2018
+ */
+ readToken();
+
+ func_->setLocation(location_);
+ func_->setReturnType(returnType.toString());
+
+ if (tok_ != Tok_RightParen) {
+ func_->parameters().clear();
+ do {
+ if (!matchParameter())
+ return false;
+ } while (match(Tok_Comma));
+ }
+ if (!match(Tok_RightParen))
+ return false;
+ return true;
+}
+
+/*!
+ A QML property argument has the form...
+
+ <type> <component>::<name>
+ <type> <QML-module>::<component>::<name>
+
+ This function splits the argument into one of those
+ two forms. The three part form is the old form, which
+ was used before the creation of QtQuick 2 and Qt
+ Components. A <QML-module> is the QML equivalent of a
+ C++ namespace. So this function splits \a arg on "::"
+ and stores the parts in the \e {type}, \e {module},
+ \e {component}, and \a {name}, fields of \a qpa. If it
+ is successful, it returns \c true. If not enough parts
+ are found, a qdoc warning is emitted and false is
+ returned.
+ */
+bool QmlDocVisitor::splitQmlPropertyArg(const Doc &doc, const QString &arg, QmlPropArgs &qpa)
+{
+ qpa.clear();
+ QStringList blankSplit = arg.split(QLatin1Char(' '));
+ if (blankSplit.size() > 1) {
+ qpa.m_type = blankSplit[0];
+ QStringList colonSplit(blankSplit[1].split("::"));
+ if (colonSplit.size() == 3) {
+ qpa.m_module = colonSplit[0];
+ qpa.m_component = colonSplit[1];
+ qpa.m_name = colonSplit[2];
+ return true;
+ } else if (colonSplit.size() == 2) {
+ qpa.m_component = colonSplit[0];
+ qpa.m_name = colonSplit[1];
+ return true;
+ } else if (colonSplit.size() == 1) {
+ qpa.m_name = colonSplit[0];
+ return true;
+ }
+ doc.location().warning(
+ QStringLiteral("Unrecognizable QML module/component qualifier for %1.").arg(arg));
+ } else {
+ doc.location().warning(QStringLiteral("Missing property type for %1.").arg(arg));
+ }
+ return false;
+}
+
+/*!
+ Applies the metacommands found in the comment.
+ */
+void QmlDocVisitor::applyMetacommands(QQmlJS::SourceLocation, Node *node, Doc &doc)
+{
+ QDocDatabase *qdb = QDocDatabase::qdocDB();
+ QSet<QString> metacommands = doc.metaCommandsUsed();
+ if (metacommands.size() > 0) {
+ metacommands.subtract(m_topics);
+ for (const auto &command : std::as_const(metacommands)) {
+ const ArgList args = doc.metaCommandArgs(command);
+ if ((command == COMMAND_QMLABSTRACT) || (command == COMMAND_ABSTRACT)) {
+ if (node->isQmlType()) {
+ node->setAbstract(true);
+ }
+ } else if (command == COMMAND_DEPRECATED) {
+ node->setDeprecated(args[0].second);
+ } else if (command == COMMAND_INQMLMODULE) {
+ qdb->addToQmlModule(args[0].first, node);
+ } else if (command == COMMAND_QMLINHERITS) {
+ if (node->name() == args[0].first)
+ doc.location().warning(
+ QStringLiteral("%1 tries to inherit itself").arg(args[0].first));
+ else if (node->isQmlType()) {
+ auto *qmlType = static_cast<QmlTypeNode *>(node);
+ qmlType->setQmlBaseName(args[0].first);
+ }
+ } else if (command == COMMAND_DEFAULT) {
+ if (!node->isQmlProperty()) {
+ doc.location().warning(QStringLiteral("Ignored '\\%1', applies only to '\\%2'")
+ .arg(command, COMMAND_QMLPROPERTY));
+ } else if (args.isEmpty() || args[0].first.isEmpty()) {
+ doc.location().warning(QStringLiteral("Expected an argument for '\\%1' (maybe you meant '\\%2'?)")
+ .arg(command, COMMAND_QMLDEFAULT));
+ } else {
+ static_cast<QmlPropertyNode *>(node)->setDefaultValue(args[0].first);
+ }
+ } else if (command == COMMAND_QMLDEFAULT) {
+ node->markDefault();
+ } else if (command == COMMAND_QMLENUMERATORSFROM) {
+ if (!node->isQmlProperty()) {
+ doc.location().warning("Ignored '\\%1', applies only to '\\%2'"_L1
+ .arg(command, COMMAND_QMLPROPERTY));
+ } else if (!static_cast<QmlPropertyNode*>(node)->setEnumNode(args[0].first, args[0].second)) {
+ doc.location().warning("Failed to find C++ enumeration '%2' passed to \\%1"_L1
+ .arg(command, args[0].first), "Use \\value commands instead"_L1);
+ }
+ } else if (command == COMMAND_QMLREADONLY) {
+ node->markReadOnly(1);
+ } else if (command == COMMAND_QMLREQUIRED) {
+ if (node->isQmlProperty())
+ static_cast<QmlPropertyNode *>(node)->setRequired();
+ } else if ((command == COMMAND_INGROUP) && !args.isEmpty()) {
+ for (const auto &argument : args)
+ QDocDatabase::qdocDB()->addToGroup(argument.first, node);
+ } else if (command == COMMAND_INTERNAL) {
+ node->setStatus(Node::Internal);
+ } else if (command == COMMAND_OBSOLETE) {
+ node->setStatus(Node::Deprecated);
+ } else if (command == COMMAND_PRELIMINARY) {
+ node->setStatus(Node::Preliminary);
+ } else if (command == COMMAND_SINCE) {
+ QString arg = args[0].first; //.join(' ');
+ node->setSince(arg);
+ } else if (command == COMMAND_WRAPPER) {
+ node->setWrapper();
+ } else {
+ doc.location().warning(
+ QStringLiteral("The \\%1 command is ignored in QML files").arg(command));
+ }
+ }
+ }
+}
+
+/*!
+ Reconstruct the qualified \a id using dot notation
+ and return the fully qualified string.
+ */
+QString QmlDocVisitor::getFullyQualifiedId(QQmlJS::AST::UiQualifiedId *id)
+{
+ QString result;
+ if (id) {
+ result = id->name.toString();
+ id = id->next;
+ while (id != nullptr) {
+ result += QChar('.') + id->name.toString();
+ id = id->next;
+ }
+ }
+ return result;
+}
+
+/*!
+ Begin the visit of the object \a definition, recording it in the
+ qdoc database. Increment the object nesting level, which is used
+ to test whether we are at the public API level. The public level
+ is level 1.
+
+ Defers the construction of a QmlTypeNode instance to
+ applyDocumentation(), by passing \c nullptr as the second
+ argument.
+ */
+bool QmlDocVisitor::visit(QQmlJS::AST::UiObjectDefinition *definition)
+{
+ QString type = getFullyQualifiedId(definition->qualifiedTypeNameId);
+ m_nestingLevel++;
+ if (m_current->isNamespace()) {
+ auto component = applyDocumentation(definition->firstSourceLocation(), nullptr);
+ Q_ASSERT(component);
+ auto *qmlTypeNode = static_cast<QmlTypeNode *>(component);
+ if (!component->doc().isEmpty())
+ qmlTypeNode->setQmlBaseName(type);
+ qmlTypeNode->setTitle(m_name);
+ qmlTypeNode->setImportList(m_importList);
+ m_importList.clear();
+ m_current = qmlTypeNode;
+ }
+
+ return true;
+}
+
+/*!
+ End the visit of the object \a definition. In particular,
+ decrement the object nesting level, which is used to test
+ whether we are at the public API level. The public API
+ level is level 1. It won't decrement below 0.
+ */
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *definition)
+{
+ if (m_nestingLevel > 0) {
+ --m_nestingLevel;
+ }
+ m_lastEndOffset = definition->lastSourceLocation().end();
+}
+
+bool QmlDocVisitor::visit(QQmlJS::AST::UiImport *import)
+{
+ QString name = m_document.mid(import->fileNameToken.offset, import->fileNameToken.length);
+ if (name[0] == '\"')
+ name = name.mid(1, name.size() - 2);
+ QString version;
+ if (import->version) {
+ const auto start = import->version->firstSourceLocation().begin();
+ const auto end = import->version->lastSourceLocation().end();
+ version = m_document.mid(start, end - start);
+ }
+ QString importUri = getFullyQualifiedId(import->importUri);
+ m_importList.append(ImportRec(name, version, importUri));
+
+ return true;
+}
+
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiImport *definition)
+{
+ m_lastEndOffset = definition->lastSourceLocation().end();
+}
+
+bool QmlDocVisitor::visit(QQmlJS::AST::UiObjectBinding *)
+{
+ ++m_nestingLevel;
+ return true;
+}
+
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiObjectBinding *)
+{
+ --m_nestingLevel;
+}
+
+bool QmlDocVisitor::visit(QQmlJS::AST::UiArrayBinding *)
+{
+ return true;
+}
+
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiArrayBinding *) {}
+
+static QString qualifiedIdToString(QQmlJS::AST::UiQualifiedId *node)
+{
+ QString s;
+
+ for (QQmlJS::AST::UiQualifiedId *it = node; it; it = it->next) {
+ s.append(it->name);
+
+ if (it->next)
+ s.append(QLatin1Char('.'));
+ }
+
+ return s;
+}
+
+/*!
+ Visits the public \a member declaration, which can be a
+ signal or a property. It is a custom signal or property.
+ Only visit the \a member if the nestingLevel is 1.
+ */
+bool QmlDocVisitor::visit(QQmlJS::AST::UiPublicMember *member)
+{
+ if (m_nestingLevel > 1) {
+ return true;
+ }
+ switch (member->type) {
+ case QQmlJS::AST::UiPublicMember::Signal: {
+ if (m_current->isQmlType()) {
+ auto *qmlType = static_cast<QmlTypeNode *>(m_current);
+ if (qmlType) {
+ FunctionNode::Metaness metaness = FunctionNode::QmlSignal;
+ QString name = member->name.toString();
+ auto *newSignal = new FunctionNode(metaness, m_current, name);
+ Parameters &parameters = newSignal->parameters();
+ for (QQmlJS::AST::UiParameterList *it = member->parameters; it; it = it->next) {
+ const QString type = it->type ? it->type->toString() : QString();
+ if (!type.isEmpty() && !it->name.isEmpty())
+ parameters.append(type, it->name.toString());
+ }
+ applyDocumentation(member->firstSourceLocation(), newSignal);
+ }
+ }
+ break;
+ }
+ case QQmlJS::AST::UiPublicMember::Property: {
+ QString type = qualifiedIdToString(member->memberType);
+ if (m_current->isQmlType()) {
+ auto *qmlType = static_cast<QmlTypeNode *>(m_current);
+ if (qmlType) {
+ QString name = member->name.toString();
+ QmlPropertyNode *qmlPropNode = qmlType->hasQmlProperty(name);
+ if (qmlPropNode == nullptr)
+ qmlPropNode = new QmlPropertyNode(qmlType, name, type, false);
+ qmlPropNode->markReadOnly(member->isReadonly());
+ if (member->isDefaultMember())
+ qmlPropNode->markDefault();
+ if (member->requiredToken().isValid())
+ qmlPropNode->setRequired();
+ applyDocumentation(member->firstSourceLocation(), qmlPropNode);
+ }
+ }
+ break;
+ }
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+/*!
+ End the visit of the \a member.
+ */
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiPublicMember *member)
+{
+ m_lastEndOffset = member->lastSourceLocation().end();
+}
+
+bool QmlDocVisitor::visit(QQmlJS::AST::IdentifierPropertyName *)
+{
+ return true;
+}
+
+/*!
+ Begin the visit of the function declaration \a fd, but only
+ if the nesting level is 1.
+ */
+bool QmlDocVisitor::visit(QQmlJS::AST::FunctionDeclaration *fd)
+{
+ if (m_nestingLevel <= 1) {
+ FunctionNode::Metaness metaness = FunctionNode::QmlMethod;
+ if (!m_current->isQmlType())
+ return true;
+ QString name = fd->name.toString();
+ auto *method = new FunctionNode(metaness, m_current, name);
+ Parameters &parameters = method->parameters();
+ QQmlJS::AST::FormalParameterList *formals = fd->formals;
+ if (formals) {
+ QQmlJS::AST::FormalParameterList *fp = formals;
+ do {
+ QString defaultValue;
+ auto initializer = fp->element->initializer;
+ if (initializer) {
+ auto loc = initializer->firstSourceLocation();
+ defaultValue = m_document.mid(loc.begin(), loc.length);
+ }
+ parameters.append(QString(), fp->element->bindingIdentifier.toString(),
+ defaultValue);
+ fp = fp->next;
+ } while (fp && fp != formals);
+ }
+ applyDocumentation(fd->firstSourceLocation(), method);
+ }
+ return true;
+}
+
+/*!
+ End the visit of the function declaration, \a fd.
+ */
+void QmlDocVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *fd)
+{
+ m_lastEndOffset = fd->lastSourceLocation().end();
+}
+
+/*!
+ Begin the visit of the signal handler declaration \a sb, but only
+ if the nesting level is 1.
+
+ This visit is now deprecated. It has been decided to document
+ public signals. If a signal handler must be discussed in the
+ documentation, that discussion must take place in the comment
+ for the signal.
+ */
+bool QmlDocVisitor::visit(QQmlJS::AST::UiScriptBinding *)
+{
+ return true;
+}
+
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiScriptBinding *sb)
+{
+ m_lastEndOffset = sb->lastSourceLocation().end();
+}
+
+bool QmlDocVisitor::visit(QQmlJS::AST::UiQualifiedId *)
+{
+ return true;
+}
+
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiQualifiedId *)
+{
+ // nothing.
+}
+
+void QmlDocVisitor::throwRecursionDepthError()
+{
+ hasRecursionDepthError = true;
+}
+
+bool QmlDocVisitor::hasError() const
+{
+ return hasRecursionDepthError;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qmlvisitor.h b/src/qdoc/qdoc/src/qdoc/qmlvisitor.h
new file mode 100644
index 000000000..201dc0e61
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlvisitor.h
@@ -0,0 +1,93 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QMLVISITOR_H
+#define QMLVISITOR_H
+
+#include "node.h"
+#include "qmltypenode.h"
+
+#include <QtCore/qstring.h>
+
+#include <private/qqmljsastvisitor_p.h>
+#include <private/qqmljsengine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+struct QmlPropArgs
+{
+ QString m_type {};
+ QString m_module {};
+ QString m_component {};
+ QString m_name;
+
+ void clear()
+ {
+ m_type.clear();
+ m_module.clear();
+ m_component.clear();
+ m_name.clear();
+ }
+};
+
+class QmlDocVisitor : public QQmlJS::AST::Visitor
+{
+public:
+ QmlDocVisitor(const QString &filePath, const QString &code, QQmlJS::Engine *engine,
+ const QSet<QString> &commands, const QSet<QString> &topics);
+ ~QmlDocVisitor() override = default;
+
+ bool visit(QQmlJS::AST::UiImport *import) override;
+ void endVisit(QQmlJS::AST::UiImport *definition) override;
+
+ bool visit(QQmlJS::AST::UiObjectDefinition *definition) override;
+ void endVisit(QQmlJS::AST::UiObjectDefinition *definition) override;
+
+ bool visit(QQmlJS::AST::UiPublicMember *member) override;
+ void endVisit(QQmlJS::AST::UiPublicMember *definition) override;
+
+ bool visit(QQmlJS::AST::UiObjectBinding *) override;
+ void endVisit(QQmlJS::AST::UiObjectBinding *) override;
+ void endVisit(QQmlJS::AST::UiArrayBinding *) override;
+ bool visit(QQmlJS::AST::UiArrayBinding *) override;
+
+ bool visit(QQmlJS::AST::IdentifierPropertyName *idproperty) override;
+
+ bool visit(QQmlJS::AST::FunctionDeclaration *) override;
+ void endVisit(QQmlJS::AST::FunctionDeclaration *) override;
+
+ bool visit(QQmlJS::AST::UiScriptBinding *) override;
+ void endVisit(QQmlJS::AST::UiScriptBinding *) override;
+
+ bool visit(QQmlJS::AST::UiQualifiedId *) override;
+ void endVisit(QQmlJS::AST::UiQualifiedId *) override;
+
+ void throwRecursionDepthError() final;
+ [[nodiscard]] bool hasError() const;
+
+private:
+ QString getFullyQualifiedId(QQmlJS::AST::UiQualifiedId *id);
+ [[nodiscard]] QQmlJS::SourceLocation precedingComment(quint32 offset) const;
+ Node *applyDocumentation(QQmlJS::SourceLocation location, Node *node);
+ void applyMetacommands(QQmlJS::SourceLocation location, Node *node, Doc &doc);
+ bool splitQmlPropertyArg(const Doc &doc, const QString &arg, QmlPropArgs &qpa);
+
+ QQmlJS::Engine *m_engine { nullptr };
+ quint32 m_lastEndOffset {};
+ quint32 m_nestingLevel {};
+ QString m_filePath {};
+ QString m_name {};
+ QString m_document {};
+ ImportList m_importList {};
+ QSet<QString> m_commands {};
+ QSet<QString> m_topics {};
+ QSet<quint32> m_usedComments {};
+ Aggregate *m_current { nullptr };
+ bool hasRecursionDepthError { false };
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/quoter.cpp b/src/qdoc/qdoc/src/qdoc/quoter.cpp
new file mode 100644
index 000000000..37799a9e9
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/quoter.cpp
@@ -0,0 +1,338 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "quoter.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qregularexpression.h>
+
+QT_BEGIN_NAMESPACE
+
+QHash<QString, QString> Quoter::s_commentHash;
+
+static void replaceMultipleNewlines(QString &s)
+{
+ const qsizetype n = s.size();
+ bool slurping = false;
+ int j = -1;
+ const QChar newLine = QLatin1Char('\n');
+ QChar *d = s.data();
+ for (int i = 0; i != n; ++i) {
+ const QChar c = d[i];
+ bool hit = (c == newLine);
+ if (slurping && hit)
+ continue;
+ d[++j] = c;
+ slurping = hit;
+ }
+ s.resize(++j);
+}
+
+// This is equivalent to line.split( QRegularExpression("\n(?!\n|$)") ) but much faster
+QStringList Quoter::splitLines(const QString &line)
+{
+ QStringList result;
+ qsizetype i = line.size();
+ while (true) {
+ qsizetype j = i - 1;
+ while (j >= 0 && line.at(j) == QLatin1Char('\n'))
+ --j;
+ while (j >= 0 && line.at(j) != QLatin1Char('\n'))
+ --j;
+ result.prepend(line.mid(j + 1, i - j - 1));
+ if (j < 0)
+ break;
+ i = j;
+ }
+ return result;
+}
+
+/*
+ Transforms 'int x = 3 + 4' into 'int x=3+4'. A white space is kept
+ between 'int' and 'x' because it is meaningful in C++.
+*/
+static void trimWhiteSpace(QString &str)
+{
+ enum { Normal, MetAlnum, MetSpace } state = Normal;
+ const qsizetype n = str.size();
+
+ int j = -1;
+ QChar *d = str.data();
+ for (int i = 0; i != n; ++i) {
+ const QChar c = d[i];
+ if (c.isLetterOrNumber()) {
+ if (state == Normal) {
+ state = MetAlnum;
+ } else {
+ if (state == MetSpace)
+ str[++j] = c;
+ state = Normal;
+ }
+ str[++j] = c;
+ } else if (c.isSpace()) {
+ if (state == MetAlnum)
+ state = MetSpace;
+ } else {
+ state = Normal;
+ str[++j] = c;
+ }
+ }
+ str.resize(++j);
+}
+
+Quoter::Quoter() : m_silent(false)
+{
+ /* We're going to hard code these delimiters:
+ * C++, Qt, Qt Script, Java:
+ //! [<id>]
+ * .pro, .py, CMake files:
+ #! [<id>]
+ * .html, .qrc, .ui, .xq, .xml files:
+ <!-- [<id>] -->
+ */
+ if (s_commentHash.empty()) {
+ s_commentHash["pro"] = "#!";
+ s_commentHash["py"] = "#!";
+ s_commentHash["cmake"] = "#!";
+ s_commentHash["html"] = "<!--";
+ s_commentHash["qrc"] = "<!--";
+ s_commentHash["ui"] = "<!--";
+ s_commentHash["xml"] = "<!--";
+ s_commentHash["xq"] = "<!--";
+ }
+}
+
+void Quoter::reset()
+{
+ m_silent = false;
+ m_plainLines.clear();
+ m_markedLines.clear();
+ m_codeLocation = Location();
+}
+
+void Quoter::quoteFromFile(const QString &userFriendlyFilePath, const QString &plainCode,
+ const QString &markedCode)
+{
+ m_silent = false;
+
+ /*
+ Split the source code into logical lines. Empty lines are
+ treated specially. Before:
+
+ p->alpha();
+ p->beta();
+
+ p->gamma();
+
+
+ p->delta();
+
+ After:
+
+ p->alpha();
+ p->beta();\n
+ p->gamma();\n\n
+ p->delta();
+
+ Newlines are preserved because they affect codeLocation.
+ */
+ m_codeLocation = Location(userFriendlyFilePath);
+
+ m_plainLines = splitLines(plainCode);
+ m_markedLines = splitLines(markedCode);
+ if (m_markedLines.size() != m_plainLines.size()) {
+ m_codeLocation.warning(
+ QStringLiteral("Something is wrong with qdoc's handling of marked code"));
+ m_markedLines = m_plainLines;
+ }
+
+ /*
+ Squeeze blanks (cat -s).
+ */
+ for (auto &line : m_markedLines)
+ replaceMultipleNewlines(line);
+ m_codeLocation.start();
+}
+
+QString Quoter::quoteLine(const Location &docLocation, const QString &command,
+ const QString &pattern)
+{
+ if (m_plainLines.isEmpty()) {
+ failedAtEnd(docLocation, command);
+ return QString();
+ }
+
+ if (pattern.isEmpty()) {
+ docLocation.warning(QStringLiteral("Missing pattern after '\\%1'").arg(command));
+ return QString();
+ }
+
+ if (match(docLocation, pattern, m_plainLines.first()))
+ return getLine();
+
+ if (!m_silent) {
+ docLocation.warning(QStringLiteral("Command '\\%1' failed").arg(command));
+ m_codeLocation.warning(QStringLiteral("Pattern '%1' didn't match here").arg(pattern));
+ m_silent = true;
+ }
+ return QString();
+}
+
+QString Quoter::quoteSnippet(const Location &docLocation, const QString &identifier)
+{
+ QString comment = commentForCode();
+ QString delimiter = comment + QString(" [%1]").arg(identifier);
+ QString t;
+ int indent = 0;
+
+ while (!m_plainLines.isEmpty()) {
+ if (match(docLocation, delimiter, m_plainLines.first())) {
+ QString startLine = getLine();
+ while (indent < startLine.size() && startLine[indent] == QLatin1Char(' '))
+ indent++;
+ break;
+ }
+ getLine();
+ }
+ while (!m_plainLines.isEmpty()) {
+ QString line = m_plainLines.first();
+ if (match(docLocation, delimiter, line)) {
+ QString lastLine = getLine(indent);
+ qsizetype dIndex = lastLine.indexOf(delimiter);
+ if (dIndex > 0) {
+ // The delimiter might be preceded on the line by other
+ // delimeters, so look for the first comment on the line.
+ QString leading = lastLine.left(dIndex);
+ dIndex = leading.indexOf(comment);
+ if (dIndex != -1)
+ leading = leading.left(dIndex);
+ if (leading.endsWith(QLatin1String("<@comment>")))
+ leading.chop(10);
+ if (!leading.trimmed().isEmpty())
+ t += leading;
+ }
+ return t;
+ }
+
+ t += removeSpecialLines(line, comment, indent);
+ }
+ failedAtEnd(docLocation, QString("snippet (%1)").arg(delimiter));
+ return t;
+}
+
+QString Quoter::quoteTo(const Location &docLocation, const QString &command, const QString &pattern)
+{
+ QString t;
+ QString comment = commentForCode();
+
+ if (pattern.isEmpty()) {
+ while (!m_plainLines.isEmpty()) {
+ QString line = m_plainLines.first();
+ t += removeSpecialLines(line, comment);
+ }
+ } else {
+ while (!m_plainLines.isEmpty()) {
+ if (match(docLocation, pattern, m_plainLines.first())) {
+ return t;
+ }
+ t += getLine();
+ }
+ failedAtEnd(docLocation, command);
+ }
+ return t;
+}
+
+QString Quoter::quoteUntil(const Location &docLocation, const QString &command,
+ const QString &pattern)
+{
+ QString t = quoteTo(docLocation, command, pattern);
+ t += getLine();
+ return t;
+}
+
+QString Quoter::getLine(int unindent)
+{
+ if (m_plainLines.isEmpty())
+ return QString();
+
+ m_plainLines.removeFirst();
+
+ QString t = m_markedLines.takeFirst();
+ int i = 0;
+ while (i < unindent && i < t.size() && t[i] == QLatin1Char(' '))
+ i++;
+
+ t = t.mid(i);
+ t += QLatin1Char('\n');
+ m_codeLocation.advanceLines(t.count(QLatin1Char('\n')));
+ return t;
+}
+
+bool Quoter::match(const Location &docLocation, const QString &pattern0, const QString &line)
+{
+ QString str = line;
+ while (str.endsWith(QLatin1Char('\n')))
+ str.truncate(str.size() - 1);
+
+ QString pattern = pattern0;
+ if (pattern.startsWith(QLatin1Char('/')) && pattern.endsWith(QLatin1Char('/'))
+ && pattern.size() > 2) {
+ QRegularExpression rx(pattern.mid(1, pattern.size() - 2));
+ if (!m_silent && !rx.isValid()) {
+ docLocation.warning(
+ QStringLiteral("Invalid regular expression '%1'").arg(rx.pattern()));
+ m_silent = true;
+ }
+ return str.indexOf(rx) != -1;
+ }
+ trimWhiteSpace(str);
+ trimWhiteSpace(pattern);
+ return str.indexOf(pattern) != -1;
+}
+
+void Quoter::failedAtEnd(const Location &docLocation, const QString &command)
+{
+ if (!m_silent && !command.isEmpty()) {
+ if (m_codeLocation.filePath().isEmpty()) {
+ docLocation.warning(QStringLiteral("Unexpected '\\%1'").arg(command));
+ } else {
+ docLocation.warning(QStringLiteral("Command '\\%1' failed at end of file '%2'")
+ .arg(command, m_codeLocation.filePath()));
+ }
+ m_silent = true;
+ }
+}
+
+QString Quoter::commentForCode() const
+{
+ QFileInfo fi = QFileInfo(m_codeLocation.fileName());
+ if (fi.fileName() == "CMakeLists.txt")
+ return "#!";
+ return s_commentHash.value(fi.suffix(), "//!");
+}
+
+QString Quoter::removeSpecialLines(const QString &line, const QString &comment, int unindent)
+{
+ QString t;
+
+ // Remove special macros to support Qt namespacing.
+ QString trimmed = line.trimmed();
+ if (trimmed.startsWith("QT_BEGIN_NAMESPACE")) {
+ getLine();
+ } else if (trimmed.startsWith("QT_END_NAMESPACE")) {
+ getLine();
+ t += QLatin1Char('\n');
+ } else if (!trimmed.startsWith(comment)) {
+ // Ordinary code
+ t += getLine(unindent);
+ } else {
+ // Comments
+ if (line.contains(QLatin1Char('\n')))
+ t += QLatin1Char('\n');
+ getLine();
+ }
+ return t;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/quoter.h b/src/qdoc/qdoc/src/qdoc/quoter.h
new file mode 100644
index 000000000..087e9ffd2
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/quoter.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QUOTER_H
+#define QUOTER_H
+
+#include "location.h"
+
+#include <QtCore/qhash.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class Quoter
+{
+public:
+ Quoter();
+
+ void reset();
+ void quoteFromFile(const QString &userFriendlyFileName, const QString &plainCode,
+ const QString &markedCode);
+ QString quoteLine(const Location &docLocation, const QString &command, const QString &pattern);
+ QString quoteTo(const Location &docLocation, const QString &command, const QString &pattern);
+ QString quoteUntil(const Location &docLocation, const QString &command, const QString &pattern);
+ QString quoteSnippet(const Location &docLocation, const QString &identifier);
+
+ static QStringList splitLines(const QString &line);
+
+private:
+ QString getLine(int unindent = 0);
+ void failedAtEnd(const Location &docLocation, const QString &command);
+ bool match(const Location &docLocation, const QString &pattern, const QString &line);
+ [[nodiscard]] QString commentForCode() const;
+ QString removeSpecialLines(const QString &line, const QString &comment, int unindent = 0);
+
+ bool m_silent {};
+ QStringList m_plainLines {};
+ QStringList m_markedLines {};
+ Location m_codeLocation {};
+ static QHash<QString, QString> s_commentHash;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/relatedclass.cpp b/src/qdoc/qdoc/src/qdoc/relatedclass.cpp
new file mode 100644
index 000000000..3eea8df92
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/relatedclass.cpp
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "relatedclass.h"
+
+#include "node.h"
+
+/*!
+ \struct RelatedClass
+ \brief A struct for indicating that a ClassNode is related in some way to another ClassNode.
+
+ This struct has nothing to do with the \c {\\relates} command. This struct
+ is used for indicating that a ClassNode is a base class of another ClassNode,
+ is a derived class of another ClassNode, or is an ignored base class of
+ another ClassNode. This struct is only used in ClassNode.
+*/
+
+/*! \fn RelatedClass::RelatedClass()
+ The default constructor does nothing. It is only used for allocating empty
+ instances of RelatedClass in containers.
+ */
+
+/*! \fn RelatedClass::RelatedClass(Access access, ClassNode *node)
+ This is the constructor used when the related class has been resolved.
+ In other words, when the ClassNode has been created so that \a node is
+ not \c nullptr.
+*/
+
+/*! \fn RelatedClass::RelatedClass(Access access, const QStringList &path, const QString &signature)
+ This is the constructor used when the related class has not bee resolved,
+ because it hasn't been created yet. In that case, we store the qualified
+ \a path name of the class and the \a signature of the class, which I think
+ is just the name of the class.
+
+ \note We might be able to simplify the whole RelatedClass concept. Maybe we
+ can get rid of it completely.
+*/
+
+/*! \fn bool RelatedClass::isPrivate() const
+ Returns \c true if this RelatedClass is marked as Access::Private.
+*/
+bool RelatedClass::isPrivate() const
+{
+ return (m_access == Access::Private);
+}
+
diff --git a/src/qdoc/qdoc/src/qdoc/relatedclass.h b/src/qdoc/qdoc/src/qdoc/relatedclass.h
new file mode 100644
index 000000000..9ca3b849a
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/relatedclass.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef RELATEDCLASS_H
+#define RELATEDCLASS_H
+
+#include "access.h"
+
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+class ClassNode;
+
+struct RelatedClass
+{
+ RelatedClass() = default;
+ // constructor for resolved base class
+ RelatedClass(Access access, ClassNode *node) : m_access(access), m_node(node) {}
+ // constructor for unresolved base class
+ RelatedClass(Access access, QStringList path) : m_access(access), m_path(std::move(path)) { }
+ [[nodiscard]] bool isPrivate() const;
+
+ Access m_access {};
+ ClassNode *m_node { nullptr };
+ QStringList m_path {};
+};
+
+QT_END_NAMESPACE
+
+#endif // RELATEDCLASS_H
diff --git a/src/qdoc/qdoc/src/qdoc/sections.cpp b/src/qdoc/qdoc/src/qdoc/sections.cpp
new file mode 100644
index 000000000..bf066c08e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/sections.cpp
@@ -0,0 +1,992 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "sections.h"
+
+#include "aggregate.h"
+#include "classnode.h"
+#include "config.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "generator.h"
+#include "utilities.h"
+#include "namespacenode.h"
+#include "qmlpropertynode.h"
+#include "qmltypenode.h"
+#include "sharedcommentnode.h"
+#include "typedefnode.h"
+#include "variablenode.h"
+
+#include <QtCore/qobjectdefs.h>
+
+QT_BEGIN_NAMESPACE
+
+QList<Section> Sections::s_stdSummarySections {
+ { "Namespaces", "namespace", "namespaces", "", Section::Summary },
+ { "Classes", "class", "classes", "", Section::Summary },
+ { "Types", "type", "types", "", Section::Summary },
+ { "Variables", "variable", "variables", "", Section::Summary },
+ { "Static Variables", "static variable", "static variables", "", Section::Summary },
+ { "Functions", "function", "functions", "", Section::Summary },
+ { "Macros", "macro", "macros", "", Section::Summary },
+};
+
+QList<Section> Sections::s_stdDetailsSections {
+ { "Namespaces", "namespace", "namespaces", "nmspace", Section::Details },
+ { "Classes", "class", "classes", "classes", Section::Details },
+ { "Type Documentation", "type", "types", "types", Section::Details },
+ { "Variable Documentation", "variable", "variables", "vars", Section::Details },
+ { "Static Variables", "static variable", "static variables", QString(), Section::Details },
+ { "Function Documentation", "function", "functions", "func", Section::Details },
+ { "Macro Documentation", "macro", "macros", "macros", Section::Details },
+};
+
+QList<Section> Sections::s_stdCppClassSummarySections {
+ { "Public Types", "public type", "public types", "", Section::Summary },
+ { "Properties", "property", "properties", "", Section::Summary },
+ { "Public Functions", "public function", "public functions", "", Section::Summary },
+ { "Public Slots", "public slot", "public slots", "", Section::Summary },
+ { "Signals", "signal", "signals", "", Section::Summary },
+ { "Public Variables", "public variable", "public variables", "", Section::Summary },
+ { "Static Public Members", "static public member", "static public members", "", Section::Summary },
+ { "Protected Types", "protected type", "protected types", "", Section::Summary },
+ { "Protected Functions", "protected function", "protected functions", "", Section::Summary },
+ { "Protected Slots", "protected slot", "protected slots", "", Section::Summary },
+ { "Protected Variables", "protected type", "protected variables", "", Section::Summary },
+ { "Static Protected Members", "static protected member", "static protected members", "", Section::Summary },
+ { "Private Types", "private type", "private types", "", Section::Summary },
+ { "Private Functions", "private function", "private functions", "", Section::Summary },
+ { "Private Slots", "private slot", "private slots", "", Section::Summary },
+ { "Static Private Members", "static private member", "static private members", "", Section::Summary },
+ { "Related Non-Members", "related non-member", "related non-members", "", Section::Summary },
+ { "Macros", "macro", "macros", "", Section::Summary },
+};
+
+QList<Section> Sections::s_stdCppClassDetailsSections {
+ { "Member Type Documentation", "member", "members", "types", Section::Details },
+ { "Property Documentation", "member", "members", "prop", Section::Details },
+ { "Member Function Documentation", "member", "members", "func", Section::Details },
+ { "Member Variable Documentation", "member", "members", "vars", Section::Details },
+ { "Related Non-Members", "member", "members", "relnonmem", Section::Details },
+ { "Macro Documentation", "member", "members", "macros", Section::Details },
+};
+
+QList<Section> Sections::s_stdQmlTypeSummarySections {
+ { "Properties", "property", "properties", "", Section::Summary },
+ { "Attached Properties", "attached property", "attached properties", "", Section::Summary },
+ { "Signals", "signal", "signals", "", Section::Summary },
+ { "Signal Handlers", "signal handler", "signal handlers", "", Section::Summary },
+ { "Attached Signals", "attached signal", "attached signals", "", Section::Summary },
+ { "Methods", "method", "methods", "", Section::Summary },
+ { "Attached Methods", "attached method", "attached methods", "", Section::Summary },
+};
+
+QList<Section> Sections::s_stdQmlTypeDetailsSections {
+ { "Property Documentation", "member", "members", "qmlprop", Section::Details },
+ { "Attached Property Documentation", "member", "members", "qmlattprop", Section::Details },
+ { "Signal Documentation", "signal", "signals", "qmlsig", Section::Details },
+ { "Signal Handler Documentation", "signal handler", "signal handlers", "qmlsighan", Section::Details },
+ { "Attached Signal Documentation", "signal", "signals", "qmlattsig", Section::Details },
+ { "Method Documentation", "member", "members", "qmlmeth", Section::Details },
+ { "Attached Method Documentation", "member", "members", "qmlattmeth", Section::Details },
+};
+
+QList<Section> Sections::s_sinceSections {
+ { "New Namespaces", "", "", "", Section::Details },
+ { "New Classes", "", "", "", Section::Details },
+ { "New Member Functions", "", "", "", Section::Details },
+ { "New Functions in Namespaces", "", "", "", Section::Details },
+ { "New Global Functions", "", "", "", Section::Details },
+ { "New Macros", "", "", "", Section::Details },
+ { "New Enum Types", "", "", "", Section::Details },
+ { "New Enum Values", "", "", "", Section::Details },
+ { "New Type Aliases", "", "", "", Section::Details },
+ { "New Properties", "", "", "", Section::Details },
+ { "New Variables", "", "", "", Section::Details },
+ { "New QML Types", "", "", "", Section::Details },
+ { "New QML Properties", "", "", "", Section::Details },
+ { "New QML Signals", "", "", "", Section::Details },
+ { "New QML Signal Handlers", "", "", "", Section::Details },
+ { "New QML Methods", "", "", "", Section::Details },
+};
+
+QList<Section> Sections::s_allMembers{ { "", "member", "members", "", Section::AllMembers } };
+
+/*!
+ \class Section
+ \brief A class for containing the elements of one documentation section
+ */
+
+/*!
+ The destructor must delete the members of collections
+ when the members are allocated on the heap.
+ */
+Section::~Section()
+{
+ clear();
+}
+
+/*!
+ A Section is now an element in a static vector, so we
+ don't have to repeatedly construct and destroy them. But
+ we do need to clear them before each call to build the
+ sections for a C++ or QML entity.
+ */
+void Section::clear()
+{
+ m_reimplementedMemberMap.clear();
+ m_members.clear();
+ m_obsoleteMembers.clear();
+ m_reimplementedMembers.clear();
+ m_inheritedMembers.clear();
+ m_classNodesList.clear();
+ m_aggregate = nullptr;
+}
+
+/*!
+ Construct a name for the \a node that can be used for sorting
+ a set of nodes into equivalence classes.
+ */
+QString sortName(const Node *node)
+{
+ QString nodeName{node->name()};
+
+ int numDigits = 0;
+ for (qsizetype i = nodeName.size() - 1; i > 0; --i) {
+ if (nodeName.at(i).digitValue() == -1)
+ break;
+ ++numDigits;
+ }
+
+ // we want 'qint8' to appear before 'qint16'
+ if (numDigits > 0) {
+ for (int i = 0; i < 4 - numDigits; ++i)
+ nodeName.insert(nodeName.size() - numDigits - 1, QLatin1Char('0'));
+ }
+
+ if (node->isClassNode())
+ return QLatin1Char('A') + nodeName;
+
+ if (node->isFunction(Node::CPP)) {
+ const auto *fn = static_cast<const FunctionNode *>(node);
+
+ QString sortNo;
+ if (fn->isCtor())
+ sortNo = QLatin1String("C");
+ else if (fn->isCCtor())
+ sortNo = QLatin1String("D");
+ else if (fn->isMCtor())
+ sortNo = QLatin1String("E");
+ else if (fn->isDtor())
+ sortNo = QLatin1String("F");
+ else if (nodeName.startsWith(QLatin1String("operator")) && nodeName.size() > 8
+ && !nodeName[8].isLetterOrNumber())
+ sortNo = QLatin1String("H");
+ else
+ sortNo = QLatin1String("G");
+
+ return sortNo + nodeName + QLatin1Char(' ') + QString::number(fn->overloadNumber(), 36);
+ }
+
+ if (node->isFunction(Node::QML))
+ return QLatin1Char('E') + nodeName + QLatin1Char(' ') +
+ QString::number(static_cast<const FunctionNode*>(node)->overloadNumber(), 36);
+
+ if (node->isProperty() || node->isVariable())
+ return QLatin1Char('G') + nodeName;
+
+ return QLatin1Char('B') + nodeName;
+}
+
+/*!
+ Inserts the \a node into this section if it is appropriate
+ for this section.
+ */
+void Section::insert(Node *node)
+{
+ bool irrelevant = false;
+ bool inherited = false;
+ if (!node->isRelatedNonmember()) {
+ Aggregate *p = node->parent();
+ if (!p->isNamespace() && p != m_aggregate) {
+ if (!p->isQmlType() || !p->isAbstract())
+ inherited = true;
+ }
+ }
+
+ if (node->isPrivate() || node->isInternal()) {
+ irrelevant = true;
+ } else if (node->isFunction()) {
+ auto *func = static_cast<FunctionNode *>(node);
+ irrelevant = (inherited && (func->isSomeCtor() || func->isDtor()));
+ } else if (node->isClassNode() || node->isEnumType() || node->isTypedef()
+ || node->isVariable()) {
+ irrelevant = (inherited && m_style != AllMembers);
+ if (!irrelevant && m_style == Details && node->isTypedef()) {
+ const auto *tdn = static_cast<const TypedefNode *>(node);
+ if (tdn->associatedEnum())
+ irrelevant = true;
+ }
+ }
+
+ if (!irrelevant) {
+ QString key = sortName(node);
+ if (node->isDeprecated()) {
+ m_obsoleteMembers.push_back(node);
+ } else {
+ if (!inherited || m_style == AllMembers)
+ m_members.push_back(node);
+
+ if (inherited && (node->parent()->isClassNode() || node->parent()->isNamespace())) {
+ if (m_inheritedMembers.isEmpty()
+ || m_inheritedMembers.last().first != node->parent()) {
+ std::pair<Aggregate *, int> p(node->parent(), 0);
+ m_inheritedMembers.append(p);
+ }
+ m_inheritedMembers.last().second++;
+ }
+ }
+ }
+}
+
+/*!
+ Returns \c true if the \a node is a reimplemented member
+ function of the current class. If true, the \a node is
+ inserted into the reimplemented member map. True
+ is returned only if \a node is inserted into the map.
+ That is, false is returned if the \a node is already in
+ the map.
+ */
+bool Section::insertReimplementedMember(Node *node)
+{
+ if (!node->isPrivate() && !node->isRelatedNonmember()) {
+ const auto *fn = static_cast<const FunctionNode *>(node);
+ if (!fn->overridesThis().isEmpty()) {
+ if (fn->parent() == m_aggregate) {
+ QString key = sortName(fn);
+ if (!m_reimplementedMemberMap.contains(key)) {
+ m_reimplementedMemberMap.insert(key, node);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/*!
+ If this section is not empty, convert its maps to sequential
+ structures for better traversal during doc generation.
+ */
+void Section::reduce()
+{
+ // TODO:TEMPORARY:INTERMEDITATE: Section uses a series of maps
+ // to internally manage the categorization of the various members
+ // of an aggregate. It further uses a secondary "flattened"
+ // (usually vector) version that is later used by consumers of a
+ // Section content.
+ //
+ // One of the uses of those maps is that of ordering, by using
+ // keys generated with `sortName`.
+ // Nonetheless, this is the only usage that comes from the keys,
+ // as they are neither necessary nor used outside of the internal
+ // code for Section.
+ //
+ // Hence, the codebase is moving towards removing the maps in
+ // favor of building a flattened, consumer ready, version of the
+ // categorization directly, cutting the intermediate conversion
+ // step.
+ //
+ // To do so while keeping as much of the old behavior as possible,
+ // we provide a sorting for the flattened version that is based on
+ // `sortName`, as the previous ordering was.
+ //
+ // This acts as a relatively heavy pessimization, as `sortName`,
+ // used as a comparator, can be called multiple times for each
+ // Node, while before it would have been called almost-once.
+ //
+ // Instead of fixing this issue, by for example caching the
+ // sortName of each Node instance, we temporarily keep the
+ // pessimization while the various maps are removed.
+ //
+ // When all the maps are removed, we can remove `sortName`, which
+ // produces strings to use as key requiring a few allocations and
+ // expensive operations, with an actual comparator function, which
+ // should be more lightweight and more than offset the
+ // multiple-calls.
+ static auto node_less_than = [](const Node* left, const Node* right) {
+ return sortName(left) < sortName(right);
+ };
+
+ std::stable_sort(m_members.begin(), m_members.end(), node_less_than);
+ std::stable_sort(m_obsoleteMembers.begin(), m_obsoleteMembers.end(), node_less_than);
+
+ m_reimplementedMembers = m_reimplementedMemberMap.values().toVector();
+
+ for (auto &cn : m_classNodesList) {
+ std::stable_sort(cn.second.begin(), cn.second.end(), node_less_than);
+ }
+}
+
+/*!
+ \class Sections
+ \brief A class for creating vectors of collections for documentation
+
+ Each element in a vector is an instance of Section, which
+ contains all the elements that will be documented in one
+ section of a reference documentation page.
+ */
+
+/*!
+ This constructor builds the vectors of sections based on the
+ type of the \a aggregate node.
+ */
+Sections::Sections(Aggregate *aggregate) : m_aggregate(aggregate)
+{
+ initAggregate(s_allMembers, m_aggregate);
+ switch (m_aggregate->nodeType()) {
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ initAggregate(s_stdCppClassSummarySections, m_aggregate);
+ initAggregate(s_stdCppClassDetailsSections, m_aggregate);
+ buildStdCppClassRefPageSections();
+ break;
+ case Node::QmlType:
+ case Node::QmlValueType:
+ initAggregate(s_stdQmlTypeSummarySections, m_aggregate);
+ initAggregate(s_stdQmlTypeDetailsSections, m_aggregate);
+ buildStdQmlTypeRefPageSections();
+ break;
+ case Node::Namespace:
+ case Node::HeaderFile:
+ case Node::Proxy:
+ default:
+ initAggregate(s_stdSummarySections, m_aggregate);
+ initAggregate(s_stdDetailsSections, m_aggregate);
+ buildStdRefPageSections();
+ break;
+ }
+}
+
+/*!
+ This constructor builds a vector of sections from the \e since
+ node map, \a nsmap
+ */
+Sections::Sections(const NodeMultiMap &nsmap) : m_aggregate(nullptr)
+{
+ if (nsmap.isEmpty())
+ return;
+ SectionVector &sections = sinceSections();
+ for (auto it = nsmap.constBegin(); it != nsmap.constEnd(); ++it) {
+ Node *node = it.value();
+ switch (node->nodeType()) {
+ case Node::QmlType:
+ sections[SinceQmlTypes].appendMember(node);
+ break;
+ case Node::Namespace:
+ sections[SinceNamespaces].appendMember(node);
+ break;
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ sections[SinceClasses].appendMember(node);
+ break;
+ case Node::Enum: {
+ // The map can contain an enum node with \since, or an enum node
+ // with \value containing a since-clause. In the latter case,
+ // key() is an empty string.
+ if (!it.key().isEmpty())
+ sections[SinceEnumTypes].appendMember(node);
+ else
+ sections[SinceEnumValues].appendMember(node);
+ break;
+ }
+ case Node::Typedef:
+ case Node::TypeAlias:
+ sections[SinceTypeAliases].appendMember(node);
+ break;
+ case Node::Function: {
+ const auto *fn = static_cast<const FunctionNode *>(node);
+ switch (fn->metaness()) {
+ case FunctionNode::QmlSignal:
+ sections[SinceQmlSignals].appendMember(node);
+ break;
+ case FunctionNode::QmlSignalHandler:
+ sections[SinceQmlSignalHandlers].appendMember(node);
+ break;
+ case FunctionNode::QmlMethod:
+ sections[SinceQmlMethods].appendMember(node);
+ break;
+ default:
+ if (fn->isMacro())
+ sections[SinceMacros].appendMember(node);
+ else {
+ Node *p = fn->parent();
+ if (p) {
+ if (p->isClassNode())
+ sections[SinceMemberFunctions].appendMember(node);
+ else if (p->isNamespace()) {
+ if (p->name().isEmpty())
+ sections[SinceGlobalFunctions].appendMember(node);
+ else
+ sections[SinceNamespaceFunctions].appendMember(node);
+ } else
+ sections[SinceGlobalFunctions].appendMember(node);
+ } else
+ sections[SinceGlobalFunctions].appendMember(node);
+ }
+ break;
+ }
+ break;
+ }
+ case Node::Property:
+ sections[SinceProperties].appendMember(node);
+ break;
+ case Node::Variable:
+ sections[SinceVariables].appendMember(node);
+ break;
+ case Node::QmlProperty:
+ sections[SinceQmlProperties].appendMember(node);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/*!
+ The behavior of the destructor depends on the type of the
+ Aggregate node that was passed to the constructor. If the
+ constructor was passed a multimap, the destruction is a
+ bit different because there was no Aggregate node.
+ */
+Sections::~Sections()
+{
+ if (m_aggregate) {
+ switch (m_aggregate->nodeType()) {
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ clear(stdCppClassSummarySections());
+ clear(stdCppClassDetailsSections());
+ allMembersSection().clear();
+ break;
+ case Node::QmlType:
+ case Node::QmlValueType:
+ clear(stdQmlTypeSummarySections());
+ clear(stdQmlTypeDetailsSections());
+ allMembersSection().clear();
+ break;
+ default:
+ clear(stdSummarySections());
+ clear(stdDetailsSections());
+ allMembersSection().clear();
+ break;
+ }
+ m_aggregate = nullptr;
+ } else {
+ clear(sinceSections());
+ }
+}
+
+/*!
+ Initialize the Aggregate in each Section of vector \a v with \a aggregate.
+ */
+void Sections::initAggregate(SectionVector &v, Aggregate *aggregate)
+{
+ for (Section &section : v)
+ section.setAggregate(aggregate);
+}
+
+/*!
+ Reset each Section in vector \a v to its initialized state.
+ */
+void Sections::clear(QList<Section> &v)
+{
+ for (Section &section : v)
+ section.clear();
+}
+
+/*!
+ Linearize the maps in each Section in \a v.
+ */
+void Sections::reduce(QList<Section> &v)
+{
+ for (Section &section : v)
+ section.reduce();
+}
+
+/*!
+ This is a private helper function for buildStdRefPageSections().
+ */
+void Sections::stdRefPageSwitch(SectionVector &v, Node *n, Node *t)
+{
+ // t is the reference node to be tested, n is the node to be distributed.
+ // t differs from n only for shared comment nodes.
+ if (!t)
+ t = n;
+
+ switch (t->nodeType()) {
+ case Node::Namespace:
+ v[StdNamespaces].insert(n);
+ return;
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ v[StdClasses].insert(n);
+ return;
+ case Node::Enum:
+ case Node::Typedef:
+ case Node::TypeAlias:
+ v[StdTypes].insert(n);
+ return;
+ case Node::Function: {
+ auto *func = static_cast<FunctionNode *>(t);
+ if (func->isMacro())
+ v[StdMacros].insert(n);
+ else
+ v[StdFunctions].insert(n);
+ }
+ return;
+ case Node::Variable: {
+ const auto *var = static_cast<const VariableNode *>(t);
+ if (!var->doc().isEmpty()) {
+ if (var->isStatic())
+ v[StdStaticVariables].insert(n);
+ else
+ v[StdVariables].insert(n);
+ }
+ }
+ return;
+ case Node::SharedComment: {
+ auto *scn = static_cast<SharedCommentNode *>(t);
+ if (!scn->doc().isEmpty() && scn->collective().size())
+ stdRefPageSwitch(
+ v, scn,
+ scn->collective().first()); // TODO: warn about mixed node types in collective?
+ }
+ return;
+ default:
+ return;
+ }
+}
+
+/*!
+ Build the section vectors for a standard reference page,
+ when the aggregate node is not a C++ class or a QML type.
+
+ If this is for a namespace page then if the namespace node
+ itself does not have documentation, only its children that
+ have documentation should be documented. In other words,
+ there are cases where a namespace is declared but does not
+ have documentation, but some of the elements declared in
+ that namespace do have documentation.
+
+ This special processing of namespaces that do not have a
+ documentation comment is meant to allow documenting its
+ members that do have documentation while avoiding posting
+ error messages for its members that are not documented.
+ */
+void Sections::buildStdRefPageSections()
+{
+ const NamespaceNode *ns = nullptr;
+ bool documentAll = true; // document all the children
+ if (m_aggregate->isNamespace()) {
+ ns = static_cast<const NamespaceNode *>(m_aggregate);
+ if (!ns->hasDoc())
+ documentAll = false; // only document children that have documentation
+ }
+ for (auto it = m_aggregate->constBegin(); it != m_aggregate->constEnd(); ++it) {
+ Node *n = *it;
+ if (documentAll || n->hasDoc()) {
+ stdRefPageSwitch(stdSummarySections(), n);
+ stdRefPageSwitch(stdDetailsSections(), n);
+ }
+ }
+ if (!m_aggregate->relatedByProxy().isEmpty()) {
+ const QList<Node *> &relatedBy = m_aggregate->relatedByProxy();
+ for (const auto &node : relatedBy)
+ stdRefPageSwitch(stdSummarySections(), node);
+ }
+ /*
+ If we are building the sections for the reference page
+ for a namespace node, include all the namespace node's
+ included children in the sections.
+ */
+ if (ns && !ns->includedChildren().isEmpty()) {
+ const QList<Node *> &children = ns->includedChildren();
+ for (const auto &child : children) {
+ if (documentAll || child->hasDoc())
+ stdRefPageSwitch(stdSummarySections(), child);
+ }
+ }
+ reduce(stdSummarySections());
+ reduce(stdDetailsSections());
+ allMembersSection().reduce();
+}
+
+/*!
+ Inserts the node \a n in one of the entries in the vector \a v
+ depending on the node's type, access attribute, and a few other
+ attributes if the node is a signal, slot, or function.
+ */
+void Sections::distributeNodeInSummaryVector(SectionVector &sv, Node *n)
+{
+ if (n->isSharedCommentNode())
+ return;
+ if (n->isFunction()) {
+ auto *fn = static_cast<FunctionNode *>(n);
+ if (fn->isRelatedNonmember()) {
+ if (fn->isMacro())
+ sv[Macros].insert(n);
+ else
+ sv[RelatedNonmembers].insert(n);
+ return;
+ }
+ if (fn->isIgnored())
+ return;
+ if (fn->isSlot()) {
+ if (fn->isPublic())
+ sv[PublicSlots].insert(fn);
+ else if (fn->isPrivate())
+ sv[PrivateSlots].insert(fn);
+ else
+ sv[ProtectedSlots].insert(fn);
+ } else if (fn->isSignal()) {
+ if (fn->isPublic())
+ sv[Signals].insert(fn);
+ } else if (fn->isPublic()) {
+ if (fn->isStatic())
+ sv[StaticPublicMembers].insert(fn);
+ else if (!sv[PublicFunctions].insertReimplementedMember(fn))
+ sv[PublicFunctions].insert(fn);
+ } else if (fn->isPrivate()) {
+ if (fn->isStatic())
+ sv[StaticPrivateMembers].insert(fn);
+ else if (!sv[PrivateFunctions].insertReimplementedMember(fn))
+ sv[PrivateFunctions].insert(fn);
+ } else { // protected
+ if (fn->isStatic())
+ sv[StaticProtectedMembers].insert(fn);
+ else if (!sv[ProtectedFunctions].insertReimplementedMember(fn))
+ sv[ProtectedFunctions].insert(fn);
+ }
+ return;
+ }
+ if (n->isRelatedNonmember()) {
+ sv[RelatedNonmembers].insert(n);
+ return;
+ }
+ if (n->isVariable()) {
+ if (n->isStatic()) {
+ if (n->isPublic())
+ sv[StaticPublicMembers].insert(n);
+ else if (n->isPrivate())
+ sv[StaticPrivateMembers].insert(n);
+ else
+ sv[StaticProtectedMembers].insert(n);
+ } else {
+ if (n->isPublic())
+ sv[PublicVariables].insert(n);
+ else if (!n->isPrivate())
+ sv[ProtectedVariables].insert(n);
+ }
+ return;
+ }
+ /*
+ Getting this far means the node is either a property
+ or some kind of type, like an enum or a typedef.
+ */
+ if (n->isTypedef() && (n->name() == QLatin1String("QtGadgetHelper")))
+ return;
+ if (n->isProperty())
+ sv[Properties].insert(n);
+ else if (n->isPublic())
+ sv[PublicTypes].insert(n);
+ else if (n->isPrivate())
+ sv[PrivateTypes].insert(n);
+ else
+ sv[ProtectedTypes].insert(n);
+}
+
+/*!
+ Inserts the node \a n in one of the entries in the vector \a v
+ depending on the node's type, access attribute, and a few other
+ attributes if the node is a signal, slot, or function.
+ */
+void Sections::distributeNodeInDetailsVector(SectionVector &dv, Node *n)
+{
+ if (n->isSharingComment())
+ return;
+
+ // t is the reference node to be tested - typically it's this node (n), but for
+ // shared comment nodes we need to distribute based on the nodes in its collective.
+ Node *t = n;
+
+ if (n->isSharedCommentNode() && n->hasDoc()) {
+ auto *scn = static_cast<SharedCommentNode *>(n);
+ if (scn->collective().size())
+ t = scn->collective().first(); // TODO: warn about mixed node types in collective?
+ }
+
+ if (t->isFunction()) {
+ auto *fn = static_cast<FunctionNode *>(t);
+ if (fn->isRelatedNonmember()) {
+ if (fn->isMacro())
+ dv[DetailsMacros].insert(n);
+ else
+ dv[DetailsRelatedNonmembers].insert(n);
+ return;
+ }
+ if (fn->isIgnored())
+ return;
+ if (!fn->hasAssociatedProperties() || !fn->doc().isEmpty())
+ dv[DetailsMemberFunctions].insert(n);
+ return;
+ }
+ if (t->isRelatedNonmember()) {
+ dv[DetailsRelatedNonmembers].insert(n);
+ return;
+ }
+ if (t->isEnumType() || t->isTypedef()) {
+ if (t->name() != QLatin1String("QtGadgetHelper"))
+ dv[DetailsMemberTypes].insert(n);
+ return;
+ }
+ if (t->isProperty())
+ dv[DetailsProperties].insert(n);
+ else if (t->isVariable() && !t->doc().isEmpty())
+ dv[DetailsMemberVariables].insert(n);
+}
+
+void Sections::distributeQmlNodeInDetailsVector(SectionVector &dv, Node *n)
+{
+ if (n->isSharingComment())
+ return;
+
+ // t is the reference node to be tested - typically it's this node (n), but for
+ // shared comment nodes we need to distribute based on the nodes in its collective.
+ Node *t = n;
+
+ if (n->isSharedCommentNode() && n->hasDoc()) {
+ if (n->isPropertyGroup()) {
+ dv[QmlProperties].insert(n);
+ return;
+ }
+ auto *scn = static_cast<SharedCommentNode *>(n);
+ if (scn->collective().size())
+ t = scn->collective().first(); // TODO: warn about mixed node types in collective?
+ }
+
+ if (t->isQmlProperty()) {
+ auto *pn = static_cast<QmlPropertyNode *>(t);
+ if (pn->isAttached())
+ dv[QmlAttachedProperties].insert(n);
+ else
+ dv[QmlProperties].insert(n);
+ } else if (t->isFunction()) {
+ auto *fn = static_cast<FunctionNode *>(t);
+ if (fn->isQmlSignal()) {
+ if (fn->isAttached())
+ dv[QmlAttachedSignals].insert(n);
+ else
+ dv[QmlSignals].insert(n);
+ } else if (fn->isQmlSignalHandler()) {
+ dv[QmlSignalHandlers].insert(n);
+ } else if (fn->isQmlMethod()) {
+ if (fn->isAttached())
+ dv[QmlAttachedMethods].insert(n);
+ else
+ dv[QmlMethods].insert(n);
+ }
+ }
+}
+
+/*!
+ Distributes a node \a n into the correct place in the summary section vector \a sv.
+ Nodes that are sharing a comment are handled recursively - for recursion, the \a
+ sharing parameter is set to \c true.
+ */
+void Sections::distributeQmlNodeInSummaryVector(SectionVector &sv, Node *n, bool sharing)
+{
+ if (n->isSharingComment() && !sharing)
+ return;
+ if (n->isQmlProperty()) {
+ auto *pn = static_cast<QmlPropertyNode *>(n);
+ if (pn->isAttached())
+ sv[QmlAttachedProperties].insert(pn);
+ else
+ sv[QmlProperties].insert(pn);
+ } else if (n->isFunction()) {
+ auto *fn = static_cast<FunctionNode *>(n);
+ if (fn->isQmlSignal()) {
+ if (fn->isAttached())
+ sv[QmlAttachedSignals].insert(fn);
+ else
+ sv[QmlSignals].insert(fn);
+ } else if (fn->isQmlSignalHandler()) {
+ sv[QmlSignalHandlers].insert(fn);
+ } else if (fn->isQmlMethod()) {
+ if (fn->isAttached())
+ sv[QmlAttachedMethods].insert(fn);
+ else
+ sv[QmlMethods].insert(fn);
+ }
+ } else if (n->isSharedCommentNode()) {
+ auto *scn = static_cast<SharedCommentNode *>(n);
+ if (scn->isPropertyGroup()) {
+ sv[QmlProperties].insert(scn);
+ } else {
+ for (const auto &child : scn->collective())
+ distributeQmlNodeInSummaryVector(sv, child, true);
+ }
+ }
+}
+
+static void pushBaseClasses(QStack<ClassNode *> &stack, ClassNode *cn)
+{
+ const QList<RelatedClass> baseClasses = cn->baseClasses();
+ for (const auto &cls : baseClasses) {
+ if (cls.m_node)
+ stack.prepend(cls.m_node);
+ }
+}
+
+/*!
+ Build the section vectors for a standard reference page,
+ when the aggregate node is a C++.
+ */
+void Sections::buildStdCppClassRefPageSections()
+{
+ SectionVector &summarySections = stdCppClassSummarySections();
+ SectionVector &detailsSections = stdCppClassDetailsSections();
+ Section &allMembers = allMembersSection();
+
+ for (auto it = m_aggregate->constBegin(); it != m_aggregate->constEnd(); ++it) {
+ Node *n = *it;
+ if (!n->isPrivate() && !n->isProperty() && !n->isRelatedNonmember()
+ && !n->isSharedCommentNode())
+ allMembers.insert(n);
+
+ distributeNodeInSummaryVector(summarySections, n);
+ distributeNodeInDetailsVector(detailsSections, n);
+ }
+ if (!m_aggregate->relatedByProxy().isEmpty()) {
+ const QList<Node *> relatedBy = m_aggregate->relatedByProxy();
+ for (const auto &node : relatedBy)
+ distributeNodeInSummaryVector(summarySections, node);
+ }
+
+ QStack<ClassNode *> stack;
+ auto *cn = static_cast<ClassNode *>(m_aggregate);
+ pushBaseClasses(stack, cn);
+ while (!stack.isEmpty()) {
+ ClassNode *cn = stack.pop();
+ for (auto it = cn->constBegin(); it != cn->constEnd(); ++it) {
+ Node *n = *it;
+ if (!n->isPrivate() && !n->isProperty() && !n->isRelatedNonmember()
+ && !n->isSharedCommentNode())
+ allMembers.insert(n);
+ }
+ pushBaseClasses(stack, cn);
+ }
+ reduce(summarySections);
+ reduce(detailsSections);
+ allMembers.reduce();
+}
+
+/*!
+ Build the section vectors for a standard reference page,
+ when the aggregate node is a QML type.
+ */
+void Sections::buildStdQmlTypeRefPageSections()
+{
+ ClassNodes *classNodes = nullptr;
+ SectionVector &summarySections = stdQmlTypeSummarySections();
+ SectionVector &detailsSections = stdQmlTypeDetailsSections();
+ Section &allMembers = allMembersSection();
+
+ const Aggregate *qtn = m_aggregate;
+ while (qtn) {
+ if (!qtn->isAbstract() || !classNodes)
+ classNodes = &allMembers.classNodesList().emplace_back(static_cast<const QmlTypeNode*>(qtn), NodeVector{});
+ for (const auto n : qtn->childNodes()) {
+ if (n->isInternal())
+ continue;
+
+ // Skip overridden property/function documentation from abstract base type
+ if (qtn != m_aggregate && qtn->isAbstract()) {
+ NodeList candidates;
+ m_aggregate->findChildren(n->name(), candidates);
+ if (std::any_of(candidates.cbegin(), candidates.cend(), [&n](const Node *c) {
+ if (c->nodeType() == n->nodeType()) {
+ if (!n->isFunction() ||
+ compare(static_cast<const FunctionNode *>(n),
+ static_cast<const FunctionNode *>(c)) == 0)
+ return true;
+ }
+ return false;
+ })) {
+ continue;
+ }
+ }
+
+ if (!n->isSharedCommentNode() || n->isPropertyGroup()) {
+ allMembers.insert(n);
+ classNodes->second.push_back(n);
+ }
+
+
+ if (qtn == m_aggregate || qtn->isAbstract()) {
+ distributeQmlNodeInSummaryVector(summarySections, n);
+ distributeQmlNodeInDetailsVector(detailsSections, n);
+ }
+ }
+ if (qtn->qmlBaseNode() == qtn) {
+ qCDebug(lcQdoc, "error: circular type definition: '%s' inherits itself",
+ qPrintable(qtn->name()));
+ break;
+ }
+ qtn = static_cast<QmlTypeNode *>(qtn->qmlBaseNode());
+ }
+
+ reduce(summarySections);
+ reduce(detailsSections);
+ allMembers.reduce();
+}
+
+/*!
+ Returns true if any sections in this object contain obsolete
+ members. If it returns false, then \a summary_spv and \a details_spv
+ have not been modified. Otherwise, both vectors will contain pointers
+ to the sections that contain obsolete members.
+ */
+bool Sections::hasObsoleteMembers(SectionPtrVector *summary_spv,
+ SectionPtrVector *details_spv) const
+{
+ const SectionVector *sections = nullptr;
+ if (m_aggregate->isClassNode())
+ sections = &stdCppClassSummarySections();
+ else if (m_aggregate->isQmlType())
+ sections = &stdQmlTypeSummarySections();
+ else
+ sections = &stdSummarySections();
+ for (const auto &section : *sections) {
+ if (!section.obsoleteMembers().isEmpty())
+ summary_spv->append(&section);
+ }
+ if (m_aggregate->isClassNode())
+ sections = &stdCppClassDetailsSections();
+ else if (m_aggregate->isQmlType())
+ sections = &stdQmlTypeDetailsSections();
+ else
+ sections = &stdDetailsSections();
+ for (const auto &it : *sections) {
+ if (!it.obsoleteMembers().isEmpty())
+ details_spv->append(&it);
+ }
+ return !summary_spv->isEmpty();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/sections.h b/src/qdoc/qdoc/src/qdoc/sections.h
new file mode 100644
index 000000000..70c73a91c
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/sections.h
@@ -0,0 +1,206 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef SECTIONS_H
+#define SECTIONS_H
+
+#include "node.h"
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+typedef std::pair<const QmlTypeNode *, NodeVector> ClassNodes;
+typedef QList<ClassNodes> ClassNodesList;
+
+class Section
+{
+public:
+ enum Style { Summary, Details, AllMembers, Accessors };
+
+public:
+ Section(
+ QString title, QString singular, QString plural,
+ QString divclass, Style style
+ ) : m_title{title}, m_singular{singular}, m_plural{plural},
+ m_divClass{divclass}, m_style{style}
+ {}
+
+ ~Section();
+
+ void insert(Node *node);
+ bool insertReimplementedMember(Node *node);
+
+ void appendMember(Node *node) { m_members.append(node); }
+
+ void clear();
+ void reduce();
+ [[nodiscard]] bool isEmpty() const
+ {
+ return (m_members.isEmpty() && m_inheritedMembers.isEmpty()
+ && m_reimplementedMemberMap.isEmpty() && m_classNodesList.isEmpty());
+ }
+
+ [[nodiscard]] Style style() const { return m_style; }
+ [[nodiscard]] const QString &title() const { return m_title; }
+ [[nodiscard]] const QString &divClass() const { return m_divClass; }
+ [[nodiscard]] const QString &singular() const { return m_singular; }
+ [[nodiscard]] const QString &plural() const { return m_plural; }
+ [[nodiscard]] const NodeVector &members() const { return m_members; }
+ [[nodiscard]] const NodeVector &reimplementedMembers() const { return m_reimplementedMembers; }
+ [[nodiscard]] const QList<std::pair<Aggregate *, int>> &inheritedMembers() const
+ {
+ return m_inheritedMembers;
+ }
+ ClassNodesList &classNodesList() { return m_classNodesList; }
+ [[nodiscard]] const NodeVector &obsoleteMembers() const { return m_obsoleteMembers; }
+ void appendMembers(const NodeVector &nv) { m_members.append(nv); }
+ [[nodiscard]] const Aggregate *aggregate() const { return m_aggregate; }
+ void setAggregate(Aggregate *t) { m_aggregate = t; }
+
+private:
+ QString m_title {};
+ QString m_singular {};
+ QString m_plural {};
+ QString m_divClass {};
+ Style m_style {};
+
+ Aggregate *m_aggregate { nullptr };
+ NodeVector m_members {};
+ NodeVector m_obsoleteMembers {};
+ NodeVector m_reimplementedMembers {};
+ QList<std::pair<Aggregate *, int>> m_inheritedMembers {};
+ ClassNodesList m_classNodesList {};
+
+ QMultiMap<QString, Node *> m_reimplementedMemberMap {};
+};
+
+typedef QList<Section> SectionVector;
+typedef QList<const Section *> SectionPtrVector;
+
+class Sections
+{
+public:
+ enum VectorIndex {
+ PublicTypes = 0,
+ DetailsMemberTypes = 0,
+ SinceNamespaces = 0,
+ StdNamespaces = 0,
+ QmlProperties = 0,
+ Properties = 1,
+ DetailsProperties = 1,
+ SinceClasses = 1,
+ StdClasses = 1,
+ QmlAttachedProperties = 1,
+ PublicFunctions = 2,
+ DetailsMemberFunctions = 2,
+ SinceMemberFunctions = 2,
+ StdTypes = 2,
+ QmlSignals = 2,
+ PublicSlots = 3,
+ DetailsMemberVariables = 3,
+ SinceNamespaceFunctions = 3,
+ StdVariables = 3,
+ QmlSignalHandlers = 3,
+ Signals = 4,
+ SinceGlobalFunctions = 4,
+ DetailsRelatedNonmembers = 4,
+ StdStaticVariables = 4,
+ QmlAttachedSignals = 4,
+ PublicVariables = 5,
+ SinceMacros = 5,
+ DetailsMacros = 5,
+ StdFunctions = 5,
+ QmlMethods = 5,
+ StaticPublicMembers = 6,
+ SinceEnumTypes = 6,
+ StdMacros = 6,
+ QmlAttachedMethods = 6,
+ SinceEnumValues = 7,
+ ProtectedTypes = 7,
+ SinceTypeAliases = 8,
+ ProtectedFunctions = 8,
+ SinceProperties = 9,
+ ProtectedSlots = 9,
+ SinceVariables = 10,
+ ProtectedVariables = 10,
+ SinceQmlTypes = 11,
+ StaticProtectedMembers = 11,
+ SinceQmlProperties = 12,
+ PrivateTypes = 12,
+ SinceQmlSignals = 13,
+ PrivateFunctions = 13,
+ SinceQmlSignalHandlers = 14,
+ PrivateSlots = 14,
+ SinceQmlMethods = 15,
+ StaticPrivateMembers = 15,
+ RelatedNonmembers = 16,
+ Macros = 17
+ };
+
+ explicit Sections(Aggregate *aggregate);
+ explicit Sections(const NodeMultiMap &nsmap);
+ ~Sections();
+
+ void clear(SectionVector &v);
+ void reduce(SectionVector &v);
+ void buildStdRefPageSections();
+ void buildStdCppClassRefPageSections();
+ void buildStdQmlTypeRefPageSections();
+
+ bool hasObsoleteMembers(SectionPtrVector *summary_spv, SectionPtrVector *details_spv) const;
+
+ static Section &allMembersSection() { return s_allMembers[0]; }
+ SectionVector &sinceSections() { return s_sinceSections; }
+ SectionVector &stdSummarySections() { return s_stdSummarySections; }
+ SectionVector &stdDetailsSections() { return s_stdDetailsSections; }
+ SectionVector &stdCppClassSummarySections() { return s_stdCppClassSummarySections; }
+ SectionVector &stdCppClassDetailsSections() { return s_stdCppClassDetailsSections; }
+ SectionVector &stdQmlTypeSummarySections() { return s_stdQmlTypeSummarySections; }
+ SectionVector &stdQmlTypeDetailsSections() { return s_stdQmlTypeDetailsSections; }
+
+ [[nodiscard]] const SectionVector &stdSummarySections() const { return s_stdSummarySections; }
+ [[nodiscard]] const SectionVector &stdDetailsSections() const { return s_stdDetailsSections; }
+ [[nodiscard]] const SectionVector &stdCppClassSummarySections() const
+ {
+ return s_stdCppClassSummarySections;
+ }
+ [[nodiscard]] const SectionVector &stdCppClassDetailsSections() const
+ {
+ return s_stdCppClassDetailsSections;
+ }
+ [[nodiscard]] const SectionVector &stdQmlTypeSummarySections() const
+ {
+ return s_stdQmlTypeSummarySections;
+ }
+ [[nodiscard]] const SectionVector &stdQmlTypeDetailsSections() const
+ {
+ return s_stdQmlTypeDetailsSections;
+ }
+
+ [[nodiscard]] Aggregate *aggregate() const { return m_aggregate; }
+
+private:
+ void stdRefPageSwitch(SectionVector &v, Node *n, Node *t = nullptr);
+ void distributeNodeInSummaryVector(SectionVector &sv, Node *n);
+ void distributeNodeInDetailsVector(SectionVector &dv, Node *n);
+ void distributeQmlNodeInDetailsVector(SectionVector &dv, Node *n);
+ void distributeQmlNodeInSummaryVector(SectionVector &sv, Node *n, bool sharing = false);
+ void initAggregate(SectionVector &v, Aggregate *aggregate);
+
+private:
+ Aggregate *m_aggregate { nullptr };
+
+ static SectionVector s_stdSummarySections;
+ static SectionVector s_stdDetailsSections;
+ static SectionVector s_stdCppClassSummarySections;
+ static SectionVector s_stdCppClassDetailsSections;
+ static SectionVector s_stdQmlTypeSummarySections;
+ static SectionVector s_stdQmlTypeDetailsSections;
+ static SectionVector s_sinceSections;
+ static SectionVector s_allMembers;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/sharedcommentnode.cpp b/src/qdoc/qdoc/src/qdoc/sharedcommentnode.cpp
new file mode 100644
index 000000000..05204d1f3
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/sharedcommentnode.cpp
@@ -0,0 +1,56 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "sharedcommentnode.h"
+
+#include "aggregate.h"
+#include "functionnode.h"
+#include "qmltypenode.h"
+
+QT_BEGIN_NAMESPACE
+
+SharedCommentNode::SharedCommentNode(QmlTypeNode *parent, int count, QString &group)
+ : Node(Node::SharedComment, parent, group)
+{
+ m_collective.reserve(count);
+}
+
+/*!
+ Searches the shared comment node's member nodes for function
+ nodes. Each function node's overload flag is set.
+ */
+void SharedCommentNode::setOverloadFlags()
+{
+ for (auto *node : m_collective) {
+ if (node->isFunction())
+ static_cast<FunctionNode *>(node)->setOverloadFlag();
+ }
+}
+
+/*!
+ Clone this node on the heap and make the clone a child of
+ \a parent.
+
+ Returns the pointer to the clone.
+ */
+Node *SharedCommentNode::clone(Aggregate *parent)
+{
+ auto *scn = new SharedCommentNode(*this); // shallow copy
+ scn->setParent(nullptr);
+ parent->addChild(scn);
+
+ return scn;
+}
+
+/*!
+ Sets the related nonmember flag in this node and in each
+ node in the shared comment's collective to \a value.
+ */
+void SharedCommentNode::setRelatedNonmember(bool value)
+{
+ Node::setRelatedNonmember(value);
+ for (auto *node : m_collective)
+ node->setRelatedNonmember(value);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/sharedcommentnode.h b/src/qdoc/qdoc/src/qdoc/sharedcommentnode.h
new file mode 100644
index 000000000..d319d7c59
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/sharedcommentnode.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef SHAREDCOMMENTNODE_H
+#define SHAREDCOMMENTNODE_H
+
+#include "node.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+class QmlTypeNode;
+
+class SharedCommentNode : public Node
+{
+public:
+ explicit SharedCommentNode(Node *node) : Node(Node::SharedComment, node->parent(), QString())
+ {
+ m_collective.reserve(1);
+ append(node);
+ }
+ SharedCommentNode(QmlTypeNode *parent, int count, QString &group);
+ ~SharedCommentNode() override { m_collective.clear(); }
+
+ [[nodiscard]] bool isPropertyGroup() const override
+ {
+ return !name().isEmpty() && !m_collective.isEmpty() && (m_collective.at(0)->isQmlProperty());
+ }
+ [[nodiscard]] qsizetype count() const { return m_collective.size(); }
+ void append(Node *node)
+ {
+ m_collective.append(node);
+ node->setSharedCommentNode(this);
+ setGenus(node->genus());
+ }
+ void sort() { std::sort(m_collective.begin(), m_collective.end(), Node::nodeNameLessThan); }
+ [[nodiscard]] const QList<Node *> &collective() const { return m_collective; }
+ void setOverloadFlags();
+ void setRelatedNonmember(bool value) override;
+ Node *clone(Aggregate *parent) override;
+
+private:
+ QList<Node *> m_collective {};
+};
+
+QT_END_NAMESPACE
+
+#endif // SHAREDCOMMENTNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/singleton.h b/src/qdoc/qdoc/src/qdoc/singleton.h
new file mode 100644
index 000000000..085a5ea64
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/singleton.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef SINGLETON_H
+#define SINGLETON_H
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class Singleton
+ \internal
+
+ Class template for singleton objects in QDoc.
+ */
+template<typename T>
+class Singleton
+{
+public:
+ Singleton(const Singleton &) = delete;
+ Singleton &operator=(const Singleton &) = delete;
+ static T &instance()
+ {
+ static T s_instance {};
+ return s_instance;
+ }
+
+protected:
+ Singleton() = default;
+};
+
+QT_END_NAMESPACE
+
+#endif // SINGLETON_H
diff --git a/src/qdoc/qdoc/src/qdoc/sourcefileparser.h b/src/qdoc/qdoc/src/qdoc/sourcefileparser.h
new file mode 100644
index 000000000..95bc71627
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/sourcefileparser.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "clangcodeparser.h"
+#include "puredocparser.h"
+
+#include <variant>
+
+#include <QString>
+#include <QFileInfo>
+
+struct CppSourceFile {};
+struct QDocSourceFile {};
+struct JsSourceFile {};
+struct UnknownSourceFile {};
+
+using SourceFileTag = std::variant<CppSourceFile, QDocSourceFile, JsSourceFile, UnknownSourceFile>;
+using TaggedSourceFile = std::pair<QString, SourceFileTag>;
+
+inline TaggedSourceFile tag_source_file(const QString& path) {
+ static QStringList cpp_file_extensions{
+ "c++", "cc", "cpp", "cxx", "mm"
+ };
+ static QStringList qdoc_file_extensions{ "qdoc" };
+ static QStringList javascript_file_extensions{ "js" };
+
+ QString extension{QFileInfo(path).suffix()};
+
+ if (cpp_file_extensions.contains(extension)) return TaggedSourceFile{path, CppSourceFile{}};
+ else if (qdoc_file_extensions.contains(extension)) return TaggedSourceFile{path, QDocSourceFile{}};
+ else if (javascript_file_extensions.contains(extension)) return TaggedSourceFile{path, JsSourceFile{}};
+
+ return TaggedSourceFile{path, UnknownSourceFile{}};
+}
+
+class SourceFileParser {
+public:
+ struct ParseResult {
+ std::vector<UntiedDocumentation> untied;
+ std::vector<TiedDocumentation> tied;
+ };
+
+public:
+ SourceFileParser(ClangCodeParser& clang_parser, PureDocParser& pure_parser)
+ : cpp_file_parser(clang_parser),
+ pure_file_parser(pure_parser)
+ {}
+
+ ParseResult operator()(const TaggedSourceFile& source) {
+ if (std::holds_alternative<CppSourceFile>(source.second))
+ return (*this)(source.first, std::get<CppSourceFile>(source.second));
+ else if (std::holds_alternative<QDocSourceFile>(source.second))
+ return (*this)(source.first, std::get<QDocSourceFile>(source.second));
+ else if (std::holds_alternative<JsSourceFile>(source.second))
+ return (*this)(source.first, std::get<JsSourceFile>(source.second));
+ else if (std::holds_alternative<UnknownSourceFile>(source.second))
+ return {{}, {}};
+
+ Q_UNREACHABLE();
+ }
+
+private:
+ ParseResult operator()(const QString& path, CppSourceFile) {
+ auto [untied, tied] = cpp_file_parser.parse_cpp_file(path);
+
+ return {untied, tied};
+ }
+
+ ParseResult operator()(const QString& path, QDocSourceFile) {
+ return {pure_file_parser.parse_qdoc_file(path), {}};
+ }
+
+ ParseResult operator()(const QString& path, JsSourceFile) {
+ return {pure_file_parser.parse_qdoc_file(path), {}};
+ }
+
+private:
+ ClangCodeParser& cpp_file_parser;
+ PureDocParser& pure_file_parser;
+};
diff --git a/src/qdoc/qdoc/src/qdoc/tagfilewriter.cpp b/src/qdoc/qdoc/src/qdoc/tagfilewriter.cpp
new file mode 100644
index 000000000..7b6b496ca
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/tagfilewriter.cpp
@@ -0,0 +1,306 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "tagfilewriter.h"
+
+#include "access.h"
+#include "aggregate.h"
+#include "classnode.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "htmlgenerator.h"
+#include "location.h"
+#include "node.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "typedefnode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class TagFileWriter
+
+ This class handles the generation of the QDoc tag files.
+ */
+
+/*!
+ Default constructor. \a qdb is the pointer to the
+ qdoc database that is used when reading and writing the
+ index files.
+ */
+TagFileWriter::TagFileWriter() : m_qdb(QDocDatabase::qdocDB()) { }
+
+/*!
+ Generate the tag file section with the given \a writer for the \a parent
+ node.
+ */
+void TagFileWriter::generateTagFileCompounds(QXmlStreamWriter &writer, const Aggregate *parent)
+{
+ const auto &nonFunctionList = const_cast<Aggregate *>(parent)->nonfunctionList();
+ for (const auto *node : nonFunctionList) {
+ if (!node->url().isNull() || node->isPrivate())
+ continue;
+
+ QString kind;
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ kind = "namespace";
+ break;
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ case Node::QmlType:
+ kind = "class";
+ break;
+ default:
+ continue;
+ }
+ const auto *aggregate = static_cast<const Aggregate *>(node);
+
+ QString access = "public";
+ if (node->isProtected())
+ access = "protected";
+
+ QString objName = node->name();
+
+ // Special case: only the root node should have an empty name.
+ if (objName.isEmpty() && node != m_qdb->primaryTreeRoot())
+ continue;
+
+ // *** Write the starting tag for the element here. ***
+ writer.writeStartElement("compound");
+ writer.writeAttribute("kind", kind);
+
+ if (node->isClassNode()) {
+ writer.writeTextElement("name", node->fullDocumentName());
+ writer.writeTextElement("filename", m_generator->fullDocumentLocation(node));
+
+ // Classes contain information about their base classes.
+ const auto *classNode = static_cast<const ClassNode *>(node);
+ const QList<RelatedClass> &bases = classNode->baseClasses();
+ for (const auto &related : bases) {
+ ClassNode *n = related.m_node;
+ if (n)
+ writer.writeTextElement("base", n->name());
+ }
+
+ // Recurse to write all members.
+ generateTagFileMembers(writer, aggregate);
+ writer.writeEndElement();
+
+ // Recurse to write all compounds.
+ generateTagFileCompounds(writer, aggregate);
+ } else {
+ writer.writeTextElement("name", node->fullDocumentName());
+ writer.writeTextElement("filename", m_generator->fullDocumentLocation(node));
+
+ // Recurse to write all members.
+ generateTagFileMembers(writer, aggregate);
+ writer.writeEndElement();
+
+ // Recurse to write all compounds.
+ generateTagFileCompounds(writer, aggregate);
+ }
+ }
+}
+
+/*!
+ Writes all the members of the \a parent node with the \a writer.
+ The node represents a C++ class, namespace, etc.
+ */
+void TagFileWriter::generateTagFileMembers(QXmlStreamWriter &writer, const Aggregate *parent)
+{
+ auto childNodes = parent->childNodes();
+ std::sort(childNodes.begin(), childNodes.end(), Node::nodeNameLessThan);
+ for (const auto *node : childNodes) {
+ if (!node->url().isNull())
+ continue;
+
+ QString nodeName;
+ QString kind;
+ switch (node->nodeType()) {
+ case Node::Enum:
+ nodeName = "member";
+ kind = "enumeration";
+ break;
+ case Node::TypeAlias: // Treated as typedef
+ case Node::Typedef:
+ nodeName = "member";
+ kind = "typedef";
+ break;
+ case Node::Property:
+ nodeName = "member";
+ kind = "property";
+ break;
+ case Node::Function:
+ nodeName = "member";
+ kind = "function";
+ break;
+ case Node::Namespace:
+ nodeName = "namespace";
+ break;
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ nodeName = "class";
+ break;
+ case Node::Variable:
+ default:
+ continue;
+ }
+
+ QString access;
+ switch (node->access()) {
+ case Access::Public:
+ access = "public";
+ break;
+ case Access::Protected:
+ access = "protected";
+ break;
+ case Access::Private:
+ default:
+ continue;
+ }
+
+ QString objName = node->name();
+
+ // Special case: only the root node should have an empty name.
+ if (objName.isEmpty() && node != m_qdb->primaryTreeRoot())
+ continue;
+
+ // *** Write the starting tag for the element here. ***
+ writer.writeStartElement(nodeName);
+ if (!kind.isEmpty())
+ writer.writeAttribute("kind", kind);
+
+ switch (node->nodeType()) {
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ writer.writeCharacters(node->fullDocumentName());
+ writer.writeEndElement();
+ break;
+ case Node::Namespace:
+ writer.writeCharacters(node->fullDocumentName());
+ writer.writeEndElement();
+ break;
+ case Node::Function: {
+ /*
+ Function nodes contain information about
+ the type of function being described.
+ */
+
+ const auto *functionNode = static_cast<const FunctionNode *>(node);
+ writer.writeAttribute("protection", access);
+ writer.writeAttribute("virtualness", functionNode->virtualness());
+ writer.writeAttribute("static", functionNode->isStatic() ? "yes" : "no");
+
+ if (functionNode->isNonvirtual())
+ writer.writeTextElement("type", functionNode->returnType());
+ else
+ writer.writeTextElement("type", "virtual " + functionNode->returnType());
+
+ writer.writeTextElement("name", objName);
+ const QStringList pieces =
+ m_generator->fullDocumentLocation(node).split(QLatin1Char('#'));
+ writer.writeTextElement("anchorfile", pieces[0]);
+ writer.writeTextElement("anchor", pieces[1]);
+ QString signature = functionNode->signature(Node::SignatureReturnType);
+ signature = signature.mid(signature.indexOf(QChar('('))).trimmed();
+ if (functionNode->isConst())
+ signature += " const";
+ if (functionNode->isFinal())
+ signature += " final";
+ if (functionNode->isOverride())
+ signature += " override";
+ if (functionNode->isPureVirtual())
+ signature += " = 0";
+ writer.writeTextElement("arglist", signature);
+ }
+ writer.writeEndElement(); // member
+ break;
+ case Node::Property: {
+ const auto *propertyNode = static_cast<const PropertyNode *>(node);
+ writer.writeAttribute("type", propertyNode->dataType());
+ writer.writeTextElement("name", objName);
+ const QStringList pieces =
+ m_generator->fullDocumentLocation(node).split(QLatin1Char('#'));
+ writer.writeTextElement("anchorfile", pieces[0]);
+ writer.writeTextElement("anchor", pieces[1]);
+ writer.writeTextElement("arglist", QString());
+ }
+ writer.writeEndElement(); // member
+ break;
+ case Node::Enum: {
+ const auto *enumNode = static_cast<const EnumNode *>(node);
+ writer.writeTextElement("name", objName);
+ const QStringList pieces =
+ m_generator->fullDocumentLocation(node).split(QLatin1Char('#'));
+ writer.writeTextElement("anchorfile", pieces[0]);
+ writer.writeTextElement("anchor", pieces[1]);
+ writer.writeEndElement(); // member
+
+ for (const auto &item : enumNode->items()) {
+ writer.writeStartElement("member");
+ writer.writeAttribute("kind", "enumvalue");
+ writer.writeTextElement("name", item.name());
+ writer.writeTextElement("anchorfile", pieces[0]);
+ writer.writeTextElement("anchor", pieces[1]);
+ writer.writeTextElement("arglist", QString());
+ writer.writeEndElement(); // member
+ }
+ } break;
+ case Node::TypeAlias: // Treated as typedef
+ case Node::Typedef: {
+ const auto *typedefNode = static_cast<const TypedefNode *>(node);
+ if (typedefNode->associatedEnum())
+ writer.writeAttribute("type", typedefNode->associatedEnum()->fullDocumentName());
+ else
+ writer.writeAttribute("type", QString());
+ writer.writeTextElement("name", objName);
+ const QStringList pieces =
+ m_generator->fullDocumentLocation(node).split(QLatin1Char('#'));
+ writer.writeTextElement("anchorfile", pieces[0]);
+ writer.writeTextElement("anchor", pieces[1]);
+ writer.writeTextElement("arglist", QString());
+ }
+ writer.writeEndElement(); // member
+ break;
+
+ case Node::Variable:
+ default:
+ break;
+ }
+ }
+}
+
+/*!
+ Writes a tag file named \a fileName.
+ */
+void TagFileWriter::generateTagFile(const QString &fileName, Generator *g)
+{
+ QFile file(fileName);
+ QFileInfo fileInfo(fileName);
+
+ // If no path was specified or it doesn't exist,
+ // default to the output directory
+ if (fileInfo.fileName() == fileName || !fileInfo.dir().exists())
+ file.setFileName(m_generator->outputDir() + QLatin1Char('/') + fileInfo.fileName());
+
+ if (!file.open(QFile::WriteOnly | QFile::Text)) {
+ Location().warning(QString("Failed to open %1 for writing.").arg(file.fileName()));
+ return;
+ }
+
+ m_generator = g;
+ QXmlStreamWriter writer(&file);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument();
+ writer.writeStartElement("tagfile");
+ generateTagFileCompounds(writer, m_qdb->primaryTreeRoot());
+ writer.writeEndElement(); // tagfile
+ writer.writeEndDocument();
+ file.close();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/tagfilewriter.h b/src/qdoc/qdoc/src/qdoc/tagfilewriter.h
new file mode 100644
index 000000000..40a1ed399
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/tagfilewriter.h
@@ -0,0 +1,33 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef TAGFILEWRITER_H
+#define TAGFILEWRITER_H
+
+#include <QtCore/qxmlstream.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+class Generator;
+class QDocDatabase;
+
+class TagFileWriter
+{
+public:
+ TagFileWriter();
+ ~TagFileWriter() = default;
+
+ void generateTagFile(const QString &fileName, Generator *generator);
+
+private:
+ void generateTagFileCompounds(QXmlStreamWriter &writer, const Aggregate *inner);
+ void generateTagFileMembers(QXmlStreamWriter &writer, const Aggregate *inner);
+
+ QDocDatabase *m_qdb { nullptr };
+ Generator *m_generator { nullptr };
+};
+
+QT_END_NAMESPACE
+
+#endif // TAGFILEWRITER_H
diff --git a/src/qdoc/qdoc/src/qdoc/template_declaration.h b/src/qdoc/qdoc/src/qdoc/template_declaration.h
new file mode 100644
index 000000000..a0aade682
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/template_declaration.h
@@ -0,0 +1,502 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <algorithm>
+#include <cstdint>
+#include <functional>
+#include <numeric>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <QString>
+
+/*
+ * Represents a general declaration that has a form that can be
+ * described by a type, name and initializer triplet, or any such form
+ * that can be described by zero or more of those same parts.
+ *
+ * For example, it can be used to represent a C++ variable declaration
+ * such as:
+ *
+ * std::vector<int> foo = { 1, 2, 3 };
+ *
+ * Where `std::vector<int>` is the type, `foo` is the name and `{ 1, 2,
+ * 3 }` is the initializer.
+ *
+ * Similarly, it can be used to represent a non-type template parameter
+ * declaration, such as the `foo` parameter in:
+ *
+ * template<int foo = 10>
+ *
+ * Where `int` is the type, `foo` is the name and `10` is the
+ * initializer.
+ *
+ * An instance can be used to represent less information dense elements
+ * by setting one or more of the fields as the empty string.
+ *
+ * For example, a template type parameter such as `T` in:
+ *
+ * template<typename T = int>
+ *
+ * Can be represented by an instance that has an empty string as the
+ * type, `T` as the name and `int` as the initializer.
+ *
+ * In general, it can be used to represent any such element that has
+ * zero or more of the three components, albeit, in QDoc, it is
+ * specifically intended to be used to represent various C++
+ * declarations.
+ *
+ * All three fields are lowered stringified version of the original
+ * declaration, so that the type should be used at the end of a
+ * pipeline where the semantic property of the represented code are not
+ * required.
+ */
+struct ValuedDeclaration
+{
+ struct PrintingPolicy
+ {
+ bool include_type = true;
+ bool include_name = true;
+ bool include_initializer = true;
+ };
+
+ std::string type;
+ std::string name;
+ std::string initializer;
+
+ // KLUDGE: Workaround for
+ // https://stackoverflow.com/questions/53408962/try-to-understand-compiler-error-message-default-member-initializer-required-be
+ static PrintingPolicy default_printing_policy() { return PrintingPolicy{}; }
+
+ /*
+ * Constructs and returns a human-readable representation of this
+ * declaration.
+ *
+ * The constructed string is formatted so that as to rebuild a
+ * possible version of the C++ code that is modeled by an instance
+ * of this type.
+ *
+ * Each component participates in the human-presentable version if
+ * it they are not the empty string.
+ *
+ * The "type" and "name" component participate with their literal
+ * representation.
+ *
+ * The "iniitlalizer" components contributes an equal symbol,
+ * followed by a space followed by the literal representation of
+ * the component.
+ *
+ * The component contributes in an ordered way, with "type"
+ * contributing first, "name" contributing second and
+ * "initializer" contributing last.
+ *
+ * Each contribution is separated by a space if the component that
+ * comes before it, if any, has contributed to the human-readable
+ * representation.
+ *
+ * For example, an instance of this type that has "type" component
+ * "int", "name" component "foo" and "iniitializer" component
+ * "100", would be represented as:
+ *
+ * int foo = 100
+ *
+ * Where "int" is the "type" component contribution, "foo" is the
+ * "name" component contribution and "= 100" is the "initializer"
+ * component contribution.
+ * Each of those contribution is separated by a space, as each
+ * "preceding" component has contributed to the representation.
+ *
+ * If we provide a similar instance with, for example, the "type"
+ * and "name" components as the empty string, then the
+ * representation would be "= 100", which is the "initializer"
+ * component contribution, the only component that is not the
+ * empty string.
+ *
+ * The policy argument allows to treat certain components as if
+ * they were the empty string.
+ *
+ * For example, given an instance of this type that has "type"
+ * component "double", "name" component "bar" and "iniitializer"
+ * component "10.2", its human-readable representation would be
+ * "double bar = 10.2".
+ *
+ * If the representation of that same instance was obtained by
+ * using a policy that excludes the "name" component, then that
+ * representation would be "double = 10.2", which is equivalent
+ * to the representation of an instance that is the same as the
+ * orginal one with the "name" component as the empty string.
+ */
+ inline std::string to_std_string(PrintingPolicy policy = default_printing_policy()) const
+ {
+ std::string s{};
+
+ if (!type.empty() && policy.include_type)
+ s += (s.empty() ? "" : " ") + type;
+
+ if (!name.empty() && policy.include_name)
+ s += (s.empty() ? "" : " ") + name;
+
+ if (!initializer.empty() && policy.include_initializer)
+ s += (s.empty() ? "= " : " = ") + initializer;
+
+ return s;
+ }
+};
+
+struct RelaxedTemplateParameter;
+
+struct TemplateDeclarationStorage
+{
+ std::vector<RelaxedTemplateParameter> parameters;
+
+ inline std::string to_std_string() const;
+};
+
+/*
+ * Represents a C++ template parameter.
+ *
+ * The model used by this representation is a slighly simplified
+ * model.
+ *
+ * In the model, template parameters are one of:
+ *
+ * - A type template parameter.
+ * - A non type template parameter.
+ * - A template template parameter.
+ *
+ * Furthermore, each parameter can:
+ *
+ * - Be a parameter pack.
+ * - Carry an additional template declaration (as a template template
+ * parameter would).
+ * - Have no declared type.
+ * - Have no declared name.
+ * - Have no declared initializer.
+ *
+ * Due to this simplified model certain incorrect parameters can be
+ * represented.
+ *
+ * For example, it might be possible to represent a parameter pack
+ * that has a default initializer, a non-type template parameter that
+ * has no type or a template template parameter that carries no
+ * template declaration.
+ *
+ * The model further elides some of the semantic that might be carried
+ * by a parameter.
+ * For example, the model has no specific concept for template
+ * constraints.
+ *
+ * Template parameters can be represented as instances of the type.
+ *
+ * For example, a type template parameter `typename T` can be
+ * represented as the following instance:
+ *
+ * RelaxedTemplateParameter{
+ * RelaxedTemplateParameter::Kind::TypeTemplateParameter,
+ * false,
+ * {
+ * "",
+ * "T",
+ * ""
+ * },
+ * {}
+ * };
+ *
+ * And a non-type template parameter pack "int... Args" as:
+ *
+ * RelaxedTemplateParameter{
+ * RelaxedTemplateParameter::Kind::NonTypeTemplateParameter,
+ * true,
+ * {
+ * "int",
+ * "Args",
+ * ""
+ * },
+ * {}
+ * };
+ *
+ * Due to the relaxed constraint and the representable incorrect
+ * parameters, the type is intended to be used for data that is
+ * already validated and known to be correct, such as data that is
+ * extracted from Clang.
+ */
+struct RelaxedTemplateParameter
+{
+ enum class Kind : std::uint8_t {
+ TypeTemplateParameter,
+ NonTypeTemplateParameter,
+ TemplateTemplateParameter
+ };
+
+ Kind kind;
+ bool is_parameter_pack;
+ ValuedDeclaration valued_declaration;
+ std::optional<TemplateDeclarationStorage> template_declaration;
+
+ /*
+ * Constructs and returns a human-readable representation of this
+ * parameter.
+ *
+ * The constructed string is formatted so that as to rebuild a
+ * possible version of the C++ code that is modeled by an instance
+ * of this type.
+ *
+ * The format of the representation varies based on the "kind" of
+ * the parameter.
+ *
+ * - A "TypeTemplateParameter", is constructed as the
+ * concatenation of the literal "typename", followed by the
+ * literal "..." if the parameter is a pack, followed by the
+ * human-readable representaion of "valued_declaration".
+ *
+ * If the human-readable representation of
+ * "valued_declaration" is not the empty string, it is
+ * preceded by a space when it contributes to the
+ * representation.
+ *
+ * For example, the C++ type template parameter "typename Foo
+ * = int", would be represented by the instance:
+ *
+ * RelaxedTemplateParameter{
+ * RelaxedTemplateParameter::Kind::TypeTemplateParameter,
+ * false,
+ * {
+ * "",
+ * "Foo",
+ * "int"
+ * },
+ * {}
+ * };
+ *
+ * And its representation would be:
+ *
+ * typename Foo = int
+ *
+ * Where "typename" is the added literal and "Foo = int" is
+ * the representation for "valued_declaration", with a space
+ * in-between the two contributions.
+ *
+ * - A "NonTypeTemplateParameter", is constructed by the
+ * contribution of the "type" compoment of "valued_declaration",
+ * followed by the literal "..." if the parameter is a pack,
+ * followed by the human-presentable version of
+ * "valued_declaration" without its "type" component
+ * contribution.
+ *
+ * If the contribution of the "type" component of
+ * "valued_declaration" is not empty, the next contribution is
+ * preceded by a space.
+ *
+ * For example, the C++ non-type template parameter "int...
+ * SIZE", would be represented by the instance:
+ *
+ *
+ * RelaxedTemplateParameter{
+ * RelaxedTemplateParameter::Kind::NonTypeTemplateParameter,
+ * true,
+ * {
+ * "int",
+ * "SIZE",
+ * ""
+ * },
+ * {}
+ * };
+ *
+ * And its representation would be:
+ *
+ * int... SIZE
+ *
+ * Where "int" is the "type" component contribution of
+ * "valued_declaration", "..." is the added literal due to
+ * the parameter being a pack and " SIZE" being the
+ * human-readable representation of "valued_declaration"
+ * without its "type" component contribution, preceded by a
+ * space.
+ *
+ * - A "TemplateTemplateParameter", is constructed by the
+ * contribution of the human-presentable representation of
+ * "template_declaration", followed by the representation of
+ * this parameter if it was a "TypeTemplateParameter", with a
+ * space between the two contributions if the
+ * human-presentable representation of "template_declaration"
+ * is not empty.
+ *
+ * For example, the C++ template template template parameter
+ * "template<typename> T", would be represented by the
+ * instance:
+ *
+ *
+ * RelaxedTemplateParameter{
+ * RelaxedTemplateParameter::Kind::TemplateTemplateParameter,
+ * false,
+ * {
+ * "",
+ * "T",
+ * ""
+ * },
+ * {
+ * RelaxedTemplateParameter{
+ * RelaxedTemplateParameter::Kind::TypeTemplateParameter,
+ * false,
+ * {
+ * "",
+ * "",
+ * ""
+ * },
+ * {}
+ * }
+ * }
+ * };
+ *
+ * And its representation would be:
+ *
+ * template <typename> typename T
+ *
+ * Where "template <typename>" human-presentable version of
+ * "template_declaration" and "typename T" is the
+ * human-presentable version of this parameter if it was a
+ * type template parameter.
+ *
+ * With a space between the two contributions.
+ */
+ inline std::string to_std_string() const
+ {
+ switch (kind) {
+ // TODO: This can probably be moved under the template
+ // template parameter case and reused through a fallback.
+ case Kind::TypeTemplateParameter: {
+ std::string valued_declaration_string = valued_declaration.to_std_string();
+
+ return std::string("typename") + (is_parameter_pack ? "..." : "")
+ + (valued_declaration_string.empty() ? "" : " ") + valued_declaration_string;
+ }
+ case Kind::NonTypeTemplateParameter: {
+ std::string type_string = valued_declaration.type + (is_parameter_pack ? "..." : "");
+
+ return type_string + (type_string.empty() ? "" : " ")
+ + valued_declaration.to_std_string(
+ ValuedDeclaration::PrintingPolicy{ false, true, true });
+ }
+ case Kind::TemplateTemplateParameter: {
+ std::string valued_declaration_string = valued_declaration.to_std_string();
+
+ return (template_declaration ? (*template_declaration).to_std_string() + " " : "")
+ + "typename" + (is_parameter_pack ? "..." : "")
+ + (valued_declaration_string.empty() ? "" : " ") + valued_declaration_string;
+ }
+ default:
+ return "";
+ }
+ }
+};
+
+/*
+ * Represents a C++ template declaration as a collection of template
+ * parameters.
+ *
+ * The parameters for the declaration follow the same relaxed rules as
+ * `RelaxedTemplateParameter` and inherit the possibility of
+ * representing incorrect declarations.
+ *
+ * Due to the relaxed constraint and the representable incorrect
+ * parameters, the type is intended to be used for data that is
+ * already validated and known to be correct, such as data that is
+ * extracted from Clang.
+ */
+struct RelaxedTemplateDeclaration : TemplateDeclarationStorage
+{
+ inline QString to_qstring() const { return QString::fromStdString(to_std_string()); }
+};
+
+/*
+ * Constructs and returns a human-readable representation of this
+ * declaration.
+ *
+ * The constructed string is formatted so as to rebuild a
+ * possible version of the C++ code that is modeled by an instance
+ * of this type.
+ *
+ * The representation of a declaration is constructed by the literal
+ * "template <", followed by the human-presentable version of each
+ * parameter in "parameters", with a comma and a space between each
+ * parameter, followed by a closing literal ">".
+ *
+ * For example, the empty declaration is represented as "template <>".
+ *
+ * While a template declaration that has a type template parameter
+ * "Foo" with initializer "int" and a non-type template parameter pack
+ * with type "int" and name "S" would be represented as:
+ *
+ * template <typename Foo = int, int... S>
+ */
+inline std::string TemplateDeclarationStorage::to_std_string() const
+{
+ if (parameters.empty())
+ return "template <>";
+
+ return "template <"
+ + std::accumulate(std::next(parameters.cbegin()), parameters.cend(),
+ parameters.front().to_std_string(),
+ [](auto &&acc, const RelaxedTemplateParameter &parameter) {
+ return acc + ", " + parameter.to_std_string();
+ })
+ + ">";
+}
+
+/*
+ * Returns true if the two template declaration represented by left
+ * and right are substitutable.
+ *
+ * QDoc uses a simplified model for template declarations and,
+ * similarly, uses a simplified model of "substitutability".
+ *
+ * Two declarations are substitutable if:
+ *
+ * - They have the same amount of parameters
+ * - For each pair of parameters with the same postion:
+ * - They have the same kind
+ * - They are both parameter packs or both are not parameter packs
+ * - If they are non-type template parameters then they have the same type
+ * - If they are both template template parameters then they both
+ * carry an additional template declaration and the additional
+ * template declarations are substitutable
+ *
+ * This means that in the simplified models, we generally ignore default arguments, name and such.
+ *
+ * This model does not follow the way C++ performs disambiguation but
+ * should be enough to handle most cases in the documentation.
+ */
+inline bool are_template_declarations_substitutable(const TemplateDeclarationStorage& left, const TemplateDeclarationStorage& right) {
+ static auto are_template_parameters_substitutable = [](const RelaxedTemplateParameter& left, const RelaxedTemplateParameter& right) {
+ if (left.kind != right.kind) return false;
+ if (left.is_parameter_pack != right.is_parameter_pack) return false;
+
+ if (left.kind == RelaxedTemplateParameter::Kind::NonTypeTemplateParameter &&
+ (left.valued_declaration.type != right.valued_declaration.type))
+ return false;
+
+ if (left.kind == RelaxedTemplateParameter::Kind::TemplateTemplateParameter) {
+ if (!left.template_declaration && right.template_declaration) return false;
+ if (left.template_declaration && !right.template_declaration) return false;
+
+ if (left.template_declaration && right.template_declaration)
+ return are_template_declarations_substitutable(*left.template_declaration, *right.template_declaration);
+ }
+
+ return true;
+ };
+
+ const auto& left_parameters = left.parameters;
+ const auto& right_parameters = right.parameters;
+
+ if (left_parameters.size() != right_parameters.size()) return false;
+
+ return std::transform_reduce(left_parameters.cbegin(), left_parameters.cend(), right_parameters.cbegin(),
+ true,
+ std::logical_and<bool>{},
+ are_template_parameters_substitutable
+ );
+}
diff --git a/src/qdoc/qdoc/src/qdoc/text.cpp b/src/qdoc/qdoc/src/qdoc/text.cpp
new file mode 100644
index 000000000..d3401ce68
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/text.cpp
@@ -0,0 +1,341 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "text.h"
+
+#include <QtCore/qregularexpression.h>
+
+#include <cstdio>
+
+QT_BEGIN_NAMESPACE
+
+Text::Text() : m_first(nullptr), m_last(nullptr) { }
+
+Text::Text(const QString &str) : m_first(nullptr), m_last(nullptr)
+{
+ operator<<(str);
+}
+
+Text::Text(const Text &text) : m_first(nullptr), m_last(nullptr)
+{
+ operator=(text);
+}
+
+Text::~Text()
+{
+ clear();
+}
+
+Text &Text::operator=(const Text &text)
+{
+ if (this != &text) {
+ clear();
+ operator<<(text);
+ }
+ return *this;
+}
+
+Text &Text::operator<<(Atom::AtomType atomType)
+{
+ return operator<<(Atom(atomType));
+}
+
+Text &Text::operator<<(const QString &string)
+{
+ return string.isEmpty() ? *this : operator<<(Atom(Atom::String, string));
+}
+
+Text &Text::operator<<(const Atom &atom)
+{
+ if (atom.count() < 2) {
+ if (m_first == nullptr) {
+ m_first = new Atom(atom.type(), atom.string());
+ m_last = m_first;
+ } else
+ m_last = new Atom(m_last, atom.type(), atom.string());
+ } else {
+ if (m_first == nullptr) {
+ m_first = new Atom(atom.type(), atom.string(), atom.string(1));
+ m_last = m_first;
+ } else
+ m_last = new Atom(m_last, atom.type(), atom.string(), atom.string(1));
+ }
+ return *this;
+}
+
+/*!
+ Special output operator for LinkAtom. It makes a copy of
+ the LinkAtom \a atom and connects the cop;y to the list
+ in this Text.
+ */
+Text &Text::operator<<(const LinkAtom &atom)
+{
+ if (m_first == nullptr) {
+ m_first = new LinkAtom(atom);
+ m_last = m_first;
+ } else
+ m_last = new LinkAtom(m_last, atom);
+ return *this;
+}
+
+Text &Text::operator<<(const Text &text)
+{
+ const Atom *atom = text.firstAtom();
+ while (atom != nullptr) {
+ operator<<(*atom);
+ atom = atom->next();
+ }
+ return *this;
+}
+
+void Text::stripFirstAtom()
+{
+ if (m_first != nullptr) {
+ if (m_first == m_last)
+ m_last = nullptr;
+ Atom *oldFirst = m_first;
+ m_first = m_first->next();
+ delete oldFirst;
+ }
+}
+
+void Text::stripLastAtom()
+{
+ if (m_last != nullptr) {
+ Atom *oldLast = m_last;
+ if (m_first == m_last) {
+ m_first = nullptr;
+ m_last = nullptr;
+ } else {
+ m_last = m_first;
+ while (m_last->next() != oldLast)
+ m_last = m_last->next();
+ m_last->setNext(nullptr);
+ }
+ delete oldLast;
+ }
+}
+
+/*!
+ This function traverses the atom list of the Text object,
+ extracting all the string parts. It concatenates them to
+ a result string and returns it.
+ */
+QString Text::toString() const
+{
+ QString str;
+ const Atom *atom = firstAtom();
+ while (atom != nullptr) {
+ if (atom->type() == Atom::String || atom->type() == Atom::AutoLink
+ || atom->type() == Atom::C)
+ str += atom->string();
+ atom = atom->next();
+ }
+ return str;
+}
+
+/*!
+ Returns true if this Text contains the substring \a str.
+ */
+bool Text::contains(const QString &str) const
+{
+ const Atom *atom = firstAtom();
+ while (atom != nullptr) {
+ if (atom->type() == Atom::String || atom->type() == Atom::AutoLink
+ || atom->type() == Atom::C)
+ if (atom->string().contains(str, Qt::CaseInsensitive))
+ return true;
+ atom = atom->next();
+ }
+ return false;
+}
+
+Text Text::subText(Atom::AtomType left, Atom::AtomType right, const Atom *from,
+ bool inclusive) const
+{
+ const Atom *begin = from ? from : firstAtom();
+ const Atom *end;
+
+ while (begin != nullptr && begin->type() != left)
+ begin = begin->next();
+ if (begin != nullptr) {
+ if (!inclusive)
+ begin = begin->next();
+ }
+
+ end = begin;
+ while (end != nullptr && end->type() != right)
+ end = end->next();
+ if (end == nullptr)
+ begin = nullptr;
+ else if (inclusive)
+ end = end->next();
+ return subText(begin, end);
+}
+
+Text Text::sectionHeading(const Atom *sectionLeft)
+{
+ if (sectionLeft != nullptr) {
+ const Atom *begin = sectionLeft;
+ while (begin != nullptr && begin->type() != Atom::SectionHeadingLeft)
+ begin = begin->next();
+ if (begin != nullptr)
+ begin = begin->next();
+
+ const Atom *end = begin;
+ while (end != nullptr && end->type() != Atom::SectionHeadingRight)
+ end = end->next();
+
+ if (end != nullptr)
+ return subText(begin, end);
+ }
+ return Text();
+}
+
+/*!
+ Prints a human-readable version of the contained atoms to stderr.
+
+ The output is formatted as a linear list of atoms, with each atom
+ being on its own line.
+
+ Each atom is represented by its type and its stringified-contents,
+ if any, with a space between the two.
+
+ Indentation is used to emphasize the possible block-level
+ relationship between consecutive atoms, increasing after a
+ "Left" atom and decreasing just before a "Right" atom.
+
+ For example, if this `Text` represented the block-comment
+ containing the text:
+
+ \c {\l {somelink} {This is a link}}
+
+ Then the human-readable output would look like the following:
+
+ \badcode
+ ParaLeft
+ Link "somelink"
+ FormattingLeft "link"
+ String "This is a link"
+ FormattingRight "link"
+ String
+ ParaRight
+ \endcode
+ */
+void Text::dump() const
+{
+ constexpr int minimum_indentation_level { 1 };
+ int indentation_level { minimum_indentation_level };
+ int indentation_width { 4 };
+
+ const Atom *atom = firstAtom();
+ while (atom != nullptr) {
+ QString str = atom->string();
+ str.replace("\\", "\\\\");
+ str.replace("\"", "\\\"");
+ str.replace("\n", "\\n");
+ static const QRegularExpression re(R"([^ -~])");
+ str.replace(re, "?");
+ if (!str.isEmpty())
+ str = " \"" + str + QLatin1Char('"');
+
+ QString atom_type = atom->typeString();
+ if (atom_type.contains("Right"))
+ indentation_level = std::max(minimum_indentation_level, indentation_level - 1);
+
+ fprintf(stderr, "%s%s%s\n",
+ QString(indentation_level * indentation_width, ' ').toLatin1().data(),
+ atom_type.toLatin1().data(), str.toLatin1().data());
+
+ if (atom_type.contains("Left"))
+ indentation_level += 1;
+
+ atom = atom->next();
+ }
+}
+
+Text Text::subText(const Atom *begin, const Atom *end)
+{
+ Text text;
+ if (begin != nullptr) {
+ while (begin != end) {
+ text << *begin;
+ begin = begin->next();
+ }
+ }
+ return text;
+}
+
+void Text::clear()
+{
+ while (m_first != nullptr) {
+ Atom *atom = m_first;
+ m_first = m_first->next();
+ delete atom;
+ }
+ m_first = nullptr;
+ m_last = nullptr;
+}
+
+int Text::compare(const Text &text1, const Text &text2)
+{
+ if (text1.isEmpty())
+ return text2.isEmpty() ? 0 : -1;
+ if (text2.isEmpty())
+ return 1;
+
+ const Atom *atom1 = text1.firstAtom();
+ const Atom *atom2 = text2.firstAtom();
+
+ for (;;) {
+ if (atom1->type() != atom2->type())
+ return (int)atom1->type() - (int)atom2->type();
+ int cmp = QString::compare(atom1->string(), atom2->string());
+ if (cmp != 0)
+ return cmp;
+
+ if (atom1 == text1.lastAtom())
+ return atom2 == text2.lastAtom() ? 0 : -1;
+ if (atom2 == text2.lastAtom())
+ return 1;
+ atom1 = atom1->next();
+ atom2 = atom2->next();
+ }
+}
+
+/*!
+ \internal
+
+ \brief Splits the current Text from \a start to end into a new Text object.
+
+ Returns a new Text from the first Atom in this Text of atom type \a start.
+ */
+Text Text::splitAtFirst(Atom::AtomType start) {
+ if (m_first == nullptr)
+ return {};
+
+ Atom *previous = nullptr;
+ Atom *current = m_first;
+
+ while (current != nullptr) {
+ if (current->type() == start)
+ break;
+ previous = current;
+ current = current->next();
+ }
+
+ if (!current)
+ return {};
+
+ Text splitText = Text(current, m_last);
+
+ // Reset this Text's first and last atom pointers based on
+ // whether all or part of the content was extracted.
+ m_first = previous ? m_first : nullptr;
+ if (m_last = previous; m_last)
+ m_last->setNext(nullptr);
+
+ return splitText;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/text.h b/src/qdoc/qdoc/src/qdoc/text.h
new file mode 100644
index 000000000..d42143909
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/text.h
@@ -0,0 +1,87 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef TEXT_H
+#define TEXT_H
+
+#include "atom.h"
+
+QT_BEGIN_NAMESPACE
+
+class Text
+{
+public:
+ Text();
+ explicit Text(const QString &str);
+ Text(const Text &text);
+ ~Text();
+
+ Text &operator=(const Text &text);
+
+ Atom *firstAtom() { return m_first; }
+ Atom *lastAtom() { return m_last; }
+ Text &operator<<(Atom::AtomType atomType);
+ Text &operator<<(const QString &string);
+ Text &operator<<(const Atom &atom);
+ Text &operator<<(const LinkAtom &atom);
+ Text &operator<<(const Text &text);
+ void stripFirstAtom();
+ void stripLastAtom();
+
+ [[nodiscard]] bool isEmpty() const { return m_first == nullptr; }
+ [[nodiscard]] bool contains(const QString &str) const;
+ [[nodiscard]] QString toString() const;
+ [[nodiscard]] const Atom *firstAtom() const { return m_first; }
+ [[nodiscard]] const Atom *lastAtom() const { return m_last; }
+ Text subText(Atom::AtomType left, Atom::AtomType right, const Atom *from = nullptr,
+ bool inclusive = false) const;
+ void dump() const;
+ void clear();
+
+ static Text subText(const Atom *begin, const Atom *end = nullptr);
+ static Text sectionHeading(const Atom *sectionBegin);
+ static int compare(const Text &text1, const Text &text2);
+
+ [[nodiscard]] Text splitAtFirst(Atom::AtomType start);
+
+private:
+ Text(Atom *start, Atom *end) : m_first(start), m_last(end)
+ {
+ Q_ASSERT(m_first != nullptr);
+ Q_ASSERT(m_last != nullptr);
+
+ m_last->setNext(nullptr);
+ }
+
+ Atom *m_first { nullptr };
+ Atom *m_last { nullptr };
+};
+
+inline bool operator==(const Text &text1, const Text &text2)
+{
+ return Text::compare(text1, text2) == 0;
+}
+inline bool operator!=(const Text &text1, const Text &text2)
+{
+ return Text::compare(text1, text2) != 0;
+}
+inline bool operator<(const Text &text1, const Text &text2)
+{
+ return Text::compare(text1, text2) < 0;
+}
+inline bool operator<=(const Text &text1, const Text &text2)
+{
+ return Text::compare(text1, text2) <= 0;
+}
+inline bool operator>(const Text &text1, const Text &text2)
+{
+ return Text::compare(text1, text2) > 0;
+}
+inline bool operator>=(const Text &text1, const Text &text2)
+{
+ return Text::compare(text1, text2) >= 0;
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/tokenizer.cpp b/src/qdoc/qdoc/src/qdoc/tokenizer.cpp
new file mode 100644
index 000000000..bfbfc53c5
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/tokenizer.cpp
@@ -0,0 +1,788 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "tokenizer.h"
+
+#include "config.h"
+#include "generator.h"
+
+#include <QtCore/qfile.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringconverter.h>
+
+#include <cctype>
+#include <cstring>
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+#define LANGUAGE_CPP "Cpp"
+
+/* qmake ignore Q_OBJECT */
+
+/*
+ Keep in sync with tokenizer.h.
+*/
+static const char *kwords[] = { "char",
+ "class",
+ "const",
+ "double",
+ "enum",
+ "explicit",
+ "friend",
+ "inline",
+ "int",
+ "long",
+ "namespace",
+ "operator",
+ "private",
+ "protected",
+ "public",
+ "short",
+ "signals",
+ "signed",
+ "slots",
+ "static",
+ "struct",
+ "template",
+ "typedef",
+ "typename",
+ "union",
+ "unsigned",
+ "using",
+ "virtual",
+ "void",
+ "volatile",
+ "__int64",
+ "default",
+ "delete",
+ "final",
+ "override",
+ "Q_OBJECT",
+ "Q_OVERRIDE",
+ "Q_PROPERTY",
+ "Q_PRIVATE_PROPERTY",
+ "Q_DECLARE_SEQUENTIAL_ITERATOR",
+ "Q_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR",
+ "Q_DECLARE_ASSOCIATIVE_ITERATOR",
+ "Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR",
+ "Q_DECLARE_FLAGS",
+ "Q_SIGNALS",
+ "Q_SLOTS",
+ "QT_COMPAT",
+ "QT_COMPAT_CONSTRUCTOR",
+ "QT_DEPRECATED",
+ "QT_MOC_COMPAT",
+ "QT_MODULE",
+ "QT3_SUPPORT",
+ "QT3_SUPPORT_CONSTRUCTOR",
+ "QT3_MOC_SUPPORT",
+ "QDOC_PROPERTY",
+ "QPrivateSignal" };
+
+static const int KwordHashTableSize = 4096;
+static int kwordHashTable[KwordHashTableSize];
+
+static QHash<QByteArray, bool> *ignoredTokensAndDirectives = nullptr;
+
+static QRegularExpression *comment = nullptr;
+static QRegularExpression *versionX = nullptr;
+static QRegularExpression *definedX = nullptr;
+
+static QRegularExpression *defines = nullptr;
+static QRegularExpression *falsehoods = nullptr;
+
+static QStringDecoder sourceDecoder;
+
+/*
+ This function is a perfect hash function for the 37 keywords of C99
+ (with a hash table size of 512). It should perform well on our
+ Qt-enhanced C++ subset.
+*/
+static int hashKword(const char *s, int len)
+{
+ return (((uchar)s[0]) + (((uchar)s[2]) << 5) + (((uchar)s[len - 1]) << 3)) % KwordHashTableSize;
+}
+
+static void insertKwordIntoHash(const char *s, int number)
+{
+ int k = hashKword(s, int(strlen(s)));
+ while (kwordHashTable[k]) {
+ if (++k == KwordHashTableSize)
+ k = 0;
+ }
+ kwordHashTable[k] = number;
+}
+
+Tokenizer::Tokenizer(const Location &loc, QFile &in)
+{
+ init();
+ m_in = in.readAll();
+ m_pos = 0;
+ start(loc);
+}
+
+Tokenizer::Tokenizer(const Location &loc, QByteArray in) : m_in(std::move(in))
+{
+ init();
+ m_pos = 0;
+ start(loc);
+}
+
+Tokenizer::~Tokenizer()
+{
+ delete[] m_lexBuf1;
+ delete[] m_lexBuf2;
+}
+
+int Tokenizer::getToken()
+{
+ token_too_long_warning_was_issued = false;
+
+ char *t = m_prevLex;
+ m_prevLex = m_lex;
+ m_lex = t;
+
+ while (m_ch != EOF) {
+ m_tokLoc = m_curLoc;
+ m_lexLen = 0;
+
+ if (isspace(m_ch)) {
+ do {
+ m_ch = getChar();
+ } while (isspace(m_ch));
+ } else if (isalpha(m_ch) || m_ch == '_') {
+ do {
+ m_ch = getChar();
+ } while (isalnum(m_ch) || m_ch == '_');
+
+ int k = hashKword(m_lex, int(m_lexLen));
+ for (;;) {
+ int i = kwordHashTable[k];
+ if (i == 0) {
+ return Tok_Ident;
+ } else if (i == -1) {
+ if (!m_parsingMacro && ignoredTokensAndDirectives->contains(m_lex)) {
+ if (ignoredTokensAndDirectives->value(m_lex)) { // it's a directive
+ int parenDepth = 0;
+ while (m_ch != EOF && (m_ch != ')' || parenDepth > 1)) {
+ if (m_ch == '(')
+ ++parenDepth;
+ else if (m_ch == ')')
+ --parenDepth;
+ m_ch = getChar();
+ }
+ if (m_ch == ')')
+ m_ch = getChar();
+ }
+ break;
+ }
+ } else if (strcmp(m_lex, kwords[i - 1]) == 0) {
+ int ret = (int)Tok_FirstKeyword + i - 1;
+ if (ret != Tok_typename)
+ return ret;
+ break;
+ }
+
+ if (++k == KwordHashTableSize)
+ k = 0;
+ }
+ } else if (isdigit(m_ch)) {
+ do {
+ m_ch = getChar();
+ } while (isalnum(m_ch) || m_ch == '.' || m_ch == '+' || m_ch == '-');
+ return Tok_Number;
+ } else {
+ switch (m_ch) {
+ case '!':
+ case '%':
+ m_ch = getChar();
+ if (m_ch == '=')
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ case '"':
+ m_ch = getChar();
+
+ while (m_ch != EOF && m_ch != '"') {
+ if (m_ch == '\\')
+ m_ch = getChar();
+ m_ch = getChar();
+ }
+ m_ch = getChar();
+
+ if (m_ch == EOF)
+ m_tokLoc.warning(
+ QStringLiteral("Unterminated C++ string literal"),
+ QStringLiteral("Maybe you forgot '/*!' at the beginning of the file?"));
+ else
+ return Tok_String;
+ break;
+ case '#':
+ return getTokenAfterPreprocessor();
+ case '&':
+ m_ch = getChar();
+ /*
+ Removed check for '&&', only interpret '&=' as an operator.
+ '&&' is also used for an rvalue reference. QTBUG-32675
+ */
+ if (m_ch == '=') {
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else {
+ return Tok_Ampersand;
+ }
+ case '\'':
+ m_ch = getChar();
+ /*
+ Allow empty character literal. QTBUG-25775
+ */
+ if (m_ch == '\'') {
+ m_ch = getChar();
+ break;
+ }
+ if (m_ch == '\\')
+ m_ch = getChar();
+ do {
+ m_ch = getChar();
+ } while (m_ch != EOF && m_ch != '\'');
+
+ if (m_ch == EOF) {
+ m_tokLoc.warning(QStringLiteral("Unterminated C++ character literal"));
+ } else {
+ m_ch = getChar();
+ return Tok_Number;
+ }
+ break;
+ case '(':
+ m_ch = getChar();
+ if (m_numPreprocessorSkipping == 0)
+ m_parenDepth++;
+ if (isspace(m_ch)) {
+ do {
+ m_ch = getChar();
+ } while (isspace(m_ch));
+ m_lexLen = 1;
+ m_lex[1] = '\0';
+ }
+ if (m_ch == '*') {
+ m_ch = getChar();
+ return Tok_LeftParenAster;
+ }
+ return Tok_LeftParen;
+ case ')':
+ m_ch = getChar();
+ if (m_numPreprocessorSkipping == 0)
+ m_parenDepth--;
+ return Tok_RightParen;
+ case '*':
+ m_ch = getChar();
+ if (m_ch == '=') {
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else {
+ return Tok_Aster;
+ }
+ case '^':
+ m_ch = getChar();
+ if (m_ch == '=') {
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else {
+ return Tok_Caret;
+ }
+ case '+':
+ m_ch = getChar();
+ if (m_ch == '+' || m_ch == '=')
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ case ',':
+ m_ch = getChar();
+ return Tok_Comma;
+ case '-':
+ m_ch = getChar();
+ if (m_ch == '-' || m_ch == '=') {
+ m_ch = getChar();
+ } else if (m_ch == '>') {
+ m_ch = getChar();
+ if (m_ch == '*')
+ m_ch = getChar();
+ }
+ return Tok_SomeOperator;
+ case '.':
+ m_ch = getChar();
+ if (m_ch == '*') {
+ m_ch = getChar();
+ } else if (m_ch == '.') {
+ do {
+ m_ch = getChar();
+ } while (m_ch == '.');
+ return Tok_Ellipsis;
+ } else if (isdigit(m_ch)) {
+ do {
+ m_ch = getChar();
+ } while (isalnum(m_ch) || m_ch == '.' || m_ch == '+' || m_ch == '-');
+ return Tok_Number;
+ }
+ return Tok_SomeOperator;
+ case '/':
+ m_ch = getChar();
+ if (m_ch == '/') {
+ do {
+ m_ch = getChar();
+ } while (m_ch != EOF && m_ch != '\n');
+ } else if (m_ch == '*') {
+ bool metDoc = false; // empty doc is no doc
+ bool metSlashAsterBang = false;
+ bool metAster = false;
+ bool metAsterSlash = false;
+
+ m_ch = getChar();
+ if (m_ch == '!')
+ metSlashAsterBang = true;
+
+ while (!metAsterSlash) {
+ if (m_ch == EOF) {
+ m_tokLoc.warning(QStringLiteral("Unterminated C++ comment"));
+ break;
+ } else {
+ if (m_ch == '*') {
+ metAster = true;
+ } else if (metAster && m_ch == '/') {
+ metAsterSlash = true;
+ } else {
+ metAster = false;
+ if (isgraph(m_ch))
+ metDoc = true;
+ }
+ }
+ m_ch = getChar();
+ }
+ if (metSlashAsterBang && metDoc)
+ return Tok_Doc;
+ else if (m_parenDepth > 0)
+ return Tok_Comment;
+ } else {
+ if (m_ch == '=')
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ }
+ break;
+ case ':':
+ m_ch = getChar();
+ if (m_ch == ':') {
+ m_ch = getChar();
+ return Tok_Gulbrandsen;
+ } else {
+ return Tok_Colon;
+ }
+ case ';':
+ m_ch = getChar();
+ return Tok_Semicolon;
+ case '<':
+ m_ch = getChar();
+ if (m_ch == '<') {
+ m_ch = getChar();
+ if (m_ch == '=')
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else if (m_ch == '=') {
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else {
+ return Tok_LeftAngle;
+ }
+ case '=':
+ m_ch = getChar();
+ if (m_ch == '=') {
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else {
+ return Tok_Equal;
+ }
+ case '>':
+ m_ch = getChar();
+ if (m_ch == '>') {
+ m_ch = getChar();
+ if (m_ch == '=')
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else if (m_ch == '=') {
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else {
+ return Tok_RightAngle;
+ }
+ case '?':
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ case '[':
+ m_ch = getChar();
+ if (m_numPreprocessorSkipping == 0)
+ m_bracketDepth++;
+ return Tok_LeftBracket;
+ case '\\':
+ m_ch = getChar();
+ m_ch = getChar(); // skip one character
+ break;
+ case ']':
+ m_ch = getChar();
+ if (m_numPreprocessorSkipping == 0)
+ m_bracketDepth--;
+ return Tok_RightBracket;
+ case '{':
+ m_ch = getChar();
+ if (m_numPreprocessorSkipping == 0)
+ m_braceDepth++;
+ return Tok_LeftBrace;
+ case '}':
+ m_ch = getChar();
+ if (m_numPreprocessorSkipping == 0)
+ m_braceDepth--;
+ return Tok_RightBrace;
+ case '|':
+ m_ch = getChar();
+ if (m_ch == '|' || m_ch == '=')
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ case '~':
+ m_ch = getChar();
+ return Tok_Tilde;
+ case '@':
+ m_ch = getChar();
+ return Tok_At;
+ default:
+ // ### We should really prevent qdoc from looking at snippet files rather than
+ // ### suppress warnings when reading them.
+ if (m_numPreprocessorSkipping == 0
+ && !(m_tokLoc.fileName().endsWith(".qdoc")
+ || m_tokLoc.fileName().endsWith(".js"))) {
+ m_tokLoc.warning(QStringLiteral("Hostile character 0x%1 in C++ source")
+ .arg((uchar)m_ch, 1, 16));
+ }
+ m_ch = getChar();
+ }
+ }
+ }
+
+ if (m_preprocessorSkipping.size() > 1) {
+ m_tokLoc.warning(QStringLiteral("Expected #endif before end of file"));
+ // clear it out or we get an infinite loop!
+ while (!m_preprocessorSkipping.isEmpty()) {
+ popSkipping();
+ }
+ }
+
+ strcpy(m_lex, "end-of-input");
+ m_lexLen = strlen(m_lex);
+ return Tok_Eoi;
+}
+
+void Tokenizer::initialize()
+{
+ Config &config = Config::instance();
+ QString versionSym = config.get(CONFIG_VERSIONSYM).asString();
+ const QLatin1String defaultEncoding("UTF-8");
+
+ QString sourceEncoding = config.get(CONFIG_SOURCEENCODING).asString(defaultEncoding);
+ if (!QStringConverter::encodingForName(sourceEncoding.toUtf8().constData())) {
+ Location().warning(QStringLiteral("Source encoding '%1' not supported, using '%2' as default.")
+ .arg(sourceEncoding, defaultEncoding));
+ sourceEncoding = defaultEncoding;
+ }
+ sourceDecoder = QStringDecoder(sourceEncoding.toUtf8().constData());
+ Q_ASSERT(sourceDecoder.isValid());
+
+ comment = new QRegularExpression("/(?:\\*.*\\*/|/.*\n|/[^\n]*$)", QRegularExpression::InvertedGreedinessOption);
+ versionX = new QRegularExpression("$cannot possibly match^");
+ if (!versionSym.isEmpty())
+ versionX->setPattern("^[ \t]*(?:" + QRegularExpression::escape(versionSym)
+ + ")[ \t]+\"([^\"]*)\"[ \t]*$");
+ definedX = new QRegularExpression("^defined ?\\(?([A-Z_0-9a-z]+) ?\\)?$");
+
+ QStringList d{config.get(CONFIG_DEFINES).asStringList()};
+ d += "qdoc";
+ defines = new QRegularExpression(QRegularExpression::anchoredPattern(d.join('|')));
+ falsehoods = new QRegularExpression(QRegularExpression::anchoredPattern(
+ config.get(CONFIG_FALSEHOODS).asStringList().join('|')));
+
+ /*
+ The keyword hash table is always cleared before any words are inserted.
+ */
+ memset(kwordHashTable, 0, sizeof(kwordHashTable));
+ for (int i = 0; i < Tok_LastKeyword - Tok_FirstKeyword + 1; i++)
+ insertKwordIntoHash(kwords[i], i + 1);
+
+ ignoredTokensAndDirectives = new QHash<QByteArray, bool>;
+
+ const QStringList tokens{config.get(LANGUAGE_CPP
+ + Config::dot
+ + CONFIG_IGNORETOKENS).asStringList()};
+ for (const auto &token : tokens) {
+ const QByteArray tb = token.toLatin1();
+ ignoredTokensAndDirectives->insert(tb, false);
+ insertKwordIntoHash(tb.data(), -1);
+ }
+
+ const QStringList directives{config.get(LANGUAGE_CPP
+ + Config::dot
+ + CONFIG_IGNOREDIRECTIVES).asStringList()};
+ for (const auto &directive : directives) {
+ const QByteArray db = directive.toLatin1();
+ ignoredTokensAndDirectives->insert(db, true);
+ insertKwordIntoHash(db.data(), -1);
+ }
+}
+
+/*!
+ The heap allocated variables are freed here. The keyword
+ hash table is not cleared here, but it is cleared in the
+ initialize() function, before any keywords are inserted.
+ */
+void Tokenizer::terminate()
+{
+ delete comment;
+ comment = nullptr;
+ delete versionX;
+ versionX = nullptr;
+ delete definedX;
+ definedX = nullptr;
+ delete defines;
+ defines = nullptr;
+ delete falsehoods;
+ falsehoods = nullptr;
+ delete ignoredTokensAndDirectives;
+ ignoredTokensAndDirectives = nullptr;
+}
+
+void Tokenizer::init()
+{
+ m_lexBuf1 = new char[(int)yyLexBufSize];
+ m_lexBuf2 = new char[(int)yyLexBufSize];
+ m_prevLex = m_lexBuf1;
+ m_prevLex[0] = '\0';
+ m_lex = m_lexBuf2;
+ m_lex[0] = '\0';
+ m_lexLen = 0;
+ m_preprocessorSkipping.push(false);
+ m_numPreprocessorSkipping = 0;
+ m_braceDepth = 0;
+ m_parenDepth = 0;
+ m_bracketDepth = 0;
+ m_ch = '\0';
+ m_parsingMacro = false;
+}
+
+void Tokenizer::start(const Location &loc)
+{
+ m_tokLoc = loc;
+ m_curLoc = loc;
+ m_curLoc.start();
+ strcpy(m_prevLex, "beginning-of-input");
+ strcpy(m_lex, "beginning-of-input");
+ m_lexLen = strlen(m_lex);
+ m_braceDepth = 0;
+ m_parenDepth = 0;
+ m_bracketDepth = 0;
+ m_ch = '\0';
+ m_ch = getChar();
+}
+
+/*
+ Returns the next token, if # was met. This function interprets the
+ preprocessor directive, skips over any #ifdef'd out tokens, and returns the
+ token after all of that.
+*/
+int Tokenizer::getTokenAfterPreprocessor()
+{
+ m_ch = getChar();
+ while (isspace(m_ch) && m_ch != '\n')
+ m_ch = getChar();
+
+ /*
+ #directive condition
+ */
+ QString directive;
+ QString condition;
+
+ while (isalpha(m_ch)) {
+ directive += QChar(m_ch);
+ m_ch = getChar();
+ }
+ if (!directive.isEmpty()) {
+ while (m_ch != EOF && m_ch != '\n') {
+ if (m_ch == '\\') {
+ m_ch = getChar();
+ if (m_ch == '\r')
+ m_ch = getChar();
+ }
+ condition += QChar(m_ch);
+ m_ch = getChar();
+ }
+ condition.remove(*comment);
+ condition = condition.simplified();
+
+ /*
+ The #if, #ifdef, #ifndef, #elif, #else, and #endif
+ directives have an effect on the skipping stack. For
+ instance, if the code processed so far is
+
+ #if 1
+ #if 0
+ #if 1
+ // ...
+ #else
+
+ the skipping stack contains, from bottom to top, false true
+ true (assuming 0 is false and 1 is true). If at least one
+ entry of the stack is true, the tokens are skipped.
+
+ This mechanism is simple yet hard to understand.
+ */
+ if (directive[0] == QChar('i')) {
+ if (directive == QString("if"))
+ pushSkipping(!isTrue(condition));
+ else if (directive == QString("ifdef"))
+ pushSkipping(!defines->match(condition).hasMatch());
+ else if (directive == QString("ifndef"))
+ pushSkipping(defines->match(condition).hasMatch());
+ } else if (directive[0] == QChar('e')) {
+ if (directive == QString("elif")) {
+ bool old = popSkipping();
+ if (old)
+ pushSkipping(!isTrue(condition));
+ else
+ pushSkipping(true);
+ } else if (directive == QString("else")) {
+ pushSkipping(!popSkipping());
+ } else if (directive == QString("endif")) {
+ popSkipping();
+ }
+ } else if (directive == QString("define")) {
+ auto match = versionX->match(condition);
+ if (match.hasMatch())
+ m_version = match.captured(1);
+ }
+ }
+
+ int tok;
+ do {
+ /*
+ We set yyLex now, and after getToken() this will be
+ yyPrevLex. This way, we skip over the preprocessor
+ directive.
+ */
+ qstrcpy(m_lex, m_prevLex);
+
+ /*
+ If getToken() meets another #, it will call
+ getTokenAfterPreprocessor() once again, which could in turn
+ call getToken() again, etc. Unless there are 10,000 or so
+ preprocessor directives in a row, this shouldn't overflow
+ the stack.
+ */
+ tok = getToken();
+ } while (m_numPreprocessorSkipping > 0 && tok != Tok_Eoi);
+ return tok;
+}
+
+/*
+ Pushes a new skipping value onto the stack. This corresponds to entering a
+ new #if block.
+*/
+void Tokenizer::pushSkipping(bool skip)
+{
+ m_preprocessorSkipping.push(skip);
+ if (skip)
+ m_numPreprocessorSkipping++;
+}
+
+/*
+ Pops a skipping value from the stack. This corresponds to reaching a #endif.
+*/
+bool Tokenizer::popSkipping()
+{
+ if (m_preprocessorSkipping.isEmpty()) {
+ m_tokLoc.warning(QStringLiteral("Unexpected #elif, #else or #endif"));
+ return true;
+ }
+
+ bool skip = m_preprocessorSkipping.pop();
+ if (skip)
+ m_numPreprocessorSkipping--;
+ return skip;
+}
+
+/*
+ Returns \c true if the condition evaluates as true, otherwise false. The
+ condition is represented by a string. Unsophisticated parsing techniques are
+ used. The preprocessing method could be named StriNg-Oriented PreProcessing,
+ as SNOBOL stands for StriNg-Oriented symBOlic Language.
+*/
+bool Tokenizer::isTrue(const QString &condition)
+{
+ int firstOr = -1;
+ int firstAnd = -1;
+ int parenDepth = 0;
+
+ /*
+ Find the first logical operator at top level, but be careful
+ about precedence. Examples:
+
+ X || Y // the or
+ X || Y || Z // the leftmost or
+ X || Y && Z // the or
+ X && Y || Z // the or
+ (X || Y) && Z // the and
+ */
+ for (int i = 0; i < condition.size() - 1; i++) {
+ QChar ch = condition[i];
+ if (ch == QChar('(')) {
+ parenDepth++;
+ } else if (ch == QChar(')')) {
+ parenDepth--;
+ } else if (parenDepth == 0) {
+ if (condition[i + 1] == ch) {
+ if (ch == QChar('|')) {
+ firstOr = i;
+ break;
+ } else if (ch == QChar('&')) {
+ if (firstAnd == -1)
+ firstAnd = i;
+ }
+ }
+ }
+ }
+ if (firstOr != -1)
+ return isTrue(condition.left(firstOr)) || isTrue(condition.mid(firstOr + 2));
+ if (firstAnd != -1)
+ return isTrue(condition.left(firstAnd)) && isTrue(condition.mid(firstAnd + 2));
+
+ QString t = condition.simplified();
+ if (t.isEmpty())
+ return true;
+
+ if (t[0] == QChar('!'))
+ return !isTrue(t.mid(1));
+ if (t[0] == QChar('(') && t.endsWith(QChar(')')))
+ return isTrue(t.mid(1, t.size() - 2));
+
+ auto match = definedX->match(t);
+ if (match.hasMatch())
+ return defines->match(match.captured(1)).hasMatch();
+ else
+ return !falsehoods->match(t).hasMatch();
+}
+
+QString Tokenizer::lexeme() const
+{
+ return sourceDecoder(m_lex);
+}
+
+QString Tokenizer::previousLexeme() const
+{
+ return sourceDecoder(m_prevLex);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/tokenizer.h b/src/qdoc/qdoc/src/qdoc/tokenizer.h
new file mode 100644
index 000000000..d5669dfb7
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/tokenizer.h
@@ -0,0 +1,179 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef TOKENIZER_H
+#define TOKENIZER_H
+
+#include "location.h"
+
+#include <QtCore/qfile.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ Here come the C++ tokens we support. The first part contains
+ all-purpose tokens; then come keywords.
+
+ If you add a keyword, make sure to modify the keyword array in
+ tokenizer.cpp as well, and possibly adjust Tok_FirstKeyword and
+ Tok_LastKeyword.
+*/
+enum {
+ Tok_Eoi,
+ Tok_Ampersand,
+ Tok_Aster,
+ Tok_Caret,
+ Tok_LeftParen,
+ Tok_RightParen,
+ Tok_LeftParenAster,
+ Tok_Equal,
+ Tok_LeftBrace,
+ Tok_RightBrace,
+ Tok_Semicolon,
+ Tok_Colon,
+ Tok_LeftAngle,
+ Tok_RightAngle,
+ Tok_Comma,
+ Tok_Ellipsis,
+ Tok_Gulbrandsen,
+ Tok_LeftBracket,
+ Tok_RightBracket,
+ Tok_Tilde,
+ Tok_SomeOperator,
+ Tok_Number,
+ Tok_String,
+ Tok_Doc,
+ Tok_Comment,
+ Tok_Ident,
+ Tok_At,
+ Tok_char,
+ Tok_class,
+ Tok_const,
+ Tok_double,
+ Tok_int,
+ Tok_long,
+ Tok_operator,
+ Tok_short,
+ Tok_signed,
+ Tok_typename,
+ Tok_unsigned,
+ Tok_void,
+ Tok_volatile,
+ Tok_int64,
+ Tok_QPrivateSignal,
+ Tok_FirstKeyword = Tok_char,
+ Tok_LastKeyword = Tok_QPrivateSignal
+};
+
+/*
+ The Tokenizer class implements lexical analysis of C++ source
+ files.
+
+ Not every operator or keyword of C++ is recognized; only those
+ that are interesting to us. Some Qt keywords or macros are also
+ recognized.
+*/
+
+class Tokenizer
+{
+public:
+ Tokenizer(const Location &loc, QByteArray in);
+ Tokenizer(const Location &loc, QFile &file);
+
+ ~Tokenizer();
+
+ int getToken();
+ void setParsingFnOrMacro(bool macro) { m_parsingMacro = macro; }
+
+ [[nodiscard]] const Location &location() const { return m_tokLoc; }
+ [[nodiscard]] QString previousLexeme() const;
+ [[nodiscard]] QString lexeme() const;
+ [[nodiscard]] QString version() const { return m_version; }
+ [[nodiscard]] int parenDepth() const { return m_parenDepth; }
+ [[nodiscard]] int bracketDepth() const { return m_bracketDepth; }
+
+ static void initialize();
+ static void terminate();
+ static bool isTrue(const QString &condition);
+
+private:
+ void init();
+ void start(const Location &loc);
+ /*
+ Represents the maximum amount of characters that a token can be composed
+ of.
+
+ When a token with more characters than the maximum amount is encountered, a
+ warning is issued and parsing continues, discarding all characters from the
+ currently parsed token that don't fit into the buffer.
+ */
+ enum { yyLexBufSize = 1048576 };
+
+ int getch() { return m_pos == m_in.size() ? EOF : m_in[m_pos++]; }
+
+ inline int getChar()
+ {
+ using namespace Qt::StringLiterals;
+
+ if (m_ch == EOF)
+ return EOF;
+ if (m_lexLen < yyLexBufSize - 1) {
+ m_lex[m_lexLen++] = (char)m_ch;
+ m_lex[m_lexLen] = '\0';
+ } else if (!token_too_long_warning_was_issued) {
+ location().warning(
+ u"The content is too long.\n"_s,
+ u"The maximum amount of characters for this content is %1.\n"_s.arg(yyLexBufSize) +
+ "Consider splitting it or reducing its size."
+ );
+
+ token_too_long_warning_was_issued = true;
+ }
+ m_curLoc.advance(QChar(m_ch));
+ int ch = getch();
+ if (ch == EOF)
+ return EOF;
+ // cast explicitly to make sure the value of ch
+ // is in range [0..255] to avoid assert messages
+ // when using debug CRT that checks its input.
+ return int(uint(uchar(ch)));
+ }
+
+ int getTokenAfterPreprocessor();
+ void pushSkipping(bool skip);
+ bool popSkipping();
+
+ Location m_tokLoc;
+ Location m_curLoc;
+ char *m_lexBuf1 { nullptr };
+ char *m_lexBuf2 { nullptr };
+ char *m_prevLex { nullptr };
+ char *m_lex { nullptr };
+ size_t m_lexLen {};
+ QStack<bool> m_preprocessorSkipping;
+ int m_numPreprocessorSkipping {};
+ int m_braceDepth {};
+ int m_parenDepth {};
+ int m_bracketDepth {};
+ int m_ch {};
+
+ QString m_version {};
+ bool m_parsingMacro {};
+
+ // Used to ensure that the warning that is issued when a token is
+ // too long to fit into our fixed sized buffer is not repeated for each
+ // character of that token after the last saved one.
+ // The flag is reset whenever a new token is requested, so as to allow
+ // reporting all such tokens that are too long during a single execution.
+ bool token_too_long_warning_was_issued{false};
+
+protected:
+ QByteArray m_in {};
+ int m_pos {};
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/topic.h b/src/qdoc/qdoc/src/qdoc/topic.h
new file mode 100644
index 000000000..1f9646864
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/topic.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef TOPIC_H
+#define TOPIC_H
+
+QT_BEGIN_NAMESPACE
+
+struct Topic
+{
+public:
+ Topic() = default;
+ Topic(QString &t, QString a) : m_topic(t), m_args(std::move(a)) { }
+ ~Topic() = default;
+
+ [[nodiscard]] bool isEmpty() const { return m_topic.isEmpty(); }
+ void clear()
+ {
+ m_topic.clear();
+ m_args.clear();
+ }
+
+ QString m_topic {};
+ QString m_args {};
+};
+typedef QList<Topic> TopicList;
+
+QT_END_NAMESPACE
+
+#endif // TOPIC_H
diff --git a/src/qdoc/qdoc/src/qdoc/tree.cpp b/src/qdoc/qdoc/src/qdoc/tree.cpp
new file mode 100644
index 000000000..5c8f7d6d1
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/tree.cpp
@@ -0,0 +1,1357 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "tree.h"
+
+#include "classnode.h"
+#include "collectionnode.h"
+#include "doc.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "htmlgenerator.h"
+#include "location.h"
+#include "node.h"
+#include "qdocdatabase.h"
+#include "text.h"
+#include "typedefnode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class Tree
+
+ This class constructs and maintains a tree of instances of
+ the subclasses of Node.
+
+ This class is now private. Only class QDocDatabase has access.
+ Please don't change this. If you must access class Tree, do it
+ though the pointer to the singleton QDocDatabase.
+
+ Tree is being converted to a forest. A static member provides a
+ map of Tree *values with the module names as the keys. There is
+ one Tree in the map for each index file read, and there is one
+ tree that is not in the map for the module whose documentation
+ is being generated.
+ */
+
+/*!
+ Constructs a Tree. \a qdb is the pointer to the singleton
+ qdoc database that is constructing the tree. This might not
+ be necessary, and it might be removed later.
+
+ \a camelCaseModuleName is the project name for this tree
+ as it appears in the qdocconf file.
+ */
+Tree::Tree(const QString &camelCaseModuleName, QDocDatabase *qdb)
+ : m_camelCaseModuleName(camelCaseModuleName),
+ m_physicalModuleName(camelCaseModuleName.toLower()),
+ m_qdb(qdb),
+ m_root(nullptr, QString())
+{
+ m_root.setPhysicalModuleName(m_physicalModuleName);
+ m_root.setTree(this);
+}
+
+/*!
+ Destroys the Tree.
+
+ There are two maps of targets, keywords, and contents.
+ One map is indexed by ref, the other by title. Both maps
+ use the same set of TargetRec objects as the values,
+ so we only need to delete the values from one of them.
+
+ The Node instances themselves are destroyed by the root
+ node's (\c m_root) destructor.
+ */
+Tree::~Tree()
+{
+ qDeleteAll(m_nodesByTargetRef);
+ m_nodesByTargetRef.clear();
+ m_nodesByTargetTitle.clear();
+}
+
+/* API members */
+
+/*!
+ Calls findClassNode() first with \a path and \a start. If
+ it finds a node, the node is returned. If not, it calls
+ findNamespaceNode() with the same parameters. The result
+ is returned.
+ */
+Node *Tree::findNodeForInclude(const QStringList &path) const
+{
+ Node *n = findClassNode(path);
+ if (n == nullptr)
+ n = findNamespaceNode(path);
+ return n;
+}
+
+/*!
+ This function searches this tree for an Aggregate node with
+ the specified \a name. It returns the pointer to that node
+ or nullptr.
+
+ We might need to split the name on '::' but we assume the
+ name is a single word at the moment.
+ */
+Aggregate *Tree::findAggregate(const QString &name)
+{
+ QStringList path = name.split(QLatin1String("::"));
+ return static_cast<Aggregate *>(findNodeRecursive(path, 0, const_cast<NamespaceNode *>(root()),
+ &Node::isFirstClassAggregate));
+}
+
+/*!
+ Find the C++ class node named \a path. Begin the search at the
+ \a start node. If the \a start node is 0, begin the search
+ at the root of the tree. Only a C++ class node named \a path is
+ acceptible. If one is not found, 0 is returned.
+ */
+ClassNode *Tree::findClassNode(const QStringList &path, const Node *start) const
+{
+ if (start == nullptr)
+ start = const_cast<NamespaceNode *>(root());
+ return static_cast<ClassNode *>(findNodeRecursive(path, 0, start, &Node::isClassNode));
+}
+
+/*!
+ Find the Namespace node named \a path. Begin the search at
+ the root of the tree. Only a Namespace node named \a path
+ is acceptible. If one is not found, 0 is returned.
+ */
+NamespaceNode *Tree::findNamespaceNode(const QStringList &path) const
+{
+ Node *start = const_cast<NamespaceNode *>(root());
+ return static_cast<NamespaceNode *>(findNodeRecursive(path, 0, start, &Node::isNamespace));
+}
+
+/*!
+ This function searches for the node specified by \a path.
+ The matching node can be one of several different types
+ including a C++ class, a C++ namespace, or a C++ header
+ file.
+
+ I'm not sure if it can be a QML type, but if that is a
+ possibility, the code can easily accommodate it.
+
+ If a matching node is found, a pointer to it is returned.
+ Otherwise 0 is returned.
+ */
+Aggregate *Tree::findRelatesNode(const QStringList &path)
+{
+ Node *n = findNodeRecursive(path, 0, root(), &Node::isRelatableType);
+ return (((n != nullptr) && n->isAggregate()) ? static_cast<Aggregate *>(n) : nullptr);
+}
+
+/*!
+ Inserts function name \a funcName and function role \a funcRole into
+ the property function map for the specified \a property.
+ */
+void Tree::addPropertyFunction(PropertyNode *property, const QString &funcName,
+ PropertyNode::FunctionRole funcRole)
+{
+ m_unresolvedPropertyMap[property].insert(funcRole, funcName);
+}
+
+/*!
+ This function resolves C++ inheritance and reimplementation
+ settings for each C++ class node found in the tree beginning
+ at \a n. It also calls itself recursively for each C++ class
+ node or namespace node it encounters.
+
+ This function does not resolve QML inheritance.
+ */
+void Tree::resolveBaseClasses(Aggregate *n)
+{
+ for (auto it = n->constBegin(); it != n->constEnd(); ++it) {
+ if ((*it)->isClassNode()) {
+ auto *cn = static_cast<ClassNode *>(*it);
+ QList<RelatedClass> &bases = cn->baseClasses();
+ for (auto &base : bases) {
+ if (base.m_node == nullptr) {
+ Node *n = m_qdb->findClassNode(base.m_path);
+ /*
+ If the node for the base class was not found,
+ the reason might be that the subclass is in a
+ namespace and the base class is in the same
+ namespace, but the base class name was not
+ qualified with the namespace name. That is the
+ case most of the time. Then restart the search
+ at the parent of the subclass node (the namespace
+ node) using the unqualified base class name.
+ */
+ if (n == nullptr) {
+ Aggregate *parent = cn->parent();
+ if (parent != nullptr)
+ // Exclude the root namespace
+ if (parent->isNamespace() && !parent->name().isEmpty())
+ n = findClassNode(base.m_path, parent);
+ }
+ if (n != nullptr) {
+ auto *bcn = static_cast<ClassNode *>(n);
+ base.m_node = bcn;
+ bcn->addDerivedClass(base.m_access, cn);
+ }
+ }
+ }
+ resolveBaseClasses(cn);
+ } else if ((*it)->isNamespace()) {
+ resolveBaseClasses(static_cast<NamespaceNode *>(*it));
+ }
+ }
+}
+
+/*!
+ */
+void Tree::resolvePropertyOverriddenFromPtrs(Aggregate *n)
+{
+ for (auto node = n->constBegin(); node != n->constEnd(); ++node) {
+ if ((*node)->isClassNode()) {
+ auto *cn = static_cast<ClassNode *>(*node);
+ for (auto property = cn->constBegin(); property != cn->constEnd(); ++property) {
+ if ((*property)->isProperty())
+ cn->resolvePropertyOverriddenFromPtrs(static_cast<PropertyNode *>(*property));
+ }
+ resolvePropertyOverriddenFromPtrs(cn);
+ } else if ((*node)->isNamespace()) {
+ resolvePropertyOverriddenFromPtrs(static_cast<NamespaceNode *>(*node));
+ }
+ }
+}
+
+/*!
+ Resolves access functions associated with each PropertyNode stored
+ in \c m_unresolvedPropertyMap, and adds them into the property node.
+ This allows the property node to list the access functions when
+ generating their documentation.
+ */
+void Tree::resolveProperties()
+{
+ for (auto propEntry = m_unresolvedPropertyMap.constBegin();
+ propEntry != m_unresolvedPropertyMap.constEnd(); ++propEntry) {
+ PropertyNode *property = propEntry.key();
+ Aggregate *parent = property->parent();
+ QString getterName = (*propEntry)[PropertyNode::FunctionRole::Getter];
+ QString setterName = (*propEntry)[PropertyNode::FunctionRole::Setter];
+ QString resetterName = (*propEntry)[PropertyNode::FunctionRole::Resetter];
+ QString notifierName = (*propEntry)[PropertyNode::FunctionRole::Notifier];
+ QString bindableName = (*propEntry)[PropertyNode::FunctionRole::Bindable];
+
+ for (auto it = parent->constBegin(); it != parent->constEnd(); ++it) {
+ if ((*it)->isFunction()) {
+ auto *function = static_cast<FunctionNode *>(*it);
+ if (function->access() == property->access()
+ && (function->status() == property->status() || function->doc().isEmpty())) {
+ if (function->name() == getterName) {
+ property->addFunction(function, PropertyNode::FunctionRole::Getter);
+ } else if (function->name() == setterName) {
+ property->addFunction(function, PropertyNode::FunctionRole::Setter);
+ } else if (function->name() == resetterName) {
+ property->addFunction(function, PropertyNode::FunctionRole::Resetter);
+ } else if (function->name() == notifierName) {
+ property->addSignal(function, PropertyNode::FunctionRole::Notifier);
+ } else if (function->name() == bindableName) {
+ property->addFunction(function, PropertyNode::FunctionRole::Bindable);
+ }
+ }
+ }
+ }
+ }
+
+ for (auto propEntry = m_unresolvedPropertyMap.constBegin();
+ propEntry != m_unresolvedPropertyMap.constEnd(); ++propEntry) {
+ PropertyNode *property = propEntry.key();
+ // redo it to set the property functions
+ if (property->overriddenFrom())
+ property->setOverriddenFrom(property->overriddenFrom());
+ }
+
+ m_unresolvedPropertyMap.clear();
+}
+
+/*!
+ For each QML class node that points to a C++ class node,
+ follow its C++ class node pointer and set the C++ class
+ node's QML class node pointer back to the QML class node.
+ */
+void Tree::resolveCppToQmlLinks()
+{
+
+ const NodeList &children = m_root.childNodes();
+ for (auto *child : children) {
+ if (child->isQmlType()) {
+ auto *qcn = static_cast<QmlTypeNode *>(child);
+ auto *cn = const_cast<ClassNode *>(qcn->classNode());
+ if (cn)
+ cn->insertQmlNativeType(qcn);
+ }
+ }
+}
+
+/*!
+ For each \a aggregate, recursively set the \\since version based on
+ \\since information from the associated physical or logical module.
+ That is, C++ and QML types inherit the \\since of their module,
+ unless that command is explicitly used in the type documentation.
+
+ In addition, resolve the since information for individual enum
+ values.
+*/
+void Tree::resolveSince(Aggregate &aggregate)
+{
+ for (auto *child : aggregate.childNodes()) {
+ // Order matters; resolve since-clauses in enum values
+ // first as EnumNode is not an Aggregate
+ if (child->isEnumType())
+ resolveEnumValueSince(static_cast<EnumNode&>(*child));
+ if (!child->isAggregate())
+ continue;
+ if (!child->since().isEmpty())
+ continue;
+
+ if (const auto collectionNode = m_qdb->getModuleNode(child))
+ child->setSince(collectionNode->since());
+
+ resolveSince(static_cast<Aggregate&>(*child));
+ }
+}
+
+/*!
+ Resolve since information for values of enum node \a en.
+
+ Enum values are not derived from Node, but they can have
+ 'since' information associated with them. Since-strings
+ for each enum item are initially stored in the Doc
+ instance of EnumNode as SinceTag atoms; parse the doc
+ and store them into each EnumItem.
+*/
+void Tree::resolveEnumValueSince(EnumNode &en)
+{
+ const QStringList enumItems{en.doc().enumItemNames()};
+ const Atom *atom = en.doc().body().firstAtom();
+ while ((atom = atom->find(Atom::ListTagLeft))) {
+ if (atom = atom->next(); !atom)
+ break;
+ if (auto val = atom->string(); enumItems.contains(val)) {
+ if (atom = atom->next(); atom && atom->next(Atom::SinceTagLeft))
+ en.setSince(val, atom->next()->next()->string());
+ }
+ }
+}
+
+/*!
+ Traverse this Tree and for each ClassNode found, remove
+ from its list of base classes any that are marked private
+ or internal. When a class is removed from a base class
+ list, promote its public pase classes to be base classes
+ of the class where the base class was removed. This is
+ done for documentation purposes. The function is recursive
+ on namespace nodes.
+ */
+void Tree::removePrivateAndInternalBases(NamespaceNode *rootNode)
+{
+ if (rootNode == nullptr)
+ rootNode = root();
+
+ for (auto node = rootNode->constBegin(); node != rootNode->constEnd(); ++node) {
+ if ((*node)->isClassNode())
+ static_cast<ClassNode *>(*node)->removePrivateAndInternalBases();
+ else if ((*node)->isNamespace())
+ removePrivateAndInternalBases(static_cast<NamespaceNode *>(*node));
+ }
+}
+
+/*!
+ */
+ClassList Tree::allBaseClasses(const ClassNode *classNode) const
+{
+ ClassList result;
+ const auto &baseClasses = classNode->baseClasses();
+ for (const auto &relatedClass : baseClasses) {
+ if (relatedClass.m_node != nullptr) {
+ result += relatedClass.m_node;
+ result += allBaseClasses(relatedClass.m_node);
+ }
+ }
+ return result;
+}
+
+/*!
+ Find the node with the specified \a path name that is of
+ the specified \a type and \a subtype. Begin the search at
+ the \a start node. If the \a start node is 0, begin the
+ search at the tree root. \a subtype is not used unless
+ \a type is \c{Page}.
+ */
+Node *Tree::findNodeByNameAndType(const QStringList &path, bool (Node::*isMatch)() const) const
+{
+ return findNodeRecursive(path, 0, root(), isMatch);
+}
+
+/*!
+ Recursive search for a node identified by \a path. Each
+ path element is a name. \a pathIndex specifies the index
+ of the name in \a path to try to match. \a start is the
+ node whose children shoulod be searched for one that has
+ that name. Each time a match is found, increment the
+ \a pathIndex and call this function recursively.
+
+ If the end of the path is reached (i.e. if a matching
+ node is found for each name in the \a path), the \a type
+ must match the type of the last matching node, and if the
+ type is \e{Page}, the \a subtype must match as well.
+
+ If the algorithm is successful, the pointer to the final
+ node is returned. Otherwise 0 is returned.
+ */
+Node *Tree::findNodeRecursive(const QStringList &path, int pathIndex, const Node *start,
+ bool (Node::*isMatch)() const) const
+{
+ if (start == nullptr || path.isEmpty())
+ return nullptr;
+ Node *node = const_cast<Node *>(start);
+ if (!node->isAggregate())
+ return ((pathIndex >= path.size()) ? node : nullptr);
+ auto *current = static_cast<Aggregate *>(node);
+ const NodeList &children = current->childNodes();
+ const QString &name = path.at(pathIndex);
+ for (auto *node : children) {
+ if (node == nullptr)
+ continue;
+ if (node->name() == name) {
+ if (pathIndex + 1 >= path.size()) {
+ if ((node->*(isMatch))())
+ return node;
+ continue;
+ } else { // Search the children of n for the next name in the path.
+ node = findNodeRecursive(path, pathIndex + 1, node, isMatch);
+ if (node != nullptr)
+ return node;
+ }
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Searches the tree for a node that matches the \a path plus
+ the \a target. The search begins at \a start and moves up
+ the parent chain from there, or, if \a start is 0, the search
+ begins at the root.
+
+ The \a flags can indicate whether to search base classes and/or
+ the enum values in enum types. \a genus further restricts
+ the type of nodes to match, i.e. CPP or QML.
+
+ If a matching node is found, \a ref is set to the HTML fragment
+ identifier to use for the link. On return, the optional
+ \a targetType parameter contains the type of the resolved
+ target; section title (Contents), \\target, \\keyword, or other
+ (Unknown).
+ */
+const Node *Tree::findNodeForTarget(const QStringList &path, const QString &target,
+ const Node *start, int flags, Node::Genus genus,
+ QString &ref, TargetRec::TargetType *targetType) const
+{
+ const Node *node = nullptr;
+
+ // Retrieves and sets ref from target for Node n.
+ // Returns n on valid (or empty) target, or nullptr on an invalid target.
+ auto set_ref_from_target = [this, &ref, &target](const Node *n) -> const Node* {
+ if (!target.isEmpty()) {
+ if (ref = getRef(target, n); ref.isEmpty())
+ return nullptr;
+ }
+ return n;
+ };
+
+ if (genus == Node::DontCare || genus == Node::DOC) {
+ if (node = findPageNodeByTitle(path.at(0)); node) {
+ if (node = set_ref_from_target(node); node)
+ return node;
+ }
+ }
+
+ const TargetRec *result = findUnambiguousTarget(path.join(QLatin1String("::")), genus);
+ if (result) {
+ ref = result->m_ref;
+ if (node = set_ref_from_target(result->m_node); node) {
+ // Delay returning references to section titles as we
+ // may find a better match below
+ if (result->m_type != TargetRec::Contents) {
+ if (targetType)
+ *targetType = result->m_type;
+ return node;
+ }
+ ref.clear();
+ }
+ }
+
+ const Node *current = start ? start : root();
+ /*
+ If the path contains one or two double colons ("::"),
+ check if the first two path elements refer to a QML type.
+ If so, path[0] is QML module identifier, and path[1] is
+ the type.
+ */
+ int path_idx = 0;
+ if ((genus == Node::QML || genus == Node::DontCare)
+ && path.size() >= 2 && !path[0].isEmpty()) {
+ if (auto *qcn = lookupQmlType(path.sliced(0, 2).join(QLatin1String("::"))); qcn) {
+ current = qcn;
+ // No further elements in the path, return the type
+ if (path.size() == 2)
+ return set_ref_from_target(qcn);
+ path_idx = 2;
+ }
+ }
+
+ while (current) {
+ if (current->isAggregate()) {
+ if (const Node *match = matchPathAndTarget(
+ path, path_idx, target, current, flags, genus, ref);
+ match != nullptr)
+ return match;
+ }
+ current = current->parent();
+ path_idx = 0;
+ }
+
+ if (node && result) {
+ // Fall back to previously found section title
+ ref = result->m_ref;
+ if (targetType)
+ *targetType = result->m_type;
+ }
+ return node;
+}
+
+/*!
+ First, the \a path is used to find a node. The \a path
+ matches some part of the node's fully quallified name.
+ If the \a target is not empty, it must match a target
+ in the matching node. If the matching of the \a path
+ and the \a target (if present) is successful, \a ref
+ is set from the \a target, and the pointer to the
+ matching node is returned. \a idx is the index into the
+ \a path where to begin the matching. The function is
+ recursive with idx being incremented for each recursive
+ call.
+
+ The matching node must be of the correct \a genus, i.e.
+ either QML or C++, but \a genus can be set to \c DontCare.
+ \a flags indicates whether to search base classes and
+ whether to search for an enum value. \a node points to
+ the node where the search should begin, assuming the
+ \a path is a not a fully-qualified name. \a node is
+ most often the root of this Tree.
+ */
+const Node *Tree::matchPathAndTarget(const QStringList &path, int idx, const QString &target,
+ const Node *node, int flags, Node::Genus genus,
+ QString &ref) const
+{
+ /*
+ If the path has been matched, then if there is a target,
+ try to match the target. If there is a target, but you
+ can't match it at the end of the path, give up; return 0.
+ */
+ if (idx == path.size()) {
+ if (!target.isEmpty()) {
+ ref = getRef(target, node);
+ if (ref.isEmpty())
+ return nullptr;
+ }
+ if (node->isFunction() && node->name() == node->parent()->name())
+ node = node->parent();
+ return node;
+ }
+
+ QString name = path.at(idx);
+ if (node->isAggregate()) {
+ NodeVector nodes;
+ static_cast<const Aggregate *>(node)->findChildren(name, nodes);
+ for (const auto *child : std::as_const(nodes)) {
+ if (genus != Node::DontCare && !(genus & child->genus()))
+ continue;
+ const Node *t = matchPathAndTarget(path, idx + 1, target, child, flags, genus, ref);
+ if (t && !t->isPrivate())
+ return t;
+ }
+ }
+ if (target.isEmpty() && (flags & SearchEnumValues)) {
+ const auto *enumNode = node->isAggregate() ?
+ findEnumNode(nullptr, node, path, idx) :
+ findEnumNode(node, nullptr, path, idx);
+ if (enumNode)
+ return enumNode;
+ }
+ if (((genus == Node::CPP) || (genus == Node::DontCare)) && node->isClassNode()
+ && (flags & SearchBaseClasses)) {
+ const ClassList bases = allBaseClasses(static_cast<const ClassNode *>(node));
+ for (const auto *base : bases) {
+ const Node *t = matchPathAndTarget(path, idx, target, base, flags, genus, ref);
+ if (t && !t->isPrivate())
+ return t;
+ if (target.isEmpty() && (flags & SearchEnumValues)) {
+ if ((t = findEnumNode(base->findChildNode(path.at(idx), genus, flags), base, path, idx)))
+ return t;
+ }
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Searches the tree for a node that matches the \a path. The
+ search begins at \a start but can move up the parent chain
+ recursively if no match is found. The \a flags are used to
+ restrict the search.
+ */
+const Node *Tree::findNode(const QStringList &path, const Node *start, int flags,
+ Node::Genus genus) const
+{
+ const Node *current = start;
+ if (current == nullptr)
+ current = root();
+
+ do {
+ const Node *node = current;
+ int i;
+ int start_idx = 0;
+
+ /*
+ If the path contains one or two double colons ("::"),
+ check first to see if the first two path strings refer
+ to a QML element. If they do, path[0] will be the QML
+ module identifier, and path[1] will be the QML type.
+ If the answer is yes, the reference identifies a QML
+ type node.
+ */
+ if (((genus == Node::QML) || (genus == Node::DontCare)) && (path.size() >= 2)
+ && !path[0].isEmpty()) {
+ QmlTypeNode *qcn = lookupQmlType(QString(path[0] + "::" + path[1]));
+ if (qcn != nullptr) {
+ node = qcn;
+ if (path.size() == 2)
+ return node;
+ start_idx = 2;
+ }
+ }
+
+ for (i = start_idx; i < path.size(); ++i) {
+ if (node == nullptr || !node->isAggregate())
+ break;
+
+ // Clear the TypesOnly flag until the last path segment, as e.g. namespaces are not
+ // types. We also ignore module nodes as they are not aggregates and thus have no
+ // children.
+ int tmpFlags = (i < path.size() - 1) ? (flags & ~TypesOnly) | IgnoreModules : flags;
+
+ const Node *next = static_cast<const Aggregate *>(node)->findChildNode(path.at(i),
+ genus, tmpFlags);
+ const Node *enumNode = (flags & SearchEnumValues) ?
+ findEnumNode(next, node, path, i) : nullptr;
+
+ if (enumNode)
+ return enumNode;
+
+
+ if (!next && ((genus == Node::CPP) || (genus == Node::DontCare))
+ && node->isClassNode() && (flags & SearchBaseClasses)) {
+ const ClassList bases = allBaseClasses(static_cast<const ClassNode *>(node));
+ for (const auto *base : bases) {
+ next = base->findChildNode(path.at(i), genus, tmpFlags);
+ if (flags & SearchEnumValues)
+ if ((enumNode = findEnumNode(next, base, path, i)))
+ return enumNode;
+ if (next)
+ break;
+ }
+ }
+ node = next;
+ }
+ if ((node != nullptr) && i == path.size())
+ return node;
+ current = current->parent();
+ } while (current != nullptr);
+
+ return nullptr;
+}
+
+
+/*!
+ \internal
+
+ Helper function to return an enum that matches the \a path at a specified \a offset.
+ If \a node is a valid enum node, the enum name is assumed to be included in the path
+ (i.e, a scoped enum). Otherwise, query the \a aggregate (typically, the class node)
+ for enum node that includes the value at the last position in \a path.
+ */
+const Node *Tree::findEnumNode(const Node *node, const Node *aggregate, const QStringList &path, int offset) const
+{
+ // Scoped enum (path ends in enum_name :: enum_value)
+ if (node && node->isEnumType() && offset == path.size() - 1) {
+ const auto *en = static_cast<const EnumNode*>(node);
+ if (en->isScoped() && en->hasItem(path.last()))
+ return en;
+ }
+
+ // Standard enum (path ends in class_name :: enum_value)
+ return (!node && aggregate && offset == path.size() - 1) ?
+ static_cast<const Aggregate *>(aggregate)->findEnumNodeForValue(path.last()) :
+ nullptr;
+}
+
+/*!
+ This function searches for a node with a canonical title
+ constructed from \a target. If the node it finds is \a node,
+ it returns the ref from that node. Otherwise it returns an
+ empty string.
+ */
+QString Tree::getRef(const QString &target, const Node *node) const
+{
+ auto it = m_nodesByTargetTitle.constFind(target);
+ if (it != m_nodesByTargetTitle.constEnd()) {
+ do {
+ if (it.value()->m_node == node)
+ return it.value()->m_ref;
+ ++it;
+ } while (it != m_nodesByTargetTitle.constEnd() && it.key() == target);
+ }
+ QString key = Utilities::asAsciiPrintable(target);
+ it = m_nodesByTargetRef.constFind(key);
+ if (it != m_nodesByTargetRef.constEnd()) {
+ do {
+ if (it.value()->m_node == node)
+ return it.value()->m_ref;
+ ++it;
+ } while (it != m_nodesByTargetRef.constEnd() && it.key() == key);
+ }
+ return QString();
+}
+
+/*!
+ Inserts a new target into the target table. \a name is the
+ key. The target record contains the \a type, a pointer to
+ the \a node, the \a priority. and a canonicalized form of
+ the \a name, which is later used.
+ */
+void Tree::insertTarget(const QString &name, const QString &title, TargetRec::TargetType type,
+ Node *node, int priority)
+{
+ auto *target = new TargetRec(name, type, node, priority);
+ m_nodesByTargetRef.insert(name, target);
+ m_nodesByTargetTitle.insert(title, target);
+}
+
+/*!
+ \internal
+
+ \a root is the root node of the tree to resolve targets for. This function
+ traverses the tree starting from the root node and processes each child
+ node. If the child node is an aggregate node, this function is called
+ recursively on the child node.
+ */
+void Tree::resolveTargets(Aggregate *root)
+{
+ for (auto *child : root->childNodes()) {
+ addToPageNodeByTitleMap(child);
+ populateTocSectionTargetMap(child);
+ addKeywordsToTargetMaps(child);
+ addTargetsToTargetMap(child);
+
+ if (child->isAggregate())
+ resolveTargets(static_cast<Aggregate *>(child));
+ }
+}
+
+/*!
+ \internal
+
+ Updates the target maps for targets associated with the given \a node.
+ */
+void Tree::addTargetsToTargetMap(Node *node) {
+ if (!node || !node->doc().hasTargets())
+ return;
+
+ for (Atom *i : std::as_const(node->doc().targets())) {
+ const QString ref = refForAtom(i);
+ const QString title = i->string();
+ if (!ref.isEmpty() && !title.isEmpty()) {
+ QString key = Utilities::asAsciiPrintable(title);
+ auto *target = new TargetRec(ref, TargetRec::Target, node, 2);
+ m_nodesByTargetRef.insert(key, target);
+ m_nodesByTargetTitle.insert(title, target);
+ }
+ }
+}
+
+/*!
+ \internal
+
+ Updates the target maps for keywords associated with the given \a node.
+ */
+void Tree::addKeywordsToTargetMaps(Node *node) {
+ if (!node->doc().hasKeywords())
+ return;
+
+ for (Atom *i : std::as_const(node->doc().keywords())) {
+ QString ref = refForAtom(i);
+ QString title = i->string();
+ if (!ref.isEmpty() && !title.isEmpty()) {
+ auto *target = new TargetRec(ref, TargetRec::Keyword, node, 1);
+ m_nodesByTargetRef.insert(Utilities::asAsciiPrintable(title), target);
+ m_nodesByTargetTitle.insert(title, target);
+ }
+ }
+}
+
+/*!
+ \internal
+
+ Populates the map of targets for each section in the table of contents for
+ the given \a node while ensuring that each target has a unique reference.
+ */
+void Tree::populateTocSectionTargetMap(Node *node) {
+ if (!node || !node->doc().hasTableOfContents())
+ return;
+
+ QStack<Atom *> tocLevels;
+ QSet<QString> anchors;
+
+ qsizetype index = 0;
+
+ for (Atom *atom: std::as_const(node->doc().tableOfContents())) {
+ while (!tocLevels.isEmpty() && tocLevels.top()->string().toInt() >= atom->string().toInt())
+ tocLevels.pop();
+
+ tocLevels.push(atom);
+
+ QString ref = refForAtom(atom);
+ const QString &title = Text::sectionHeading(atom).toString();
+ if (ref.isEmpty() || title.isEmpty())
+ continue;
+
+ if (anchors.contains(ref)) {
+ QStringList refParts;
+ for (const auto tocLevel : tocLevels)
+ refParts << refForAtom(tocLevel);
+
+ refParts << QString::number(index);
+ ref = refParts.join(QLatin1Char('-'));
+ }
+
+ anchors.insert(ref);
+ if (atom->next(Atom::SectionHeadingLeft))
+ atom->next()->append(ref);
+ ++index;
+
+ const QString &key = Utilities::asAsciiPrintable(title);
+ auto *target = new TargetRec(ref, TargetRec::Contents, node, 3);
+ m_nodesByTargetRef.insert(key, target);
+ m_nodesByTargetTitle.insert(title, target);
+ }
+}
+
+/*!
+ \internal
+
+ Checks if the \a node's title is registered in the page nodes by title map.
+ If not, it stores the page node in the map.
+ */
+void Tree::addToPageNodeByTitleMap(Node *node) {
+ if (!node || !node->isTextPageNode())
+ return;
+
+ auto *pageNode = static_cast<PageNode *>(node);
+ QString key = pageNode->title();
+ if (key.isEmpty())
+ return;
+
+ if (key.contains(QChar(' ')))
+ key = Utilities::asAsciiPrintable(key);
+ const QList<PageNode *> nodes = m_pageNodesByTitle.values(key);
+
+ bool alreadyThere = std::any_of(nodes.cbegin(), nodes.cend(), [&](const auto &knownNode) {
+ return knownNode->isExternalPage() && knownNode->name() == pageNode->name();
+ });
+
+ if (!alreadyThere)
+ m_pageNodesByTitle.insert(key, pageNode);
+}
+
+/*!
+ Searches for a \a target anchor, matching the given \a genus, and returns
+ the associated TargetRec instance.
+ */
+const TargetRec *Tree::findUnambiguousTarget(const QString &target, Node::Genus genus) const
+{
+ auto findBestCandidate = [&](const TargetMap &tgtMap, const QString &key) {
+ TargetRec *best = nullptr;
+ auto [it, end] = tgtMap.equal_range(key);
+ while (it != end) {
+ TargetRec *candidate = it.value();
+ if ((genus == Node::DontCare) || (genus & candidate->genus())) {
+ if (!best || (candidate->m_priority < best->m_priority))
+ best = candidate;
+ }
+ ++it;
+ }
+ return best;
+ };
+
+ TargetRec *bestTarget = findBestCandidate(m_nodesByTargetTitle, target);
+ if (!bestTarget)
+ bestTarget = findBestCandidate(m_nodesByTargetRef, Utilities::asAsciiPrintable(target));
+
+ return bestTarget;
+}
+
+/*!
+ This function searches for a node with the specified \a title.
+ */
+const PageNode *Tree::findPageNodeByTitle(const QString &title) const
+{
+ PageNodeMultiMap::const_iterator it;
+ if (title.contains(QChar(' ')))
+ it = m_pageNodesByTitle.constFind(Utilities::asAsciiPrintable(title));
+ else
+ it = m_pageNodesByTitle.constFind(title);
+ if (it != m_pageNodesByTitle.constEnd()) {
+ /*
+ Reporting all these duplicate section titles is probably
+ overkill. We should report the duplicate file and let
+ that suffice.
+ */
+ PageNodeMultiMap::const_iterator j = it;
+ ++j;
+ if (j != m_pageNodesByTitle.constEnd() && j.key() == it.key()) {
+ while (j != m_pageNodesByTitle.constEnd()) {
+ if (j.key() == it.key() && j.value()->url().isEmpty()) {
+ break; // Just report one duplicate for now.
+ }
+ ++j;
+ }
+ if (j != m_pageNodesByTitle.cend()) {
+ it.value()->location().warning("This page title exists in more than one file: "
+ + title);
+ j.value()->location().warning("[It also exists here]");
+ }
+ }
+ return it.value();
+ }
+ return nullptr;
+}
+
+/*!
+ Returns a canonical title for the \a atom, if the \a atom
+ is a SectionLeft, SectionHeadingLeft, Keyword, or Target.
+ */
+QString Tree::refForAtom(const Atom *atom)
+{
+ Q_ASSERT(atom);
+
+ switch (atom->type()) {
+ case Atom::SectionLeft:
+ atom = atom->next();
+ [[fallthrough]];
+ case Atom::SectionHeadingLeft:
+ if (atom->count() == 2)
+ return atom->string(1);
+ return Utilities::asAsciiPrintable(Text::sectionHeading(atom).toString());
+ case Atom::Target:
+ [[fallthrough]];
+ case Atom::Keyword:
+ return Utilities::asAsciiPrintable(atom->string());
+ default:
+ return {};
+ }
+}
+
+/*!
+ \fn const CNMap &Tree::groups() const
+ Returns a const reference to the collection of all
+ group nodes.
+*/
+
+/*!
+ \fn const ModuleMap &Tree::modules() const
+ Returns a const reference to the collection of all
+ module nodes.
+*/
+
+/*!
+ \fn const QmlModuleMap &Tree::qmlModules() const
+ Returns a const reference to the collection of all
+ QML module nodes.
+*/
+
+/*!
+ Returns a pointer to the collection map specified by \a type.
+ Returns null if \a type is not specified.
+ */
+CNMap *Tree::getCollectionMap(Node::NodeType type)
+{
+ switch (type) {
+ case Node::Group:
+ return &m_groups;
+ case Node::Module:
+ return &m_modules;
+ case Node::QmlModule:
+ return &m_qmlModules;
+ default:
+ break;
+ }
+ return nullptr;
+}
+
+/*!
+ Searches this tree for a collection named \a name with the
+ specified \a type. If the collection is found, a pointer
+ to it is returned. If a collection is not found, null is
+ returned.
+ */
+CollectionNode *Tree::getCollection(const QString &name, Node::NodeType type)
+{
+ CNMap *map = getCollectionMap(type);
+ if (map) {
+ auto it = map->constFind(name);
+ if (it != map->cend())
+ return it.value();
+ }
+ return nullptr;
+}
+
+/*!
+ Find the group, module, or QML module named \a name and return a
+ pointer to that collection node. \a type specifies which kind of
+ collection node you want. If a collection node with the specified \a
+ name and \a type is not found, a new one is created, and the pointer
+ to the new one is returned.
+
+ If a new collection node is created, its parent is the tree
+ root, and the new collection node is marked \e{not seen}.
+
+ \a genus must be specified, i.e. it must not be \c{DontCare}.
+ If it is \c{DontCare}, 0 is returned, which is a programming
+ error.
+ */
+CollectionNode *Tree::findCollection(const QString &name, Node::NodeType type)
+{
+ CNMap *m = getCollectionMap(type);
+ if (!m) // error
+ return nullptr;
+ auto it = m->constFind(name);
+ if (it != m->cend())
+ return it.value();
+ CollectionNode *cn = new CollectionNode(type, root(), name);
+ cn->markNotSeen();
+ m->insert(name, cn);
+ return cn;
+}
+
+/*! \fn CollectionNode *Tree::findGroup(const QString &name)
+ Find the group node named \a name and return a pointer
+ to it. If the group node is not found, add a new group
+ node named \a name and return a pointer to the new one.
+
+ If a new group node is added, its parent is the tree root,
+ and the new group node is marked \e{not seen}.
+ */
+
+/*! \fn CollectionNode *Tree::findModule(const QString &name)
+ Find the module node named \a name and return a pointer
+ to it. If a matching node is not found, add a new module
+ node named \a name and return a pointer to that one.
+
+ If a new module node is added, its parent is the tree root,
+ and the new module node is marked \e{not seen}.
+ */
+
+/*! \fn CollectionNode *Tree::findQmlModule(const QString &name)
+ Find the QML module node named \a name and return a pointer
+ to it. If a matching node is not found, add a new QML module
+ node named \a name and return a pointer to that one.
+
+ If a new QML module node is added, its parent is the tree root,
+ and the new node is marked \e{not seen}.
+ */
+
+/*! \fn CollectionNode *Tree::addGroup(const QString &name)
+ Looks up the group node named \a name in the collection
+ of all group nodes. If a match is found, a pointer to the
+ node is returned. Otherwise, a new group node named \a name
+ is created and inserted into the collection, and the pointer
+ to that node is returned.
+ */
+
+/*! \fn CollectionNode *Tree::addModule(const QString &name)
+ Looks up the module node named \a name in the collection
+ of all module nodes. If a match is found, a pointer to the
+ node is returned. Otherwise, a new module node named \a name
+ is created and inserted into the collection, and the pointer
+ to that node is returned.
+ */
+
+/*! \fn CollectionNode *Tree::addQmlModule(const QString &name)
+ Looks up the QML module node named \a name in the collection
+ of all QML module nodes. If a match is found, a pointer to the
+ node is returned. Otherwise, a new QML module node named \a name
+ is created and inserted into the collection, and the pointer
+ to that node is returned.
+ */
+
+/*!
+ Looks up the group node named \a name in the collection
+ of all group nodes. If a match is not found, a new group
+ node named \a name is created and inserted into the collection.
+ Then append \a node to the group's members list, and append the
+ group name to the list of group names in \a node. The parent of
+ \a node is not changed by this function. Returns a pointer to
+ the group node.
+ */
+CollectionNode *Tree::addToGroup(const QString &name, Node *node)
+{
+ CollectionNode *cn = findGroup(name);
+ if (!node->isInternal()) {
+ cn->addMember(node);
+ node->appendGroupName(name);
+ }
+ return cn;
+}
+
+/*!
+ Looks up the module node named \a name in the collection
+ of all module nodes. If a match is not found, a new module
+ node named \a name is created and inserted into the collection.
+ Then append \a node to the module's members list. The parent of
+ \a node is not changed by this function. Returns the module node.
+ */
+CollectionNode *Tree::addToModule(const QString &name, Node *node)
+{
+ CollectionNode *cn = findModule(name);
+ cn->addMember(node);
+ node->setPhysicalModuleName(name);
+ return cn;
+}
+
+/*!
+ Looks up the QML module named \a name. If it isn't there,
+ create it. Then append \a node to the QML module's member
+ list. The parent of \a node is not changed by this function.
+ Returns the pointer to the QML module node.
+ */
+CollectionNode *Tree::addToQmlModule(const QString &name, Node *node)
+{
+ QStringList qmid;
+ QStringList dotSplit;
+ QStringList blankSplit = name.split(QLatin1Char(' '));
+ qmid.append(blankSplit[0]);
+ if (blankSplit.size() > 1) {
+ qmid.append(blankSplit[0] + blankSplit[1]);
+ dotSplit = blankSplit[1].split(QLatin1Char('.'));
+ qmid.append(blankSplit[0] + dotSplit[0]);
+ }
+
+ CollectionNode *cn = findQmlModule(blankSplit[0]);
+ cn->addMember(node);
+ node->setQmlModule(cn);
+ if (node->isQmlType()) {
+ QmlTypeNode *n = static_cast<QmlTypeNode *>(node);
+ for (int i = 0; i < qmid.size(); ++i) {
+ QString key = qmid[i] + "::" + node->name();
+ insertQmlType(key, n);
+ }
+ }
+ return cn;
+}
+
+/*!
+ If the QML type map does not contain \a key, insert node
+ \a n with the specified \a key.
+ */
+void Tree::insertQmlType(const QString &key, QmlTypeNode *n)
+{
+ if (!m_qmlTypeMap.contains(key))
+ m_qmlTypeMap.insert(key, n);
+}
+
+/*!
+ Finds the function node with the specifried name \a path that
+ also has the specified \a parameters and returns a pointer to
+ the first matching function node if one is found.
+
+ This function begins searching the tree at \a relative for
+ the \l {FunctionNode} {function node} identified by \a path
+ that has the specified \a parameters. The \a flags are
+ used to restrict the search. If a matching node is found, a
+ pointer to it is returned. Otherwise, nullis returned. If
+ \a relative is ull, the search begins at the tree root.
+ */
+const FunctionNode *Tree::findFunctionNode(const QStringList &path, const Parameters &parameters,
+ const Node *relative, Node::Genus genus) const
+{
+ if (path.size() == 3 && !path[0].isEmpty()
+ && ((genus == Node::QML) || (genus == Node::DontCare))) {
+ QmlTypeNode *qcn = lookupQmlType(QString(path[0] + "::" + path[1]));
+ if (qcn == nullptr) {
+ QStringList p(path[1]);
+ Node *n = findNodeByNameAndType(p, &Node::isQmlType);
+ if ((n != nullptr) && n->isQmlType())
+ qcn = static_cast<QmlTypeNode *>(n);
+ }
+ if (qcn != nullptr)
+ return static_cast<const FunctionNode *>(qcn->findFunctionChild(path[2], parameters));
+ }
+
+ if (relative == nullptr)
+ relative = root();
+ else if (genus != Node::DontCare) {
+ if (!(genus & relative->genus()))
+ relative = root();
+ }
+
+ do {
+ Node *node = const_cast<Node *>(relative);
+ int i;
+
+ for (i = 0; i < path.size(); ++i) {
+ if (node == nullptr || !node->isAggregate())
+ break;
+
+ Aggregate *aggregate = static_cast<Aggregate *>(node);
+ Node *next = nullptr;
+ if (i == path.size() - 1)
+ next = aggregate->findFunctionChild(path.at(i), parameters);
+ else
+ next = aggregate->findChildNode(path.at(i), genus);
+
+ if ((next == nullptr) && aggregate->isClassNode()) {
+ const ClassList bases = allBaseClasses(static_cast<const ClassNode *>(aggregate));
+ for (auto *base : bases) {
+ if (i == path.size() - 1)
+ next = base->findFunctionChild(path.at(i), parameters);
+ else
+ next = base->findChildNode(path.at(i), genus);
+
+ if (next != nullptr)
+ break;
+ }
+ }
+
+ node = next;
+ } // for (i = 0; i < path.size(); ++i)
+
+ if (node && i == path.size() && node->isFunction()) {
+ // A function node was found at the end of the path.
+ // If it is not marked private, return it. If it is
+ // marked private, then if it overrides a function,
+ // find that function instead because it might not
+ // be marked private. If all the overloads are
+ // marked private, return the original function node.
+ // This should be replace with findOverriddenFunctionNode().
+ const FunctionNode *fn = static_cast<const FunctionNode *>(node);
+ const FunctionNode *FN = fn;
+ while (FN->isPrivate() && !FN->overridesThis().isEmpty()) {
+ QStringList path = FN->overridesThis().split("::");
+ FN = m_qdb->findFunctionNode(path, parameters, relative, genus);
+ if (FN == nullptr)
+ break;
+ if (!FN->isPrivate())
+ return FN;
+ }
+ return fn;
+ }
+ relative = relative->parent();
+ } while (relative);
+ return nullptr;
+}
+
+/*!
+ Search this tree recursively from \a parent to find a function
+ node with the specified \a tag. If no function node is found
+ with the required \a tag, return 0.
+ */
+FunctionNode *Tree::findFunctionNodeForTag(const QString &tag, Aggregate *parent)
+{
+ if (parent == nullptr)
+ parent = root();
+ const NodeList &children = parent->childNodes();
+ for (Node *n : children) {
+ if (n != nullptr && n->isFunction() && n->hasTag(tag))
+ return static_cast<FunctionNode *>(n);
+ }
+ for (Node *n : children) {
+ if (n != nullptr && n->isAggregate()) {
+ n = findFunctionNodeForTag(tag, static_cast<Aggregate *>(n));
+ if (n != nullptr)
+ return static_cast<FunctionNode *>(n);
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ There should only be one macro node for macro name \a t.
+ The macro node is not built until the \macro command is seen.
+ */
+FunctionNode *Tree::findMacroNode(const QString &t, const Aggregate *parent)
+{
+ if (parent == nullptr)
+ parent = root();
+ const NodeList &children = parent->childNodes();
+ for (Node *n : children) {
+ if (n != nullptr && (n->isMacro() || n->isFunction()) && n->name() == t)
+ return static_cast<FunctionNode *>(n);
+ }
+ for (Node *n : children) {
+ if (n != nullptr && n->isAggregate()) {
+ FunctionNode *fn = findMacroNode(t, static_cast<Aggregate *>(n));
+ if (fn != nullptr)
+ return fn;
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Add the class and struct names in \a arg to the \e {don't document}
+ map.
+ */
+void Tree::addToDontDocumentMap(QString &arg)
+{
+ arg.remove(QChar('('));
+ arg.remove(QChar(')'));
+ QString t = arg.simplified();
+ QStringList sl = t.split(QChar(' '));
+ if (sl.isEmpty())
+ return;
+ for (const QString &s : sl) {
+ if (!m_dontDocumentMap.contains(s))
+ m_dontDocumentMap.insert(s, nullptr);
+ }
+}
+
+/*!
+ The \e {don't document} map has been loaded with the names
+ of classes and structs in the current module that are not
+ documented and should not be documented. Now traverse the
+ map, and for each class or struct name, find the class node
+ that represents that class or struct and mark it with the
+ \C DontDocument status.
+
+ This results in a map of the class and struct nodes in the
+ module that are in the public API but are not meant to be
+ used by anyone. They are only used internally, but for one
+ reason or another, they must have public visibility.
+ */
+void Tree::markDontDocumentNodes()
+{
+ for (auto it = m_dontDocumentMap.begin(); it != m_dontDocumentMap.end(); ++it) {
+ Aggregate *node = findAggregate(it.key());
+ if (node != nullptr)
+ node->setStatus(Node::DontDocument);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/tree.h b/src/qdoc/qdoc/src/qdoc/tree.h
new file mode 100644
index 000000000..d0bed25d6
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/tree.h
@@ -0,0 +1,183 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef TREE_H
+#define TREE_H
+
+#include "examplenode.h"
+#include "namespacenode.h"
+#include "node.h"
+#include "propertynode.h"
+#include "proxynode.h"
+#include "qmltypenode.h"
+
+#include <QtCore/qstack.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+class CollectionNode;
+class FunctionNode;
+class QDocDatabase;
+
+struct TargetRec
+{
+public:
+ enum TargetType { Unknown, Target, Keyword, Contents };
+
+ TargetRec(QString name, TargetRec::TargetType type, Node *node, int priority)
+ : m_node(node), m_ref(std::move(name)), m_type(type), m_priority(priority)
+ {
+ // Discard the dedicated ref for keywords - they always
+ // link to the top of the QDoc comment they appear in
+ if (type == Keyword)
+ m_ref.clear();
+ }
+
+ [[nodiscard]] bool isEmpty() const { return m_ref.isEmpty(); }
+ [[nodiscard]] Node::Genus genus() const { return (m_node ? m_node->genus() : Node::DontCare); }
+
+ Node *m_node { nullptr };
+ QString m_ref {};
+ TargetType m_type {};
+ int m_priority {};
+};
+
+typedef QMultiMap<QString, TargetRec *> TargetMap;
+typedef QMultiMap<QString, PageNode *> PageNodeMultiMap;
+typedef QMap<QString, QmlTypeNode *> QmlTypeMap;
+typedef QMultiMap<QString, const ExampleNode *> ExampleNodeMap;
+
+class Tree
+{
+ friend class QDocForest;
+ friend class QDocDatabase;
+
+private: // Note the constructor and destructor are private.
+ typedef QMap<PropertyNode::FunctionRole, QString> RoleMap;
+ typedef QMap<PropertyNode *, RoleMap> PropertyMap;
+
+ Tree(const QString &camelCaseModuleName, QDocDatabase *qdb);
+ ~Tree();
+
+public: // Of necessity, a few public functions remain.
+ [[nodiscard]] Node *findNodeByNameAndType(const QStringList &path,
+ bool (Node::*isMatch)() const) const;
+
+ [[nodiscard]] const QString &camelCaseModuleName() const { return m_camelCaseModuleName; }
+ [[nodiscard]] const QString &physicalModuleName() const { return m_physicalModuleName; }
+ [[nodiscard]] const QString &indexFileName() const { return m_indexFileName; }
+ [[nodiscard]] const QString &indexTitle() const { return m_indexTitle; }
+ void setIndexTitle(const QString &t) { m_indexTitle = t; }
+ NodeList &proxies() { return m_proxies; }
+ void appendProxy(ProxyNode *t) { m_proxies.append(t); }
+ void addToDontDocumentMap(QString &arg);
+ void markDontDocumentNodes();
+ static QString refForAtom(const Atom *atom);
+
+private: // The rest of the class is private.
+ Aggregate *findAggregate(const QString &name);
+ [[nodiscard]] Node *findNodeForInclude(const QStringList &path) const;
+ ClassNode *findClassNode(const QStringList &path, const Node *start = nullptr) const;
+ [[nodiscard]] NamespaceNode *findNamespaceNode(const QStringList &path) const;
+ const FunctionNode *findFunctionNode(const QStringList &path, const Parameters &parameters,
+ const Node *relative, Node::Genus genus) const;
+ Node *findNodeRecursive(const QStringList &path, int pathIndex, const Node *start,
+ bool (Node::*)() const) const;
+ const Node *findNodeForTarget(const QStringList &path, const QString &target, const Node *node,
+ int flags, Node::Genus genus, QString &ref,
+ TargetRec::TargetType *targetType = nullptr) const;
+ const Node *matchPathAndTarget(const QStringList &path, int idx, const QString &target,
+ const Node *node, int flags, Node::Genus genus,
+ QString &ref) const;
+
+ const Node *findNode(const QStringList &path, const Node *relative, int flags,
+ Node::Genus genus) const;
+
+ Aggregate *findRelatesNode(const QStringList &path);
+ const Node *findEnumNode(const Node *node, const Node *aggregate, const QStringList &path, int offset) const;
+ QString getRef(const QString &target, const Node *node) const;
+ void insertTarget(const QString &name, const QString &title, TargetRec::TargetType type,
+ Node *node, int priority);
+ void resolveTargets(Aggregate *root);
+ void addToPageNodeByTitleMap(Node *node);
+ void populateTocSectionTargetMap(Node *node);
+ void addKeywordsToTargetMaps(Node *node);
+ void addTargetsToTargetMap(Node *node);
+
+ const TargetRec *findUnambiguousTarget(const QString &target, Node::Genus genus) const;
+ [[nodiscard]] const PageNode *findPageNodeByTitle(const QString &title) const;
+
+ void addPropertyFunction(PropertyNode *property, const QString &funcName,
+ PropertyNode::FunctionRole funcRole);
+ void resolveBaseClasses(Aggregate *n);
+ void resolvePropertyOverriddenFromPtrs(Aggregate *n);
+ void resolveProperties();
+ void resolveCppToQmlLinks();
+ void resolveSince(Aggregate &aggregate);
+ void resolveEnumValueSince(EnumNode &en);
+ void removePrivateAndInternalBases(NamespaceNode *rootNode);
+ NamespaceNode *root() { return &m_root; }
+ [[nodiscard]] const NamespaceNode *root() const { return &m_root; }
+
+ ClassList allBaseClasses(const ClassNode *classe) const;
+
+ CNMap *getCollectionMap(Node::NodeType type);
+ [[nodiscard]] const CNMap &groups() const { return m_groups; }
+ [[nodiscard]] const CNMap &modules() const { return m_modules; }
+ [[nodiscard]] const CNMap &qmlModules() const { return m_qmlModules; }
+
+ CollectionNode *getCollection(const QString &name, Node::NodeType type);
+ CollectionNode *findCollection(const QString &name, Node::NodeType type);
+
+ CollectionNode *findGroup(const QString &name) { return findCollection(name, Node::Group); }
+ CollectionNode *findModule(const QString &name) { return findCollection(name, Node::Module); }
+ CollectionNode *findQmlModule(const QString &name)
+ {
+ return findCollection(name, Node::QmlModule);
+ }
+
+ CollectionNode *addGroup(const QString &name) { return findGroup(name); }
+ CollectionNode *addModule(const QString &name) { return findModule(name); }
+ CollectionNode *addQmlModule(const QString &name) { return findQmlModule(name); }
+
+ CollectionNode *addToGroup(const QString &name, Node *node);
+ CollectionNode *addToModule(const QString &name, Node *node);
+ CollectionNode *addToQmlModule(const QString &name, Node *node);
+
+ [[nodiscard]] QmlTypeNode *lookupQmlType(const QString &name) const
+ {
+ return m_qmlTypeMap.value(name);
+ }
+ void insertQmlType(const QString &key, QmlTypeNode *n);
+ void addExampleNode(ExampleNode *n) { m_exampleNodeMap.insert(n->title(), n); }
+ ExampleNodeMap &exampleNodeMap() { return m_exampleNodeMap; }
+ void setIndexFileName(const QString &t) { m_indexFileName = t; }
+
+ FunctionNode *findFunctionNodeForTag(const QString &tag, Aggregate *parent = nullptr);
+ FunctionNode *findMacroNode(const QString &t, const Aggregate *parent = nullptr);
+
+private:
+ QString m_camelCaseModuleName {};
+ QString m_physicalModuleName {};
+ QString m_indexFileName {};
+ QString m_indexTitle {};
+ QDocDatabase *m_qdb { nullptr };
+ NamespaceNode m_root;
+ PropertyMap m_unresolvedPropertyMap {};
+ PageNodeMultiMap m_pageNodesByTitle {};
+ TargetMap m_nodesByTargetRef {};
+ TargetMap m_nodesByTargetTitle {};
+ CNMap m_groups {};
+ CNMap m_modules {};
+ CNMap m_qmlModules {};
+ QmlTypeMap m_qmlTypeMap {};
+ ExampleNodeMap m_exampleNodeMap {};
+ NodeList m_proxies {};
+ NodeMap m_dontDocumentMap {};
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/typedefnode.cpp b/src/qdoc/qdoc/src/qdoc/typedefnode.cpp
new file mode 100644
index 000000000..997e570d3
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/typedefnode.cpp
@@ -0,0 +1,55 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "typedefnode.h"
+
+#include "aggregate.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class TypedefNode
+ */
+
+/*!
+ */
+void TypedefNode::setAssociatedEnum(const EnumNode *enume)
+{
+ m_associatedEnum = enume;
+}
+
+/*!
+ Clone this node on the heap and make the clone a child of
+ \a parent.
+
+ Returns the pointer to the clone.
+ */
+Node *TypedefNode::clone(Aggregate *parent)
+{
+ auto *tn = new TypedefNode(*this); // shallow copy
+ tn->setParent(nullptr);
+ parent->addChild(tn);
+
+ return tn;
+}
+
+/*!
+ \class TypeAliasNode
+ */
+
+/*!
+ Clone this node on the heap and make the clone a child of
+ \a parent.
+
+ Returns the pointer to the clone.
+ */
+Node *TypeAliasNode::clone(Aggregate *parent)
+{
+ auto *tan = new TypeAliasNode(*this); // shallow copy
+ tan->setParent(nullptr);
+ parent->addChild(tan);
+
+ return tan;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/typedefnode.h b/src/qdoc/qdoc/src/qdoc/typedefnode.h
new file mode 100644
index 000000000..839cd6a0b
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/typedefnode.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef TYPEDEFNODE_H
+#define TYPEDEFNODE_H
+
+#include "enumnode.h"
+#include "node.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+class TypedefNode : public Node
+{
+public:
+ TypedefNode(Aggregate *parent, const QString &name, NodeType type = Typedef)
+ : Node(type, parent, name)
+ {
+ }
+
+ bool hasAssociatedEnum() const { return m_associatedEnum != nullptr; }
+ const EnumNode *associatedEnum() const { return m_associatedEnum; }
+ Node *clone(Aggregate *parent) override;
+
+private:
+ void setAssociatedEnum(const EnumNode *t);
+
+ friend class EnumNode;
+
+ const EnumNode *m_associatedEnum { nullptr };
+};
+
+class TypeAliasNode : public TypedefNode
+{
+public:
+ TypeAliasNode(Aggregate *parent, const QString &name, const QString &aliasedType)
+ : TypedefNode(parent, name, NodeType::TypeAlias), m_aliasedType(aliasedType)
+ {
+ }
+
+ const QString &aliasedType() const { return m_aliasedType; }
+ Node *clone(Aggregate *parent) override;
+
+private:
+ QString m_aliasedType {};
+};
+
+QT_END_NAMESPACE
+
+#endif // TYPEDEFNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/utilities.cpp b/src/qdoc/qdoc/src/qdoc/utilities.cpp
new file mode 100644
index 000000000..2825804d6
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/utilities.cpp
@@ -0,0 +1,254 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QtCore/qprocess.h>
+#include <QCryptographicHash>
+#include "location.h"
+#include "utilities.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcQdoc, "qt.qdoc")
+Q_LOGGING_CATEGORY(lcQdocClang, "qt.qdoc.clang")
+
+/*!
+ \namespace Utilities
+ \internal
+ \brief This namespace holds QDoc-internal utility methods.
+ */
+namespace Utilities {
+static inline void setDebugEnabled(bool value)
+{
+ const_cast<QLoggingCategory &>(lcQdoc()).setEnabled(QtDebugMsg, value);
+ const_cast<QLoggingCategory &>(lcQdocClang()).setEnabled(QtDebugMsg, value);
+}
+
+void startDebugging(const QString &message)
+{
+ setDebugEnabled(true);
+ qCDebug(lcQdoc, "START DEBUGGING: %ls", qUtf16Printable(message));
+}
+
+void stopDebugging(const QString &message)
+{
+ qCDebug(lcQdoc, "STOP DEBUGGING: %ls", qUtf16Printable(message));
+ setDebugEnabled(false);
+}
+
+bool debugging()
+{
+ return lcQdoc().isEnabled(QtDebugMsg);
+}
+
+/*!
+ \internal
+ Convenience method that's used to get the correct punctuation character for
+ the words at \a wordPosition in a list of \a numberOfWords length.
+ For the last position in the list, returns "." (full stop). For any other
+ word, this method calls comma().
+
+ \sa comma()
+ */
+QString separator(qsizetype wordPosition, qsizetype numberOfWords)
+{
+ static QString terminator = QStringLiteral(".");
+ if (wordPosition == numberOfWords - 1)
+ return terminator;
+ else
+ return comma(wordPosition, numberOfWords);
+}
+
+/*!
+ \internal
+ Convenience method that's used to get the correct punctuation character for
+ the words at \a wordPosition in a list of \a numberOfWords length.
+
+ For a list of length one, returns an empty QString. For a list of length
+ two, returns the string " and ". For any length beyond two, returns the
+ string ", " until the last element, which returns ", and ".
+
+ \sa comma()
+ */
+QString comma(qsizetype wordPosition, qsizetype numberOfWords)
+{
+ if (wordPosition == numberOfWords - 1)
+ return QString();
+ if (numberOfWords == 2)
+ return QStringLiteral(" and ");
+ if (wordPosition == 0 || wordPosition < numberOfWords - 2)
+ return QStringLiteral(", ");
+ return QStringLiteral(", and ");
+}
+
+/*!
+ \brief Returns an ascii-printable representation of \a str.
+
+ Replace non-ascii-printable characters in \a str from a subset of such
+ characters. The subset includes alphanumeric (alnum) characters
+ ([a-zA-Z0-9]), space, punctuation characters, and common symbols. Non-alnum
+ characters in this subset are replaced by a single hyphen. Leading,
+ trailing, and consecutive hyphens are removed, such that the resulting
+ string does not start or end with a hyphen. All characters are converted to
+ lowercase.
+
+ If any character in \a str is non-latin, or latin and not found in the
+ aforementioned subset (e.g. 'ß', 'å', or 'ö'), a hash of \a str is appended
+ to the final string.
+
+ Returns a string that is normalized for use where ascii-printable strings
+ are required, such as file names or fragment identifiers in URLs.
+
+ The implementation is equivalent to:
+
+ \code
+ name.replace(QRegularExpression("[^A-Za-z0-9]+"), " ");
+ name = name.simplified();
+ name.replace(QLatin1Char(' '), QLatin1Char('-'));
+ name = name.toLower();
+ \endcode
+
+ However, it has been measured to be approximately four times faster.
+*/
+QString asAsciiPrintable(const QString &str)
+{
+ auto legal_ascii = [](const uint value) {
+ const uint start_ascii_subset{ 32 };
+ const uint end_ascii_subset{ 126 };
+
+ return value >= start_ascii_subset && value <= end_ascii_subset;
+ };
+
+ QString result;
+ bool begun = false;
+ bool has_non_alnum_content{ false };
+
+ for (const auto &c : str) {
+ char16_t u = c.unicode();
+ if (!legal_ascii(u))
+ has_non_alnum_content = true;
+ if (u >= 'A' && u <= 'Z')
+ u += 'a' - 'A';
+ if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) {
+ result += QLatin1Char(u);
+ begun = true;
+ } else if (begun) {
+ result += QLatin1Char('-');
+ begun = false;
+ }
+ }
+ if (result.endsWith(QLatin1Char('-')))
+ result.chop(1);
+
+ if (has_non_alnum_content) {
+ auto title_hash = QString::fromLocal8Bit(
+ QCryptographicHash::hash(str.toUtf8(), QCryptographicHash::Md5).toHex());
+ title_hash.truncate(8);
+ if (!result.isEmpty())
+ result.append(QLatin1Char('-'));
+ result.append(title_hash);
+ }
+
+ return result;
+}
+
+/*!
+ \internal
+*/
+static bool runProcess(const QString &program, const QStringList &arguments,
+ QByteArray *stdOutIn, QByteArray *stdErrIn)
+{
+ QProcess process;
+ process.start(program, arguments, QProcess::ReadWrite);
+ if (!process.waitForStarted()) {
+ qCDebug(lcQdoc).nospace() << "Unable to start " << process.program()
+ << ": " << process.errorString();
+ return false;
+ }
+ process.closeWriteChannel();
+ const bool finished = process.waitForFinished();
+ const QByteArray stdErr = process.readAllStandardError();
+ if (stdErrIn)
+ *stdErrIn = stdErr;
+ if (stdOutIn)
+ *stdOutIn = process.readAllStandardOutput();
+
+ if (!finished) {
+ qCDebug(lcQdoc).nospace() << process.program() << " timed out: " << stdErr;
+ process.kill();
+ return false;
+ }
+
+ if (process.exitStatus() != QProcess::NormalExit) {
+ qCDebug(lcQdoc).nospace() << process.program() << " crashed: " << stdErr;
+ return false;
+ }
+
+ if (process.exitCode() != 0) {
+ qCDebug(lcQdoc).nospace() << process.program() << " exited with "
+ << process.exitCode() << ": " << stdErr;
+ return false;
+ }
+
+ return true;
+}
+
+/*!
+ \internal
+*/
+static QByteArray frameworkSuffix() {
+ return QByteArrayLiteral(" (framework directory)");
+}
+
+/*!
+ \internal
+ Determine the compiler's internal include paths from the output of
+
+ \badcode
+ [clang++|g++] -E -x c++ - -v </dev/null
+ \endcode
+
+ Output looks like:
+
+ \badcode
+ #include <...> search starts here:
+ /usr/local/include
+ /System/Library/Frameworks (framework directory)
+ End of search list.
+ \endcode
+*/
+QStringList getInternalIncludePaths(const QString &compiler)
+{
+ QStringList result;
+ QStringList arguments;
+ arguments << QStringLiteral("-E") << QStringLiteral("-x") << QStringLiteral("c++")
+ << QStringLiteral("-") << QStringLiteral("-v");
+ QByteArray stdOut;
+ QByteArray stdErr;
+ if (!runProcess(compiler, arguments, &stdOut, &stdErr))
+ return result;
+ const QByteArrayList stdErrLines = stdErr.split('\n');
+ bool isIncludeDir = false;
+ for (const QByteArray &line : stdErrLines) {
+ if (isIncludeDir) {
+ if (line.startsWith(QByteArrayLiteral("End of search list"))) {
+ isIncludeDir = false;
+ } else {
+ QByteArray prefix("-I");
+ QByteArray headerPath{line.trimmed()};
+ if (headerPath.endsWith(frameworkSuffix())) {
+ headerPath.truncate(headerPath.size() - frameworkSuffix().size());
+ prefix = QByteArrayLiteral("-F");
+ }
+ result.append(QString::fromLocal8Bit(prefix + headerPath));
+ }
+ } else if (line.startsWith(QByteArrayLiteral("#include <...> search starts here"))) {
+ isIncludeDir = true;
+ }
+ }
+
+ return result;
+}
+
+} // namespace Utilities
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/utilities.h b/src/qdoc/qdoc/src/qdoc/utilities.h
new file mode 100644
index 000000000..0d485f650
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/utilities.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef UTILITIES_H
+#define UTILITIES_H
+
+#include <QtCore/qstring.h>
+#include <QtCore/qloggingcategory.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcQdoc)
+Q_DECLARE_LOGGING_CATEGORY(lcQdocClang)
+
+namespace Utilities {
+void startDebugging(const QString &message);
+void stopDebugging(const QString &message);
+bool debugging();
+
+QString separator(qsizetype wordPosition, qsizetype numberOfWords);
+QString comma(qsizetype wordPosition, qsizetype numberOfWords);
+QString asAsciiPrintable(const QString &name);
+QStringList getInternalIncludePaths(const QString &compiler);
+}
+
+QT_END_NAMESPACE
+
+#endif // UTILITIES_H
diff --git a/src/qdoc/qdoc/src/qdoc/variablenode.cpp b/src/qdoc/qdoc/src/qdoc/variablenode.cpp
new file mode 100644
index 000000000..11c8363f3
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/variablenode.cpp
@@ -0,0 +1,23 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "variablenode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Clone this node on the heap and make the clone a child of
+ \a parent.
+
+ Returns a pointer to the clone.
+ */
+Node *VariableNode::clone(Aggregate *parent)
+{
+ auto *vn = new VariableNode(*this); // shallow copy
+ vn->setParent(nullptr);
+ parent->addChild(vn);
+
+ return vn;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/variablenode.h b/src/qdoc/qdoc/src/qdoc/variablenode.h
new file mode 100644
index 000000000..7db1252fe
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/variablenode.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef VARIABLENODE_H
+#define VARIABLENODE_H
+
+#include "aggregate.h"
+#include "node.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class VariableNode : public Node
+{
+public:
+ VariableNode(Aggregate *parent, const QString &name);
+
+ void setLeftType(const QString &leftType) { m_leftType = leftType; }
+ void setRightType(const QString &rightType) { m_rightType = rightType; }
+ void setStatic(bool b) { m_static = b; }
+
+ [[nodiscard]] const QString &leftType() const { return m_leftType; }
+ [[nodiscard]] const QString &rightType() const { return m_rightType; }
+ [[nodiscard]] QString dataType() const { return m_leftType + m_rightType; }
+ [[nodiscard]] bool isStatic() const override { return m_static; }
+ Node *clone(Aggregate *parent) override;
+
+private:
+ QString m_leftType {};
+ QString m_rightType {};
+ bool m_static { false };
+};
+
+inline VariableNode::VariableNode(Aggregate *parent, const QString &name)
+ : Node(Variable, parent, name)
+{
+ setGenus(Node::CPP);
+}
+
+QT_END_NAMESPACE
+
+#endif // VARIABLENODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/webxmlgenerator.cpp b/src/qdoc/qdoc/src/qdoc/webxmlgenerator.cpp
new file mode 100644
index 000000000..c2cc38161
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/webxmlgenerator.cpp
@@ -0,0 +1,903 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "webxmlgenerator.h"
+
+#include "aggregate.h"
+#include "collectionnode.h"
+#include "config.h"
+#include "helpprojectwriter.h"
+#include "node.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "quoter.h"
+#include "utilities.h"
+
+#include <QtCore/qxmlstream.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+static CodeMarker *marker_ = nullptr;
+
+WebXMLGenerator::WebXMLGenerator(FileResolver& file_resolver) : HtmlGenerator(file_resolver) {}
+
+void WebXMLGenerator::initializeGenerator()
+{
+ HtmlGenerator::initializeGenerator();
+}
+
+void WebXMLGenerator::terminateGenerator()
+{
+ HtmlGenerator::terminateGenerator();
+}
+
+QString WebXMLGenerator::format()
+{
+ return "WebXML";
+}
+
+QString WebXMLGenerator::fileExtension() const
+{
+ // As this is meant to be an intermediate format,
+ // use .html for internal references. The name of
+ // the output file is set separately in
+ // beginSubPage() calls.
+ return "html";
+}
+
+/*!
+ Most of the output is generated by QDocIndexFiles and the append() callback.
+ Some pages produce supplementary output while being generated, and that's
+ handled here.
+*/
+qsizetype WebXMLGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker)
+{
+ if (m_supplement && currentWriter)
+ addAtomElements(*currentWriter.data(), atom, relative, marker);
+ return 0;
+}
+
+void WebXMLGenerator::generateCppReferencePage(Aggregate *aggregate, CodeMarker * /* marker */)
+{
+ QByteArray data;
+ QXmlStreamWriter writer(&data);
+ writer.setAutoFormatting(true);
+ beginSubPage(aggregate, Generator::fileName(aggregate, "webxml"));
+ writer.writeStartDocument();
+ writer.writeStartElement("WebXML");
+ writer.writeStartElement("document");
+
+ generateIndexSections(writer, aggregate);
+
+ writer.writeEndElement(); // document
+ writer.writeEndElement(); // WebXML
+ writer.writeEndDocument();
+
+ out() << data;
+ endSubPage();
+}
+
+void WebXMLGenerator::generatePageNode(PageNode *pn, CodeMarker * /* marker */)
+{
+ QByteArray data;
+ currentWriter.reset(new QXmlStreamWriter(&data));
+ currentWriter->setAutoFormatting(true);
+ beginSubPage(pn, Generator::fileName(pn, "webxml"));
+ currentWriter->writeStartDocument();
+ currentWriter->writeStartElement("WebXML");
+ currentWriter->writeStartElement("document");
+
+ generateIndexSections(*currentWriter.data(), pn);
+
+ currentWriter->writeEndElement(); // document
+ currentWriter->writeEndElement(); // WebXML
+ currentWriter->writeEndDocument();
+
+ out() << data;
+ endSubPage();
+}
+
+void WebXMLGenerator::generateExampleFilePage(const Node *en, ResolvedFile resolved_file, CodeMarker* /* marker */)
+{
+ // TODO: [generator-insufficient-structural-abstraction]
+
+ QByteArray data;
+ QXmlStreamWriter writer(&data);
+ writer.setAutoFormatting(true);
+ beginSubPage(en, linkForExampleFile(resolved_file.get_query(), "webxml"));
+ writer.writeStartDocument();
+ writer.writeStartElement("WebXML");
+ writer.writeStartElement("document");
+ writer.writeStartElement("page");
+ writer.writeAttribute("name", resolved_file.get_query());
+ writer.writeAttribute("href", linkForExampleFile(resolved_file.get_query()));
+ const QString title = exampleFileTitle(static_cast<const ExampleNode *>(en), resolved_file.get_query());
+ writer.writeAttribute("title", title);
+ writer.writeAttribute("fulltitle", title);
+ writer.writeAttribute("subtitle", resolved_file.get_query());
+ writer.writeStartElement("description");
+
+ if (Config::instance().get(CONFIG_LOCATIONINFO).asBool()) {
+ writer.writeAttribute("path", resolved_file.get_path());
+ writer.writeAttribute("line", "0");
+ writer.writeAttribute("column", "0");
+ }
+
+ Quoter quoter;
+ Doc::quoteFromFile(en->doc().location(), quoter, resolved_file);
+ QString code = quoter.quoteTo(en->location(), QString(), QString());
+ writer.writeTextElement("code", trimmedTrailing(code, QString(), QString()));
+
+ writer.writeEndElement(); // description
+ writer.writeEndElement(); // page
+ writer.writeEndElement(); // document
+ writer.writeEndElement(); // WebXML
+ writer.writeEndDocument();
+
+ out() << data;
+ endSubPage();
+}
+
+void WebXMLGenerator::generateIndexSections(QXmlStreamWriter &writer, Node *node)
+{
+ marker_ = CodeMarker::markerForFileName(node->location().filePath());
+ auto qdocIndexFiles = QDocIndexFiles::qdocIndexFiles();
+ if (qdocIndexFiles) {
+ qdocIndexFiles->generateIndexSections(writer, node, this);
+ // generateIndexSections does nothing for groups, so handle them explicitly
+ if (node->isGroup())
+ qdocIndexFiles->generateIndexSection(writer, node, this);
+ }
+}
+
+// Handles callbacks from QDocIndexFiles to add documentation to node
+void WebXMLGenerator::append(QXmlStreamWriter &writer, Node *node)
+{
+ Q_ASSERT(marker_);
+
+ writer.writeStartElement("description");
+ if (Config::instance().get(CONFIG_LOCATIONINFO).asBool()) {
+ writer.writeAttribute("path", node->doc().location().filePath());
+ writer.writeAttribute("line", QString::number(node->doc().location().lineNo()));
+ writer.writeAttribute("column", QString::number(node->doc().location().columnNo()));
+ }
+
+ if (node->isTextPageNode())
+ generateRelations(writer, node);
+
+ if (node->isModule()) {
+ writer.writeStartElement("generatedlist");
+ writer.writeAttribute("contents", "classesbymodule");
+ auto *cnn = static_cast<CollectionNode *>(node);
+
+ if (cnn->hasNamespaces()) {
+ writer.writeStartElement("section");
+ writer.writeStartElement("heading");
+ writer.writeAttribute("level", "1");
+ writer.writeCharacters("Namespaces");
+ writer.writeEndElement(); // heading
+ NodeMap namespaces{cnn->getMembers(Node::Namespace)};
+ generateAnnotatedList(writer, node, namespaces);
+ writer.writeEndElement(); // section
+ }
+ if (cnn->hasClasses()) {
+ writer.writeStartElement("section");
+ writer.writeStartElement("heading");
+ writer.writeAttribute("level", "1");
+ writer.writeCharacters("Classes");
+ writer.writeEndElement(); // heading
+ NodeMap classes{cnn->getMembers([](const Node *n){ return n->isClassNode(); })};
+ generateAnnotatedList(writer, node, classes);
+ writer.writeEndElement(); // section
+ }
+ writer.writeEndElement(); // generatedlist
+ }
+
+ m_inLink = m_inSectionHeading = m_hasQuotingInformation = false;
+
+ const Atom *atom = node->doc().body().firstAtom();
+ while (atom)
+ atom = addAtomElements(writer, atom, node, marker_);
+
+ QList<Text> alsoList = node->doc().alsoList();
+ supplementAlsoList(node, alsoList);
+
+ if (!alsoList.isEmpty()) {
+ writer.writeStartElement("see-also");
+ for (const auto &item : alsoList) {
+ const auto *atom = item.firstAtom();
+ while (atom)
+ atom = addAtomElements(writer, atom, node, marker_);
+ }
+ writer.writeEndElement(); // see-also
+ }
+
+ if (node->isExample()) {
+ m_supplement = true;
+ generateRequiredLinks(node, marker_);
+ m_supplement = false;
+ } else if (node->isGroup()) {
+ auto *cn = static_cast<CollectionNode *>(node);
+ if (!cn->noAutoList())
+ generateAnnotatedList(writer, node, cn->members());
+ }
+
+ writer.writeEndElement(); // description
+}
+
+void WebXMLGenerator::generateDocumentation(Node *node)
+{
+ // Don't generate nodes that are already processed, or if they're not supposed to
+ // generate output, ie. external, index or images nodes.
+ if (!node->url().isNull() || node->isExternalPage() || node->isIndexNode())
+ return;
+
+ if (node->isInternal() && !m_showInternal)
+ return;
+
+ if (node->parent()) {
+ if (node->isNamespace() || node->isClassNode() || node->isHeader())
+ generateCppReferencePage(static_cast<Aggregate *>(node), nullptr);
+ else if (node->isCollectionNode()) {
+ if (node->wasSeen()) {
+ // see remarks in base class impl.
+ m_qdb->mergeCollections(static_cast<CollectionNode *>(node));
+ generatePageNode(static_cast<PageNode *>(node), nullptr);
+ }
+ } else if (node->isTextPageNode())
+ generatePageNode(static_cast<PageNode *>(node), nullptr);
+ // else if TODO: anything else?
+ }
+
+ if (node->isAggregate()) {
+ auto *aggregate = static_cast<Aggregate *>(node);
+ for (auto c : aggregate->childNodes()) {
+ if ((c->isAggregate() || c->isTextPageNode() || c->isCollectionNode())
+ && !c->isPrivate())
+ generateDocumentation(c);
+ }
+ }
+}
+
+const Atom *WebXMLGenerator::addAtomElements(QXmlStreamWriter &writer, const Atom *atom,
+ const Node *relative, CodeMarker *marker)
+{
+ bool keepQuoting = false;
+
+ if (!atom)
+ return nullptr;
+
+ switch (atom->type()) {
+ case Atom::AnnotatedList: {
+ const CollectionNode *cn = m_qdb->getCollectionNode(atom->string(), Node::Group);
+ if (cn)
+ generateAnnotatedList(writer, relative, cn->members());
+ } break;
+ case Atom::AutoLink: {
+ const Node *node{nullptr};
+ QString link{};
+
+ if (!m_inLink && !m_inSectionHeading) {
+ link = getAutoLink(atom, relative, &node, Node::API);
+
+ if (!link.isEmpty() && node && node->isDeprecated()
+ && relative->parent() != node && !relative->isDeprecated()) {
+ link.clear();
+ }
+ }
+
+ startLink(writer, atom, node, link);
+
+ writer.writeCharacters(atom->string());
+
+ if (m_inLink) {
+ writer.writeEndElement(); // link
+ m_inLink = false;
+ }
+
+ break;
+ }
+ case Atom::BaseName:
+ break;
+ case Atom::BriefLeft:
+
+ writer.writeStartElement("brief");
+ switch (relative->nodeType()) {
+ case Node::Property:
+ writer.writeCharacters("This property");
+ break;
+ case Node::Variable:
+ writer.writeCharacters("This variable");
+ break;
+ default:
+ break;
+ }
+ if (relative->isProperty() || relative->isVariable()) {
+ QString str;
+ const Atom *a = atom->next();
+ while (a != nullptr && a->type() != Atom::BriefRight) {
+ if (a->type() == Atom::String || a->type() == Atom::AutoLink)
+ str += a->string();
+ a = a->next();
+ }
+ str[0] = str[0].toLower();
+ if (str.endsWith('.'))
+ str.chop(1);
+
+ const QList<QStringView> words = QStringView{str}.split(' ');
+ if (!words.isEmpty()) {
+ QStringView first(words.at(0));
+ if (!(first == u"contains" || first == u"specifies" || first == u"describes"
+ || first == u"defines" || first == u"holds" || first == u"determines"))
+ writer.writeCharacters(" holds ");
+ else
+ writer.writeCharacters(" ");
+ }
+ }
+ break;
+
+ case Atom::BriefRight:
+ if (relative->isProperty() || relative->isVariable())
+ writer.writeCharacters(".");
+
+ writer.writeEndElement(); // brief
+ break;
+
+ case Atom::C:
+ writer.writeStartElement("teletype");
+ if (m_inLink)
+ writer.writeAttribute("type", "normal");
+ else
+ writer.writeAttribute("type", "highlighted");
+
+ writer.writeCharacters(plainCode(atom->string()));
+ writer.writeEndElement(); // teletype
+ break;
+
+ case Atom::Code:
+ if (!m_hasQuotingInformation)
+ writer.writeTextElement(
+ "code", trimmedTrailing(plainCode(atom->string()), QString(), QString()));
+ else
+ keepQuoting = true;
+ break;
+
+ case Atom::CodeBad:
+ writer.writeTextElement("badcode",
+ trimmedTrailing(plainCode(atom->string()), QString(), QString()));
+ break;
+
+ case Atom::CodeQuoteArgument:
+ if (m_quoting) {
+ if (quoteCommand == "dots") {
+ writer.writeAttribute("indent", atom->string());
+ writer.writeCharacters("...");
+ } else {
+ writer.writeCharacters(atom->string());
+ }
+ writer.writeEndElement(); // code
+ keepQuoting = true;
+ }
+ break;
+
+ case Atom::CodeQuoteCommand:
+ if (m_quoting) {
+ quoteCommand = atom->string();
+ writer.writeStartElement(quoteCommand);
+ }
+ break;
+
+ case Atom::ExampleFileLink: {
+ if (!m_inLink) {
+ QString link = linkForExampleFile(atom->string());
+ if (!link.isEmpty())
+ startLink(writer, atom, relative, link);
+ }
+ } break;
+
+ case Atom::ExampleImageLink: {
+ if (!m_inLink) {
+ QString link = atom->string();
+ if (!link.isEmpty())
+ startLink(writer, atom, nullptr, "images/used-in-examples/" + link);
+ }
+ } break;
+
+ case Atom::FootnoteLeft:
+ writer.writeStartElement("footnote");
+ break;
+
+ case Atom::FootnoteRight:
+ writer.writeEndElement(); // footnote
+ break;
+
+ case Atom::FormatEndif:
+ writer.writeEndElement(); // raw
+ break;
+ case Atom::FormatIf:
+ writer.writeStartElement("raw");
+ writer.writeAttribute("format", atom->string());
+ break;
+ case Atom::FormattingLeft: {
+ if (atom->string() == ATOM_FORMATTING_BOLD)
+ writer.writeStartElement("bold");
+ else if (atom->string() == ATOM_FORMATTING_ITALIC)
+ writer.writeStartElement("italic");
+ else if (atom->string() == ATOM_FORMATTING_UNDERLINE)
+ writer.writeStartElement("underline");
+ else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT)
+ writer.writeStartElement("subscript");
+ else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT)
+ writer.writeStartElement("superscript");
+ else if (atom->string() == ATOM_FORMATTING_TELETYPE)
+ writer.writeStartElement("teletype");
+ else if (atom->string() == ATOM_FORMATTING_PARAMETER)
+ writer.writeStartElement("argument");
+ else if (atom->string() == ATOM_FORMATTING_INDEX)
+ writer.writeStartElement("index");
+ } break;
+
+ case Atom::FormattingRight: {
+ if (atom->string() == ATOM_FORMATTING_BOLD)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_ITALIC)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_UNDERLINE)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_TELETYPE)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_PARAMETER)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_INDEX)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_TRADEMARK && appendTrademark(atom))
+ writer.writeCharacters(QChar(0x2122)); // 'TM' symbol
+ }
+ if (m_inLink) {
+ writer.writeEndElement(); // link
+ m_inLink = false;
+ }
+ break;
+
+ case Atom::GeneratedList:
+ writer.writeStartElement("generatedlist");
+ writer.writeAttribute("contents", atom->string());
+ writer.writeEndElement();
+ break;
+
+ // TODO: The other generators treat inlineimage and image
+ // simultaneously as the diffirences aren't big. It should be
+ // possible to do the same for webxmlgenerator instead of
+ // repeating the code.
+
+ // TODO: [generator-insufficient-structural-abstraction]
+ case Atom::Image: {
+ auto maybe_resolved_file{file_resolver.resolve(atom->string())};
+ if (!maybe_resolved_file) {
+ // TODO: [uncentralized-admonition][failed-resolve-file]
+ relative->location().warning(QStringLiteral("Missing image: %1").arg(atom->string()));
+ } else {
+ ResolvedFile file{*maybe_resolved_file};
+ QString file_name{QFileInfo{file.get_path()}.fileName()};
+
+ // TODO: [uncentralized-output-directory-structure]
+ Config::copyFile(relative->doc().location(), file.get_path(), file_name, outputDir() + QLatin1String("/images"));
+
+ writer.writeStartElement("image");
+ // TODO: [uncentralized-output-directory-structure]
+ writer.writeAttribute("href", "images/" + file_name);
+ writer.writeEndElement();
+ // TODO: [uncentralized-output-directory-structure]
+ setImageFileName(relative, "images/" + file_name);
+ }
+ break;
+ }
+ // TODO: [generator-insufficient-structural-abstraction]
+ case Atom::InlineImage: {
+ auto maybe_resolved_file{file_resolver.resolve(atom->string())};
+ if (!maybe_resolved_file) {
+ // TODO: [uncentralized-admonition][failed-resolve-file]
+ relative->location().warning(QStringLiteral("Missing image: %1").arg(atom->string()));
+ } else {
+ ResolvedFile file{*maybe_resolved_file};
+ QString file_name{QFileInfo{file.get_path()}.fileName()};
+
+ // TODO: [uncentralized-output-directory-structure]
+ Config::copyFile(relative->doc().location(), file.get_path(), file_name, outputDir() + QLatin1String("/images"));
+
+ writer.writeStartElement("inlineimage");
+ // TODO: [uncentralized-output-directory-structure]
+ writer.writeAttribute("href", "images/" + file_name);
+ writer.writeEndElement();
+ // TODO: [uncentralized-output-directory-structure]
+ setImageFileName(relative, "images/" + file_name);
+ }
+ break;
+ }
+ case Atom::ImageText:
+ break;
+
+ case Atom::ImportantLeft:
+ writer.writeStartElement("para");
+ writer.writeTextElement("bold", "Important:");
+ writer.writeCharacters(" ");
+ break;
+
+ case Atom::LegaleseLeft:
+ writer.writeStartElement("legalese");
+ break;
+
+ case Atom::LegaleseRight:
+ writer.writeEndElement(); // legalese
+ break;
+
+ case Atom::Link:
+ case Atom::LinkNode:
+ if (!m_inLink) {
+ const Node *node = nullptr;
+ QString link = getLink(atom, relative, &node);
+ if (!link.isEmpty())
+ startLink(writer, atom, node, link);
+ }
+ break;
+
+ case Atom::ListLeft:
+ writer.writeStartElement("list");
+
+ if (atom->string() == ATOM_LIST_BULLET)
+ writer.writeAttribute("type", "bullet");
+ else if (atom->string() == ATOM_LIST_TAG)
+ writer.writeAttribute("type", "definition");
+ else if (atom->string() == ATOM_LIST_VALUE) {
+ if (relative->isEnumType())
+ writer.writeAttribute("type", "enum");
+ else
+ writer.writeAttribute("type", "definition");
+ } else {
+ writer.writeAttribute("type", "ordered");
+ if (atom->string() == ATOM_LIST_UPPERALPHA)
+ writer.writeAttribute("start", "A");
+ else if (atom->string() == ATOM_LIST_LOWERALPHA)
+ writer.writeAttribute("start", "a");
+ else if (atom->string() == ATOM_LIST_UPPERROMAN)
+ writer.writeAttribute("start", "I");
+ else if (atom->string() == ATOM_LIST_LOWERROMAN)
+ writer.writeAttribute("start", "i");
+ else // (atom->string() == ATOM_LIST_NUMERIC)
+ writer.writeAttribute("start", "1");
+ }
+ break;
+
+ case Atom::ListItemNumber:
+ break;
+ case Atom::ListTagLeft: {
+ writer.writeStartElement("definition");
+
+ writer.writeTextElement(
+ "term", plainCode(marker->markedUpEnumValue(atom->next()->string(), relative)));
+ } break;
+
+ case Atom::ListTagRight:
+ writer.writeEndElement(); // definition
+ break;
+
+ case Atom::ListItemLeft:
+ writer.writeStartElement("item");
+ break;
+
+ case Atom::ListItemRight:
+ writer.writeEndElement(); // item
+ break;
+
+ case Atom::ListRight:
+ writer.writeEndElement(); // list
+ break;
+
+ case Atom::NoteLeft:
+ writer.writeStartElement("para");
+ writer.writeTextElement("bold", "Note:");
+ writer.writeCharacters(" ");
+ break;
+
+ // End admonition elements
+ case Atom::ImportantRight:
+ case Atom::NoteRight:
+ case Atom::WarningRight:
+ writer.writeEndElement(); // para
+ break;
+
+ case Atom::Nop:
+ break;
+
+ case Atom::CaptionLeft:
+ case Atom::ParaLeft:
+ writer.writeStartElement("para");
+ break;
+
+ case Atom::CaptionRight:
+ case Atom::ParaRight:
+ writer.writeEndElement(); // para
+ break;
+
+ case Atom::QuotationLeft:
+ writer.writeStartElement("quote");
+ break;
+
+ case Atom::QuotationRight:
+ writer.writeEndElement(); // quote
+ break;
+
+ case Atom::RawString:
+ writer.writeCharacters(atom->string());
+ break;
+
+ case Atom::SectionLeft:
+ writer.writeStartElement("section");
+ writer.writeAttribute("id",
+ Utilities::asAsciiPrintable(Text::sectionHeading(atom).toString()));
+ break;
+
+ case Atom::SectionRight:
+ writer.writeEndElement(); // section
+ break;
+
+ case Atom::SectionHeadingLeft: {
+ writer.writeStartElement("heading");
+ int unit = atom->string().toInt(); // + hOffset(relative)
+ writer.writeAttribute("level", QString::number(unit));
+ m_inSectionHeading = true;
+ } break;
+
+ case Atom::SectionHeadingRight:
+ writer.writeEndElement(); // heading
+ m_inSectionHeading = false;
+ break;
+
+ case Atom::SidebarLeft:
+ case Atom::SidebarRight:
+ break;
+
+ case Atom::SnippetCommand:
+ if (m_quoting) {
+ writer.writeStartElement(atom->string());
+ }
+ break;
+
+ case Atom::SnippetIdentifier:
+ if (m_quoting) {
+ writer.writeAttribute("identifier", atom->string());
+ writer.writeEndElement();
+ keepQuoting = true;
+ }
+ break;
+
+ case Atom::SnippetLocation:
+ if (m_quoting) {
+ const QString &location = atom->string();
+ writer.writeAttribute("location", location);
+ auto maybe_resolved_file{file_resolver.resolve(location)};
+ // const QString resolved = Doc::resolveFile(Location(), location);
+ if (maybe_resolved_file)
+ writer.writeAttribute("path", (*maybe_resolved_file).get_path());
+ else {
+ // TODO: [uncetnralized-admonition][failed-resolve-file]
+ QString details = std::transform_reduce(
+ file_resolver.get_search_directories().cbegin(),
+ file_resolver.get_search_directories().cend(),
+ u"Searched directories:"_s,
+ std::plus(),
+ [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
+ );
+
+ relative->location().warning(u"Cannot find file to quote from: %1"_s.arg(location), details);
+ }
+ }
+ break;
+
+ case Atom::String:
+ writer.writeCharacters(atom->string());
+ break;
+ case Atom::TableLeft:
+ writer.writeStartElement("table");
+ if (atom->string().contains("%"))
+ writer.writeAttribute("width", atom->string());
+ break;
+
+ case Atom::TableRight:
+ writer.writeEndElement(); // table
+ break;
+
+ case Atom::TableHeaderLeft:
+ writer.writeStartElement("header");
+ break;
+
+ case Atom::TableHeaderRight:
+ writer.writeEndElement(); // header
+ break;
+
+ case Atom::TableRowLeft:
+ writer.writeStartElement("row");
+ break;
+
+ case Atom::TableRowRight:
+ writer.writeEndElement(); // row
+ break;
+
+ case Atom::TableItemLeft: {
+ writer.writeStartElement("item");
+ QStringList spans = atom->string().split(",");
+ if (spans.size() == 2) {
+ if (spans.at(0) != "1")
+ writer.writeAttribute("colspan", spans.at(0).trimmed());
+ if (spans.at(1) != "1")
+ writer.writeAttribute("rowspan", spans.at(1).trimmed());
+ }
+ } break;
+ case Atom::TableItemRight:
+ writer.writeEndElement(); // item
+ break;
+
+ case Atom::Target:
+ writer.writeStartElement("target");
+ writer.writeAttribute("name", Utilities::asAsciiPrintable(atom->string()));
+ writer.writeEndElement();
+ break;
+
+ case Atom::WarningLeft:
+ writer.writeStartElement("para");
+ writer.writeTextElement("bold", "Warning:");
+ writer.writeCharacters(" ");
+ break;
+
+ case Atom::UnhandledFormat:
+ case Atom::UnknownCommand:
+ writer.writeCharacters(atom->typeString());
+ break;
+ default:
+ break;
+ }
+
+ m_hasQuotingInformation = keepQuoting;
+ return atom->next();
+}
+
+void WebXMLGenerator::startLink(QXmlStreamWriter &writer, const Atom *atom, const Node *node,
+ const QString &link)
+{
+ QString fullName = link;
+ if (node)
+ fullName = node->fullName();
+ if (!fullName.isEmpty() && !link.isEmpty()) {
+ writer.writeStartElement("link");
+ if (atom && !atom->string().isEmpty())
+ writer.writeAttribute("raw", atom->string());
+ else
+ writer.writeAttribute("raw", fullName);
+ writer.writeAttribute("href", link);
+ writer.writeAttribute("type", targetType(node));
+ if (node) {
+ switch (node->nodeType()) {
+ case Node::Enum:
+ writer.writeAttribute("enum", fullName);
+ break;
+ case Node::Example: {
+ const auto *en = static_cast<const ExampleNode *>(node);
+ const QString fileTitle = atom ? exampleFileTitle(en, atom->string()) : QString();
+ if (!fileTitle.isEmpty()) {
+ writer.writeAttribute("page", fileTitle);
+ break;
+ }
+ }
+ Q_FALLTHROUGH();
+ case Node::Page:
+ writer.writeAttribute("page", fullName);
+ break;
+ case Node::Property: {
+ const auto *propertyNode = static_cast<const PropertyNode *>(node);
+ if (!propertyNode->getters().empty())
+ writer.writeAttribute("getter", propertyNode->getters().at(0)->fullName());
+ } break;
+ default:
+ break;
+ }
+ }
+ m_inLink = true;
+ }
+}
+
+void WebXMLGenerator::endLink(QXmlStreamWriter &writer)
+{
+ if (m_inLink) {
+ writer.writeEndElement(); // link
+ m_inLink = false;
+ }
+}
+
+void WebXMLGenerator::generateRelations(QXmlStreamWriter &writer, const Node *node)
+{
+ if (node && !node->links().empty()) {
+ std::pair<QString, QString> anchorPair;
+ const Node *linkNode;
+
+ for (auto it = node->links().cbegin(); it != node->links().cend(); ++it) {
+
+ linkNode = m_qdb->findNodeForTarget(it.value().first, node);
+
+ if (!linkNode)
+ linkNode = node;
+
+ if (linkNode == node)
+ anchorPair = it.value();
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ writer.writeStartElement("relation");
+ writer.writeAttribute("href", anchorPair.first);
+ writer.writeAttribute("type", targetType(linkNode));
+
+ switch (it.key()) {
+ case Node::StartLink:
+ writer.writeAttribute("meta", "start");
+ break;
+ case Node::NextLink:
+ writer.writeAttribute("meta", "next");
+ break;
+ case Node::PreviousLink:
+ writer.writeAttribute("meta", "previous");
+ break;
+ case Node::ContentsLink:
+ writer.writeAttribute("meta", "contents");
+ break;
+ default:
+ writer.writeAttribute("meta", "");
+ }
+ writer.writeAttribute("description", anchorPair.second);
+ writer.writeEndElement(); // link
+ }
+ }
+}
+
+void WebXMLGenerator::generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative,
+ const NodeMap &nodeMap)
+{
+ generateAnnotatedList(writer, relative, nodeMap.values());
+}
+
+void WebXMLGenerator::generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative,
+ const NodeList &nodeList)
+{
+ writer.writeStartElement("table");
+ writer.writeAttribute("width", "100%");
+
+ for (const auto *node : nodeList) {
+ writer.writeStartElement("row");
+ writer.writeStartElement("item");
+ writer.writeStartElement("para");
+ const QString link = linkForNode(node, relative);
+ startLink(writer, node->doc().body().firstAtom(), node, link);
+ endLink(writer);
+ writer.writeEndElement(); // para
+ writer.writeEndElement(); // item
+
+ writer.writeStartElement("item");
+ writer.writeStartElement("para");
+ writer.writeCharacters(node->doc().briefText().toString());
+ writer.writeEndElement(); // para
+ writer.writeEndElement(); // item
+ writer.writeEndElement(); // row
+ }
+ writer.writeEndElement(); // table
+}
+
+QString WebXMLGenerator::fileBase(const Node *node) const
+{
+ return Generator::fileBase(node);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/webxmlgenerator.h b/src/qdoc/qdoc/src/qdoc/webxmlgenerator.h
new file mode 100644
index 000000000..7065670b2
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/webxmlgenerator.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef WEBXMLGENERATOR_H
+#define WEBXMLGENERATOR_H
+
+#include "codemarker.h"
+#include "htmlgenerator.h"
+#include "qdocindexfiles.h"
+
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/qxmlstream.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+class WebXMLGenerator : public HtmlGenerator, public IndexSectionWriter
+{
+public:
+ WebXMLGenerator(FileResolver& file_resolver);
+
+ void initializeGenerator() override;
+ void terminateGenerator() override;
+ QString format() override;
+ // from IndexSectionWriter
+ void append(QXmlStreamWriter &writer, Node *node) override;
+
+protected:
+ qsizetype generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker) override;
+ void generateCppReferencePage(Aggregate *aggregate, CodeMarker *marker) override;
+ void generatePageNode(PageNode *pn, CodeMarker *marker) override;
+ void generateDocumentation(Node *node) override;
+ void generateExampleFilePage(const Node *en, ResolvedFile file, CodeMarker *marker = nullptr) override;
+ [[nodiscard]] QString fileExtension() const override;
+
+ virtual const Atom *addAtomElements(QXmlStreamWriter &writer, const Atom *atom,
+ const Node *relative, CodeMarker *marker);
+ virtual void generateIndexSections(QXmlStreamWriter &writer, Node *node);
+
+private:
+ void generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative,
+ const NodeMap &nodeMap);
+ void generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative,
+ const NodeList &nodeList);
+ void generateRelations(QXmlStreamWriter &writer, const Node *node);
+ void startLink(QXmlStreamWriter &writer, const Atom *atom, const Node *node,
+ const QString &link);
+ void endLink(QXmlStreamWriter &writer);
+ QString fileBase(const Node *node) const override;
+
+ bool m_hasQuotingInformation { false };
+ QString quoteCommand {};
+ QScopedPointer<QXmlStreamWriter> currentWriter {};
+ bool m_supplement { false };
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/xmlgenerator.cpp b/src/qdoc/qdoc/src/qdoc/xmlgenerator.cpp
new file mode 100644
index 000000000..ffad5259d
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/xmlgenerator.cpp
@@ -0,0 +1,487 @@
+// Copyright (C) 2019 Thibaut Cuvelier
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "xmlgenerator.h"
+
+#include "enumnode.h"
+#include "examplenode.h"
+#include "functionnode.h"
+#include "qdocdatabase.h"
+#include "typedefnode.h"
+
+using namespace Qt::Literals::StringLiterals;
+
+QT_BEGIN_NAMESPACE
+
+const QRegularExpression XmlGenerator::m_funcLeftParen(QStringLiteral("^\\S+(\\(.*\\))"));
+
+XmlGenerator::XmlGenerator(FileResolver& file_resolver) : Generator(file_resolver) {}
+
+/*!
+ Do not display \brief for QML types, document and collection nodes
+ */
+bool XmlGenerator::hasBrief(const Node *node)
+{
+ return !(node->isQmlType() || node->isPageNode() || node->isCollectionNode());
+}
+
+/*!
+ Determines whether the list atom should be shown with three columns
+ (constant-value-description).
+ */
+bool XmlGenerator::isThreeColumnEnumValueTable(const Atom *atom)
+{
+ while (atom && !(atom->type() == Atom::ListRight && atom->string() == ATOM_LIST_VALUE)) {
+ if (atom->type() == Atom::ListItemLeft && !matchAhead(atom, Atom::ListItemRight))
+ return true;
+ atom = atom->next();
+ }
+ return false;
+}
+
+/*!
+ Determines whether the list atom should be shown with just one column (value).
+ */
+bool XmlGenerator::isOneColumnValueTable(const Atom *atom)
+{
+ if (atom->type() != Atom::ListLeft || atom->string() != ATOM_LIST_VALUE)
+ return false;
+
+ while (atom && atom->type() != Atom::ListTagRight)
+ atom = atom->next();
+
+ if (atom) {
+ if (!matchAhead(atom, Atom::ListItemLeft))
+ return false;
+ if (!atom->next())
+ return false;
+ return matchAhead(atom->next(), Atom::ListItemRight);
+ }
+ return false;
+}
+
+/*!
+ Header offset depending on the type of the node
+ */
+int XmlGenerator::hOffset(const Node *node)
+{
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ case Node::Module:
+ return 2;
+ case Node::QmlModule:
+ case Node::QmlValueType:
+ case Node::QmlType:
+ case Node::Page:
+ case Node::Group:
+ return 1;
+ case Node::Enum:
+ case Node::TypeAlias:
+ case Node::Typedef:
+ case Node::Function:
+ case Node::Property:
+ default:
+ return 3;
+ }
+}
+
+/*!
+ Rewrites the brief of this node depending on its first word.
+ Only for properties and variables (does nothing otherwise).
+ */
+void XmlGenerator::rewritePropertyBrief(const Atom *atom, const Node *relative)
+{
+ if (relative->nodeType() != Node::Property && relative->nodeType() != Node::Variable)
+ return;
+ atom = atom->next();
+ if (!atom || atom->type() != Atom::String)
+ return;
+
+ const QString firstWord =
+ atom->string().toLower().section(' ', 0, 0, QString::SectionSkipEmpty);
+ const QStringList words{ "the", "a", "an", "whether", "which" };
+ if (words.contains(firstWord)) {
+ QString str = QLatin1String("This ")
+ + QLatin1String(relative->nodeType() == Node::Property ? "property" : "variable")
+ + QLatin1String(" holds ") + atom->string().left(1).toLower()
+ + atom->string().mid(1);
+ const_cast<Atom *>(atom)->setString(str);
+ }
+}
+
+/*!
+ Returns the type of this atom as an enumeration.
+ */
+Node::NodeType XmlGenerator::typeFromString(const Atom *atom)
+{
+ const auto &name = atom->string();
+ if (name.startsWith(QLatin1String("qml")))
+ return Node::QmlModule;
+ else if (name.startsWith(QLatin1String("groups")))
+ return Node::Group;
+ else
+ return Node::Module;
+}
+
+/*!
+ For images shown in examples, set the image file to the one it
+ will have once the documentation is generated.
+ */
+void XmlGenerator::setImageFileName(const Node *relative, const QString &fileName)
+{
+ if (relative->isExample()) {
+ const auto cen = static_cast<const ExampleNode *>(relative);
+ if (cen->imageFileName().isEmpty()) {
+ auto *en = const_cast<ExampleNode *>(cen);
+ en->setImageFileName(fileName);
+ }
+ }
+}
+
+/*!
+ Handles the differences in lists between list tags and since tags, and
+ returns the content of the list entry \a atom (first member of the pair).
+ It also returns the number of items to skip ahead (second member of the pair).
+ */
+std::pair<QString, int> XmlGenerator::getAtomListValue(const Atom *atom)
+{
+ const Atom *lookAhead = atom->next();
+ if (!lookAhead)
+ return std::pair<QString, int>(QString(), 1);
+
+ QString t = lookAhead->string();
+ lookAhead = lookAhead->next();
+ if (!lookAhead || lookAhead->type() != Atom::ListTagRight)
+ return std::pair<QString, int>(QString(), 1);
+
+ lookAhead = lookAhead->next();
+ int skipAhead;
+ if (lookAhead && lookAhead->type() == Atom::SinceTagLeft) {
+ lookAhead = lookAhead->next();
+ Q_ASSERT(lookAhead && lookAhead->type() == Atom::String);
+ t += QLatin1String(" (since ");
+ if (lookAhead->string().at(0).isDigit())
+ t += QLatin1String("Qt ");
+ t += lookAhead->string() + QLatin1String(")");
+ skipAhead = 4;
+ } else {
+ skipAhead = 1;
+ }
+ return std::pair<QString, int>(t, skipAhead);
+}
+
+/*!
+ Parses the table attributes from the given \a atom.
+ This method returns a pair containing the width (%) and
+ the attribute for this table (either "generic" or
+ "borderless").
+ */
+std::pair<QString, QString> XmlGenerator::getTableWidthAttr(const Atom *atom)
+{
+ QString p0, p1;
+ QString attr = "generic";
+ QString width;
+ if (atom->count() > 0) {
+ p0 = atom->string(0);
+ if (atom->count() > 1)
+ p1 = atom->string(1);
+ }
+ if (!p0.isEmpty()) {
+ if (p0 == QLatin1String("borderless"))
+ attr = p0;
+ else if (p0.contains(QLatin1Char('%')))
+ width = p0;
+ }
+ if (!p1.isEmpty()) {
+ if (p1 == QLatin1String("borderless"))
+ attr = p1;
+ else if (p1.contains(QLatin1Char('%')))
+ width = p1;
+ }
+
+ // Many times, in the documentation, there is a space before the % sign:
+ // this breaks the parsing logic above.
+ if (width == QLatin1String("%")) {
+ // The percentage is typically stored in p0, parse it as an int.
+ bool ok = false;
+ int widthPercentage = p0.toInt(&ok);
+ if (ok) {
+ width = QString::number(widthPercentage) + "%";
+ } else {
+ width = {};
+ }
+ }
+
+ return {width, attr};
+}
+
+/*!
+ Registers an anchor reference and returns a unique
+ and cleaned copy of the reference (the one that should be
+ used in the output).
+ To ensure unicity throughout the document, this method
+ uses the \a refMap cache.
+ */
+QString XmlGenerator::registerRef(const QString &ref, bool xmlCompliant)
+{
+ QString cleanRef = Generator::cleanRef(ref, xmlCompliant);
+
+ for (;;) {
+ QString &prevRef = refMap[cleanRef.toLower()];
+ if (prevRef.isEmpty()) {
+ // This reference has never been met before for this document: register it.
+ prevRef = ref;
+ break;
+ } else if (prevRef == ref) {
+ // This exact same reference was already found. This case typically occurs within refForNode.
+ break;
+ }
+ cleanRef += QLatin1Char('x');
+ }
+ return cleanRef;
+}
+
+/*!
+ Generates a clean and unique reference for the given \a node.
+ This reference may depend on the type of the node (typedef,
+ QML signal, etc.)
+ */
+QString XmlGenerator::refForNode(const Node *node)
+{
+ QString ref;
+ switch (node->nodeType()) {
+ case Node::Enum:
+ ref = node->name() + "-enum";
+ break;
+ case Node::Typedef: {
+ const auto *tdf = static_cast<const TypedefNode *>(node);
+ if (tdf->associatedEnum())
+ return refForNode(tdf->associatedEnum());
+ } Q_FALLTHROUGH();
+ case Node::TypeAlias:
+ ref = node->name() + "-typedef";
+ break;
+ case Node::Function: {
+ const auto fn = static_cast<const FunctionNode *>(node);
+ switch (fn->metaness()) {
+ case FunctionNode::QmlSignal:
+ ref = fn->name() + "-signal";
+ break;
+ case FunctionNode::QmlSignalHandler:
+ ref = fn->name() + "-signal-handler";
+ break;
+ case FunctionNode::QmlMethod:
+ ref = fn->name() + "-method";
+ if (fn->overloadNumber() != 0)
+ ref += QLatin1Char('-') + QString::number(fn->overloadNumber());
+ break;
+ default:
+ if (fn->hasOneAssociatedProperty() && fn->doc().isEmpty()) {
+ return refForNode(fn->associatedProperties()[0]);
+ } else {
+ ref = fn->name();
+ if (fn->overloadNumber() != 0)
+ ref += QLatin1Char('-') + QString::number(fn->overloadNumber());
+ }
+ break;
+ }
+ } break;
+ case Node::SharedComment: {
+ if (!node->isPropertyGroup())
+ break;
+ } Q_FALLTHROUGH();
+ case Node::QmlProperty:
+ if (node->isAttached())
+ ref = node->name() + "-attached-prop";
+ else
+ ref = node->name() + "-prop";
+ break;
+ case Node::Property:
+ ref = node->name() + "-prop";
+ break;
+ case Node::Variable:
+ ref = node->name() + "-var";
+ break;
+ default:
+ break;
+ }
+ return registerRef(ref);
+}
+
+/*!
+ Construct the link string for the \a node and return it.
+ The \a relative node is used to decide whether the link
+ we are generating is in the same file as the target.
+ Note the relative node can be 0, which pretty much
+ guarantees that the link and the target aren't in the
+ same file.
+ */
+QString XmlGenerator::linkForNode(const Node *node, const Node *relative)
+{
+ if (node == nullptr)
+ return QString();
+ if (!node->url().isNull())
+ return node->url();
+ if (fileBase(node).isEmpty())
+ return QString();
+ if (node->isPrivate())
+ return QString();
+
+ QString fn = fileName(node);
+ if (node->parent() && node->parent()->isQmlType() && node->parent()->isAbstract()) {
+ if (Generator::qmlTypeContext()) {
+ if (Generator::qmlTypeContext()->inherits(node->parent())) {
+ fn = fileName(Generator::qmlTypeContext());
+ } else if (node->parent()->isInternal() && !noLinkErrors()) {
+ node->doc().location().warning(
+ QStringLiteral("Cannot link to property in internal type '%1'")
+ .arg(node->parent()->name()));
+ return QString();
+ }
+ }
+ }
+
+ QString link = fn;
+
+ if (!node->isPageNode() || node->isPropertyGroup()) {
+ QString ref = refForNode(node);
+ if (relative && fn == fileName(relative) && ref == refForNode(relative))
+ return QString();
+
+ link += QLatin1Char('#');
+ link += ref;
+ }
+
+ /*
+ If the output is going to subdirectories, the
+ two nodes have different output directories if 'node'
+ was read from index.
+ */
+ if (relative && (node != relative)) {
+ if (useOutputSubdirs() && !node->isExternalPage() && node->isIndexNode())
+ link.prepend("../%1/"_L1.arg(node->tree()->physicalModuleName()));
+ }
+ return link;
+}
+
+/*!
+ This function is called for links, i.e. for words that
+ are marked with the qdoc link command. For autolinks
+ that are not marked with the qdoc link command, the
+ getAutoLink() function is called
+
+ It returns the string for a link found by using the data
+ in the \a atom to search the database. It also sets \a node
+ to point to the target node for that link. \a relative points
+ to the node holding the qdoc comment where the link command
+ was found.
+ */
+QString XmlGenerator::getLink(const Atom *atom, const Node *relative, const Node **node)
+{
+ const QString &t = atom->string();
+
+ if (t.isEmpty())
+ return t;
+
+ if (t.at(0) == QChar('h')) {
+ if (t.startsWith("http:") || t.startsWith("https:"))
+ return t;
+ } else if (t.at(0) == QChar('f')) {
+ if (t.startsWith("file:") || t.startsWith("ftp:"))
+ return t;
+ } else if (t.at(0) == QChar('m')) {
+ if (t.startsWith("mailto:"))
+ return t;
+ }
+ return getAutoLink(atom, relative, node);
+}
+
+/*!
+ This function is called for autolinks, i.e. for words that
+ are not marked with the qdoc link command that qdoc has
+ reason to believe should be links.
+
+ It returns the string for a link found by using the data
+ in the \a atom to search the database. It also sets \a node
+ to point to the target node for that link. \a relative points
+ to the node holding the qdoc comment where the link command
+ was found.
+ */
+QString XmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const Node **node,
+ Node::Genus genus)
+{
+ QString ref;
+
+ *node = m_qdb->findNodeForAtom(atom, relative, ref, genus);
+ if (!(*node))
+ return QString();
+
+ QString link = (*node)->url();
+ if (link.isNull()) {
+ link = linkForNode(*node, relative);
+ } else if (link.isEmpty()) {
+ return link; // Explicit empty url (node is ignored as a link target)
+ }
+ if (!ref.isEmpty()) {
+ qsizetype hashtag = link.lastIndexOf(QChar('#'));
+ if (hashtag != -1)
+ link.truncate(hashtag);
+ link += QLatin1Char('#') + ref;
+ }
+ return link;
+}
+
+std::pair<QString, QString> XmlGenerator::anchorForNode(const Node *node)
+{
+ std::pair<QString, QString> anchorPair;
+
+ anchorPair.first = Generator::fileName(node);
+ if (node->isTextPageNode())
+ anchorPair.second = node->title();
+
+ return anchorPair;
+}
+
+/*!
+ Returns a string describing the \a node type.
+ */
+QString XmlGenerator::targetType(const Node *node)
+{
+ if (!node)
+ return QStringLiteral("external");
+
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ return QStringLiteral("namespace");
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ return QStringLiteral("class");
+ case Node::Page:
+ case Node::Example:
+ return QStringLiteral("page");
+ case Node::Enum:
+ return QStringLiteral("enum");
+ case Node::TypeAlias:
+ return QStringLiteral("alias");
+ case Node::Typedef:
+ return QStringLiteral("typedef");
+ case Node::Property:
+ return QStringLiteral("property");
+ case Node::Function:
+ return QStringLiteral("function");
+ case Node::Variable:
+ return QStringLiteral("variable");
+ case Node::Module:
+ return QStringLiteral("module");
+ default:
+ break;
+ }
+ return QString();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/xmlgenerator.h b/src/qdoc/qdoc/src/qdoc/xmlgenerator.h
new file mode 100644
index 000000000..5f7ba67fd
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/xmlgenerator.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2019 Thibaut Cuvelier
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef XMLGENERATOR_H
+#define XMLGENERATOR_H
+
+#include "node.h"
+#include "generator.h"
+#include "filesystem/fileresolver.h"
+
+#include <QtCore/qmap.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class XmlGenerator : public Generator
+{
+public:
+ explicit XmlGenerator(FileResolver& file_resolver);
+
+protected:
+ QHash<QString, QString> refMap;
+
+ static bool hasBrief(const Node *node);
+ static bool isThreeColumnEnumValueTable(const Atom *atom);
+ static bool isOneColumnValueTable(const Atom *atom);
+ static int hOffset(const Node *node);
+
+ static void rewritePropertyBrief(const Atom *atom, const Node *relative);
+ static Node::NodeType typeFromString(const Atom *atom);
+ static void setImageFileName(const Node *relative, const QString &fileName);
+ static std::pair<QString, int> getAtomListValue(const Atom *atom);
+ static std::pair<QString, QString> getTableWidthAttr(const Atom *atom);
+
+ QString registerRef(const QString &ref, bool xmlCompliant = false);
+ QString refForNode(const Node *node);
+ QString linkForNode(const Node *node, const Node *relative);
+ QString getLink(const Atom *atom, const Node *relative, const Node **node);
+ QString getAutoLink(const Atom *atom, const Node *relative, const Node **node,
+ Node::Genus = Node::DontCare);
+
+ std::pair<QString, QString> anchorForNode(const Node *node);
+
+ static QString targetType(const Node *node);
+
+protected:
+ static const QRegularExpression m_funcLeftParen;
+ const Node *m_linkNode { nullptr };
+};
+
+QT_END_NAMESPACE
+
+#endif // XMLGENERATOR_H