Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
documentationtraverser.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
5
6#include "aggregate.h"
7#include "codemarker.h"
9#include "config.h"
12#include "node.h"
13#include "pagenode.h"
14#include "qmltypenode.h"
15#include "tree.h"
16
17#include <QtCore/qfileinfo.h>
18
20
21using namespace Qt::Literals;
22
23/*!
24 \class DocumentationTraverser
25 \internal
26 \brief Traverses the node tree and dispatches to a handler for documentation
27 generation.
28
29 DocumentationTraverser encapsulates the tree traversal logic that was
30 previously embedded in Generator::generateDocumentation(). It handles:
31
32 \list
33 \li Filtering nodes (URL nodes, index nodes, excluded by policy,
34 external pages).
35 \li Looking up the appropriate CodeMarker for each node.
36 \li Dispatching by node type to the handler's generate* methods.
37 \li Recursively processing child nodes.
38 \endlist
39
40 The actual content generation and file lifecycle are delegated to an
41 DocumentationHandler, enabling different generators to share traversal
42 logic while implementing their own output strategies.
43
44 \section1 Usage
45
46 \code
47 DocumentationTraverser traverser;
48 MyDocumentationHandler handler(writer, resolver);
49 traverser.traverse(rootNode, handler);
50 \endcode
51
52 \sa DocumentationHandler, Generator, TemplateGenerator
53*/
54
55/*!
56 \internal
57 Traverses the node tree starting from \a root, dispatching to \a handler
58 for each documentable node.
59*/
61{
62 traverseNode(root, handler);
63}
64
65/*!
66 \internal
67 Processes a single node and recursively processes its children.
68
69 This method handles:
70 \list
71 \li Filtering nodes that should be skipped.
72 \li Looking up the CodeMarker for syntax highlighting.
73 \li Dispatching by node type to the handler.
74 \li Managing file lifecycle via handler callbacks.
75 \li Recursively processing child nodes.
76 \endlist
77*/
78void DocumentationTraverser::traverseNode(Node *node, DocumentationHandler &handler)
79{
80 if (shouldSkip(node))
81 return;
82
83 CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
84
85 if (node->parent() != nullptr) {
86 if (node->isCollectionNode()) {
87 /*
88 A collection node collects: groups, C++ modules, or QML
89 modules. Testing for a CollectionNode must be done
90 before testing for a TextPageNode because a
91 CollectionNode is a PageNode at this point.
92
93 Don't output an HTML page for the collection node unless
94 the \group, \module, or \qmlmodule command was actually
95 seen by qdoc in the qdoc comment for the node.
96
97 A key prerequisite in this case is the call to
98 mergeCollections(cn). We must determine whether this
99 group, module or QML module has members in other
100 modules. We know at this point that cn's members list
101 contains only members in the current module. Therefore,
102 before outputting the page for cn, we must search for
103 members of cn in the other modules and add them to the
104 members list.
105 */
106 auto *cn = static_cast<CollectionNode *>(node);
107 if (cn->wasSeen()) {
108 handler.mergeCollections(cn);
109 handler.beginDocument(handler.fileName(node));
110 handler.generateCollectionNode(cn, marker);
111 handler.endDocument();
112 } else if (cn->isGenericCollection()) {
113 // Currently used only for the module's related orphans page
114 // but can be generalized for other kinds of collections if
115 // other use cases pop up.
116 QString name = cn->name().toLower();
117 name.replace(QChar(' '), QString("-"));
118 QString filename = cn->tree()->physicalModuleName() + "-" + name + "."
119 + QFileInfo(handler.fileName(node)).suffix();
120 handler.beginDocument(filename);
121 handler.generateGenericCollectionPage(cn, marker);
122 handler.endDocument();
123 }
124 } else if (node->isTextPageNode()) {
125 handler.beginDocument(handler.fileName(node));
126 handler.generatePageNode(static_cast<PageNode *>(node), marker);
127 handler.endDocument();
128 } else if (node->isAggregate()) {
129 if ((node->isClassNode() || node->isHeader() || node->isNamespace())
130 && node->docMustBeGenerated()) {
131 handler.beginDocument(handler.fileName(node));
132 handler.generateCppReferencePage(static_cast<Aggregate *>(node), marker);
133 handler.endDocument();
134 } else if (node->isQmlType()) {
135 handler.beginDocument(handler.fileName(node));
136 auto *qcn = static_cast<QmlTypeNode *>(node);
137 handler.generateQmlTypePage(qcn, marker);
138 handler.endDocument();
139 } else if (node->isProxyNode()) {
140 handler.beginDocument(handler.fileName(node));
141 handler.generateProxyPage(static_cast<Aggregate *>(node), marker);
142 handler.endDocument();
143 }
144 }
145 }
146
147 // Recursively process children
148 if (node->isAggregate()) {
149 auto *aggregate = static_cast<Aggregate *>(node);
150 const NodeList &children = aggregate->childNodes();
151 for (auto *child : children) {
152 if (child->isPageNode()) {
153 traverseNode(child, handler);
154 } else if (!node->parent() && child->isInAPI() && !child->isRelatedNonmember()
155 && !child->doc().isAutoGenerated()) {
156 // Warn if there are documented non-page-generating nodes in the root namespace
157 child->location().warning(
158 u"No documentation generated for %1 '%2' in global scope."_s
159 .arg(child->nodeTypeString(), child->name()),
160 u"Maybe you forgot to use the '\\relates' command?"_s);
161 child->setStatus(Status::DontDocument);
162 } else if (child->isQmlModule() && !child->wasSeen()) {
163 // An undocumented QML module that was constructed as a placeholder
164 auto *qmlModule = static_cast<CollectionNode *>(child);
165 for (const auto *member : qmlModule->members()) {
166 member->location().warning(
167 u"Undocumented QML module '%1' referred by type '%2' or its members"_s
168 .arg(qmlModule->name(), member->name()),
169 u"Maybe you forgot to document '\\qmlmodule %1'?"_s
170 .arg(qmlModule->name()));
171 }
172 } else if (child->isQmlType() && !child->hasDoc()) {
173 // A placeholder QML type with incorrect module identifier
174 auto *qmlType = static_cast<QmlTypeNode *>(child);
175 if (auto qmid = qmlType->logicalModuleName(); !qmid.isEmpty())
176 qmlType->location().warning(
177 u"No such type '%1' in QML module '%2'"_s.arg(qmlType->name(), qmid));
178 }
179 }
180 }
181}
182
183/*!
184 \internal
185 Returns true if the node should be skipped during traversal.
186
187 Nodes are skipped if they:
188 \list
189 \li Have a URL (external documentation).
190 \li Are index nodes (from loaded .index files).
191 \li Are excluded by the inclusion policy.
192 \li Are external pages.
193 \endlist
194*/
195bool DocumentationTraverser::shouldSkip(const Node *node) const
196{
197 if (!node->url().isNull())
198 return true;
199 if (node->isIndexNode())
200 return true;
201
202 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
203 const NodeContext context = node->createContext();
204 if (!InclusionFilter::isIncluded(policy, context))
205 return true;
206
207 if (node->isExternalPage())
208 return true;
209
210 return false;
211}
212
213QT_END_NAMESPACE
Interface handling documentation generation during tree traversal.
virtual void generateQmlTypePage(QmlTypeNode *qcn, CodeMarker *marker)=0
virtual void generateCppReferencePage(Aggregate *aggregate, CodeMarker *marker)=0
virtual void endDocument()=0
virtual void generateCollectionNode(CollectionNode *cn, CodeMarker *marker)=0
virtual void generateGenericCollectionPage(CollectionNode *cn, CodeMarker *marker)=0
virtual void generatePageNode(PageNode *pn, CodeMarker *marker)=0
virtual void mergeCollections(CollectionNode *cn)=0
virtual void generateProxyPage(Aggregate *aggregate, CodeMarker *marker)=0
Traverses the node tree and dispatches to a handler for documentation generation.
void traverse(Node *root, DocumentationHandler &handler)
static bool isIncluded(const InclusionPolicy &policy, const NodeContext &context)
Combined button and popup list for selecting options.
QList< Node * > NodeList
Definition node.h:45
The Node class is the base class for all the nodes in QDoc's parse tree.
bool isExternalPage() const
Returns true if the node type is ExternalPage.
Definition node.h:99
virtual bool docMustBeGenerated() const
This function is called to perform a test to decide if the node must have documentation generated.
Definition node.h:195
bool isNamespace() const
Returns true if the node type is Namespace.
Definition node.h:108
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
Definition node.h:121
bool isHeader() const
Returns true if the node type is HeaderFile.
Definition node.h:105
virtual bool isTextPageNode() const
Returns true if the node is a PageNode but not an Aggregate.
Definition node.h:153
Aggregate * parent() const
Returns the node's parent pointer.
Definition node.h:208
virtual bool isAggregate() const
Returns true if this node is an aggregate, which means it inherits Aggregate and can therefore have c...
Definition node.h:136
const Location & location() const
If this node's definition location is empty, this function returns this node's declaration location.
Definition node.h:231
bool isProxyNode() const
Returns true if the node type is Proxy.
Definition node.h:113
NodeContext createContext() const
Definition node.cpp:175
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
Definition node.h:143
virtual bool isCollectionNode() const
Returns true if this is an instance of CollectionNode.
Definition node.h:144
bool isIndexNode() const
Returns true if this node was created from something in an index file.
Definition node.h:106