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