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