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 "atom.h"
7#include "collectionnode.h"
8#include "functionnode.h"
9#include "generator.h"
10#include "qdocindexfiles.h"
11#include "tree.h"
12
13#include <QtCore/qregularexpression.h>
14#include <stack>
15
16QT_BEGIN_NAMESPACE
17
18static NodeMultiMap emptyNodeMultiMap_;
19
20/*!
21 \class QDocForest
22
23 A class representing a forest of Tree objects.
24
25 This private class manages a collection of Tree objects (a
26 forest) for the singleton QDocDatabase object. It is only
27 accessed by that singleton QDocDatabase object, which is a
28 friend. Each tree in the forest is an instance of class
29 Tree, which is a mostly private class. Both QDocForest and
30 QDocDatabase are friends of Tree and have full access.
31
32 There are two kinds of trees in the forest, differing not
33 in structure but in use. One Tree is the primary tree. It
34 is the tree representing the module being documented. All
35 the other trees in the forest are called index trees. Each
36 one represents the contents of the index file for one of
37 the modules the current module must be able to link to.
38
39 The instances of subclasses of Node in the primary tree
40 will contain documentation in an instance of Doc. The
41 index trees contain no documentation, and each Node in
42 an index tree is marked as an index node.
43
44 Each tree is named with the name of its module.
45
46 The search order is created by searchOrder(), if it has
47 not already been created. The search order and module
48 names arrays have parallel structure, i.e. modulNames_[i]
49 is the module name of the Tree at searchOrder_[i].
50
51 The primary tree is always the first tree in the search
52 order. i.e., when the database is searched, the primary
53 tree is always searched first, unless a specific tree is
54 being searched.
55 */
56
57/*!
58 Destroys the qdoc forest. This requires deleting
59 each Tree in the forest. Note that the forest has
60 been transferred into the search order array, so
61 what is really being used to destroy the forest
62 is the search order array.
63 */
64QDocForest::~QDocForest()
65{
66 for (auto *entry : m_searchOrder)
67 delete entry;
68 m_forest.clear();
69 m_searchOrder.clear();
70 m_indexSearchOrder.clear();
71 m_moduleNames.clear();
72 m_primaryTree = nullptr;
73}
74
75/*!
76 Initializes the forest prior to a traversal and
77 returns a pointer to the primary tree. If the
78 forest is empty, it returns \nullptr.
79 */
80Tree *QDocForest::firstTree()
81{
82 m_currentIndex = 0;
83 return (!searchOrder().isEmpty() ? searchOrder()[0] : nullptr);
84}
85
86/*!
87 Increments the forest's current tree index. If the current
88 tree index is still within the forest, the function returns
89 the pointer to the current tree. Otherwise it returns \nullptr.
90 */
91Tree *QDocForest::nextTree()
92{
93 ++m_currentIndex;
94 return (m_currentIndex < searchOrder().size() ? searchOrder()[m_currentIndex] : nullptr);
95}
96
97/*!
98 \fn Tree *QDocForest::primaryTree()
99
100 Returns the pointer to the primary tree.
101 */
102
103/*!
104 Finds the tree for module \a t in the forest and
105 sets the primary tree to be that tree. After the
106 primary tree is set, that tree is removed from the
107 forest.
108
109 \node It gets re-inserted into the forest after the
110 search order is built.
111 */
112void QDocForest::setPrimaryTree(const QString &t)
113{
114 QString T = t.toLower();
115 m_primaryTree = findTree(t: T);
116 m_forest.remove(key: T);
117 if (m_primaryTree == nullptr)
118 qCCritical(lcQdoc) << "Error: Could not set primary tree to" << t;
119}
120
121/*!
122 If the search order array is empty, create the search order.
123 If the search order array is not empty, do nothing.
124 */
125void QDocForest::setSearchOrder(const QStringList &t)
126{
127 if (!m_searchOrder.isEmpty())
128 return;
129
130 /* Allocate space for the search order. */
131 m_searchOrder.reserve(asize: m_forest.size() + 1);
132 m_searchOrder.clear();
133 m_moduleNames.reserve(asize: m_forest.size() + 1);
134 m_moduleNames.clear();
135
136 /* The primary tree is always first in the search order. */
137 QString primaryName = primaryTree()->physicalModuleName();
138 m_searchOrder.append(t: m_primaryTree);
139 m_moduleNames.append(t: primaryName);
140 m_forest.remove(key: primaryName);
141
142 for (const QString &m : t) {
143 if (primaryName != m) {
144 auto it = m_forest.find(key: m);
145 if (it != m_forest.end()) {
146 m_searchOrder.append(t: it.value());
147 m_moduleNames.append(t: m);
148 m_forest.remove(key: m);
149 }
150 }
151 }
152 /*
153 If any trees remain in the forest, just add them
154 to the search order sequentially, because we don't
155 know any better at this point.
156 */
157 if (!m_forest.isEmpty()) {
158 for (auto it = m_forest.begin(); it != m_forest.end(); ++it) {
159 m_searchOrder.append(t: it.value());
160 m_moduleNames.append(t: it.key());
161 }
162 m_forest.clear();
163 }
164
165 /*
166 Rebuild the forest after constructing the search order.
167 It was destroyed during construction of the search order,
168 but it is needed for module-specific searches.
169
170 Note that this loop also inserts the primary tree into the
171 forrest. That is a requirement.
172 */
173 for (int i = 0; i < m_searchOrder.size(); ++i) {
174 if (!m_forest.contains(key: m_moduleNames.at(i))) {
175 m_forest.insert(key: m_moduleNames.at(i), value: m_searchOrder.at(i));
176 }
177 }
178}
179
180/*!
181 Returns an ordered array of Tree pointers that represents
182 the order in which the trees should be searched. The first
183 Tree in the array is the tree for the current module, i.e.
184 the module for which qdoc is generating documentation.
185
186 The other Tree pointers in the array represent the index
187 files that were loaded in preparation for generating this
188 module's documentation. Each Tree pointer represents one
189 index file. The index file Tree points have been ordered
190 heuristically to, hopefully, minimize searching. Thr order
191 will probably be changed.
192
193 If the search order array is empty, this function calls
194 indexSearchOrder(). The search order array is empty while
195 the index files are being loaded, but some searches must
196 be performed during this time, notably searches for base
197 class nodes. These searches require a temporary search
198 order. The temporary order changes throughout the loading
199 of the index files, but it is always the tree for the
200 current index file first, followed by the trees for the
201 index files that have already been loaded. The only
202 ordering required in this temporary search order is that
203 the current tree must be searched first.
204 */
205const QList<Tree *> &QDocForest::searchOrder()
206{
207 if (m_searchOrder.isEmpty())
208 return indexSearchOrder();
209 return m_searchOrder;
210}
211
212/*!
213 There are two search orders used by qdoc when searching for
214 things. The normal search order is returned by searchOrder(),
215 but this normal search order is not known until all the index
216 files have been read. At that point, setSearchOrder() is
217 called.
218
219 During the reading of the index files, the vector holding
220 the normal search order remains empty. Whenever the search
221 order is requested, if that vector is empty, this function
222 is called to return a temporary search order, which includes
223 all the index files that have been read so far, plus the
224 one being read now. That one is prepended to the front of
225 the vector.
226 */
227const QList<Tree *> &QDocForest::indexSearchOrder()
228{
229 if (m_forest.size() > m_indexSearchOrder.size())
230 m_indexSearchOrder.prepend(t: m_primaryTree);
231 return m_indexSearchOrder;
232}
233
234/*!
235 Create a new Tree for the index file for the specified
236 \a module and add it to the forest. Return the pointer
237 to its root.
238 */
239NamespaceNode *QDocForest::newIndexTree(const QString &module)
240{
241 m_primaryTree = new Tree(module, m_qdb);
242 m_forest.insert(key: module.toLower(), value: m_primaryTree);
243 return m_primaryTree->root();
244}
245
246/*!
247 Create a new Tree for use as the primary tree. This tree
248 will represent the primary module. \a module is camel case.
249 */
250void QDocForest::newPrimaryTree(const QString &module)
251{
252 m_primaryTree = new Tree(module, m_qdb);
253}
254
255/*!
256 Searches through the forest for a node named \a targetPath
257 and returns a pointer to it if found. The \a relative node
258 is the starting point. It only makes sense for the primary
259 tree, which is searched first. After the primary tree has
260 been searched, \a relative is set to 0 for searching the
261 other trees, which are all index trees. With relative set
262 to 0, the starting point for each index tree is the root
263 of the index tree.
264
265 If \a targetPath is resolved successfully but it refers to
266 a \\section title, continue the search, keeping the section
267 title as a fallback if no higher-priority targets are found.
268 */
269const Node *QDocForest::findNodeForTarget(QStringList &targetPath, const Node *relative,
270 Node::Genus genus, QString &ref)
271{
272 int flags = SearchBaseClasses | SearchEnumValues;
273
274 QString entity = targetPath.takeFirst();
275 QStringList entityPath = entity.split(sep: "::");
276
277 QString target;
278 if (!targetPath.isEmpty())
279 target = targetPath.takeFirst();
280
281 TargetRec::TargetType type = TargetRec::Unknown;
282 const Node *tocNode = nullptr;
283 for (const auto *tree : searchOrder()) {
284 const Node *n = tree->findNodeForTarget(path: entityPath, target, node: relative, flags, genus, ref, targetType: &type);
285 if (n) {
286 // Targets referring to non-section titles are returned immediately
287 if (type != TargetRec::Contents)
288 return n;
289 if (!tocNode)
290 tocNode = n;
291 }
292 relative = nullptr;
293 }
294 return tocNode;
295}
296
297/*!
298 Finds the FunctionNode for the qualified function name
299 in \a path, that also has the specified \a parameters.
300 Returns a pointer to the first matching function.
301
302 \a relative is a node in the primary tree where the search
303 should begin. It is only used when searching the primary
304 tree. \a genus can be used to force the search to find a
305 C++ function or a QML function.
306 */
307const FunctionNode *QDocForest::findFunctionNode(const QStringList &path,
308 const Parameters &parameters, const Node *relative,
309 Node::Genus genus)
310{
311 for (const auto *tree : searchOrder()) {
312 const FunctionNode *fn = tree->findFunctionNode(path, parameters, relative, genus);
313 if (fn)
314 return fn;
315 relative = nullptr;
316 }
317 return nullptr;
318}
319
320/*! \class QDocDatabase
321 This class provides exclusive access to the qdoc database,
322 which consists of a forrest of trees and a lot of maps and
323 other useful data structures.
324 */
325
326QDocDatabase *QDocDatabase::s_qdocDB = nullptr;
327NodeMap QDocDatabase::s_typeNodeMap;
328NodeMultiMap QDocDatabase::s_obsoleteClasses;
329NodeMultiMap QDocDatabase::s_classesWithObsoleteMembers;
330NodeMultiMap QDocDatabase::s_obsoleteQmlTypes;
331NodeMultiMap QDocDatabase::s_qmlTypesWithObsoleteMembers;
332NodeMultiMap QDocDatabase::s_cppClasses;
333NodeMultiMap QDocDatabase::s_qmlBasicTypes;
334NodeMultiMap QDocDatabase::s_qmlTypes;
335NodeMultiMap QDocDatabase::s_examples;
336NodeMultiMapMap QDocDatabase::s_newClassMaps;
337NodeMultiMapMap QDocDatabase::s_newQmlTypeMaps;
338NodeMultiMapMap QDocDatabase::s_newEnumValueMaps;
339NodeMultiMapMap QDocDatabase::s_newSinceMaps;
340
341/*!
342 Constructs the singleton qdoc database object. The singleton
343 constructs the \a forest_ object, which is also a singleton.
344 \a m_showInternal is normally false. If it is true, qdoc will
345 write documentation for nodes marked \c internal.
346
347 \a singleExec_ is false when qdoc is being used in the standard
348 way of running qdoc twices for each module, first with -prepare
349 and then with -generate. First the -prepare phase is run for
350 each module, then the -generate phase is run for each module.
351
352 When \a singleExec_ is true, qdoc is run only once. During the
353 single execution, qdoc processes the qdocconf files for all the
354 modules sequentially in a loop. Each source file for each module
355 is read exactly once.
356 */
357QDocDatabase::QDocDatabase() : m_forest(this)
358{
359 // nothing
360}
361
362/*!
363 Creates the singleton. Allows only one instance of the class
364 to be created. Returns a pointer to the singleton.
365*/
366QDocDatabase *QDocDatabase::qdocDB()
367{
368 if (s_qdocDB == nullptr) {
369 s_qdocDB = new QDocDatabase;
370 initializeDB();
371 }
372 return s_qdocDB;
373}
374
375/*!
376 Destroys the singleton.
377 */
378void QDocDatabase::destroyQdocDB()
379{
380 if (s_qdocDB != nullptr) {
381 delete s_qdocDB;
382 s_qdocDB = nullptr;
383 }
384}
385
386/*!
387 Initialize data structures in the singleton qdoc database.
388
389 In particular, the type node map is initialized with a lot
390 type names that don't refer to documented types. For example,
391 many C++ standard types are included. These might be documented
392 here at some point, but for now they are not. Other examples
393 include \c array and \c data, which are just generic names
394 used as place holders in function signatures that appear in
395 the documentation.
396
397 \note Do not add QML basic types into this list as it will
398 break linking to those types.
399 */
400void QDocDatabase::initializeDB()
401{
402 s_typeNodeMap.insert(key: "accepted", value: nullptr);
403 s_typeNodeMap.insert(key: "actionPerformed", value: nullptr);
404 s_typeNodeMap.insert(key: "activated", value: nullptr);
405 s_typeNodeMap.insert(key: "alias", value: nullptr);
406 s_typeNodeMap.insert(key: "anchors", value: nullptr);
407 s_typeNodeMap.insert(key: "any", value: nullptr);
408 s_typeNodeMap.insert(key: "array", value: nullptr);
409 s_typeNodeMap.insert(key: "autoSearch", value: nullptr);
410 s_typeNodeMap.insert(key: "axis", value: nullptr);
411 s_typeNodeMap.insert(key: "backClicked", value: nullptr);
412 s_typeNodeMap.insert(key: "boomTime", value: nullptr);
413 s_typeNodeMap.insert(key: "border", value: nullptr);
414 s_typeNodeMap.insert(key: "buttonClicked", value: nullptr);
415 s_typeNodeMap.insert(key: "callback", value: nullptr);
416 s_typeNodeMap.insert(key: "char", value: nullptr);
417 s_typeNodeMap.insert(key: "clicked", value: nullptr);
418 s_typeNodeMap.insert(key: "close", value: nullptr);
419 s_typeNodeMap.insert(key: "closed", value: nullptr);
420 s_typeNodeMap.insert(key: "cond", value: nullptr);
421 s_typeNodeMap.insert(key: "data", value: nullptr);
422 s_typeNodeMap.insert(key: "dataReady", value: nullptr);
423 s_typeNodeMap.insert(key: "dateString", value: nullptr);
424 s_typeNodeMap.insert(key: "dateTimeString", value: nullptr);
425 s_typeNodeMap.insert(key: "datetime", value: nullptr);
426 s_typeNodeMap.insert(key: "day", value: nullptr);
427 s_typeNodeMap.insert(key: "deactivated", value: nullptr);
428 s_typeNodeMap.insert(key: "drag", value: nullptr);
429 s_typeNodeMap.insert(key: "easing", value: nullptr);
430 s_typeNodeMap.insert(key: "error", value: nullptr);
431 s_typeNodeMap.insert(key: "exposure", value: nullptr);
432 s_typeNodeMap.insert(key: "fatalError", value: nullptr);
433 s_typeNodeMap.insert(key: "fileSelected", value: nullptr);
434 s_typeNodeMap.insert(key: "flags", value: nullptr);
435 s_typeNodeMap.insert(key: "float", value: nullptr);
436 s_typeNodeMap.insert(key: "focus", value: nullptr);
437 s_typeNodeMap.insert(key: "focusZone", value: nullptr);
438 s_typeNodeMap.insert(key: "format", value: nullptr);
439 s_typeNodeMap.insert(key: "framePainted", value: nullptr);
440 s_typeNodeMap.insert(key: "from", value: nullptr);
441 s_typeNodeMap.insert(key: "frontClicked", value: nullptr);
442 s_typeNodeMap.insert(key: "function", value: nullptr);
443 s_typeNodeMap.insert(key: "hasOpened", value: nullptr);
444 s_typeNodeMap.insert(key: "hovered", value: nullptr);
445 s_typeNodeMap.insert(key: "hoveredTitle", value: nullptr);
446 s_typeNodeMap.insert(key: "hoveredUrl", value: nullptr);
447 s_typeNodeMap.insert(key: "imageCapture", value: nullptr);
448 s_typeNodeMap.insert(key: "imageProcessing", value: nullptr);
449 s_typeNodeMap.insert(key: "index", value: nullptr);
450 s_typeNodeMap.insert(key: "initialized", value: nullptr);
451 s_typeNodeMap.insert(key: "isLoaded", value: nullptr);
452 s_typeNodeMap.insert(key: "item", value: nullptr);
453 s_typeNodeMap.insert(key: "key", value: nullptr);
454 s_typeNodeMap.insert(key: "keysequence", value: nullptr);
455 s_typeNodeMap.insert(key: "listViewClicked", value: nullptr);
456 s_typeNodeMap.insert(key: "loadRequest", value: nullptr);
457 s_typeNodeMap.insert(key: "locale", value: nullptr);
458 s_typeNodeMap.insert(key: "location", value: nullptr);
459 s_typeNodeMap.insert(key: "long", value: nullptr);
460 s_typeNodeMap.insert(key: "message", value: nullptr);
461 s_typeNodeMap.insert(key: "messageReceived", value: nullptr);
462 s_typeNodeMap.insert(key: "mode", value: nullptr);
463 s_typeNodeMap.insert(key: "month", value: nullptr);
464 s_typeNodeMap.insert(key: "name", value: nullptr);
465 s_typeNodeMap.insert(key: "number", value: nullptr);
466 s_typeNodeMap.insert(key: "object", value: nullptr);
467 s_typeNodeMap.insert(key: "offset", value: nullptr);
468 s_typeNodeMap.insert(key: "ok", value: nullptr);
469 s_typeNodeMap.insert(key: "openCamera", value: nullptr);
470 s_typeNodeMap.insert(key: "openImage", value: nullptr);
471 s_typeNodeMap.insert(key: "openVideo", value: nullptr);
472 s_typeNodeMap.insert(key: "padding", value: nullptr);
473 s_typeNodeMap.insert(key: "parent", value: nullptr);
474 s_typeNodeMap.insert(key: "path", value: nullptr);
475 s_typeNodeMap.insert(key: "photoModeSelected", value: nullptr);
476 s_typeNodeMap.insert(key: "position", value: nullptr);
477 s_typeNodeMap.insert(key: "precision", value: nullptr);
478 s_typeNodeMap.insert(key: "presetClicked", value: nullptr);
479 s_typeNodeMap.insert(key: "preview", value: nullptr);
480 s_typeNodeMap.insert(key: "previewSelected", value: nullptr);
481 s_typeNodeMap.insert(key: "progress", value: nullptr);
482 s_typeNodeMap.insert(key: "puzzleLost", value: nullptr);
483 s_typeNodeMap.insert(key: "qmlSignal", value: nullptr);
484 s_typeNodeMap.insert(key: "rectangle", value: nullptr);
485 s_typeNodeMap.insert(key: "request", value: nullptr);
486 s_typeNodeMap.insert(key: "requestId", value: nullptr);
487 s_typeNodeMap.insert(key: "section", value: nullptr);
488 s_typeNodeMap.insert(key: "selected", value: nullptr);
489 s_typeNodeMap.insert(key: "send", value: nullptr);
490 s_typeNodeMap.insert(key: "settingsClicked", value: nullptr);
491 s_typeNodeMap.insert(key: "shoe", value: nullptr);
492 s_typeNodeMap.insert(key: "short", value: nullptr);
493 s_typeNodeMap.insert(key: "signed", value: nullptr);
494 s_typeNodeMap.insert(key: "sizeChanged", value: nullptr);
495 s_typeNodeMap.insert(key: "size_t", value: nullptr);
496 s_typeNodeMap.insert(key: "sockaddr", value: nullptr);
497 s_typeNodeMap.insert(key: "someOtherSignal", value: nullptr);
498 s_typeNodeMap.insert(key: "sourceSize", value: nullptr);
499 s_typeNodeMap.insert(key: "startButtonClicked", value: nullptr);
500 s_typeNodeMap.insert(key: "state", value: nullptr);
501 s_typeNodeMap.insert(key: "std::initializer_list", value: nullptr);
502 s_typeNodeMap.insert(key: "std::list", value: nullptr);
503 s_typeNodeMap.insert(key: "std::map", value: nullptr);
504 s_typeNodeMap.insert(key: "std::pair", value: nullptr);
505 s_typeNodeMap.insert(key: "std::string", value: nullptr);
506 s_typeNodeMap.insert(key: "std::vector", value: nullptr);
507 s_typeNodeMap.insert(key: "stringlist", value: nullptr);
508 s_typeNodeMap.insert(key: "swapPlayers", value: nullptr);
509 s_typeNodeMap.insert(key: "symbol", value: nullptr);
510 s_typeNodeMap.insert(key: "t", value: nullptr);
511 s_typeNodeMap.insert(key: "T", value: nullptr);
512 s_typeNodeMap.insert(key: "tagChanged", value: nullptr);
513 s_typeNodeMap.insert(key: "timeString", value: nullptr);
514 s_typeNodeMap.insert(key: "timeout", value: nullptr);
515 s_typeNodeMap.insert(key: "to", value: nullptr);
516 s_typeNodeMap.insert(key: "toggled", value: nullptr);
517 s_typeNodeMap.insert(key: "type", value: nullptr);
518 s_typeNodeMap.insert(key: "unsigned", value: nullptr);
519 s_typeNodeMap.insert(key: "urllist", value: nullptr);
520 s_typeNodeMap.insert(key: "va_list", value: nullptr);
521 s_typeNodeMap.insert(key: "value", value: nullptr);
522 s_typeNodeMap.insert(key: "valueEmitted", value: nullptr);
523 s_typeNodeMap.insert(key: "videoFramePainted", value: nullptr);
524 s_typeNodeMap.insert(key: "videoModeSelected", value: nullptr);
525 s_typeNodeMap.insert(key: "videoRecorder", value: nullptr);
526 s_typeNodeMap.insert(key: "void", value: nullptr);
527 s_typeNodeMap.insert(key: "volatile", value: nullptr);
528 s_typeNodeMap.insert(key: "wchar_t", value: nullptr);
529 s_typeNodeMap.insert(key: "x", value: nullptr);
530 s_typeNodeMap.insert(key: "y", value: nullptr);
531 s_typeNodeMap.insert(key: "zoom", value: nullptr);
532 s_typeNodeMap.insert(key: "zoomTo", value: nullptr);
533}
534
535/*! \fn NamespaceNode *QDocDatabase::primaryTreeRoot()
536 Returns a pointer to the root node of the primary tree.
537 */
538
539/*!
540 \fn const CNMap &QDocDatabase::groups()
541 Returns a const reference to the collection of all
542 group nodes in the primary tree.
543*/
544
545/*!
546 \fn const CNMap &QDocDatabase::modules()
547 Returns a const reference to the collection of all
548 module nodes in the primary tree.
549*/
550
551/*!
552 \fn const CNMap &QDocDatabase::qmlModules()
553 Returns a const reference to the collection of all
554 QML module nodes in the primary tree.
555*/
556
557/*! \fn CollectionNode *QDocDatabase::findGroup(const QString &name)
558 Find the group node named \a name and return a pointer
559 to it. If a matching node is not found, add a new group
560 node named \a name and return a pointer to that one.
561
562 If a new group node is added, its parent is the tree root,
563 and the new group node is marked \e{not seen}.
564 */
565
566/*! \fn CollectionNode *QDocDatabase::findModule(const QString &name)
567 Find the module node named \a name and return a pointer
568 to it. If a matching node is not found, add a new module
569 node named \a name and return a pointer to that one.
570
571 If a new module node is added, its parent is the tree root,
572 and the new module node is marked \e{not seen}.
573 */
574
575/*! \fn CollectionNode *QDocDatabase::addGroup(const QString &name)
576 Looks up the group named \a name in the primary tree. If
577 a match is found, a pointer to the node is returned.
578 Otherwise, a new group node named \a name is created and
579 inserted into the collection, and the pointer to that node
580 is returned.
581 */
582
583/*! \fn CollectionNode *QDocDatabase::addModule(const QString &name)
584 Looks up the module named \a name in the primary tree. If
585 a match is found, a pointer to the node is returned.
586 Otherwise, a new module node named \a name is created and
587 inserted into the collection, and the pointer to that node
588 is returned.
589 */
590
591/*! \fn CollectionNode *QDocDatabase::addQmlModule(const QString &name)
592 Looks up the QML module named \a name in the primary tree.
593 If a match is found, a pointer to the node is returned.
594 Otherwise, a new QML module node named \a name is created
595 and inserted into the collection, and the pointer to that
596 node is returned.
597 */
598
599/*! \fn CollectionNode *QDocDatabase::addToGroup(const QString &name, Node *node)
600 Looks up the group node named \a name in the collection
601 of all group nodes. If a match is not found, a new group
602 node named \a name is created and inserted into the collection.
603 Then append \a node to the group's members list, and append the
604 group node to the member list of the \a node. The parent of the
605 \a node is not changed by this function. Returns a pointer to
606 the group node.
607 */
608
609/*! \fn CollectionNode *QDocDatabase::addToModule(const QString &name, Node *node)
610 Looks up the module node named \a name in the collection
611 of all module nodes. If a match is not found, a new module
612 node named \a name is created and inserted into the collection.
613 Then append \a node to the module's members list. The parent of
614 \a node is not changed by this function. Returns the module node.
615 */
616
617/*! \fn Collection *QDocDatabase::addToQmlModule(const QString &name, Node *node)
618 Looks up the QML module named \a name. If it isn't there,
619 create it. Then append \a node to the QML module's member
620 list. The parent of \a node is not changed by this function.
621 */
622
623/*!
624 Looks up the QML type node identified by the qualified Qml
625 type \a name and returns a pointer to the QML type node.
626 */
627QmlTypeNode *QDocDatabase::findQmlType(const QString &name)
628{
629 QmlTypeNode *qcn = m_forest.lookupQmlType(name);
630 if (qcn)
631 return qcn;
632 return nullptr;
633}
634
635/*!
636 Looks up the QML type node identified by the Qml module id
637 \a qmid and QML type \a name and returns a pointer to the
638 QML type node. The key is \a qmid + "::" + \a name.
639
640 If the QML module id is empty, it looks up the QML type by
641 \a name only.
642 */
643QmlTypeNode *QDocDatabase::findQmlType(const QString &qmid, const QString &name)
644{
645 if (!qmid.isEmpty()) {
646 QString t = qmid + "::" + name;
647 QmlTypeNode *qcn = m_forest.lookupQmlType(name: t);
648 if (qcn)
649 return qcn;
650 }
651
652 QStringList path(name);
653 Node *n = m_forest.findNodeByNameAndType(path, isMatch: &Node::isQmlType);
654 if (n && n->isQmlType())
655 return static_cast<QmlTypeNode *>(n);
656 return nullptr;
657}
658
659/*!
660 Looks up the QML type node identified by the Qml module id
661 constructed from the strings in the \a import record and the
662 QML type \a name and returns a pointer to the QML type node.
663 If a QML type node is not found, 0 is returned.
664 */
665QmlTypeNode *QDocDatabase::findQmlType(const ImportRec &import, const QString &name)
666{
667 if (!import.isEmpty()) {
668 QStringList dotSplit;
669 dotSplit = name.split(sep: QLatin1Char('.'));
670 QString qmName;
671 if (import.m_importUri.isEmpty())
672 qmName = import.m_moduleName;
673 else
674 qmName = import.m_importUri;
675 for (const auto &namePart : dotSplit) {
676 QString qualifiedName = qmName + "::" + namePart;
677 QmlTypeNode *qcn = m_forest.lookupQmlType(name: qualifiedName);
678 if (qcn)
679 return qcn;
680 }
681 }
682 return nullptr;
683}
684
685/*!
686 This function calls a set of functions for each tree in the
687 forest that has not already been analyzed. In this way, when
688 running qdoc in \e singleExec mode, each tree is analyzed in
689 turn, and its classes and types are added to the appropriate
690 node maps.
691 */
692void QDocDatabase::processForest()
693{
694 processForest(func: &QDocDatabase::findAllClasses);
695 processForest(func: &QDocDatabase::findAllFunctions);
696 processForest(func: &QDocDatabase::findAllObsoleteThings);
697 processForest(func: &QDocDatabase::findAllLegaleseTexts);
698 processForest(func: &QDocDatabase::findAllSince);
699 processForest(func: &QDocDatabase::findAllAttributions);
700 resolveNamespaces();
701}
702
703/*!
704 This function calls \a func for each tree in the forest,
705 ensuring that \a func is called only once per tree.
706
707 \sa processForest()
708 */
709void QDocDatabase::processForest(FindFunctionPtr func)
710{
711 Tree *t = m_forest.firstTree();
712 while (t) {
713 if (!m_completedFindFunctions.values(key: t).contains(t: func)) {
714 (this->*(func))(t->root());
715 m_completedFindFunctions.insert(key: t, value: func);
716 }
717 t = m_forest.nextTree();
718 }
719}
720
721/*!
722 Returns a reference to the collection of legalese texts.
723 */
724TextToNodeMap &QDocDatabase::getLegaleseTexts()
725{
726 processForest(func: &QDocDatabase::findAllLegaleseTexts);
727 return m_legaleseTexts;
728}
729
730/*!
731 Returns a reference to the map of C++ classes with obsolete members.
732 */
733NodeMultiMap &QDocDatabase::getClassesWithObsoleteMembers()
734{
735 processForest(func: &QDocDatabase::findAllObsoleteThings);
736 return s_classesWithObsoleteMembers;
737}
738
739/*!
740 Returns a reference to the map of obsolete QML types.
741 */
742NodeMultiMap &QDocDatabase::getObsoleteQmlTypes()
743{
744 processForest(func: &QDocDatabase::findAllObsoleteThings);
745 return s_obsoleteQmlTypes;
746}
747
748/*!
749 Returns a reference to the map of QML types with obsolete members.
750 */
751NodeMultiMap &QDocDatabase::getQmlTypesWithObsoleteMembers()
752{
753 processForest(func: &QDocDatabase::findAllObsoleteThings);
754 return s_qmlTypesWithObsoleteMembers;
755}
756
757/*!
758 Returns a reference to the map of QML basic types.
759 */
760NodeMultiMap &QDocDatabase::getQmlValueTypes()
761{
762 processForest(func: &QDocDatabase::findAllClasses);
763 return s_qmlBasicTypes;
764}
765
766/*!
767 Returns a reference to the multimap of QML types.
768 */
769NodeMultiMap &QDocDatabase::getQmlTypes()
770{
771 processForest(func: &QDocDatabase::findAllClasses);
772 return s_qmlTypes;
773}
774
775/*!
776 Returns a reference to the multimap of example nodes.
777 */
778NodeMultiMap &QDocDatabase::getExamples()
779{
780 processForest(func: &QDocDatabase::findAllClasses);
781 return s_examples;
782}
783
784/*!
785 Returns a reference to the multimap of attribution nodes.
786 */
787NodeMultiMap &QDocDatabase::getAttributions()
788{
789 processForest(func: &QDocDatabase::findAllAttributions);
790 return m_attributions;
791}
792
793/*!
794 Returns a reference to the map of obsolete C++ clases.
795 */
796NodeMultiMap &QDocDatabase::getObsoleteClasses()
797{
798 processForest(func: &QDocDatabase::findAllObsoleteThings);
799 return s_obsoleteClasses;
800}
801
802/*!
803 Returns a reference to the map of all C++ classes.
804 */
805NodeMultiMap &QDocDatabase::getCppClasses()
806{
807 processForest(func: &QDocDatabase::findAllClasses);
808 return s_cppClasses;
809}
810
811/*!
812 Returns the function index. This data structure is used to
813 output the function index page.
814 */
815NodeMapMap &QDocDatabase::getFunctionIndex()
816{
817 processForest(func: &QDocDatabase::findAllFunctions);
818 return m_functionIndex;
819}
820
821/*!
822 Finds all the nodes containing legalese text and puts them
823 in a map.
824 */
825void QDocDatabase::findAllLegaleseTexts(Aggregate *node)
826{
827 for (const auto &childNode : node->childNodes()) {
828 if (childNode->isPrivate())
829 continue;
830 if (!childNode->doc().legaleseText().isEmpty())
831 m_legaleseTexts.insert(key: childNode->doc().legaleseText(), value: childNode);
832 if (childNode->isAggregate())
833 findAllLegaleseTexts(node: static_cast<Aggregate *>(childNode));
834 }
835}
836
837/*!
838 \fn void QDocDatabase::findAllObsoleteThings(Aggregate *node)
839
840 Finds all nodes with status = Deprecated and sorts them into
841 maps. They can be C++ classes, QML types, or they can be
842 functions, enum types, typedefs, methods, etc.
843 */
844
845/*!
846 \fn void QDocDatabase::findAllSince(Aggregate *node)
847
848 Finds all the nodes in \a node where a \e{since} command appeared
849 in the qdoc comment and sorts them into maps according to the kind
850 of node.
851
852 This function is used for generating the "New Classes... in x.y"
853 section on the \e{What's New in Qt x.y} page.
854 */
855
856/*!
857 Find the \a key in the map of new class maps, and return a
858 reference to the value, which is a NodeMap. If \a key is not
859 found, return a reference to an empty NodeMap.
860 */
861const NodeMultiMap &QDocDatabase::getClassMap(const QString &key)
862{
863 processForest(func: &QDocDatabase::findAllSince);
864 auto it = s_newClassMaps.constFind(key);
865 return (it != s_newClassMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
866}
867
868/*!
869 Find the \a key in the map of new QML type maps, and return a
870 reference to the value, which is a NodeMap. If the \a key is not
871 found, return a reference to an empty NodeMap.
872 */
873const NodeMultiMap &QDocDatabase::getQmlTypeMap(const QString &key)
874{
875 processForest(func: &QDocDatabase::findAllSince);
876 auto it = s_newQmlTypeMaps.constFind(key);
877 return (it != s_newQmlTypeMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
878}
879
880/*!
881 Find the \a key in the map of new \e {since} maps, and return
882 a reference to the value, which is a NodeMultiMap. If \a key
883 is not found, return a reference to an empty NodeMultiMap.
884 */
885const NodeMultiMap &QDocDatabase::getSinceMap(const QString &key)
886{
887 processForest(func: &QDocDatabase::findAllSince);
888 auto it = s_newSinceMaps.constFind(key);
889 return (it != s_newSinceMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
890}
891
892/*!
893 Performs several housekeeping tasks prior to generating the
894 documentation. These tasks create required data structures
895 and resolve links.
896 */
897void QDocDatabase::resolveStuff()
898{
899 const auto &config = Config::instance();
900 if (config.dualExec() || config.preparing()) {
901 // order matters
902 primaryTree()->resolveBaseClasses(n: primaryTreeRoot());
903 primaryTree()->resolvePropertyOverriddenFromPtrs(n: primaryTreeRoot());
904 primaryTreeRoot()->normalizeOverloads();
905 primaryTree()->markDontDocumentNodes();
906 primaryTree()->removePrivateAndInternalBases(rootNode: primaryTreeRoot());
907 primaryTree()->resolveProperties();
908 primaryTreeRoot()->markUndocumentedChildrenInternal();
909 primaryTreeRoot()->resolveQmlInheritance();
910 primaryTree()->resolveTargets(root: primaryTreeRoot());
911 primaryTree()->resolveCppToQmlLinks();
912 primaryTree()->resolveSince(aggregate&: *primaryTreeRoot());
913 }
914 if (config.singleExec() && config.generating()) {
915 primaryTree()->resolveBaseClasses(n: primaryTreeRoot());
916 primaryTree()->resolvePropertyOverriddenFromPtrs(n: primaryTreeRoot());
917 primaryTreeRoot()->resolveQmlInheritance();
918 primaryTree()->resolveCppToQmlLinks();
919 primaryTree()->resolveSince(aggregate&: *primaryTreeRoot());
920 }
921 if (!config.preparing()) {
922 resolveNamespaces();
923 resolveProxies();
924 resolveBaseClasses();
925 updateNavigation();
926 }
927 if (config.dualExec())
928 QDocIndexFiles::destroyQDocIndexFiles();
929}
930
931void QDocDatabase::resolveBaseClasses()
932{
933 Tree *t = m_forest.firstTree();
934 while (t) {
935 t->resolveBaseClasses(n: t->root());
936 t = m_forest.nextTree();
937 }
938}
939
940/*!
941 Returns a reference to the namespace map. Constructs the
942 namespace map if it hasn't been constructed yet.
943
944 \note This function must not be called in the prepare phase.
945 */
946NodeMultiMap &QDocDatabase::getNamespaces()
947{
948 resolveNamespaces();
949 return m_namespaceIndex;
950}
951
952/*!
953 Multiple namespace nodes for namespace X can exist in the
954 qdoc database in different trees. This function first finds
955 all namespace nodes in all the trees and inserts them into
956 a multimap. Then it combines all the namespace nodes that
957 have the same name into a single namespace node of that
958 name and inserts that combined namespace node into an index.
959 */
960void QDocDatabase::resolveNamespaces()
961{
962 if (!m_namespaceIndex.isEmpty())
963 return;
964
965 bool linkErrors = !Config::instance().get(CONFIG_NOLINKERRORS).asBool();
966 NodeMultiMap namespaceMultimap;
967 Tree *t = m_forest.firstTree();
968 while (t) {
969 t->root()->findAllNamespaces(namespaces&: namespaceMultimap);
970 t = m_forest.nextTree();
971 }
972 const QList<QString> keys = namespaceMultimap.uniqueKeys();
973 for (const QString &key : keys) {
974 NamespaceNode *ns = nullptr;
975 NamespaceNode *indexNamespace = nullptr;
976 const NodeList namespaces = namespaceMultimap.values(key);
977 qsizetype count = namespaceMultimap.remove(key);
978 if (count > 0) {
979 for (auto *node : namespaces) {
980 ns = static_cast<NamespaceNode *>(node);
981 if (ns->isDocumentedHere())
982 break;
983 else if (ns->hadDoc())
984 indexNamespace = ns; // namespace was documented but in another tree
985 ns = nullptr;
986 }
987 if (ns) {
988 for (auto *node : namespaces) {
989 auto *nsNode = static_cast<NamespaceNode *>(node);
990 if (nsNode->hadDoc() && nsNode != ns) {
991 ns->doc().location().warning(
992 QStringLiteral("Namespace %1 documented more than once")
993 .arg(a: nsNode->name()), QStringLiteral("also seen here: %1")
994 .arg(a: nsNode->doc().location().toString()));
995 }
996 }
997 } else if (!indexNamespace) {
998 // Warn about documented children in undocumented namespaces.
999 // As the namespace can be documented outside this project,
1000 // skip the warning if -no-link-errors is set
1001 if (linkErrors) {
1002 for (auto *node : namespaces) {
1003 if (!node->isIndexNode())
1004 static_cast<NamespaceNode *>(node)->reportDocumentedChildrenInUndocumentedNamespace();
1005 }
1006 }
1007 } else {
1008 for (auto *node : namespaces) {
1009 auto *nsNode = static_cast<NamespaceNode *>(node);
1010 if (nsNode != indexNamespace)
1011 nsNode->setDocNode(indexNamespace);
1012 }
1013 }
1014 }
1015 /*
1016 If there are multiple namespace nodes with the same
1017 name where one of them will be the main reference page
1018 for the namespace, include all nodes in the public
1019 API of the namespace.
1020 */
1021 if (ns && count > 1) {
1022 for (auto *node : namespaces) {
1023 auto *nameSpaceNode = static_cast<NamespaceNode *>(node);
1024 if (nameSpaceNode != ns) {
1025 for (auto it = nameSpaceNode->constBegin(); it != nameSpaceNode->constEnd();
1026 ++it) {
1027 Node *anotherNs = *it;
1028 if (anotherNs && anotherNs->isPublic() && !anotherNs->isInternal())
1029 ns->includeChild(child: anotherNs);
1030 }
1031 }
1032 }
1033 }
1034 /*
1035 Add the main namespace reference node to index, or the last seen
1036 namespace if the main one was not found.
1037 */
1038 if (!ns)
1039 ns = indexNamespace ? indexNamespace : static_cast<NamespaceNode *>(namespaces.last());
1040 m_namespaceIndex.insert(key: ns->name(), value: ns);
1041 }
1042}
1043
1044/*!
1045 Each instance of class Tree that represents an index file
1046 must be traversed to find all instances of class ProxyNode.
1047 For each ProxyNode found, look up the ProxyNode's name in
1048 the primary Tree. If it is found, it means that the proxy
1049 node contains elements (normally just functions) that are
1050 documented in the module represented by the Tree containing
1051 the proxy node but that are related to the node we found in
1052 the primary tree.
1053 */
1054void QDocDatabase::resolveProxies()
1055{
1056 // The first tree is the primary tree.
1057 // Skip the primary tree.
1058 Tree *t = m_forest.firstTree();
1059 t = m_forest.nextTree();
1060 while (t) {
1061 const NodeList &proxies = t->proxies();
1062 if (!proxies.isEmpty()) {
1063 for (auto *node : proxies) {
1064 const auto *pn = static_cast<ProxyNode *>(node);
1065 if (pn->count() > 0) {
1066 Aggregate *aggregate = primaryTree()->findAggregate(name: pn->name());
1067 if (aggregate != nullptr)
1068 aggregate->appendToRelatedByProxy(t: pn->childNodes());
1069 }
1070 }
1071 }
1072 t = m_forest.nextTree();
1073 }
1074}
1075
1076/*!
1077 Finds the function node for the qualified function path in
1078 \a target and returns a pointer to it. The \a target is a
1079 function signature with or without parameters but without
1080 the return type.
1081
1082 \a relative is the node in the primary tree where the search
1083 begins. It is not used in the other trees, if the node is not
1084 found in the primary tree. \a genus can be used to force the
1085 search to find a C++ function or a QML function.
1086
1087 The entire forest is searched, but the first match is accepted.
1088 */
1089const FunctionNode *QDocDatabase::findFunctionNode(const QString &target, const Node *relative,
1090 Node::Genus genus)
1091{
1092 QString signature;
1093 QString function = target;
1094 qsizetype length = target.size();
1095 if (function.endsWith(s: "()"))
1096 function.chop(n: 2);
1097 if (function.endsWith(c: QChar(')'))) {
1098 qsizetype position = function.lastIndexOf(c: QChar('('));
1099 signature = function.mid(position: position + 1, n: length - position - 2);
1100 function = function.left(n: position);
1101 }
1102 QStringList path = function.split(sep: "::");
1103 return m_forest.findFunctionNode(path, parameters: Parameters(signature), relative, genus);
1104}
1105
1106/*!
1107 This function is called for autolinking to a \a type,
1108 which could be a function return type or a parameter
1109 type. The tree node that represents the \a type is
1110 returned. All the trees are searched until a match is
1111 found. When searching the primary tree, the search
1112 begins at \a relative and proceeds up the parent chain.
1113 When searching the index trees, the search begins at the
1114 root.
1115 */
1116const Node *QDocDatabase::findTypeNode(const QString &type, const Node *relative, Node::Genus genus)
1117{
1118 QStringList path = type.split(sep: "::");
1119 if ((path.size() == 1) && (path.at(i: 0)[0].isLower() || path.at(i: 0) == QString("T"))) {
1120 auto it = s_typeNodeMap.find(key: path.at(i: 0));
1121 if (it != s_typeNodeMap.end())
1122 return it.value();
1123 }
1124 return m_forest.findTypeNode(path, relative, genus);
1125}
1126
1127/*!
1128 Finds the node that will generate the documentation that
1129 contains the \a target and returns a pointer to it.
1130
1131 Can this be improved by using the target map in Tree?
1132 */
1133const Node *QDocDatabase::findNodeForTarget(const QString &target, const Node *relative)
1134{
1135 const Node *node = nullptr;
1136 if (target.isEmpty())
1137 node = relative;
1138 else if (target.endsWith(s: ".html"))
1139 node = findNodeByNameAndType(path: QStringList(target), isMatch: &Node::isPageNode);
1140 else {
1141 QStringList path = target.split(sep: "::");
1142 int flags = SearchBaseClasses | SearchEnumValues;
1143 for (const auto *tree : searchOrder()) {
1144 const Node *n = tree->findNode(path, relative, flags, genus: Node::DontCare);
1145 if (n)
1146 return n;
1147 relative = nullptr;
1148 }
1149 node = findPageNodeByTitle(title: target);
1150 }
1151 return node;
1152}
1153
1154QStringList QDocDatabase::groupNamesForNode(Node *node)
1155{
1156 QStringList result;
1157 CNMap *m = primaryTree()->getCollectionMap(type: Node::Group);
1158
1159 if (!m)
1160 return result;
1161
1162 for (auto it = m->cbegin(); it != m->cend(); ++it)
1163 if (it.value()->members().contains(t: node))
1164 result << it.key();
1165
1166 return result;
1167}
1168
1169/*!
1170 Reads and parses the qdoc index files listed in \a indexFiles.
1171 */
1172void QDocDatabase::readIndexes(const QStringList &indexFiles)
1173{
1174 QStringList filesToRead;
1175 for (const QString &file : indexFiles) {
1176 QString fn = file.mid(position: file.lastIndexOf(c: QChar('/')) + 1);
1177 if (!isLoaded(t: fn))
1178 filesToRead << file;
1179 else
1180 qCCritical(lcQdoc) << "Index file" << file << "is already in memory.";
1181 }
1182 QDocIndexFiles::qdocIndexFiles()->readIndexes(indexFiles: filesToRead);
1183}
1184
1185/*!
1186 Generates a qdoc index file and write it to \a fileName. The
1187 index file is generated with the parameters \a url and \a title,
1188 using the generator \a g.
1189 */
1190void QDocDatabase::generateIndex(const QString &fileName, const QString &url, const QString &title,
1191 Generator *g)
1192{
1193 QString t = fileName.mid(position: fileName.lastIndexOf(c: QChar('/')) + 1);
1194 primaryTree()->setIndexFileName(t);
1195 QDocIndexFiles::qdocIndexFiles()->generateIndex(fileName, url, title, g);
1196 QDocIndexFiles::destroyQDocIndexFiles();
1197}
1198
1199/*!
1200 Find a node of the specified \a type that is reached with
1201 the specified \a path qualified with the name of one of the
1202 open namespaces (might not be any open ones). If the node
1203 is found in an open namespace, prefix \a path with the name
1204 of the open namespace and "::" and return a pointer to the
1205 node. Otherwise return \c nullptr.
1206
1207 This function only searches in the current primary tree.
1208 */
1209Node *QDocDatabase::findNodeInOpenNamespace(QStringList &path, bool (Node::*isMatch)() const)
1210{
1211 if (path.isEmpty())
1212 return nullptr;
1213 Node *n = nullptr;
1214 if (!m_openNamespaces.isEmpty()) {
1215 const auto &openNamespaces = m_openNamespaces;
1216 for (const QString &t : openNamespaces) {
1217 QStringList p;
1218 if (t != path[0])
1219 p = t.split(sep: "::") + path;
1220 else
1221 p = path;
1222 n = primaryTree()->findNodeByNameAndType(path: p, isMatch);
1223 if (n) {
1224 path = p;
1225 break;
1226 }
1227 }
1228 }
1229 return n;
1230}
1231
1232/*!
1233 Returns the collection node representing the module that \a relative
1234 node belongs to, or \c nullptr if there is no such module in the
1235 primary tree.
1236*/
1237const CollectionNode *QDocDatabase::getModuleNode(const Node *relative)
1238{
1239 Node::NodeType moduleType{Node::Module};
1240 QString moduleName;
1241 switch (relative->genus())
1242 {
1243 case Node::CPP:
1244 moduleType = Node::Module;
1245 moduleName = relative->physicalModuleName();
1246 break;
1247 case Node::QML:
1248 moduleType = Node::QmlModule;
1249 moduleName = relative->logicalModuleName();
1250 break;
1251 default:
1252 return nullptr;
1253 }
1254 if (moduleName.isEmpty())
1255 return nullptr;
1256
1257 return primaryTree()->getCollection(name: moduleName, type: moduleType);
1258}
1259
1260/*!
1261 Finds all the collection nodes of the specified \a type
1262 and merges them into the collection node map \a cnm. Nodes
1263 that match the \a relative node are not included.
1264 */
1265void QDocDatabase::mergeCollections(Node::NodeType type, CNMap &cnm, const Node *relative)
1266{
1267 cnm.clear();
1268 CNMultiMap cnmm;
1269 for (auto *tree : searchOrder()) {
1270 CNMap *m = tree->getCollectionMap(type);
1271 if (m && !m->isEmpty()) {
1272 for (auto it = m->cbegin(); it != m->cend(); ++it) {
1273 if (!it.value()->isInternal())
1274 cnmm.insert(key: it.key(), value: it.value());
1275 }
1276 }
1277 }
1278 if (cnmm.isEmpty())
1279 return;
1280 static const QRegularExpression singleDigit("\\b([0-9])\\b");
1281 const QStringList keys = cnmm.uniqueKeys();
1282 for (const auto &key : keys) {
1283 const QList<CollectionNode *> values = cnmm.values(key);
1284 CollectionNode *n = nullptr;
1285 for (auto *value : values) {
1286 if (value && value->wasSeen() && value != relative) {
1287 n = value;
1288 break;
1289 }
1290 }
1291 if (n) {
1292 if (values.size() > 1) {
1293 for (CollectionNode *value : values) {
1294 if (value != n) {
1295 // Allow multiple (major) versions of QML modules
1296 if ((n->isQmlModule())
1297 && n->logicalModuleIdentifier() != value->logicalModuleIdentifier()) {
1298 if (value->wasSeen() && value != relative
1299 && !value->members().isEmpty())
1300 cnm.insert(key: value->fullTitle().toLower(), value);
1301 continue;
1302 }
1303 for (Node *t : value->members())
1304 n->addMember(node: t);
1305 }
1306 }
1307 }
1308 QString sortKey = n->fullTitle().toLower();
1309 if (sortKey.startsWith(s: "the "))
1310 sortKey.remove(i: 0, len: 4);
1311 sortKey.replace(re: singleDigit, after: "0\\1");
1312 cnm.insert(key: sortKey, value: n);
1313 }
1314 }
1315}
1316
1317/*!
1318 Finds all the collection nodes with the same name
1319 and type as \a c and merges their members into the
1320 members list of \a c.
1321
1322 For QML modules, only nodes with matching
1323 module identifiers are merged to avoid merging
1324 modules with different (major) versions.
1325 */
1326void QDocDatabase::mergeCollections(CollectionNode *c)
1327{
1328 if (c == nullptr)
1329 return;
1330
1331 // REMARK: This form of merging is usually called during the
1332 // generation phase om-the-fly when a source-of-truth collection
1333 // is required.
1334 // In practice, this means a collection could be merged many, many
1335 // times during the lifetime of a generation.
1336 // To avoid repeating the merging process each time, which could
1337 // be time consuming, we use a small flag that is set directly on
1338 // the collection to bail-out early.
1339 //
1340 // The merging process is only meaningful for collections when the
1341 // collection references are spread troughout multiple projects.
1342 // The part of information that exists in other project is read
1343 // before the generation phase, such that when the generation
1344 // phase comes, we already have all the information we need for
1345 // merging such that we can consider all version of a certain
1346 // collection node immutable, making the caching inherently
1347 // correct at any point of the generation.
1348 //
1349 // This implies that this operation is unsafe if it is performed
1350 // before all the index files are loaded.
1351 // Indeed, this is a prerequisite, with the current structure, to
1352 // perform this optmization.
1353 //
1354 // At the current time, this is true and is expected not to
1355 // change.
1356 //
1357 // Do note that this is not applied to the other overload of
1358 // mergeCollections as we cannot as safely ensure its consistency
1359 // and, as the result of the merging depends on multiple
1360 // parameters, it would require an actual memoization of the call.
1361 //
1362 // Note that this is a defensive optimization and we are assuming
1363 // that it is effective based on heuristical data. As this is
1364 // expected to disappear, at least in its current form, in the
1365 // future, a more thorough analysis was not performed.
1366 if (c->isMerged()) {
1367 return;
1368 }
1369
1370 for (auto *tree : searchOrder()) {
1371 CollectionNode *cn = tree->getCollection(name: c->name(), type: c->nodeType());
1372 if (cn && cn != c) {
1373 if ((cn->isQmlModule())
1374 && cn->logicalModuleIdentifier() != c->logicalModuleIdentifier())
1375 continue;
1376
1377 for (auto *node : cn->members())
1378 c->addMember(node);
1379
1380 // REMARK: The merging process is performed to ensure that
1381 // references to the collection in external projects are
1382 // taken into account before consuming the collection.
1383 //
1384 // This works by having QDoc construct empty collections
1385 // as soon as a reference to a collection is encountered
1386 // and filling details later on when its definition is
1387 // found.
1388 //
1389 // This initially-empty collection is always saved to the
1390 // primaryTree and it is the collection that is directly
1391 // accessible to consumers during the generation process.
1392 //
1393 // Nonetheless, when the definition for the collection is
1394 // not in the same project as the one that is being
1395 // compiled, its details will never be filled in.
1396 //
1397 // Indeed, the details will live in the index file for the
1398 // project where the collection is defined, if any, and
1399 // the node for it, which has complete information, will
1400 // live in some non-primaryTree.
1401 //
1402 // The merging process itself is used by consumers during
1403 // the generation process because they access the
1404 // primaryTree version of the collection expecting a
1405 // source-of-truth.
1406 // To ensure that this is the case for usages that
1407 // requires linking, we need to merge not only the members
1408 // of the collection that reside in external versions of
1409 // the collection; but some of the data that reside in the
1410 // definition of the collection intself, namely the title
1411 // and the url.
1412 //
1413 // A collection that contains the data of a definition is
1414 // always marked as seen, hence we use that to discern
1415 // whether we are working with a placeholder node or not,
1416 // and fill in the data if we encounter a node that
1417 // represents a definition.
1418 //
1419 // The way in which QDoc works implies that collection are
1420 // globally scoped between projects.
1421 // The repetition of the definition for the same
1422 // collection is warned for as a duplicate documentation,
1423 // such that we can expect a single valid source of truth
1424 // for a given collection in each project.
1425 // It is currently unknown if this warning is applicable
1426 // when the repeated collection is defined in two
1427 // different projects.
1428 //
1429 // As QDoc implicitly would not correctly support this
1430 // case, we assume that only one declaration exists for
1431 // each collection, such that the first encoutered one
1432 // must be the source of truth and that there is no need
1433 // to copy any data after the first copy is performed.
1434 // KLUDGE: Note that this process is done as a hackish
1435 // solution to QTBUG-104237 and should not be considered
1436 // final or dependable.
1437 if (!c->wasSeen() && cn->wasSeen()) {
1438 c->markSeen();
1439 c->setTitle(cn->title());
1440 c->setUrl(cn->url());
1441 }
1442 }
1443 }
1444
1445 c->markMerged();
1446}
1447
1448/*!
1449 Searches for the node that matches the path in \a atom and the
1450 specified \a genus. The \a relative node is used if the first
1451 leg of the path is empty, i.e. if the path begins with '#'.
1452 The function also sets \a ref if there remains an unused leg
1453 in the path after the node is found. The node is returned as
1454 well as the \a ref. If the returned node pointer is null,
1455 \a ref is also not valid.
1456 */
1457const Node *QDocDatabase::findNodeForAtom(const Atom *a, const Node *relative, QString &ref,
1458 Node::Genus genus)
1459{
1460 const Node *node = nullptr;
1461
1462 Atom *atom = const_cast<Atom *>(a);
1463 QStringList targetPath = atom->string().split(sep: QLatin1Char('#'));
1464 QString first = targetPath.first().trimmed();
1465
1466 Tree *domain = nullptr;
1467
1468 if (atom->isLinkAtom()) {
1469 domain = atom->domain();
1470 genus = atom->genus();
1471 }
1472
1473 if (first.isEmpty())
1474 node = relative; // search for a target on the current page.
1475 else if (domain) {
1476 if (first.endsWith(s: ".html"))
1477 node = domain->findNodeByNameAndType(path: QStringList(first), isMatch: &Node::isPageNode);
1478 else if (first.endsWith(c: QChar(')'))) {
1479 QString signature;
1480 QString function = first;
1481 qsizetype length = first.size();
1482 if (function.endsWith(s: "()"))
1483 function.chop(n: 2);
1484 if (function.endsWith(c: QChar(')'))) {
1485 qsizetype position = function.lastIndexOf(c: QChar('('));
1486 signature = function.mid(position: position + 1, n: length - position - 2);
1487 function = function.left(n: position);
1488 }
1489 QStringList path = function.split(sep: "::");
1490 node = domain->findFunctionNode(path, parameters: Parameters(signature), relative: nullptr, genus);
1491 }
1492 if (node == nullptr) {
1493 int flags = SearchBaseClasses | SearchEnumValues;
1494 QStringList nodePath = first.split(sep: "::");
1495 QString target;
1496 targetPath.removeFirst();
1497 if (!targetPath.isEmpty())
1498 target = targetPath.takeFirst();
1499 if (relative && relative->tree()->physicalModuleName() != domain->physicalModuleName())
1500 relative = nullptr;
1501 return domain->findNodeForTarget(path: nodePath, target, node: relative, flags, genus, ref);
1502 }
1503 } else {
1504 if (first.endsWith(s: ".html"))
1505 node = findNodeByNameAndType(path: QStringList(first), isMatch: &Node::isPageNode);
1506 else if (first.endsWith(c: QChar(')')))
1507 node = findFunctionNode(target: first, relative, genus);
1508 if (node == nullptr)
1509 return findNodeForTarget(targetPath, relative, genus, ref);
1510 }
1511
1512 if (node != nullptr && ref.isEmpty()) {
1513 if (!node->url().isEmpty())
1514 return node;
1515 targetPath.removeFirst();
1516 if (!targetPath.isEmpty()) {
1517 ref = node->root()->tree()->getRef(target: targetPath.first(), node);
1518 if (ref.isEmpty())
1519 node = nullptr;
1520 }
1521 }
1522 return node;
1523}
1524
1525/*!
1526 Updates navigation (previous/next page links and the navigation parent)
1527 for pages listed in the TOC, specified by the \c navigation.toctitles
1528 configuration variable.
1529
1530 if \c navigation.toctitles.inclusive is \c true, include also the TOC
1531 page(s) themselves as a 'root' item in the navigation bar (breadcrumbs)
1532 that are generated for HTML output.
1533*/
1534void QDocDatabase::updateNavigation()
1535{
1536 // Restrict searching only to the local (primary) tree
1537 QList<Tree *> searchOrder = this->searchOrder();
1538 setLocalSearch();
1539
1540 const QString configVar = CONFIG_NAVIGATION +
1541 Config::dot +
1542 CONFIG_TOCTITLES;
1543
1544 // TODO: [direct-configuration-access]
1545 // The configuration is currently a singleton with some generally
1546 // global mutable state.
1547 //
1548 // Accessing the data in this form complicates testing and
1549 // requires tests that inhibit any test parallelization, as the
1550 // tests are not self contained.
1551 //
1552 // This should be generally avoived. Possibly, we should strive
1553 // for Config to be a POD type that generally is scoped to
1554 // main and whose data is destructured into dependencies when
1555 // the dependencies are constructed.
1556 bool inclusive{Config::instance().get(
1557 var: configVar + Config::dot + CONFIG_INCLUSIVE).asBool()};
1558
1559 // TODO: [direct-configuration-access]
1560 const auto tocTitles{Config::instance().get(var: configVar).asStringList()};
1561
1562 for (const auto &tocTitle : tocTitles) {
1563 if (const auto candidateTarget = findNodeForTarget(target: tocTitle, relative: nullptr); candidateTarget && candidateTarget->isPageNode()) {
1564 auto tocPage{static_cast<const PageNode*>(candidateTarget)};
1565
1566 Text body = tocPage->doc().body();
1567
1568 auto *atom = body.firstAtom();
1569
1570 std::pair<PageNode *, Atom *> prev { nullptr, nullptr };
1571
1572 std::stack<const PageNode *> tocStack;
1573 tocStack.push(x: inclusive ? tocPage : nullptr);
1574
1575 bool inItem = false;
1576
1577 // TODO: Understand how much we use this form of looping over atoms.
1578 // If it is used a few times we might consider providing
1579 // an iterator for Text to make use of a simpler
1580 // range-for loop.
1581 while (atom) {
1582 switch (atom->type()) {
1583 case Atom::ListItemLeft:
1584 // Not known if we're going to have a link, push a temporary
1585 tocStack.push(x: nullptr);
1586 inItem = true;
1587 break;
1588 case Atom::ListItemRight:
1589 tocStack.pop();
1590 inItem = false;
1591 break;
1592 case Atom::Link: {
1593 if (!inItem)
1594 break;
1595
1596 // TODO: [unnecessary-output-parameter]
1597 // We currently need an lvalue string to
1598 // pass to findNodeForAtom, as the
1599 // outparameter ref.
1600 //
1601 // Apart from the general problems with output
1602 // parameters, we shouldn't be forced to
1603 // instanciate an unnecessary object at call
1604 // site.
1605 //
1606 // Understand what the correct way to avoid this is.
1607 // This requires changes to findNodeForAtom
1608 // and should be addressed in the context of
1609 // revising that method.
1610 QString unused{};
1611 // TODO: Having to const cast is really a code
1612 // smell and could result in undefined
1613 // behavior in some specific cases (e.g point
1614 // to something that is actually const).
1615 //
1616 // We should understand how to sequence the
1617 // code so that we have access to mutable data
1618 // when we need it and "freeze" the data
1619 // afterwards.
1620 //
1621 // If it we expect this form of mutability at
1622 // this point we should expose a non-const API
1623 // for the database, possibly limited to a
1624 // very specific scope of execution.
1625 //
1626 // Understand what the correct sequencing for
1627 // this processing is and revise this part.
1628 auto candidatePage = const_cast<Node *>(findNodeForAtom(a: atom, relative: nullptr, ref&: unused));
1629 if (!candidatePage || !candidatePage->isPageNode()) break;
1630
1631 auto page{static_cast<PageNode*>(candidatePage)};
1632
1633 // ignore self-references
1634 if (page == prev.first) break;
1635
1636 if (prev.first) {
1637 prev.first->setLink(
1638 linkType: Node::NextLink,
1639 link: page->title(),
1640 // TODO: [possible-assertion-failure][imprecise-types][atoms-link]
1641 // As with other structures in QDoc we
1642 // are able to call methods that are
1643 // valid only on very specific states.
1644 //
1645 // For some of those calls we have
1646 // some defensive programming measures
1647 // that allow us to at least identify
1648 // the error during debugging, while
1649 // for others this may currently hide
1650 // some logic error.
1651 //
1652 // To avoid those cases, we should
1653 // strive to move those cases to a
1654 // compilation error, requiring a
1655 // statically analyzable state that
1656 // represents the current model.
1657 //
1658 // This would ensure that those
1659 // lingering class of bugs are
1660 // eliminated completely, forces a
1661 // more explicit codebase where the
1662 // current capabilities do not depend
1663 // on runtime values might not be
1664 // generally visible, and does not
1665 // require us to incur into the
1666 // required state, which may be rare,
1667 // simplifying our abilities to
1668 // evaluate all possible states.
1669 //
1670 // For linking atoms, LinkAtom is
1671 // available and might be a good
1672 // enough solution to move linkText
1673 // to.
1674 desc: atom->linkText()
1675 );
1676 page->setLink(
1677 linkType: Node::PreviousLink,
1678 link: prev.first->title(),
1679 // TODO: [possible-assertion-failure][imprecise-types][atoms-link]
1680 desc: prev.second->linkText()
1681 );
1682 }
1683
1684 if (page == tocPage)
1685 break;
1686
1687 // Find the navigation parent from the stack; we may have null pointers
1688 // for non-link list items, so skip those.
1689 qsizetype popped = 0;
1690 while (tocStack.size() > 1 && !tocStack.top()) {
1691 tocStack.pop();
1692 ++popped;
1693 }
1694
1695 page->setNavigationParent(tocStack.empty() ? nullptr : tocStack.top());
1696
1697 while (--popped > 0)
1698 tocStack.push(x: nullptr);
1699
1700 tocStack.push(x: page);
1701 prev = { page, atom };
1702 }
1703 break;
1704 default:
1705 break;
1706 }
1707
1708 atom = atom->next();
1709 }
1710 } else {
1711 Config::instance().get(var: configVar).location()
1712 .warning(QStringLiteral("Failed to find table of contents with title '%1'")
1713 .arg(a: tocTitle));
1714 }
1715 }
1716
1717 // Restore search order
1718 setSearchOrder(searchOrder);
1719}
1720
1721QT_END_NAMESPACE
1722

source code of qttools/src/qdoc/qdoc/qdocdatabase.cpp