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

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