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
qdocdatabase.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qdocdatabase.h"
5
6#include "aggregate.h"
7#include "atom.h"
9#include "functionnode.h"
10#include "generator.h"
11#include "genustypes.h"
12#include "qdocindexfiles.h"
13#include "qdoclogging.h"
15#include "qmltypenode.h"
16#include "tree.h"
17#include "utilities.h"
18
19#include <QtCore/qregularexpression.h>
20#include <stack>
21
22QT_BEGIN_NAMESPACE
23
24using namespace Qt::StringLiterals;
26
27/*!
28 \class QDocForest
29
30 A class representing a forest of Tree objects.
31
32 This private class manages a collection of Tree objects (a
33 forest) for the singleton QDocDatabase object. It is only
34 accessed by that singleton QDocDatabase object, which is a
35 friend. Each tree in the forest is an instance of class
36 Tree, which is a mostly private class. Both QDocForest and
37 QDocDatabase are friends of Tree and have full access.
38
39 There are two kinds of trees in the forest, differing not
40 in structure but in use. One Tree is the primary tree. It
41 is the tree representing the module being documented. All
42 the other trees in the forest are called index trees. Each
43 one represents the contents of the index file for one of
44 the modules the current module must be able to link to.
45
46 The instances of subclasses of Node in the primary tree
47 will contain documentation in an instance of Doc. The
48 index trees contain no documentation, and each Node in
49 an index tree is marked as an index node.
50
51 Each tree is named with the name of its module.
52
53 The search order is created by searchOrder(), if it has
54 not already been created. The search order and module
55 names arrays have parallel structure, i.e. modulNames_[i]
56 is the module name of the Tree at searchOrder_[i].
57
58 The primary tree is always the first tree in the search
59 order. i.e., when the database is searched, the primary
60 tree is always searched first, unless a specific tree is
61 being searched.
62 */
63
64/*!
65 Destroys the qdoc forest. This requires deleting
66 each Tree in the forest. Note that the forest has
67 been transferred into the search order array, so
68 what is really being used to destroy the forest
69 is the search order array.
70 */
71QDocForest::~QDocForest()
72{
73 for (auto *entry : m_searchOrder)
74 delete entry;
75 m_forest.clear();
76 m_searchOrder.clear();
77 m_indexSearchOrder.clear();
78 m_moduleNames.clear();
79 m_primaryTree = nullptr;
80}
81
82/*!
83 Initializes the forest prior to a traversal and
84 returns a pointer to the primary tree. If the
85 forest is empty, it returns \nullptr.
86 */
87Tree *QDocForest::firstTree()
88{
89 m_currentIndex = 0;
90 return (!searchOrder().isEmpty() ? searchOrder()[0] : nullptr);
91}
92
93/*!
94 Increments the forest's current tree index. If the current
95 tree index is still within the forest, the function returns
96 the pointer to the current tree. Otherwise it returns \nullptr.
97 */
98Tree *QDocForest::nextTree()
99{
100 ++m_currentIndex;
101 return (m_currentIndex < searchOrder().size() ? searchOrder()[m_currentIndex] : nullptr);
102}
103
104/*!
105 \fn Tree *QDocForest::primaryTree()
106
107 Returns the pointer to the primary tree.
108 */
109
110/*!
111 Finds the tree for module \a t in the forest and
112 sets the primary tree to be that tree. After the
113 primary tree is set, that tree is removed from the
114 forest.
115
116 \node It gets re-inserted into the forest after the
117 search order is built.
118 */
119void QDocForest::setPrimaryTree(const QString &t)
120{
121 QString T = t.toLower();
122 m_primaryTree = findTree(T);
123 m_forest.remove(T);
124 if (m_primaryTree == nullptr)
125 qCCritical(lcQdoc) << "Error: Could not set primary tree to" << t;
126}
127
128/*!
129 If the search order array is empty, create the search order.
130 If the search order array is not empty, do nothing.
131 */
132void QDocForest::setSearchOrder(const QStringList &t)
133{
134 if (!m_searchOrder.isEmpty())
135 return;
136
137 /* Allocate space for the search order. */
138 m_searchOrder.reserve(m_forest.size() + 1);
139 m_searchOrder.clear();
140 m_moduleNames.reserve(m_forest.size() + 1);
141 m_moduleNames.clear();
142
143 /* The primary tree is always first in the search order. */
144 QString primaryName = primaryTree()->physicalModuleName();
145 m_searchOrder.append(m_primaryTree);
146 m_moduleNames.append(primaryName);
147 m_forest.remove(primaryName);
148
149 for (const QString &m : t) {
150 if (primaryName != m) {
151 auto it = m_forest.find(m);
152 if (it != m_forest.end()) {
153 m_searchOrder.append(it.value());
154 m_moduleNames.append(m);
155 m_forest.remove(m);
156 }
157 }
158 }
159 /*
160 If any trees remain in the forest, just add them
161 to the search order sequentially, because we don't
162 know any better at this point.
163 */
164 if (!m_forest.isEmpty()) {
165 for (auto it = m_forest.begin(); it != m_forest.end(); ++it) {
166 m_searchOrder.append(it.value());
167 m_moduleNames.append(it.key());
168 }
169 m_forest.clear();
170 }
171
172 /*
173 Rebuild the forest after constructing the search order.
174 It was destroyed during construction of the search order,
175 but it is needed for module-specific searches.
176
177 Note that this loop also inserts the primary tree into the
178 forrest. That is a requirement.
179 */
180 for (int i = 0; i < m_searchOrder.size(); ++i) {
181 if (!m_forest.contains(m_moduleNames.at(i))) {
182 m_forest.insert(m_moduleNames.at(i), m_searchOrder.at(i));
183 }
184 }
185}
186
187/*!
188 Returns an ordered array of Tree pointers that represents
189 the order in which the trees should be searched. The first
190 Tree in the array is the tree for the current module, i.e.
191 the module for which qdoc is generating documentation.
192
193 The other Tree pointers in the array represent the index
194 files that were loaded in preparation for generating this
195 module's documentation. Each Tree pointer represents one
196 index file. The index file Tree points have been ordered
197 heuristically to, hopefully, minimize searching. Thr order
198 will probably be changed.
199
200 If the search order array is empty, this function calls
201 indexSearchOrder(). The search order array is empty while
202 the index files are being loaded, but some searches must
203 be performed during this time, notably searches for base
204 class nodes. These searches require a temporary search
205 order. The temporary order changes throughout the loading
206 of the index files, but it is always the tree for the
207 current index file first, followed by the trees for the
208 index files that have already been loaded. The only
209 ordering required in this temporary search order is that
210 the current tree must be searched first.
211 */
212const QList<Tree *> &QDocForest::searchOrder()
213{
214 if (m_searchOrder.isEmpty())
215 return indexSearchOrder();
216 return m_searchOrder;
217}
218
219/*!
220 There are two search orders used by qdoc when searching for
221 things. The normal search order is returned by searchOrder(),
222 but this normal search order is not known until all the index
223 files have been read. At that point, setSearchOrder() is
224 called.
225
226 During the reading of the index files, the vector holding
227 the normal search order remains empty. Whenever the search
228 order is requested, if that vector is empty, this function
229 is called to return a temporary search order, which includes
230 all the index files that have been read so far, plus the
231 one being read now. That one is prepended to the front of
232 the vector.
233 */
234const QList<Tree *> &QDocForest::indexSearchOrder()
235{
236 if (m_forest.size() > m_indexSearchOrder.size())
237 m_indexSearchOrder.prepend(m_primaryTree);
238 return m_indexSearchOrder;
239}
240
241/*!
242 Create a new Tree for the index file for the specified
243 \a module and add it to the forest. Return the pointer
244 to its root.
245 */
246NamespaceNode *QDocForest::newIndexTree(const QString &module)
247{
248 m_primaryTree = new Tree(module, m_qdb);
249 m_forest.insert(module.toLower(), m_primaryTree);
250 return m_primaryTree->root();
251}
252
253/*!
254 Create a new Tree for use as the primary tree. This tree
255 will represent the primary module. \a module is camel case.
256 */
257void QDocForest::newPrimaryTree(const QString &module)
258{
259 m_primaryTree = new Tree(module, m_qdb);
260}
261
262/*!
263 Searches through the forest for a node named \a targetPath
264 and returns a pointer to it if found. The \a relative node
265 is the starting point. It only makes sense for the primary
266 tree, which is searched first. After the primary tree has
267 been searched, \a relative is set to 0 for searching the
268 other trees, which are all index trees. With relative set
269 to 0, the starting point for each index tree is the root
270 of the index tree.
271
272 If \a targetPath is resolved successfully but it refers to
273 a \\section title, continue the search, keeping the section
274 title as a fallback if no higher-priority targets are found.
275 */
276const Node *QDocForest::findNodeForTarget(QStringList &targetPath, const Node *relative,
277 Genus genus, QString &ref, int findFlags)
278{
279 int flags = SearchBaseClasses | SearchEnumValues | findFlags;
280
281 QString entity = targetPath.takeFirst();
282 QStringList entityPath = entity.split("::");
283
284 QString target;
285 if (!targetPath.isEmpty())
286 target = targetPath.takeFirst();
287
289 const Node *tocNode = nullptr;
290 for (const auto *tree : searchOrder()) {
291 const Node *n = tree->findNodeForTarget(entityPath, target, relative, flags, genus, ref, &type);
292 if (n) {
293 // Targets referring to non-section titles are returned immediately
294 if (type != TargetRec::Contents)
295 return n;
296 if (!tocNode)
297 tocNode = n;
298 }
299 relative = nullptr;
300 }
301 return tocNode;
302}
303
304/*!
305 Finds the FunctionNode for the qualified function name
306 in \a path, that also has the specified \a parameters.
307 Returns a pointer to the first matching function.
308
309 \a relative is a node in the primary tree where the search
310 should begin. It is only used when searching the primary
311 tree. \a genus can be used to force the search to find a
312 C++ function or a QML function.
313 */
314const FunctionNode *QDocForest::findFunctionNode(const QStringList &path,
315 const Parameters &parameters, const Node *relative,
316 Genus genus)
317{
318 for (const auto *tree : searchOrder()) {
319 const FunctionNode *fn = tree->findFunctionNode(path, parameters, relative, genus);
320 if (fn)
321 return fn;
322 relative = nullptr;
323 }
324 return nullptr;
325}
326
327/*! \class QDocDatabase
328 This class provides exclusive access to the qdoc database,
329 which consists of a forrest of trees and a lot of maps and
330 other useful data structures.
331 */
332
333QDocDatabase *QDocDatabase::s_qdocDB = nullptr;
334NodeMap QDocDatabase::s_typeNodeMap;
335NodeMultiMap QDocDatabase::s_obsoleteClasses;
336NodeMultiMap QDocDatabase::s_classesWithObsoleteMembers;
337NodeMultiMap QDocDatabase::s_obsoleteQmlTypes;
338NodeMultiMap QDocDatabase::s_qmlTypesWithObsoleteMembers;
339NodeMultiMap QDocDatabase::s_cppClasses;
340NodeMultiMap QDocDatabase::s_qmlBasicTypes;
341NodeMultiMap QDocDatabase::s_qmlTypes;
342NodeMultiMap QDocDatabase::s_examples;
343NodeMultiMapMap QDocDatabase::s_newClassMaps;
344NodeMultiMapMap QDocDatabase::s_newQmlTypeMaps;
345NodeMultiMapMap QDocDatabase::s_newEnumValueMaps;
346NodeMultiMapMap QDocDatabase::s_newSinceMaps;
347
348/*!
349 Constructs the singleton qdoc database object. The singleton
350 constructs the \a forest_ object, which is also a singleton.
351 \a m_showInternal is normally false. If it is true, qdoc will
352 write documentation for nodes marked \c internal.
353
354 \a singleExec_ is false when qdoc is being used in the standard
355 way of running qdoc twices for each module, first with --prepare
356 and then with --generate. First the --prepare phase is run for
357 each module, then the --generate phase is run for each module.
358
359 When \a singleExec_ is true, qdoc is run only once. During the
360 single execution, qdoc processes the qdocconf files for all the
361 modules sequentially in a loop. Each source file for each module
362 is read exactly once.
363 */
364QDocDatabase::QDocDatabase() : m_forest(this)
365{
366 // nothing
367}
368
369/*!
370 Creates the singleton. Allows only one instance of the class
371 to be created. Returns a pointer to the singleton.
372*/
374{
375 if (s_qdocDB == nullptr) {
376 s_qdocDB = new QDocDatabase;
377 initializeDB();
378 }
379 return s_qdocDB;
380}
381
382/*!
383 Destroys the singleton.
384 */
386{
387 if (s_qdocDB != nullptr) {
388 delete s_qdocDB;
389 s_qdocDB = nullptr;
390 }
391}
392
393/*!
394 Initialize data structures in the singleton qdoc database.
395
396 In particular, the type node map is initialized with a lot
397 type names that don't refer to documented types. For example,
398 many C++ standard types are included. These might be documented
399 here at some point, but for now they are not. Other examples
400 include \c array and \c data, which are just generic names
401 used as place holders in function signatures that appear in
402 the documentation.
403
404 \note Do not add QML basic types into this list as it will
405 break linking to those types.
406 */
407void QDocDatabase::initializeDB()
408{
409 s_typeNodeMap.insert("accepted", nullptr);
410 s_typeNodeMap.insert("actionPerformed", nullptr);
411 s_typeNodeMap.insert("activated", nullptr);
412 s_typeNodeMap.insert("alias", nullptr);
413 s_typeNodeMap.insert("anchors", nullptr);
414 s_typeNodeMap.insert("any", nullptr);
415 s_typeNodeMap.insert("array", nullptr);
416 s_typeNodeMap.insert("autoSearch", nullptr);
417 s_typeNodeMap.insert("axis", nullptr);
418 s_typeNodeMap.insert("backClicked", nullptr);
419 s_typeNodeMap.insert("boomTime", nullptr);
420 s_typeNodeMap.insert("border", nullptr);
421 s_typeNodeMap.insert("buttonClicked", nullptr);
422 s_typeNodeMap.insert("callback", nullptr);
423 s_typeNodeMap.insert("char", nullptr);
424 s_typeNodeMap.insert("clicked", nullptr);
425 s_typeNodeMap.insert("close", nullptr);
426 s_typeNodeMap.insert("closed", nullptr);
427 s_typeNodeMap.insert("cond", nullptr);
428 s_typeNodeMap.insert("data", nullptr);
429 s_typeNodeMap.insert("dataReady", nullptr);
430 s_typeNodeMap.insert("dateString", nullptr);
431 s_typeNodeMap.insert("dateTimeString", nullptr);
432 s_typeNodeMap.insert("datetime", nullptr);
433 s_typeNodeMap.insert("day", nullptr);
434 s_typeNodeMap.insert("deactivated", nullptr);
435 s_typeNodeMap.insert("drag", nullptr);
436 s_typeNodeMap.insert("easing", nullptr);
437 s_typeNodeMap.insert("error", nullptr);
438 s_typeNodeMap.insert("exposure", nullptr);
439 s_typeNodeMap.insert("fatalError", nullptr);
440 s_typeNodeMap.insert("fileSelected", nullptr);
441 s_typeNodeMap.insert("flags", nullptr);
442 s_typeNodeMap.insert("float", nullptr);
443 s_typeNodeMap.insert("focus", nullptr);
444 s_typeNodeMap.insert("focusZone", nullptr);
445 s_typeNodeMap.insert("format", nullptr);
446 s_typeNodeMap.insert("framePainted", nullptr);
447 s_typeNodeMap.insert("from", nullptr);
448 s_typeNodeMap.insert("frontClicked", nullptr);
449 s_typeNodeMap.insert("function", nullptr);
450 s_typeNodeMap.insert("hasOpened", nullptr);
451 s_typeNodeMap.insert("hovered", nullptr);
452 s_typeNodeMap.insert("hoveredTitle", nullptr);
453 s_typeNodeMap.insert("hoveredUrl", nullptr);
454 s_typeNodeMap.insert("imageCapture", nullptr);
455 s_typeNodeMap.insert("imageProcessing", nullptr);
456 s_typeNodeMap.insert("index", nullptr);
457 s_typeNodeMap.insert("initialized", nullptr);
458 s_typeNodeMap.insert("isLoaded", nullptr);
459 s_typeNodeMap.insert("item", nullptr);
460 s_typeNodeMap.insert("key", nullptr);
461 s_typeNodeMap.insert("keysequence", nullptr);
462 s_typeNodeMap.insert("listViewClicked", nullptr);
463 s_typeNodeMap.insert("loadRequest", nullptr);
464 s_typeNodeMap.insert("locale", nullptr);
465 s_typeNodeMap.insert("location", nullptr);
466 s_typeNodeMap.insert("long", nullptr);
467 s_typeNodeMap.insert("message", nullptr);
468 s_typeNodeMap.insert("messageReceived", nullptr);
469 s_typeNodeMap.insert("mode", nullptr);
470 s_typeNodeMap.insert("month", nullptr);
471 s_typeNodeMap.insert("name", nullptr);
472 s_typeNodeMap.insert("number", nullptr);
473 s_typeNodeMap.insert("object", nullptr);
474 s_typeNodeMap.insert("offset", nullptr);
475 s_typeNodeMap.insert("ok", nullptr);
476 s_typeNodeMap.insert("openCamera", nullptr);
477 s_typeNodeMap.insert("openImage", nullptr);
478 s_typeNodeMap.insert("openVideo", nullptr);
479 s_typeNodeMap.insert("padding", nullptr);
480 s_typeNodeMap.insert("parent", nullptr);
481 s_typeNodeMap.insert("path", nullptr);
482 s_typeNodeMap.insert("photoModeSelected", nullptr);
483 s_typeNodeMap.insert("position", nullptr);
484 s_typeNodeMap.insert("precision", nullptr);
485 s_typeNodeMap.insert("presetClicked", nullptr);
486 s_typeNodeMap.insert("preview", nullptr);
487 s_typeNodeMap.insert("previewSelected", nullptr);
488 s_typeNodeMap.insert("progress", nullptr);
489 s_typeNodeMap.insert("puzzleLost", nullptr);
490 s_typeNodeMap.insert("qmlSignal", nullptr);
491 s_typeNodeMap.insert("rectangle", nullptr);
492 s_typeNodeMap.insert("request", nullptr);
493 s_typeNodeMap.insert("requestId", nullptr);
494 s_typeNodeMap.insert("section", nullptr);
495 s_typeNodeMap.insert("selected", nullptr);
496 s_typeNodeMap.insert("send", nullptr);
497 s_typeNodeMap.insert("settingsClicked", nullptr);
498 s_typeNodeMap.insert("shoe", nullptr);
499 s_typeNodeMap.insert("short", nullptr);
500 s_typeNodeMap.insert("signed", nullptr);
501 s_typeNodeMap.insert("sizeChanged", nullptr);
502 s_typeNodeMap.insert("size_t", nullptr);
503 s_typeNodeMap.insert("sockaddr", nullptr);
504 s_typeNodeMap.insert("someOtherSignal", nullptr);
505 s_typeNodeMap.insert("sourceSize", nullptr);
506 s_typeNodeMap.insert("startButtonClicked", nullptr);
507 s_typeNodeMap.insert("state", nullptr);
508 s_typeNodeMap.insert("std::initializer_list", nullptr);
509 s_typeNodeMap.insert("std::list", nullptr);
510 s_typeNodeMap.insert("std::map", nullptr);
511 s_typeNodeMap.insert("std::pair", nullptr);
512 s_typeNodeMap.insert("std::string", nullptr);
513 s_typeNodeMap.insert("std::vector", nullptr);
514 s_typeNodeMap.insert("stringlist", nullptr);
515 s_typeNodeMap.insert("swapPlayers", nullptr);
516 s_typeNodeMap.insert("symbol", nullptr);
517 s_typeNodeMap.insert("t", nullptr);
518 s_typeNodeMap.insert("T", nullptr);
519 s_typeNodeMap.insert("tagChanged", nullptr);
520 s_typeNodeMap.insert("timeString", nullptr);
521 s_typeNodeMap.insert("timeout", nullptr);
522 s_typeNodeMap.insert("to", nullptr);
523 s_typeNodeMap.insert("toggled", nullptr);
524 s_typeNodeMap.insert("type", nullptr);
525 s_typeNodeMap.insert("unsigned", nullptr);
526 s_typeNodeMap.insert("urllist", nullptr);
527 s_typeNodeMap.insert("va_list", nullptr);
528 s_typeNodeMap.insert("value", nullptr);
529 s_typeNodeMap.insert("valueEmitted", nullptr);
530 s_typeNodeMap.insert("videoFramePainted", nullptr);
531 s_typeNodeMap.insert("videoModeSelected", nullptr);
532 s_typeNodeMap.insert("videoRecorder", nullptr);
533 s_typeNodeMap.insert("void", nullptr);
534 s_typeNodeMap.insert("volatile", nullptr);
535 s_typeNodeMap.insert("wchar_t", nullptr);
536 s_typeNodeMap.insert("x", nullptr);
537 s_typeNodeMap.insert("y", nullptr);
538 s_typeNodeMap.insert("zoom", nullptr);
539 s_typeNodeMap.insert("zoomTo", nullptr);
540}
541
542/*! \fn NamespaceNode *QDocDatabase::primaryTreeRoot()
543 Returns a pointer to the root node of the primary tree.
544 */
545
546/*!
547 \fn const CNMap &QDocDatabase::groups()
548 Returns a const reference to the collection of all
549 group nodes in the primary tree.
550*/
551
552/*!
553 \fn const CNMap &QDocDatabase::modules()
554 Returns a const reference to the collection of all
555 module nodes in the primary tree.
556*/
557
558/*!
559 \fn const CNMap &QDocDatabase::qmlModules()
560 Returns a const reference to the collection of all
561 QML module nodes in the primary tree.
562*/
563
564/*! \fn CollectionNode *QDocDatabase::findGroup(const QString &name)
565 Find the group node named \a name and return a pointer
566 to it. If a matching node is not found, add a new group
567 node named \a name and return a pointer to that one.
568
569 If a new group node is added, its parent is the tree root,
570 and the new group node is marked \e{not seen}.
571 */
572
573/*! \fn CollectionNode *QDocDatabase::findModule(const QString &name)
574 Find the module node named \a name and return a pointer
575 to it. If a matching node is not found, add a new module
576 node named \a name and return a pointer to that one.
577
578 If a new module node is added, its parent is the tree root,
579 and the new module node is marked \e{not seen}.
580 */
581
582/*! \fn CollectionNode *QDocDatabase::addGroup(const QString &name)
583 Looks up the group named \a name in the primary tree. If
584 a match is found, a pointer to the node is returned.
585 Otherwise, a new group node named \a name is created and
586 inserted into the collection, and the pointer to that node
587 is returned.
588 */
589
590/*! \fn CollectionNode *QDocDatabase::addModule(const QString &name)
591 Looks up the module named \a name in the primary tree. If
592 a match is found, a pointer to the node is returned.
593 Otherwise, a new module node named \a name is created and
594 inserted into the collection, and the pointer to that node
595 is returned.
596 */
597
598/*! \fn CollectionNode *QDocDatabase::addQmlModule(const QString &name)
599 Looks up the QML module named \a name in the primary tree.
600 If a match is found, a pointer to the node is returned.
601 Otherwise, a new QML module node named \a name is created
602 and inserted into the collection, and the pointer to that
603 node is returned.
604 */
605
606/*! \fn CollectionNode *QDocDatabase::addToGroup(const QString &name, Node *node)
607 Looks up the group node named \a name in the collection
608 of all group nodes. If a match is not found, a new group
609 node named \a name is created and inserted into the collection.
610 Then append \a node to the group's members list, and append the
611 group node to the member list of the \a node. The parent of the
612 \a node is not changed by this function. Returns a pointer to
613 the group node.
614 */
615
616/*! \fn CollectionNode *QDocDatabase::addToModule(const QString &name, Node *node)
617 Looks up the module node named \a name in the collection
618 of all module nodes. If a match is not found, a new module
619 node named \a name is created and inserted into the collection.
620 Then append \a node to the module's members list. The parent of
621 \a node is not changed by this function. Returns the module node.
622 */
623
624/*! \fn Collection *QDocDatabase::addToQmlModule(const QString &name, Node *node)
625 Looks up the QML module named \a name. If it isn't there,
626 create it. Then append \a node to the QML module's member
627 list. The parent of \a node is not changed by this function.
628 */
629
630/*! \fn QmlTypeNode *QDocDatabase::findQmlType(const QString &name)
631 Returns the QML type node identified by the qualified
632 QML type \a name, or \c nullptr if no type was found.
633 */
634
635/*!
636 Returns the QML type node identified by the QML module id
637 \a qmid and QML type \a name, or \c nullptr if no type
638 was found.
639
640 If the QML module id is empty, looks up the QML type by
641 \a name only.
642
643 When \a tree is given, the lookup is restricted to that
644 tree instead of searching the whole forest.
645 */
646QmlTypeNode *QDocDatabase::findQmlType(const QString &qmid, const QString &name,
647 const Node *relative, Tree *tree)
648{
649 if (!qmid.isEmpty()) {
650 QmlTypeNode *qcn = tree
651 ? tree->lookupQmlType(qmid + u"::"_s + name, relative)
652 : m_forest.lookupQmlType(qmid + u"::"_s + name, relative);
653 if (qcn)
654 return qcn;
655 }
656
657 // Try unqualified lookup first (uses context-aware disambiguation)
658 QmlTypeNode *qcn = tree
659 ? tree->lookupQmlType(name, relative)
660 : m_forest.lookupQmlType(name, relative);
661 if (qcn)
662 return qcn;
663
664 // Fallback to path-based search
665 QStringList path(name);
666 Node *node = tree
667 ? tree->findNodeByNameAndType(path, &Node::isQmlType)
668 : findNodeByNameAndType(path, &Node::isQmlType);
669 return static_cast<QmlTypeNode *>(node);
670}
671
672/*!
673 Returns the QML type node identified by the QML module id
674 constructed from the strings in the import \a record and the
675 QML type \a name. Returns \c nullptr if no type was not found.
676 */
677QmlTypeNode *QDocDatabase::findQmlType(const ImportRec &record, const QString &name, const Node *relative)
678{
679 if (record.isEmpty())
680 return nullptr;
681
682 QString type{name};
683
684 // If the import is under a namespace (id) and the type name is not prefixed with that id,
685 // then we know the type is not available under this import.
686 if (!record.m_importId.isEmpty()) {
687 const QString namespacePrefix{"%1."_L1.arg(record.m_importId)};
688 if (!type.startsWith(namespacePrefix))
689 return nullptr;
690 type.remove(0, namespacePrefix.size());
691 }
692
693 const QString qmName = record.m_importUri.isEmpty() ? record.m_moduleName : record.m_importUri;
694 return m_forest.lookupQmlType(qmName + u"::"_s + type, relative);
695}
696
697/*!
698 Returns the QML node identified by the QML module id \a qmid
699 and \a name, searching in the primary tree only. If \a qmid
700 is an empty string, searches for the node using name only.
701
702 Returns \c nullptr if no node was found.
703*/
704QmlTypeNode *QDocDatabase::findQmlTypeInPrimaryTree(const QString &qmid, const QString &name)
705{
706 if (!qmid.isEmpty())
707 return primaryTree()->lookupQmlType(qmid + u"::"_s + name);
708 return static_cast<QmlTypeNode *>(primaryTreeRoot()->findChildNode(name, Genus::QML, TypesOnly));
709}
710
711/*!
712 This function calls a set of functions for each tree in the
713 forest that has not already been analyzed. In this way, when
714 running qdoc in \e singleExec mode, each tree is analyzed in
715 turn, and its classes and types are added to the appropriate
716 node maps.
717 */
719{
720 processForest(&QDocDatabase::findAllClasses);
721 processForest(&QDocDatabase::findAllFunctions);
722 processForest(&QDocDatabase::findAllObsoleteThings);
723 processForest(&QDocDatabase::findAllLegaleseTexts);
724 processForest(&QDocDatabase::findAllSince);
725 processForest(&QDocDatabase::findAllAttributions);
727}
728
729/*!
730 This function calls \a func for each tree in the forest,
731 ensuring that \a func is called only once per tree.
732
733 \sa processForest()
734 */
735void QDocDatabase::processForest(FindFunctionPtr func)
736{
737 Tree *t = m_forest.firstTree();
738 while (t) {
739 if (!m_completedFindFunctions.values(t).contains(func)) {
740 (this->*(func))(t->root());
741 m_completedFindFunctions.insert(t, func);
742 }
743 t = m_forest.nextTree();
744 }
745}
746
747/*!
748 Returns a reference to the collection of legalese texts.
749 */
751{
752 processForest(&QDocDatabase::findAllLegaleseTexts);
753 return m_legaleseTexts;
754}
755
756/*!
757 Returns a reference to the map of C++ classes with obsolete members.
758 */
760{
761 processForest(&QDocDatabase::findAllObsoleteThings);
762 return s_classesWithObsoleteMembers;
763}
764
765/*!
766 Returns a reference to the map of obsolete QML types.
767 */
769{
770 processForest(&QDocDatabase::findAllObsoleteThings);
771 return s_obsoleteQmlTypes;
772}
773
774/*!
775 Returns a reference to the map of QML types with obsolete members.
776 */
778{
779 processForest(&QDocDatabase::findAllObsoleteThings);
780 return s_qmlTypesWithObsoleteMembers;
781}
782
783/*!
784 Returns a reference to the map of QML basic types.
785 */
787{
788 processForest(&QDocDatabase::findAllClasses);
789 return s_qmlBasicTypes;
790}
791
792/*!
793 Returns a reference to the multimap of QML types.
794 */
796{
797 processForest(&QDocDatabase::findAllClasses);
798 return s_qmlTypes;
799}
800
801/*!
802 Returns a reference to the multimap of example nodes.
803 */
805{
806 processForest(&QDocDatabase::findAllClasses);
807 return s_examples;
808}
809
810/*!
811 Returns a reference to the multimap of attribution nodes.
812 */
814{
815 processForest(&QDocDatabase::findAllAttributions);
816 return m_attributions;
817}
818
819/*!
820 Returns a reference to the map of obsolete C++ clases.
821 */
823{
824 processForest(&QDocDatabase::findAllObsoleteThings);
825 return s_obsoleteClasses;
826}
827
828/*!
829 Returns a reference to the map of all C++ classes.
830 */
832{
833 processForest(&QDocDatabase::findAllClasses);
834 return s_cppClasses;
835}
836
837/*!
838 Returns the function index. This data structure is used to
839 output the function index page.
840 */
842{
843 processForest(&QDocDatabase::findAllFunctions);
844 return m_functionIndex;
845}
846
847/*!
848 Finds all the nodes containing legalese text and puts them
849 in a map.
850 */
851void QDocDatabase::findAllLegaleseTexts(Aggregate *node)
852{
853 for (const auto &childNode : node->childNodes()) {
854 if (childNode->isPrivate())
855 continue;
856 if (!childNode->doc().legaleseText().isEmpty())
857 m_legaleseTexts.insert(childNode->doc().legaleseText(), childNode);
858 if (childNode->isAggregate())
859 findAllLegaleseTexts(static_cast<Aggregate *>(childNode));
860 }
861}
862
863/*!
864 \fn void QDocDatabase::findAllObsoleteThings(Aggregate *node)
865
866 Finds all nodes with status = Deprecated and sorts them into
867 maps. They can be C++ classes, QML types, or they can be
868 functions, enum types, typedefs, methods, etc.
869 */
870
871/*!
872 \fn void QDocDatabase::findAllSince(Aggregate *node)
873
874 Finds all the nodes in \a node where a \e{since} command appeared
875 in the qdoc comment and sorts them into maps according to the kind
876 of node.
877
878 This function is used for generating the "New Classes... in x.y"
879 section on the \e{What's New in Qt x.y} page.
880 */
881
882/*!
883 \fn const CollectionNode *QDocDatabase::findConceptNode(const QString &name)
884
885 Non-creating, cross-tree lookup. findConcept()/findCollection() fabricate a
886 placeholder on a miss, which would shadow a dependency-module concept with an
887 empty primary-tree node and emit a dangling local href. getCollectionNode()
888 searches the same forest order without that side effect.
889 */
890
891/*!
892 Find the \a key in the map of new class maps, and return a
893 reference to the value, which is a NodeMap. If \a key is not
894 found, return a reference to an empty NodeMap.
895 */
896const NodeMultiMap &QDocDatabase::getClassMap(const QString &key)
897{
898 processForest(&QDocDatabase::findAllSince);
899 auto it = s_newClassMaps.constFind(key);
900 return (it != s_newClassMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
901}
902
903/*!
904 Find the \a key in the map of new QML type maps, and return a
905 reference to the value, which is a NodeMap. If the \a key is not
906 found, return a reference to an empty NodeMap.
907 */
908const NodeMultiMap &QDocDatabase::getQmlTypeMap(const QString &key)
909{
910 processForest(&QDocDatabase::findAllSince);
911 auto it = s_newQmlTypeMaps.constFind(key);
912 return (it != s_newQmlTypeMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
913}
914
915/*!
916 Find the \a key in the map of new \e {since} maps, and return
917 a reference to the value, which is a NodeMultiMap. If \a key
918 is not found, return a reference to an empty NodeMultiMap.
919 */
920const NodeMultiMap &QDocDatabase::getSinceMap(const QString &key)
921{
922 processForest(&QDocDatabase::findAllSince);
923 auto it = s_newSinceMaps.constFind(key);
924 return (it != s_newSinceMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
925}
926
927/*!
928 Performs several housekeeping tasks prior to generating the
929 documentation. These tasks create required data structures
930 and resolve links.
931
932 The order of operations is important in some cases. For example,
933 the call to removePrivateAndInternalBases must follow the call to
934 markUndocumentedChildrenInternal to ensure correct behavior.
935 */
937{
938 const auto &config = Config::instance();
939 if (config.dualExec() || config.preparing()) {
940 // order matters
941 primaryTree()->resolveBaseClasses(primaryTreeRoot());
942 primaryTree()->resolvePropertyOverriddenFromPtrs(primaryTreeRoot());
947 primaryTree()->validatePropertyDocumentation(primaryTreeRoot());
949 primaryTree()->removePrivateAndInternalBases(primaryTreeRoot());
951 primaryTree()->resolveTargets(primaryTreeRoot());
952 primaryTree()->resolveCppToQmlLinks();
953 primaryTree()->resolveSince(*primaryTreeRoot());
955 }
956 if (config.singleExec() && config.generating()) {
957 primaryTree()->resolveBaseClasses(primaryTreeRoot());
958 primaryTree()->resolvePropertyOverriddenFromPtrs(primaryTreeRoot());
960 primaryTree()->resolveCppToQmlLinks();
961 primaryTree()->resolveSince(*primaryTreeRoot());
963 }
964 if (!config.preparing()) {
969 }
970 if (config.dualExec())
971 QDocIndexFiles::destroyQDocIndexFiles();
972}
973
975{
976 Tree *t = m_forest.firstTree();
977 while (t) {
978 t->resolveBaseClasses(t->root());
979 if (t != primaryTree())
980 t->root()->resolveQmlInheritance();
981 t = m_forest.nextTree();
982 }
983}
984
985/*!
986 Gathers the fully-qualified concept names referenced by \a node.
987 Template-head and direct-concept references live on the optional
988 RelaxedTemplateDeclaration carried by the node; trailing-requires
989 and constrained-auto references are accumulated on the FunctionNode.
990 */
991static void collectConceptReferences(const Node *node, QStringList &refs)
992{
993 if (const auto &td = node->templateDecl(); td.has_value()) {
994 for (const auto &name : td->referenced_concepts)
995 refs.append(QString::fromStdString(name));
996 }
997 if (node->isFunction()) {
998 const auto *fn = static_cast<const FunctionNode *>(node);
999 refs += fn->referencedConcepts();
1000 }
1001}
1002
1003/*!
1004 Walks every documented node under \a parent and registers each constrained
1005 item as a member of the concept's CollectionNode for each concept it
1006 references.
1007
1008 Recurses into aggregates so the entire primary tree is covered.
1009 */
1011{
1012 for (auto *child : std::as_const(parent->childNodes())) {
1013 // A skipped node is not registered as a concept user, but its
1014 // documented descendants still are: a documented class nested in an
1015 // internal namespace, for example, should still appear in its concept's
1016 // "Used by" list. Visibility filtering and recursion are therefore
1017 // independent.
1018 const bool registerChild =
1019 !child->isPrivate() && !child->isInternal() && !child->isDontDocument();
1020
1021 if (registerChild) {
1022 QStringList refs;
1023 collectConceptReferences(child, refs);
1024 refs.sort();
1025 refs.removeDuplicates();
1026
1027 for (const QString &conceptName : std::as_const(refs)) {
1028 // Cross-tree lookup. The forest search order covers the primary
1029 // tree plus every dependency-module index tree. A concept
1030 // declared in module B and referenced from module A can be
1031 // found through the same cross-tree lookup machinery.
1032 // Concepts without a \\concept block anywhere in the corpus
1033 // return \c{nullptr} and are silently skipped; references to
1034 // undocumented concepts (such as std::integral) are common and
1035 // not actionable.
1036 //
1037 // addMember() deduplicates membership centrally, so re-running
1038 // this pass is safe and produces a stable "Used by" listing
1039 // without per-call guard logic.
1040 if (auto *cn = db.findMutableCollectionNode(conceptName, NodeType::Concept))
1041 cn->addMember(child);
1042 }
1043 }
1044
1045 if (child->isAggregate())
1046 registerConceptUsersUnder(static_cast<Aggregate *>(child), db);
1047 }
1048}
1049
1050/*!
1051 Builds the concept-to-users reverse index after parsing finishes
1052 and before any generator runs. The forward direction — which
1053 concepts a constrained declaration references — is captured
1054 during the libclang AST pass; this pass turns those isolated
1055 references into bidirectional collection membership so concept
1056 reference pages can render a \e {Used by} section through the
1057 same scaffolding group and module collection pages already use.
1058 */
1060{
1061 registerConceptUsersUnder(primaryTreeRoot(), *this);
1062}
1063
1064/*!
1065 Returns a reference to the namespace map. Constructs the
1066 namespace map if it hasn't been constructed yet.
1067
1068 \note This function must not be called in the prepare phase.
1069 */
1071{
1073 return m_namespaceIndex;
1074}
1075
1076/*!
1077 Multiple namespace nodes for namespace X can exist in the
1078 qdoc database in different trees. This function first finds
1079 all namespace nodes in all the trees and inserts them into
1080 a multimap. Then it combines all the namespace nodes that
1081 have the same name into a single namespace node of that
1082 name and inserts that combined namespace node into an index.
1083 */
1085{
1086 if (!m_namespaceIndex.isEmpty())
1087 return;
1088
1089 bool linkErrors = !Config::instance().get(CONFIG_NOLINKERRORS).asBool();
1090 NodeMultiMap namespaceMultimap;
1091 Tree *t = m_forest.firstTree();
1092 while (t) {
1093 t->root()->findAllNamespaces(namespaceMultimap);
1094 t = m_forest.nextTree();
1095 }
1096 const QList<QString> keys = namespaceMultimap.uniqueKeys();
1097 for (const QString &key : keys) {
1098 NamespaceNode *ns = nullptr;
1099 NamespaceNode *indexNamespace = nullptr;
1100 const NodeList namespaces = namespaceMultimap.values(key);
1101 qsizetype count = namespaceMultimap.remove(key);
1102 if (count > 0) {
1103 for (auto *node : namespaces) {
1104 ns = static_cast<NamespaceNode *>(node);
1105 if (ns->isDocumentedHere())
1106 break;
1107 else if (ns->hadDoc())
1108 indexNamespace = ns; // namespace was documented but in another tree
1109 ns = nullptr;
1110 }
1111 if (ns) {
1112 for (auto *node : namespaces) {
1113 auto *nsNode = static_cast<NamespaceNode *>(node);
1114 if (nsNode->hadDoc() && nsNode != ns) {
1115 ns->doc().location().warning(
1116 QStringLiteral("Namespace %1 documented more than once")
1117 .arg(nsNode->name()), QStringLiteral("also seen here: %1")
1118 .arg(nsNode->doc().location().toString()));
1119 }
1120 }
1121 } else if (!indexNamespace) {
1122 // Warn about documented children in undocumented namespaces.
1123 // As the namespace can be documented outside this project,
1124 // skip the warning if --no-link-errors is set
1125 if (linkErrors) {
1126 for (auto *node : namespaces) {
1127 if (!node->isIndexNode())
1128 static_cast<NamespaceNode *>(node)->reportDocumentedChildrenInUndocumentedNamespace();
1129 }
1130 }
1131 } else {
1132 for (auto *node : namespaces) {
1133 auto *nsNode = static_cast<NamespaceNode *>(node);
1134 if (nsNode != indexNamespace)
1135 nsNode->setDocNode(indexNamespace);
1136 }
1137 }
1138 }
1139 /*
1140 If there are multiple namespace nodes with the same
1141 name where one of them will be the main reference page
1142 for the namespace, include all nodes in the public
1143 API of the namespace.
1144 */
1145 if (ns && count > 1) {
1146 for (auto *node : namespaces) {
1147 auto *nameSpaceNode = static_cast<NamespaceNode *>(node);
1148 if (nameSpaceNode != ns) {
1149 for (auto it = nameSpaceNode->constBegin(); it != nameSpaceNode->constEnd();
1150 ++it) {
1151 Node *anotherNs = *it;
1152 if (anotherNs && anotherNs->isPublic() && !anotherNs->isInternal())
1153 ns->includeChild(anotherNs);
1154 }
1155 }
1156 }
1157 }
1158 /*
1159 Add the main namespace reference node to index, or the last seen
1160 namespace if the main one was not found.
1161 */
1162 if (!ns)
1163 ns = indexNamespace ? indexNamespace : static_cast<NamespaceNode *>(namespaces.last());
1164 m_namespaceIndex.insert(ns->name(), ns);
1165 }
1166}
1167
1168/*!
1169 Each instance of class Tree that represents an index file
1170 must be traversed to find all instances of class ProxyNode.
1171 For each ProxyNode found, look up the ProxyNode's name in
1172 the primary Tree. If it is found, it means that the proxy
1173 node contains elements (normally just functions) that are
1174 documented in the module represented by the Tree containing
1175 the proxy node but that are related to the node we found in
1176 the primary tree.
1177 */
1179{
1180 // The first tree is the primary tree.
1181 // Skip the primary tree.
1182 Tree *t = m_forest.firstTree();
1183 t = m_forest.nextTree();
1184 while (t) {
1185 const NodeList &proxies = t->proxies();
1186 if (!proxies.isEmpty()) {
1187 for (auto *node : proxies) {
1188 const auto *pn = static_cast<ProxyNode *>(node);
1189 if (pn->count() > 0) {
1190 Aggregate *aggregate = primaryTree()->findAggregate(pn->name());
1191 if (aggregate != nullptr)
1192 aggregate->appendToRelatedByProxy(pn->childNodes());
1193 }
1194 }
1195 }
1196 t = m_forest.nextTree();
1197 }
1198}
1199
1200/*!
1201 Finds the function node for the qualified function path in
1202 \a target and returns a pointer to it. The \a target is a
1203 function signature with or without parameters but without
1204 the return type.
1205
1206 \a relative is the node in the primary tree where the search
1207 begins. It is not used in the other trees, if the node is not
1208 found in the primary tree. \a genus can be used to force the
1209 search to find a C++ function or a QML function.
1210
1211 The entire forest is searched, but the first match is accepted.
1212 */
1213const FunctionNode *QDocDatabase::findFunctionNode(const QString &target, const Node *relative,
1214 Genus genus)
1215{
1216 QString function = target;
1217 const QString signature = Parameters::extractParameters(function);
1218 const Parameters parameters(signature.trimmed());
1219 if (!parameters.isValid())
1220 return nullptr;
1221
1222 const QStringList path = function.split("::"_L1);
1223 return m_forest.findFunctionNode(path, parameters, relative, genus);
1224}
1225
1226/*!
1227 This function is called for autolinking to a \a type,
1228 which could be a function return type or a parameter
1229 type. The tree node that represents the \a type is
1230 returned. All the trees are searched until a match is
1231 found. When searching the primary tree, the search
1232 begins at \a relative and proceeds up the parent chain.
1233 When searching the index trees, the search begins at the
1234 root.
1235 */
1236const Node *QDocDatabase::findTypeNode(const QString &type, const Node *relative, Genus genus)
1237{
1238 // For QML contexts with qualified names containing ".", try import-aware lookup first
1239 if ((genus == Genus::QML || (relative && relative->genus() == Genus::QML)) &&
1240 type.contains('.') && !type.contains("::")) {
1241 if (relative && relative->isQmlType()) {
1242 const QmlTypeNode *qmlType = static_cast<const QmlTypeNode*>(relative);
1243 const ImportList &imports = qmlType->importList();
1244
1245 for (const auto &import : imports) {
1246 if (QmlTypeNode *found = findQmlType(import, type)) {
1247 return found;
1248 }
1249 }
1250 }
1251
1252 // Fall back to regular path-based lookup for QML qualified names
1253 QStringList path = type.split(".");
1254 if ((path.size() == 1) && (path.at(0)[0].isLower() || path.at(0) == QString("T"))) {
1255 auto it = s_typeNodeMap.find(path.at(0));
1256 if (it != s_typeNodeMap.end())
1257 return it.value();
1258 }
1259
1260 // Try the full qualified path first
1261 const Node *node = m_forest.findTypeNode(path, relative, genus);
1262 if (node)
1263 return node;
1264
1265 // If the full path fails and we have multiple segments, try just the last segment
1266 // This handles cases like "TM.BaseType" where "TM" is an alias we can't resolve
1267 // but "BaseType" might be findable as a QML type
1268 if (path.size() > 1) {
1269 const Node *lastSegmentNode = m_forest.findTypeNode(QStringList{path.last()}, relative, genus);
1270 if (lastSegmentNode && lastSegmentNode->isQmlType())
1271 return lastSegmentNode;
1272 }
1273
1274 return nullptr;
1275 }
1276
1277 // For C++ contexts or QML types with "::" notation, use C++ path splitting
1278 QStringList path = type.split("::");
1279 if ((path.size() == 1) && (path.at(0)[0].isLower() || path.at(0) == QString("T"))) {
1280 auto it = s_typeNodeMap.find(path.at(0));
1281 if (it != s_typeNodeMap.end())
1282 return it.value();
1283 }
1284 return m_forest.findTypeNode(path, relative, genus);
1285}
1286
1287/*!
1288 Finds the node that will generate the documentation that
1289 contains the \a target and returns a pointer to it.
1290
1291 Can this be improved by using the target map in Tree?
1292 */
1293const Node *QDocDatabase::findNodeForTarget(const QString &target, const Node *relative)
1294{
1295 const Node *node = nullptr;
1296 if (target.isEmpty())
1297 node = relative;
1298 else if (target.endsWith(".html"))
1299 node = findNodeByNameAndType(QStringList(target), &Node::isPageNode);
1300 else {
1301 QStringList path = target.split("::");
1302 int flags = SearchBaseClasses | SearchEnumValues;
1303 for (const auto *tree : searchOrder()) {
1304 const Node *n = tree->findNode(path, relative, flags, Genus::DontCare);
1305 if (n)
1306 return n;
1307 relative = nullptr;
1308 }
1309 node = findPageNodeByTitle(target);
1310 }
1311 return node;
1312}
1313
1314/*!
1315 Finds the node for \a target relative to \a relative, with genus and
1316 module scoping.
1317
1318 When \a moduleName names a loaded module, the search is restricted
1319 to that module's tree. Targets with a trailing parameter list use
1320 function-specific lookup.
1321
1322 A target written in QML dot-path notation, such as
1323 \c {Type.property} or \c {Module.Type}, resolves through the
1324 import-aware QML lookup when \a origin is \l {LinkReferenceOrigin}
1325 {LinkReferenceOrigin::Explicit} and the \a genus is QML or
1326 DontCare. Automatically produced references keep the historical
1327 interpretation, and an explicit non-QML genus remains a
1328 restriction the QML lookup does not bypass. URL-like targets, HTML
1329 filenames, and titles containing spaces keep their ordinary
1330 interpretation.
1331
1332 QML lookup uses the import list and logical module of the QML type
1333 enclosing \a relative to interpret references in member documentation.
1334 General lookup keeps \a relative as its context. Ordinary QML name
1335 lookup takes precedence over import-alias expansion. Explicit QML genus
1336 links retain type-only lookup as a fallback.
1337
1338 The \a searchFlags preserve link criteria, including restrictions to
1339 attached properties, through both QML and general lookup.
1340
1341 A fragment in a QML dot-path target must exist in the target
1342 documentation. When \a ref is non-null, it receives the fragment's
1343 identifier on success.
1344*/
1345const Node *QDocDatabase::findNodeForTarget(const QString &target, const Node *relative,
1346 Genus genus, const QString &moduleName,
1347 QString *ref, LinkReferenceOrigin origin,
1348 int searchFlags)
1349{
1350 if (target.isEmpty())
1351 return relative;
1352
1353 Tree *domain = moduleName.isEmpty() ? nullptr : findTree(moduleName);
1354 const int flags = SearchBaseClasses | SearchEnumValues | searchFlags;
1355
1356 const Node *qmlContext = relative;
1357 while (qmlContext && !qmlContext->isQmlType()) {
1358 const Node *parent = qmlContext->parent();
1359 if (!parent || parent->genus() != Genus::QML)
1360 break;
1361 qmlContext = parent;
1362 }
1363
1364 QStringList targetPath = Utilities::pathAndFragment(target);
1365 const QString first = targetPath.first().trimmed();
1366 QString qmlName = first;
1367 Parameters::extractParameters(qmlName);
1368
1369 const bool qmlDotPath =
1370 origin == LinkReferenceOrigin::Explicit
1371 && (genus == Genus::DontCare || genus == Genus::QML)
1372 && qmlName.contains('.'_L1)
1373 && !qmlName.contains("::"_L1)
1374 && !qmlName.contains("://"_L1)
1375 && !qmlName.contains(' '_L1)
1376 && !qmlName.endsWith(".html"_L1);
1377
1378 auto withFragment = [targetPath, ref](const Node *node) -> const Node * {
1379 QString localRef;
1380 node = setReferenceFromFragment(targetPath.value(1), node, localRef);
1381 if (node && ref)
1382 *ref = localRef;
1383 return node;
1384 };
1385
1386 auto findQmlDotPathNode = [this, qmlContext, domain, &withFragment, genus, flags](const QString &path)
1387 -> const Node *
1388 {
1389 const Node *node = findQmlNode(path, qmlContext, FindFlags::fromInt(flags), domain);
1390
1391 if (!node && qmlContext && qmlContext->isQmlType()) {
1392 const QmlTypeNode *qmlRelative = static_cast<const QmlTypeNode *>(qmlContext);
1393 const QStringList pieces = path.split('.'_L1);
1394 if (pieces.size() >= 2) {
1395 for (const ImportRec &import : qmlRelative->importList()) {
1396 if (import.m_importId.isEmpty() || import.m_importId != pieces.first())
1397 continue;
1398 const QString qmid = import.m_importUri.isEmpty()
1399 ? import.m_moduleName
1400 : import.m_importUri;
1401 const QString qualifiedPath = qmid + path.mid(pieces.first().size());
1402 node = findQmlNode(qualifiedPath, qmlContext, FindFlags::fromInt(flags),
1403 domain);
1404 if (node)
1405 break;
1406 }
1407 }
1408 }
1409
1410 if (!node && genus == Genus::QML)
1411 node = findTypeNode(path, qmlContext, genus);
1412
1413 // The type-only fallback can return a node from another tree.
1414 if (node && domain && node->tree() != domain)
1415 node = nullptr;
1416 return withFragment(node);
1417 };
1418
1419 if (domain) {
1420 const Node *node = nullptr;
1421 if (target.endsWith(".html"_L1)) {
1422 node = domain->findNodeByNameAndType(QStringList(target), &Node::isPageNode);
1423 } else if (target.endsWith(')'_L1)) {
1424 QString function = target;
1425 const QString signature = Parameters::extractParameters(function);
1426 const Parameters parameters(signature.trimmed());
1427
1428 const QStringList path = function.split("::"_L1);
1429 if (parameters.isValid())
1430 node = domain->findFunctionNode(path, parameters, nullptr, genus);
1431 }
1432 if (node)
1433 return node;
1434
1435 if (qmlDotPath) {
1436 if (const Node *qmlNode = findQmlDotPathNode(first))
1437 return qmlNode;
1438 }
1439
1440 QStringList nodePath = target.split("::"_L1);
1441 if (relative && relative->tree()->physicalModuleName() != domain->physicalModuleName())
1442 relative = nullptr;
1443 QString localRef;
1444 const Node *result =
1445 domain->findNodeForTarget(nodePath, {}, relative, flags, genus, localRef);
1446 if (result && ref)
1447 *ref = localRef;
1448 return result;
1449 }
1450
1451 if (target.endsWith(".html"_L1))
1452 return findNodeByNameAndType(QStringList(target), &Node::isPageNode);
1453
1454 if (target.endsWith(')'_L1)) {
1455 const Node *node = findFunctionNode(target, relative, genus);
1456 if (node)
1457 return node;
1458 }
1459
1460 if (qmlDotPath) {
1461 if (const Node *qmlNode = findQmlDotPathNode(first))
1462 return qmlNode;
1463 }
1464
1465 QString localRef;
1466 const Node *node = findNodeForTarget(targetPath, relative, genus, localRef, flags);
1467 if (node) {
1468 if (ref)
1469 *ref = localRef;
1470 return node;
1471 }
1472
1473 return findPageNodeByTitle(target);
1474}
1475
1477{
1478 QStringList result;
1479 CNMap *m = primaryTree()->getCollectionMap(NodeType::Group);
1480
1481 if (!m)
1482 return result;
1483
1484 for (auto it = m->cbegin(); it != m->cend(); ++it)
1485 if (it.value()->members().contains(node))
1486 result << it.key();
1487
1488 return result;
1489}
1490
1491/*!
1492 Reads and parses the qdoc index files listed in \a indexFiles.
1493 */
1494void QDocDatabase::readIndexes(const QStringList &indexFiles)
1495{
1496 QStringList filesToRead;
1497 for (const QString &file : indexFiles) {
1498 QString fn = file.mid(file.lastIndexOf(QChar('/')) + 1);
1499 if (!isLoaded(fn))
1500 filesToRead << file;
1501 else
1502 qCCritical(lcQdoc) << "Index file" << file << "is already in memory.";
1503 }
1504 QDocIndexFiles::qdocIndexFiles()->readIndexes(filesToRead);
1505}
1506
1507/*!
1508 Generates a qdoc index file and writes it to \a fileName. The
1509 index file is generated with the parameters \a url, \a title,
1510 and \a context, which supplies publication locations and serialization
1511 policy independently of the rendering implementation.
1512 */
1513void QDocDatabase::generateIndex(const QString &fileName, const QString &url, const QString &title,
1514 const IndexSerializationContext &context)
1515{
1516 QString t = fileName.mid(fileName.lastIndexOf(QChar('/')) + 1);
1517 primaryTree()->setIndexFileName(t);
1518 QDocIndexFiles::qdocIndexFiles()->generateIndex(fileName, url, title, context);
1519 QDocIndexFiles::destroyQDocIndexFiles();
1520}
1521
1522/*!
1523 Returns the collection node representing the module that \a relative
1524 node belongs to, or \c nullptr if there is no such module in the
1525 primary tree.
1526*/
1528{
1529 NodeType moduleType{NodeType::Module};
1530 QString moduleName;
1531 switch (relative->genus())
1532 {
1533 case Genus::CPP:
1534 moduleType = NodeType::Module;
1535 moduleName = relative->physicalModuleName();
1536 break;
1537 case Genus::QML:
1538 moduleType = NodeType::QmlModule;
1539 moduleName = relative->logicalModuleName();
1540 break;
1541 default:
1542 return nullptr;
1543 }
1544 if (moduleName.isEmpty())
1545 return nullptr;
1546
1547 return primaryTree()->getCollection(moduleName, moduleType);
1548}
1549
1550/*!
1551 Finds all the collection nodes of the specified \a type
1552 and merges them into the collection node map \a cnm. Nodes
1553 that match the \a relative node are not included.
1554 */
1555void QDocDatabase::mergeCollections(NodeType type, CNMap &cnm, const Node *relative)
1556{
1557 cnm.clear();
1558 CNMultiMap cnmm;
1559 for (auto *tree : searchOrder()) {
1560 CNMap *m = tree->getCollectionMap(type);
1561 if (m && !m->isEmpty()) {
1562 for (auto it = m->cbegin(); it != m->cend(); ++it) {
1563 if (!it.value()->isInternal())
1564 cnmm.insert(it.key(), it.value());
1565 }
1566 }
1567 }
1568 if (cnmm.isEmpty())
1569 return;
1570 static const QRegularExpression singleDigit("\\b([0-9])\\b");
1571 const QStringList keys = cnmm.uniqueKeys();
1572 for (const auto &key : keys) {
1573 const QList<CollectionNode *> values = cnmm.values(key);
1574 CollectionNode *n = nullptr;
1575 for (auto *value : values) {
1576 if (value && value->wasSeen() && value != relative) {
1577 n = value;
1578 break;
1579 }
1580 }
1581 if (n) {
1582 if (values.size() > 1) {
1583 for (CollectionNode *value : values) {
1584 if (value != n) {
1585 // Allow multiple (major) versions of QML modules
1586 if ((n->isQmlModule())
1587 && n->logicalModuleIdentifier() != value->logicalModuleIdentifier()) {
1588 if (value->wasSeen() && value != relative)
1589 cnm.insert(value->fullTitle().toLower(), value);
1590 continue;
1591 }
1592 for (Node *t : value->members())
1593 n->addMember(t);
1594 }
1595 }
1596 }
1597 QString sortKey = n->fullTitle().toLower();
1598 if (sortKey.startsWith("the "))
1599 sortKey.remove(0, 4);
1600 sortKey.replace(singleDigit, "0\\1");
1601 cnm.insert(sortKey, n);
1602 }
1603 }
1604}
1605
1606/*!
1607 Finds all the collection nodes with the same name
1608 and type as \a c and merges their members into the
1609 members list of \a c.
1610
1611 For QML modules, only nodes with matching
1612 module identifiers are merged to avoid merging
1613 modules with different (major) versions.
1614 */
1616{
1617 if (c == nullptr)
1618 return;
1619
1620 // REMARK: This form of merging is usually called during the
1621 // generation phase om-the-fly when a source-of-truth collection
1622 // is required.
1623 // In practice, this means a collection could be merged many, many
1624 // times during the lifetime of a generation.
1625 // To avoid repeating the merging process each time, which could
1626 // be time consuming, we use a small flag that is set directly on
1627 // the collection to bail-out early.
1628 //
1629 // The merging process is only meaningful for collections when the
1630 // collection references are spread troughout multiple projects.
1631 // The part of information that exists in other project is read
1632 // before the generation phase, such that when the generation
1633 // phase comes, we already have all the information we need for
1634 // merging such that we can consider all version of a certain
1635 // collection node immutable, making the caching inherently
1636 // correct at any point of the generation.
1637 //
1638 // This implies that this operation is unsafe if it is performed
1639 // before all the index files are loaded.
1640 // Indeed, this is a prerequisite, with the current structure, to
1641 // perform this optmization.
1642 //
1643 // At the current time, this is true and is expected not to
1644 // change.
1645 //
1646 // Do note that this is not applied to the other overload of
1647 // mergeCollections as we cannot as safely ensure its consistency
1648 // and, as the result of the merging depends on multiple
1649 // parameters, it would require an actual memoization of the call.
1650 //
1651 // Note that this is a defensive optimization and we are assuming
1652 // that it is effective based on heuristical data. As this is
1653 // expected to disappear, at least in its current form, in the
1654 // future, a more thorough analysis was not performed.
1655 if (c->isMerged()) {
1656 return;
1657 }
1658
1659 for (auto *tree : searchOrder()) {
1660 CollectionNode *cn = tree->getCollection(c->name(), c->nodeType());
1661 if (cn && cn != c) {
1662 if ((cn->isQmlModule())
1663 && cn->logicalModuleIdentifier() != c->logicalModuleIdentifier())
1664 continue;
1665
1666 for (auto *node : cn->members())
1667 c->addMember(node);
1668
1669 // REMARK: The merging process is performed to ensure that
1670 // references to the collection in external projects are
1671 // taken into account before consuming the collection.
1672 //
1673 // This works by having QDoc construct empty collections
1674 // as soon as a reference to a collection is encountered
1675 // and filling details later on when its definition is
1676 // found.
1677 //
1678 // This initially-empty collection is always saved to the
1679 // primaryTree and it is the collection that is directly
1680 // accessible to consumers during the generation process.
1681 //
1682 // Nonetheless, when the definition for the collection is
1683 // not in the same project as the one that is being
1684 // compiled, its details will never be filled in.
1685 //
1686 // Indeed, the details will live in the index file for the
1687 // project where the collection is defined, if any, and
1688 // the node for it, which has complete information, will
1689 // live in some non-primaryTree.
1690 //
1691 // The merging process itself is used by consumers during
1692 // the generation process because they access the
1693 // primaryTree version of the collection expecting a
1694 // source-of-truth.
1695 // To ensure that this is the case for usages that
1696 // requires linking, we need to merge not only the members
1697 // of the collection that reside in external versions of
1698 // the collection; but some of the data that reside in the
1699 // definition of the collection intself, namely the title
1700 // and the url.
1701 //
1702 // A collection that contains the data of a definition is
1703 // always marked as seen, hence we use that to discern
1704 // whether we are working with a placeholder node or not,
1705 // and fill in the data if we encounter a node that
1706 // represents a definition.
1707 //
1708 // The way in which QDoc works implies that collection are
1709 // globally scoped between projects.
1710 // The repetition of the definition for the same
1711 // collection is warned for as a duplicate documentation,
1712 // such that we can expect a single valid source of truth
1713 // for a given collection in each project.
1714 // It is currently unknown if this warning is applicable
1715 // when the repeated collection is defined in two
1716 // different projects.
1717 //
1718 // As QDoc implicitly would not correctly support this
1719 // case, we assume that only one declaration exists for
1720 // each collection, such that the first encoutered one
1721 // must be the source of truth and that there is no need
1722 // to copy any data after the first copy is performed.
1723 // KLUDGE: Note that this process is done as a hackish
1724 // solution to QTBUG-104237 and should not be considered
1725 // final or dependable.
1726 if (!c->wasSeen() && cn->wasSeen()) {
1727 c->markSeen();
1728 c->setTitle(cn->title());
1729 c->setUrl(cn->url());
1730 c->setResolvedPhysicalModuleName(cn->tree()->physicalModuleName());
1731 }
1732 }
1733 }
1734
1736}
1737
1738/*!
1739 Returns the node to a piece of QML API documentation that corresponds to
1740 the given \a path, or \c nullptr if the path cannot be resolved.
1741 The \a relative node specifies where the link or reference was used.
1742 The \a flags let the caller specify or restrict the types of nodes to
1743 find or prioritize, such as attached properties instead of regular
1744 properties.
1745
1746 Searches only \a tree when provided, or the whole forest otherwise.
1747
1748 This function doesn't handle the use of fragments to refer to elements in
1749 the target documentation.
1750*/
1751const Node *QDocDatabase::findQmlNode(const QString &path, const Node *relative, FindFlags flags,
1752 Tree *tree)
1753{
1754 QString qmlPath = path;
1755 const QString signature = Parameters::extractParameters(qmlPath);
1756 const bool findFunction = !signature.isNull();
1757 QStringList pieces;
1758 for (const QString &piece : qmlPath.split('.'_L1))
1759 pieces.append(piece.split("::"_L1));
1760
1761 // Try to resolve a leading QML module name.
1762 QString candidateName;
1763 const Node *moduleNode = nullptr;
1764 qsizetype modulePrefixSize = 0;
1765 for (; modulePrefixSize < pieces.size(); ++modulePrefixSize) {
1766 candidateName += pieces.at(modulePrefixSize);
1767 const Node *node = tree
1768 ? tree->findNodeByNameAndType({candidateName}, &Node::isQmlModule)
1769 : findNodeByNameAndType({candidateName}, &Node::isQmlModule);
1770 if (!node)
1771 break;
1772
1773 moduleNode = node;
1774 candidateName += '.'_L1;
1775 }
1776
1777 if (modulePrefixSize == pieces.size())
1778 return findFunction ? nullptr : moduleNode;
1779
1780 QmlTypeNode *qmlType = nullptr;
1781 if (moduleNode) {
1782 pieces.remove(0, modulePrefixSize);
1783 qmlType = findQmlType(moduleNode->name(), pieces.takeFirst(), relative, tree);
1784 } else
1785 qmlType = findQmlType(pieces.takeFirst(), relative, tree);
1786
1787 if (!qmlType || pieces.isEmpty())
1788 return findFunction ? nullptr : qmlType;
1789
1790 const auto searchType = flags.testFlag(QmlAttachedProperties)
1792 const auto findProperty = [searchType](const QmlTypeNode *type, const QString &name)
1793 -> const QmlPropertyNode * {
1794 const QmlPropertyNode *property = type->hasQmlProperty(name, searchType);
1795 if (!property && searchType == Aggregate::UnattachedProperties)
1796 property = type->hasQmlProperty(name, Aggregate::AttachedProperties);
1797 return property;
1798 };
1799
1800 // Consume a property only when its type can be followed. Otherwise the
1801 // remaining name may be a documented group such as "values.integer".
1802 while (pieces.size() > 1) {
1803 const QmlPropertyNode *property = findProperty(qmlType, pieces.first());
1804 if (!property)
1805 break;
1806
1807 QmlTypeNode *const nextType = moduleNode
1808 ? findQmlType(moduleNode->name(), property->dataType(), relative, tree)
1809 : findQmlType(property->dataType(), relative, tree);
1810 if (!nextType)
1811 break;
1812
1813 qmlType = nextType;
1814 pieces.removeFirst();
1815 }
1816
1817 if (findFunction) {
1818 if (flags.testFlag(QmlAttachedProperties) || pieces.size() != 1)
1819 return nullptr;
1820 const Parameters parameters(signature.trimmed());
1821 if (!parameters.isValid())
1822 return nullptr;
1823 return qmlType->findFunctionChild(pieces.first(), parameters);
1824 }
1825
1826 const QString memberName = pieces.join('.'_L1);
1827 if (const QmlPropertyNode *property = findProperty(qmlType, memberName))
1828 return property;
1829
1830 if (flags.testFlag(QmlAttachedProperties))
1831 return nullptr;
1832 return qmlType->findChildNode(memberName, Genus::QML);
1833}
1834
1835/*!
1836 Resolves \a fragment in the documentation of \a node and stores its
1837 reference in \a ref. Returns \c nullptr if the fragment cannot be
1838 resolved. An empty fragment leaves the node unchanged.
1839*/
1840const Node *QDocDatabase::setReferenceFromFragment(const QString &fragment, const Node *node,
1841 QString &ref)
1842{
1843 if (!node || fragment.isEmpty())
1844 return node;
1845
1846 ref = node->tree()->getRef(fragment, node);
1847 return ref.isEmpty() ? nullptr : node;
1848}
1849
1850/*!
1851 Searches for the node that matches the path in \a atom and the
1852 specified \a genus. The \a relative node is used if the first
1853 leg of the path is empty, i.e. if the path begins with '#'.
1854 The function also sets \a ref if there remains an unused leg
1855 in the path after the node is found. The node is returned as
1856 well as the \a ref. If the returned node pointer is null,
1857 \a ref is also not valid.
1858 */
1859const Node *QDocDatabase::findNodeForAtom(const Atom *a, const Node *relative, QString &ref,
1860 Genus genus)
1861{
1862 const Node *node = nullptr;
1863
1864 Atom *atom = const_cast<Atom *>(a);
1865 QStringList targetPath = Utilities::pathAndFragment(atom->string());
1866 QString first = targetPath.first().trimmed();
1867 const QString fragment = targetPath.value(1);
1868
1869 Tree *domain = nullptr;
1870
1871 if (atom->isLinkAtom() || atom->type() == Atom::Link) {
1872 if (auto *atomDomain = atom->domain())
1873 domain = atomDomain;
1874 if (atom->genus() != Genus::DontCare)
1875 genus = atom->genus();
1876
1877 QString qmlName = first;
1878 Parameters::extractParameters(qmlName);
1879 if (!qmlName.contains("://"_L1) && !qmlName.contains(' '_L1)
1880 && !qmlName.endsWith(".html"_L1) && qmlName.contains('.'_L1)) {
1881 const auto flags = FindFlags::fromInt(a->flags());
1882 if (const Node *found = findQmlNode(first, relative, flags))
1883 return setReferenceFromFragment(fragment, found, ref);
1884 }
1885 }
1886
1887 if (first.isEmpty())
1888 node = relative; // search for a target on the current page.
1889 else if (domain) {
1890 if (first.endsWith(".html"))
1891 node = domain->findNodeByNameAndType(QStringList(first), &Node::isPageNode);
1892 else if (first.endsWith(QChar(')'))) {
1893 QString function = first;
1894 const QString signature = Parameters::extractParameters(function);
1895 const Parameters parameters(signature.trimmed());
1896
1897 const QStringList path = function.split("::"_L1);
1898 if (parameters.isValid())
1899 node = domain->findFunctionNode(path, parameters, nullptr, genus);
1900 }
1901 if (node == nullptr) {
1902 int flags = SearchBaseClasses | SearchEnumValues;
1903 QStringList nodePath = first.split("::");
1904 QString target;
1905 targetPath.removeFirst();
1906 if (!targetPath.isEmpty())
1907 target = targetPath.takeFirst();
1908 if (relative && relative->tree()->physicalModuleName() != domain->physicalModuleName())
1909 relative = nullptr;
1910 return domain->findNodeForTarget(nodePath, target, relative, flags, genus, ref);
1911 }
1912 } else {
1913 if (first.endsWith(".html"))
1914 node = findNodeByNameAndType(QStringList(first), &Node::isPageNode);
1915 else if (first.endsWith(QChar(')')))
1916 node = findFunctionNode(first, relative, genus);
1917 if (node == nullptr) {
1918 // For QML contexts with qualified names containing ".", use the same logic as findTypeNode
1919 if (genus == Genus::QML && first.contains('.') && !first.contains("::")) {
1920 // Try import-aware lookup using findTypeNode logic
1921 node = findTypeNode(first, relative, genus);
1922 if (node)
1923 return setReferenceFromFragment(fragment, node, ref);
1924 }
1925 return findNodeForTarget(targetPath, relative, genus, ref, atom->flags());
1926 }
1927 }
1928
1929 if (node != nullptr && ref.isEmpty()) {
1930 if (!node->url().isEmpty())
1931 return node;
1932 targetPath.removeFirst();
1933 if (!targetPath.isEmpty()) {
1934 ref = node->root()->tree()->getRef(targetPath.first(), node);
1935 if (ref.isEmpty())
1936 node = nullptr;
1937 }
1938 }
1939 return node;
1940}
1941
1942/*!
1943 Updates navigation (previous/next page links and the navigation parent)
1944 for pages listed in the TOC, specified by the \c navigation.toctitles
1945 configuration variable.
1946
1947 if \c navigation.toctitles.inclusive is \c true, include also the TOC
1948 page(s) themselves as a 'root' item in the navigation bar (breadcrumbs)
1949 that are generated for HTML output.
1950*/
1952{
1953 // Restrict searching only to the local (primary) tree
1954 QList<Tree *> searchOrder = this->searchOrder();
1956
1957 const QString configVar = CONFIG_NAVIGATION +
1958 Config::dot +
1960
1961 // TODO: [direct-configuration-access]
1962 // The configuration is currently a singleton with some generally
1963 // global mutable state.
1964 //
1965 // Accessing the data in this form complicates testing and
1966 // requires tests that inhibit any test parallelization, as the
1967 // tests are not self contained.
1968 //
1969 // This should be generally avoived. Possibly, we should strive
1970 // for Config to be a POD type that generally is scoped to
1971 // main and whose data is destructured into dependencies when
1972 // the dependencies are constructed.
1973 bool inclusive{Config::instance().get(
1974 configVar + Config::dot + CONFIG_INCLUSIVE).asBool()};
1975
1976 // TODO: [direct-configuration-access]
1977 const auto tocTitles{Config::instance().get(configVar).asStringList()};
1978
1979 for (const auto &tocTitle : tocTitles) {
1980 if (const auto candidateTarget = findNodeForTarget(tocTitle, nullptr); candidateTarget && candidateTarget->isPageNode()) {
1981 auto tocPage{static_cast<const PageNode*>(candidateTarget)};
1982
1983 Text body = tocPage->doc().body();
1984
1985 auto *atom = body.firstAtom();
1986
1987 std::pair<PageNode *, QString> prev { nullptr, QString() };
1988
1989 std::stack<const PageNode *> tocStack;
1990 tocStack.push(inclusive ? tocPage : nullptr);
1991
1992 bool inItem = false;
1993
1994 // TODO: Understand how much we use this form of looping over atoms.
1995 // If it is used a few times we might consider providing
1996 // an iterator for Text to make use of a simpler
1997 // range-for loop.
1998 while (atom) {
1999 switch (atom->type()) {
2000 case Atom::ListItemLeft:
2001 // Not known if we're going to have a link, push a temporary
2002 tocStack.push(nullptr);
2003 inItem = true;
2004 break;
2005 case Atom::ListItemRight:
2006 tocStack.pop();
2007 inItem = false;
2008 break;
2009 case Atom::Link: {
2010 if (!inItem)
2011 break;
2012
2013 // TODO: [unnecessary-output-parameter]
2014 // We currently need an lvalue string to
2015 // pass to findNodeForAtom, as the
2016 // outparameter ref.
2017 //
2018 // Apart from the general problems with output
2019 // parameters, we shouldn't be forced to
2020 // instanciate an unnecessary object at call
2021 // site.
2022 //
2023 // Understand what the correct way to avoid this is.
2024 // This requires changes to findNodeForAtom
2025 // and should be addressed in the context of
2026 // revising that method.
2027 QString unused{};
2028 // TODO: Having to const cast is really a code
2029 // smell and could result in undefined
2030 // behavior in some specific cases (e.g point
2031 // to something that is actually const).
2032 //
2033 // We should understand how to sequence the
2034 // code so that we have access to mutable data
2035 // when we need it and "freeze" the data
2036 // afterwards.
2037 //
2038 // If it we expect this form of mutability at
2039 // this point we should expose a non-const API
2040 // for the database, possibly limited to a
2041 // very specific scope of execution.
2042 //
2043 // Understand what the correct sequencing for
2044 // this processing is and revise this part.
2045 auto candidatePage = const_cast<Node *>(findNodeForAtom(atom, nullptr, unused));
2046 if (!candidatePage || !candidatePage->isPageNode()) break;
2047
2048 auto page{static_cast<PageNode*>(candidatePage)};
2049
2050 // ignore self-references
2051 if (page == prev.first) break;
2052
2053 if (prev.first) {
2054 prev.first->setLink(
2055 Node::NextLink,
2056 page->title(),
2057 // TODO: [possible-assertion-failure][imprecise-types][atoms-link]
2058 // As with other structures in QDoc we
2059 // are able to call methods that are
2060 // valid only on very specific states.
2061 //
2062 // For some of those calls we have
2063 // some defensive programming measures
2064 // that allow us to at least identify
2065 // the error during debugging, while
2066 // for others this may currently hide
2067 // some logic error.
2068 //
2069 // To avoid those cases, we should
2070 // strive to move those cases to a
2071 // compilation error, requiring a
2072 // statically analyzable state that
2073 // represents the current model.
2074 //
2075 // This would ensure that those
2076 // lingering class of bugs are
2077 // eliminated completely, forces a
2078 // more explicit codebase where the
2079 // current capabilities do not depend
2080 // on runtime values might not be
2081 // generally visible, and does not
2082 // require us to incur into the
2083 // required state, which may be rare,
2084 // simplifying our abilities to
2085 // evaluate all possible states.
2086 //
2087 // For linking atoms, LinkAtom is
2088 // available and might be a good
2089 // enough solution to move linkText
2090 // to.
2091 atom->linkText()
2092 );
2093 page->setLink(
2094 Node::PreviousLink,
2095 prev.first->title(),
2096 prev.second
2097 );
2098 }
2099
2100 if (page == tocPage)
2101 break;
2102
2103 // Find the navigation parent from the stack; we may have null pointers
2104 // for non-link list items, so skip those.
2105 qsizetype popped = 0;
2106 while (tocStack.size() > 1 && !tocStack.top()) {
2107 tocStack.pop();
2108 ++popped;
2109 }
2110
2111 page->setNavigationParent(tocStack.empty() ? nullptr : tocStack.top());
2112
2113 while (--popped > 0)
2114 tocStack.push(nullptr);
2115
2116 tocStack.push(page);
2117 // TODO: [possible-assertion-failure][imprecise-types][atoms-link]
2118 prev = { page, atom->linkText() };
2119 }
2120 break;
2121
2122 case Atom::AnnotatedList:
2123 case Atom::GeneratedList: {
2124 if (const auto *cn = getCollectionNode(atom->string(), NodeType::Group)) {
2125 const auto sortOrder{Generator::sortOrder(atom->strings().last())};
2126 NodeList members{cn->members()};
2127 // Drop non-page nodes and index nodes so that we do not generate navigational
2128 // links pointing outside of this documentation set.
2129 members.erase(std::remove_if(members.begin(), members.end(),
2130 [](const Node *n) {
2131 return n->isIndexNode() || !n->isPageNode() || n->isExternalPage();
2132 }), members.end());
2133 if (members.isEmpty())
2134 break;
2135
2136 if (sortOrder == Qt::DescendingOrder)
2137 std::sort(members.rbegin(), members.rend(), Node::nodeSortKeyOrNameLessThan);
2138 else
2139 std::sort(members.begin(), members.end(), Node::nodeSortKeyOrNameLessThan);
2140
2141 // `members` now has local PageNode pointers, adjust prev/next links for each.
2142 // Do not set a navigation parent node as group members use the group node as
2143 // their nav. parent.
2144 for (auto *m : members) {
2145 auto *page = static_cast<PageNode *>(m);
2146 if (prev.first) {
2147 prev.first->setLink(Node::NextLink, page->title(), page->fullName());
2148 page->setLink(Node::PreviousLink, prev.first->title(), prev.second);
2149 }
2150 prev = { page, page->fullName() };
2151 }
2152 }
2153 }
2154 break;
2155
2156 default:
2157 break;
2158 }
2159
2160 atom = atom->next();
2161 }
2162 } else {
2163 Config::instance().get(configVar).location()
2164 .warning(QStringLiteral("Failed to find table of contents with title '%1'")
2165 .arg(tocTitle));
2166 }
2167 }
2168
2169 // Restore search order
2170 setSearchOrder(searchOrder);
2171}
2172
2173QT_END_NAMESPACE
void resolveRelates()
Adopts each non-aggregate C++ node (function/macro, typedef, enum, variable, or a shared comment node...
void resolveQmlInheritance()
Resolves the inheritance information for all QML type children of this aggregate.
void normalizeOverloads()
Sorts the lists of overloads in the function map and assigns overload numbers.
@ AttachedProperties
Definition aggregate.h:27
@ UnattachedProperties
Definition aggregate.h:26
void findAllNamespaces(NodeMultiMap &namespaces)
For each child of this node, if the child is a namespace node, insert the child into the namespaces m...
void markUndocumentedChildrenInternal()
Mark all child nodes that have no documentation as having internal status.
FunctionNode * findFunctionChild(const FunctionNode *clone)
Returns the function node that is a child of this node, such that the function described has the same...
The Atom class is the fundamental unit for representing documents internally.
Definition atom.h:19
AtomType type() const
Return the type of this atom.
Definition atom.h:144
@ Link
Definition atom.h:63
virtual bool isLinkAtom() const
Definition atom.h:152
virtual Tree * domain()
Definition atom.h:154
virtual Genus genus()
Definition atom.h:153
A class for holding the members of a collection of doc pages.
This node is used to represent any kind of function being documented.
This class represents a C++ namespace.
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
const Node * findTypeNode(const QString &type, const Node *relative, Genus genus)
This function is called for autolinking to a type, which could be a function return type or a paramet...
NodeMapMap & getFunctionIndex()
Returns the function index.
const NodeMultiMap & getClassMap(const QString &key)
Find the key in the map of new class maps, and return a reference to the value, which is a NodeMap.
QmlTypeNode * findQmlType(const QString &qmid, const QString &name, const Node *relative=nullptr, Tree *tree=nullptr)
Returns the QML type node identified by the QML module id qmid and QML type name, or nullptr if no ty...
const Node * findQmlNode(const QString &path, const Node *relative, FindFlags flags, Tree *tree=nullptr)
Returns the node to a piece of QML API documentation that corresponds to the given path,...
const NodeMultiMap & getQmlTypeMap(const QString &key)
Find the key in the map of new QML type maps, and return a reference to the value,...
void resolveNamespaces()
Multiple namespace nodes for namespace X can exist in the qdoc database in different trees.
TextToNodeMap & getLegaleseTexts()
Returns a reference to the collection of legalese texts.
const Node * findNodeForTarget(const QString &target, const Node *relative, Genus genus, const QString &moduleName, QString *ref=nullptr, LinkReferenceOrigin origin=LinkReferenceOrigin::Auto, int searchFlags=0)
Finds the node for target relative to relative, with genus and module scoping.
NodeMultiMap & getAttributions()
Returns a reference to the multimap of attribution nodes.
static void destroyQdocDB()
Destroys the singleton.
NodeMultiMap & getQmlTypesWithObsoleteMembers()
Returns a reference to the map of QML types with obsolete members.
NodeMultiMap & getObsoleteQmlTypes()
Returns a reference to the map of obsolete QML types.
QmlTypeNode * findQmlType(const ImportRec &import, const QString &name, const Node *relative=nullptr)
const FunctionNode * findFunctionNode(const QString &target, const Node *relative, Genus genus)
Finds the function node for the qualified function path in target and returns a pointer to it.
static QDocDatabase * qdocDB()
Creates the singleton.
void generateIndex(const QString &fileName, const QString &url, const QString &title, const IndexSerializationContext &context)
Generates a qdoc index file and writes it to fileName.
void resolveBaseClasses()
NodeMultiMap & getQmlTypes()
Returns a reference to the multimap of QML types.
NodeMultiMap & getClassesWithObsoleteMembers()
Returns a reference to the map of C++ classes with obsolete members.
NodeMultiMap & getQmlValueTypes()
Returns a reference to the map of QML basic types.
NodeMultiMap & getCppClasses()
Returns a reference to the map of all C++ classes.
QStringList groupNamesForNode(Node *node)
NamespaceNode * primaryTreeRoot()
Returns a pointer to the root node of the primary tree.
void readIndexes(const QStringList &indexFiles)
Reads and parses the qdoc index files listed in indexFiles.
void resolveConceptUsers()
Builds the concept-to-users reverse index after parsing finishes and before any generator runs.
const Node * findNodeForTarget(const QString &target, const Node *relative)
Finds the node that will generate the documentation that contains the target and returns a pointer to...
const Node * findNodeForAtom(const Atom *atom, const Node *relative, QString &ref, Genus genus=Genus::DontCare)
Searches for the node that matches the path in atom and the specified genus.
void mergeCollections(NodeType type, CNMap &cnm, const Node *relative)
Finds all the collection nodes of the specified type and merges them into the collection node map cnm...
NodeMultiMap & getNamespaces()
Returns a reference to the namespace map.
QmlTypeNode * findQmlTypeInPrimaryTree(const QString &qmid, const QString &name)
Returns the QML node identified by the QML module id qmid and name, searching in the primary tree onl...
const NodeMultiMap & getSinceMap(const QString &key)
Find the key in the map of new {since} maps, and return a reference to the value, which is a NodeMult...
NodeMultiMap & getExamples()
Returns a reference to the multimap of example nodes.
void processForest()
This function calls a set of functions for each tree in the forest that has not already been analyzed...
void(QDocDatabase::*)(Aggregate *) FindFunctionPtr
void updateNavigation()
Updates navigation (previous/next page links and the navigation parent) for pages listed in the TOC,...
void resolveStuff()
Performs several housekeeping tasks prior to generating the documentation.
Tree * primaryTree()
NodeMultiMap & getObsoleteClasses()
Returns a reference to the map of obsolete C++ clases.
const CollectionNode * getModuleNode(const Node *relative)
Returns the collection node representing the module that relative node belongs to,...
void resolveProxies()
Each instance of class Tree that represents an index file must be traversed to find all instances of ...
void mergeCollections(CollectionNode *c)
Finds all the collection nodes with the same name and type as c and merges their members into the mem...
void setLocalSearch()
A class representing a forest of Tree objects.
This class imports QDoc index files and serializes index sections for module indexes and WebXML outpu...
const ImportList & importList() const
Definition qmltypenode.h:51
This class constructs and maintains a tree of instances of the subclasses of Node.
Definition tree.h:58
void markDontDocumentNodes()
The {don't document} map has been loaded with the names of classes and structs in the current module ...
Definition tree.cpp:1650
NodeList & proxies()
Definition tree.h:78
void resolveProperties()
Resolves access functions associated with each PropertyNode stored in m_unresolvedPropertyMap,...
Definition tree.cpp:261
#define CONFIG_TOCTITLES
Definition config.h:466
#define CONFIG_NOLINKERRORS
Definition config.h:435
#define CONFIG_NAVIGATION
Definition config.h:434
#define CONFIG_INCLUSIVE
Definition config.h:421
NodeType
Definition genustypes.h:165
QMultiMap< QString, CollectionNode * > CNMultiMap
Definition node.h:53
QList< Node * > NodeList
Definition node.h:45
QMap< QString, NodeMultiMap > NodeMultiMapMap
Definition node.h:51
QMap< QString, Node * > NodeMap
Definition node.h:48
QMap< QString, NodeMap > NodeMapMap
Definition node.h:49
QMap< QString, CollectionNode * > CNMap
Definition node.h:52
static void collectConceptReferences(const Node *node, QStringList &refs)
Gathers the fully-qualified concept names referenced by node.
static void registerConceptUsersUnder(Aggregate *parent, QDocDatabase &db)
Walks every documented node under parent and registers each constrained item as a member of the conce...
static NodeMultiMap emptyNodeMultiMap_
QT_BEGIN_NAMESPACE typedef QMultiMap< Text, const Node * > TextToNodeMap
@ QmlAttachedProperties
@ SearchBaseClasses
@ SearchEnumValues
@ TypesOnly
QList< ImportRec > ImportList
Definition qmltypenode.h:20
QMultiMap< QString, Node * > NodeMultiMap
Definition generator.h:36
bool isEmpty() const
Definition importrec.h:30
The Node class is the base class for all the nodes in QDoc's parse tree.
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
Definition node.h:123
Genus genus() const override
Returns this node's Genus.
Definition node.h:85
virtual bool isPageNode() const
Returns true if this node represents something that generates a documentation page.
Definition node.h:150
Aggregate * parent() const
Returns the node's parent pointer, that is, the aggregate under which the node is documented.
Definition node.h:211
const std::optional< RelaxedTemplateDeclaration > & templateDecl() const
Definition node.h:247
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
Definition node.h:101
Aggregate * root() const
virtual Tree * tree() const
Returns a pointer to the Tree this node is in.
Definition node.cpp:899
A class for parsing and managing a function parameter list.
Definition main.cpp:28
bool isValid() const
Definition parameters.h:33
A record of a linkable target within the documentation.
Definition tree.h:27
TargetType
A type of a linkable target record.
Definition tree.h:29
@ Unknown
Definition tree.h:29