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, const Node *relative)
640{
641 if (!qmid.isEmpty()) {
642 if (auto *qcn = m_forest.lookupQmlType(qmid + u"::"_s + name, relative); qcn)
643 return qcn;
644 }
645
646 // Try unqualified lookup first (uses context-aware disambiguation)
647 if (auto *qcn = m_forest.lookupQmlType(name, relative); qcn)
648 return qcn;
649
650 // Fallback to path-based search
651 QStringList path(name);
652 return static_cast<QmlTypeNode *>(m_forest.findNodeByNameAndType(path, &Node::isQmlType));
653}
654
655/*!
656 Returns the QML type node identified by the QML module id
657 constructed from the strings in the import \a record and the
658 QML type \a name. Returns \c nullptr if no type was not found.
659 */
660QmlTypeNode *QDocDatabase::findQmlType(const ImportRec &record, const QString &name, const Node *relative)
661{
662 if (record.isEmpty())
663 return nullptr;
664
665 QString type{name};
666
667 // If the import is under a namespace (id) and the type name is not prefixed with that id,
668 // then we know the type is not available under this import.
669 if (!record.m_importId.isEmpty()) {
670 const QString namespacePrefix{"%1."_L1.arg(record.m_importId)};
671 if (!type.startsWith(namespacePrefix))
672 return nullptr;
673 type.remove(0, namespacePrefix.size());
674 }
675
676 const QString qmName = record.m_importUri.isEmpty() ? record.m_moduleName : record.m_importUri;
677 return m_forest.lookupQmlType(qmName + u"::"_s + type, relative);
678}
679
680/*!
681 Returns the QML node identified by the QML module id \a qmid
682 and \a name, searching in the primary tree only. If \a qmid
683 is an empty string, searches for the node using name only.
684
685 Returns \c nullptr if no node was found.
686*/
687QmlTypeNode *QDocDatabase::findQmlTypeInPrimaryTree(const QString &qmid, const QString &name)
688{
689 if (!qmid.isEmpty())
690 return primaryTree()->lookupQmlType(qmid + u"::"_s + name);
691 return static_cast<QmlTypeNode *>(primaryTreeRoot()->findChildNode(name, Genus::QML, TypesOnly));
692}
693
694/*!
695 This function calls a set of functions for each tree in the
696 forest that has not already been analyzed. In this way, when
697 running qdoc in \e singleExec mode, each tree is analyzed in
698 turn, and its classes and types are added to the appropriate
699 node maps.
700 */
702{
703 processForest(&QDocDatabase::findAllClasses);
704 processForest(&QDocDatabase::findAllFunctions);
705 processForest(&QDocDatabase::findAllObsoleteThings);
706 processForest(&QDocDatabase::findAllLegaleseTexts);
707 processForest(&QDocDatabase::findAllSince);
708 processForest(&QDocDatabase::findAllAttributions);
710}
711
712/*!
713 This function calls \a func for each tree in the forest,
714 ensuring that \a func is called only once per tree.
715
716 \sa processForest()
717 */
718void QDocDatabase::processForest(FindFunctionPtr func)
719{
720 Tree *t = m_forest.firstTree();
721 while (t) {
722 if (!m_completedFindFunctions.values(t).contains(func)) {
723 (this->*(func))(t->root());
724 m_completedFindFunctions.insert(t, func);
725 }
726 t = m_forest.nextTree();
727 }
728}
729
730/*!
731 Returns a reference to the collection of legalese texts.
732 */
734{
735 processForest(&QDocDatabase::findAllLegaleseTexts);
736 return m_legaleseTexts;
737}
738
739/*!
740 Returns a reference to the map of C++ classes with obsolete members.
741 */
743{
744 processForest(&QDocDatabase::findAllObsoleteThings);
745 return s_classesWithObsoleteMembers;
746}
747
748/*!
749 Returns a reference to the map of obsolete QML types.
750 */
752{
753 processForest(&QDocDatabase::findAllObsoleteThings);
754 return s_obsoleteQmlTypes;
755}
756
757/*!
758 Returns a reference to the map of QML types with obsolete members.
759 */
761{
762 processForest(&QDocDatabase::findAllObsoleteThings);
763 return s_qmlTypesWithObsoleteMembers;
764}
765
766/*!
767 Returns a reference to the map of QML basic types.
768 */
770{
771 processForest(&QDocDatabase::findAllClasses);
772 return s_qmlBasicTypes;
773}
774
775/*!
776 Returns a reference to the multimap of QML types.
777 */
779{
780 processForest(&QDocDatabase::findAllClasses);
781 return s_qmlTypes;
782}
783
784/*!
785 Returns a reference to the multimap of example nodes.
786 */
788{
789 processForest(&QDocDatabase::findAllClasses);
790 return s_examples;
791}
792
793/*!
794 Returns a reference to the multimap of attribution nodes.
795 */
797{
798 processForest(&QDocDatabase::findAllAttributions);
799 return m_attributions;
800}
801
802/*!
803 Returns a reference to the map of obsolete C++ clases.
804 */
806{
807 processForest(&QDocDatabase::findAllObsoleteThings);
808 return s_obsoleteClasses;
809}
810
811/*!
812 Returns a reference to the map of all C++ classes.
813 */
815{
816 processForest(&QDocDatabase::findAllClasses);
817 return s_cppClasses;
818}
819
820/*!
821 Returns the function index. This data structure is used to
822 output the function index page.
823 */
825{
826 processForest(&QDocDatabase::findAllFunctions);
827 return m_functionIndex;
828}
829
830/*!
831 Finds all the nodes containing legalese text and puts them
832 in a map.
833 */
834void QDocDatabase::findAllLegaleseTexts(Aggregate *node)
835{
836 for (const auto &childNode : node->childNodes()) {
837 if (childNode->isPrivate())
838 continue;
839 if (!childNode->doc().legaleseText().isEmpty())
840 m_legaleseTexts.insert(childNode->doc().legaleseText(), childNode);
841 if (childNode->isAggregate())
842 findAllLegaleseTexts(static_cast<Aggregate *>(childNode));
843 }
844}
845
846/*!
847 \fn void QDocDatabase::findAllObsoleteThings(Aggregate *node)
848
849 Finds all nodes with status = Deprecated and sorts them into
850 maps. They can be C++ classes, QML types, or they can be
851 functions, enum types, typedefs, methods, etc.
852 */
853
854/*!
855 \fn void QDocDatabase::findAllSince(Aggregate *node)
856
857 Finds all the nodes in \a node where a \e{since} command appeared
858 in the qdoc comment and sorts them into maps according to the kind
859 of node.
860
861 This function is used for generating the "New Classes... in x.y"
862 section on the \e{What's New in Qt x.y} page.
863 */
864
865/*!
866 Find the \a key in the map of new class maps, and return a
867 reference to the value, which is a NodeMap. If \a key is not
868 found, return a reference to an empty NodeMap.
869 */
870const NodeMultiMap &QDocDatabase::getClassMap(const QString &key)
871{
872 processForest(&QDocDatabase::findAllSince);
873 auto it = s_newClassMaps.constFind(key);
874 return (it != s_newClassMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
875}
876
877/*!
878 Find the \a key in the map of new QML type maps, and return a
879 reference to the value, which is a NodeMap. If the \a key is not
880 found, return a reference to an empty NodeMap.
881 */
882const NodeMultiMap &QDocDatabase::getQmlTypeMap(const QString &key)
883{
884 processForest(&QDocDatabase::findAllSince);
885 auto it = s_newQmlTypeMaps.constFind(key);
886 return (it != s_newQmlTypeMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
887}
888
889/*!
890 Find the \a key in the map of new \e {since} maps, and return
891 a reference to the value, which is a NodeMultiMap. If \a key
892 is not found, return a reference to an empty NodeMultiMap.
893 */
894const NodeMultiMap &QDocDatabase::getSinceMap(const QString &key)
895{
896 processForest(&QDocDatabase::findAllSince);
897 auto it = s_newSinceMaps.constFind(key);
898 return (it != s_newSinceMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
899}
900
901/*!
902 Performs several housekeeping tasks prior to generating the
903 documentation. These tasks create required data structures
904 and resolve links.
905 */
907{
908 const auto &config = Config::instance();
909 if (config.dualExec() || config.preparing()) {
910 // order matters
911 primaryTree()->resolveBaseClasses(primaryTreeRoot());
912 primaryTree()->resolvePropertyOverriddenFromPtrs(primaryTreeRoot());
916 primaryTree()->removePrivateAndInternalBases(primaryTreeRoot());
917 primaryTree()->resolveProperties();
918 primaryTree()->validatePropertyDocumentation(primaryTreeRoot());
921 primaryTree()->resolveTargets(primaryTreeRoot());
922 primaryTree()->resolveCppToQmlLinks();
923 primaryTree()->resolveSince(*primaryTreeRoot());
924 }
925 if (config.singleExec() && config.generating()) {
926 primaryTree()->resolveBaseClasses(primaryTreeRoot());
927 primaryTree()->resolvePropertyOverriddenFromPtrs(primaryTreeRoot());
929 primaryTree()->resolveCppToQmlLinks();
930 primaryTree()->resolveSince(*primaryTreeRoot());
931 }
932 if (!config.preparing()) {
937 }
938 if (config.dualExec())
939 QDocIndexFiles::destroyQDocIndexFiles();
940}
941
943{
944 Tree *t = m_forest.firstTree();
945 while (t) {
946 t->resolveBaseClasses(t->root());
947 if (t != primaryTree())
948 t->root()->resolveQmlInheritance();
949 t = m_forest.nextTree();
950 }
951}
952
953/*!
954 Returns a reference to the namespace map. Constructs the
955 namespace map if it hasn't been constructed yet.
956
957 \note This function must not be called in the prepare phase.
958 */
960{
962 return m_namespaceIndex;
963}
964
965/*!
966 Multiple namespace nodes for namespace X can exist in the
967 qdoc database in different trees. This function first finds
968 all namespace nodes in all the trees and inserts them into
969 a multimap. Then it combines all the namespace nodes that
970 have the same name into a single namespace node of that
971 name and inserts that combined namespace node into an index.
972 */
974{
975 if (!m_namespaceIndex.isEmpty())
976 return;
977
978 bool linkErrors = !Config::instance().get(CONFIG_NOLINKERRORS).asBool();
979 NodeMultiMap namespaceMultimap;
980 Tree *t = m_forest.firstTree();
981 while (t) {
982 t->root()->findAllNamespaces(namespaceMultimap);
983 t = m_forest.nextTree();
984 }
985 const QList<QString> keys = namespaceMultimap.uniqueKeys();
986 for (const QString &key : keys) {
987 NamespaceNode *ns = nullptr;
988 NamespaceNode *indexNamespace = nullptr;
989 const NodeList namespaces = namespaceMultimap.values(key);
990 qsizetype count = namespaceMultimap.remove(key);
991 if (count > 0) {
992 for (auto *node : namespaces) {
993 ns = static_cast<NamespaceNode *>(node);
994 if (ns->isDocumentedHere())
995 break;
996 else if (ns->hadDoc())
997 indexNamespace = ns; // namespace was documented but in another tree
998 ns = nullptr;
999 }
1000 if (ns) {
1001 for (auto *node : namespaces) {
1002 auto *nsNode = static_cast<NamespaceNode *>(node);
1003 if (nsNode->hadDoc() && nsNode != ns) {
1004 ns->doc().location().warning(
1005 QStringLiteral("Namespace %1 documented more than once")
1006 .arg(nsNode->name()), QStringLiteral("also seen here: %1")
1007 .arg(nsNode->doc().location().toString()));
1008 }
1009 }
1010 } else if (!indexNamespace) {
1011 // Warn about documented children in undocumented namespaces.
1012 // As the namespace can be documented outside this project,
1013 // skip the warning if -no-link-errors is set
1014 if (linkErrors) {
1015 for (auto *node : namespaces) {
1016 if (!node->isIndexNode())
1017 static_cast<NamespaceNode *>(node)->reportDocumentedChildrenInUndocumentedNamespace();
1018 }
1019 }
1020 } else {
1021 for (auto *node : namespaces) {
1022 auto *nsNode = static_cast<NamespaceNode *>(node);
1023 if (nsNode != indexNamespace)
1024 nsNode->setDocNode(indexNamespace);
1025 }
1026 }
1027 }
1028 /*
1029 If there are multiple namespace nodes with the same
1030 name where one of them will be the main reference page
1031 for the namespace, include all nodes in the public
1032 API of the namespace.
1033 */
1034 if (ns && count > 1) {
1035 for (auto *node : namespaces) {
1036 auto *nameSpaceNode = static_cast<NamespaceNode *>(node);
1037 if (nameSpaceNode != ns) {
1038 for (auto it = nameSpaceNode->constBegin(); it != nameSpaceNode->constEnd();
1039 ++it) {
1040 Node *anotherNs = *it;
1041 if (anotherNs && anotherNs->isPublic() && !anotherNs->isInternal())
1042 ns->includeChild(anotherNs);
1043 }
1044 }
1045 }
1046 }
1047 /*
1048 Add the main namespace reference node to index, or the last seen
1049 namespace if the main one was not found.
1050 */
1051 if (!ns)
1052 ns = indexNamespace ? indexNamespace : static_cast<NamespaceNode *>(namespaces.last());
1053 m_namespaceIndex.insert(ns->name(), ns);
1054 }
1055}
1056
1057/*!
1058 Each instance of class Tree that represents an index file
1059 must be traversed to find all instances of class ProxyNode.
1060 For each ProxyNode found, look up the ProxyNode's name in
1061 the primary Tree. If it is found, it means that the proxy
1062 node contains elements (normally just functions) that are
1063 documented in the module represented by the Tree containing
1064 the proxy node but that are related to the node we found in
1065 the primary tree.
1066 */
1068{
1069 // The first tree is the primary tree.
1070 // Skip the primary tree.
1071 Tree *t = m_forest.firstTree();
1072 t = m_forest.nextTree();
1073 while (t) {
1074 const NodeList &proxies = t->proxies();
1075 if (!proxies.isEmpty()) {
1076 for (auto *node : proxies) {
1077 const auto *pn = static_cast<ProxyNode *>(node);
1078 if (pn->count() > 0) {
1079 Aggregate *aggregate = primaryTree()->findAggregate(pn->name());
1080 if (aggregate != nullptr)
1081 aggregate->appendToRelatedByProxy(pn->childNodes());
1082 }
1083 }
1084 }
1085 t = m_forest.nextTree();
1086 }
1087}
1088
1089/*!
1090 Finds the function node for the qualified function path in
1091 \a target and returns a pointer to it. The \a target is a
1092 function signature with or without parameters but without
1093 the return type.
1094
1095 \a relative is the node in the primary tree where the search
1096 begins. It is not used in the other trees, if the node is not
1097 found in the primary tree. \a genus can be used to force the
1098 search to find a C++ function or a QML function.
1099
1100 The entire forest is searched, but the first match is accepted.
1101 */
1102const FunctionNode *QDocDatabase::findFunctionNode(const QString &target, const Node *relative,
1103 Genus genus)
1104{
1105 QString signature;
1106 QString function = target;
1107 qsizetype length = target.size();
1108 if (function.endsWith("()"))
1109 function.chop(2);
1110 if (function.endsWith(QChar(')'))) {
1111 qsizetype position = function.lastIndexOf(QChar('('));
1112 signature = function.mid(position + 1, length - position - 2);
1113 function = function.left(position);
1114 }
1115 QStringList path = function.split("::");
1116 return m_forest.findFunctionNode(path, Parameters(signature), relative, genus);
1117}
1118
1119/*!
1120 This function is called for autolinking to a \a type,
1121 which could be a function return type or a parameter
1122 type. The tree node that represents the \a type is
1123 returned. All the trees are searched until a match is
1124 found. When searching the primary tree, the search
1125 begins at \a relative and proceeds up the parent chain.
1126 When searching the index trees, the search begins at the
1127 root.
1128 */
1129const Node *QDocDatabase::findTypeNode(const QString &type, const Node *relative, Genus genus)
1130{
1131 // For QML contexts with qualified names containing ".", try import-aware lookup first
1132 if ((genus == Genus::QML || (relative && relative->genus() == Genus::QML)) &&
1133 type.contains('.') && !type.contains("::")) {
1134 if (relative && relative->isQmlType()) {
1135 const QmlTypeNode *qmlType = static_cast<const QmlTypeNode*>(relative);
1136 const ImportList &imports = qmlType->importList();
1137
1138 for (const auto &import : imports) {
1139 if (QmlTypeNode *found = findQmlType(import, type)) {
1140 return found;
1141 }
1142 }
1143 }
1144
1145 // Fall back to regular path-based lookup for QML qualified names
1146 QStringList path = type.split(".");
1147 if ((path.size() == 1) && (path.at(0)[0].isLower() || path.at(0) == QString("T"))) {
1148 auto it = s_typeNodeMap.find(path.at(0));
1149 if (it != s_typeNodeMap.end())
1150 return it.value();
1151 }
1152
1153 // Try the full qualified path first
1154 const Node *node = m_forest.findTypeNode(path, relative, genus);
1155 if (node)
1156 return node;
1157
1158 // If the full path fails and we have multiple segments, try just the last segment
1159 // This handles cases like "TM.BaseType" where "TM" is an alias we can't resolve
1160 // but "BaseType" might be findable as a QML type
1161 if (path.size() > 1) {
1162 const Node *lastSegmentNode = m_forest.findTypeNode(QStringList{path.last()}, relative, genus);
1163 if (lastSegmentNode && lastSegmentNode->isQmlType())
1164 return lastSegmentNode;
1165 }
1166
1167 return nullptr;
1168 }
1169
1170 // For C++ contexts or QML types with "::" notation, use C++ path splitting
1171 QStringList path = type.split("::");
1172 if ((path.size() == 1) && (path.at(0)[0].isLower() || path.at(0) == QString("T"))) {
1173 auto it = s_typeNodeMap.find(path.at(0));
1174 if (it != s_typeNodeMap.end())
1175 return it.value();
1176 }
1177 return m_forest.findTypeNode(path, relative, genus);
1178}
1179
1180/*!
1181 Finds the node that will generate the documentation that
1182 contains the \a target and returns a pointer to it.
1183
1184 Can this be improved by using the target map in Tree?
1185 */
1186const Node *QDocDatabase::findNodeForTarget(const QString &target, const Node *relative)
1187{
1188 const Node *node = nullptr;
1189 if (target.isEmpty())
1190 node = relative;
1191 else if (target.endsWith(".html"))
1192 node = findNodeByNameAndType(QStringList(target), &Node::isPageNode);
1193 else {
1194 QStringList path = target.split("::");
1195 int flags = SearchBaseClasses | SearchEnumValues;
1196 for (const auto *tree : searchOrder()) {
1197 const Node *n = tree->findNode(path, relative, flags, Genus::DontCare);
1198 if (n)
1199 return n;
1200 relative = nullptr;
1201 }
1202 node = findPageNodeByTitle(target);
1203 }
1204 return node;
1205}
1206
1208{
1209 QStringList result;
1210 CNMap *m = primaryTree()->getCollectionMap(NodeType::Group);
1211
1212 if (!m)
1213 return result;
1214
1215 for (auto it = m->cbegin(); it != m->cend(); ++it)
1216 if (it.value()->members().contains(node))
1217 result << it.key();
1218
1219 return result;
1220}
1221
1222/*!
1223 Reads and parses the qdoc index files listed in \a indexFiles.
1224 */
1225void QDocDatabase::readIndexes(const QStringList &indexFiles)
1226{
1227 QStringList filesToRead;
1228 for (const QString &file : indexFiles) {
1229 QString fn = file.mid(file.lastIndexOf(QChar('/')) + 1);
1230 if (!isLoaded(fn))
1231 filesToRead << file;
1232 else
1233 qCCritical(lcQdoc) << "Index file" << file << "is already in memory.";
1234 }
1235 QDocIndexFiles::qdocIndexFiles()->readIndexes(filesToRead);
1236}
1237
1238/*!
1239 Generates a qdoc index file and write it to \a fileName. The
1240 index file is generated with the parameters \a url and \a title,
1241 using the generator \a g.
1242 */
1243void QDocDatabase::generateIndex(const QString &fileName, const QString &url, const QString &title)
1244{
1245 QString t = fileName.mid(fileName.lastIndexOf(QChar('/')) + 1);
1246 primaryTree()->setIndexFileName(t);
1247 QDocIndexFiles::qdocIndexFiles()->generateIndex(fileName, url, title);
1248 QDocIndexFiles::destroyQDocIndexFiles();
1249}
1250
1251/*!
1252 Returns the collection node representing the module that \a relative
1253 node belongs to, or \c nullptr if there is no such module in the
1254 primary tree.
1255*/
1257{
1258 NodeType moduleType{NodeType::Module};
1259 QString moduleName;
1260 switch (relative->genus())
1261 {
1262 case Genus::CPP:
1263 moduleType = NodeType::Module;
1264 moduleName = relative->physicalModuleName();
1265 break;
1266 case Genus::QML:
1267 moduleType = NodeType::QmlModule;
1268 moduleName = relative->logicalModuleName();
1269 break;
1270 default:
1271 return nullptr;
1272 }
1273 if (moduleName.isEmpty())
1274 return nullptr;
1275
1276 return primaryTree()->getCollection(moduleName, moduleType);
1277}
1278
1279/*!
1280 Finds all the collection nodes of the specified \a type
1281 and merges them into the collection node map \a cnm. Nodes
1282 that match the \a relative node are not included.
1283 */
1284void QDocDatabase::mergeCollections(NodeType type, CNMap &cnm, const Node *relative)
1285{
1286 cnm.clear();
1287 CNMultiMap cnmm;
1288 for (auto *tree : searchOrder()) {
1289 CNMap *m = tree->getCollectionMap(type);
1290 if (m && !m->isEmpty()) {
1291 for (auto it = m->cbegin(); it != m->cend(); ++it) {
1292 if (!it.value()->isInternal())
1293 cnmm.insert(it.key(), it.value());
1294 }
1295 }
1296 }
1297 if (cnmm.isEmpty())
1298 return;
1299 static const QRegularExpression singleDigit("\\b([0-9])\\b");
1300 const QStringList keys = cnmm.uniqueKeys();
1301 for (const auto &key : keys) {
1302 const QList<CollectionNode *> values = cnmm.values(key);
1303 CollectionNode *n = nullptr;
1304 for (auto *value : values) {
1305 if (value && value->wasSeen() && value != relative) {
1306 n = value;
1307 break;
1308 }
1309 }
1310 if (n) {
1311 if (values.size() > 1) {
1312 for (CollectionNode *value : values) {
1313 if (value != n) {
1314 // Allow multiple (major) versions of QML modules
1315 if ((n->isQmlModule())
1316 && n->logicalModuleIdentifier() != value->logicalModuleIdentifier()) {
1317 if (value->wasSeen() && value != relative)
1318 cnm.insert(value->fullTitle().toLower(), value);
1319 continue;
1320 }
1321 for (Node *t : value->members())
1322 n->addMember(t);
1323 }
1324 }
1325 }
1326 QString sortKey = n->fullTitle().toLower();
1327 if (sortKey.startsWith("the "))
1328 sortKey.remove(0, 4);
1329 sortKey.replace(singleDigit, "0\\1");
1330 cnm.insert(sortKey, n);
1331 }
1332 }
1333}
1334
1335/*!
1336 Finds all the collection nodes with the same name
1337 and type as \a c and merges their members into the
1338 members list of \a c.
1339
1340 For QML modules, only nodes with matching
1341 module identifiers are merged to avoid merging
1342 modules with different (major) versions.
1343 */
1345{
1346 if (c == nullptr)
1347 return;
1348
1349 // REMARK: This form of merging is usually called during the
1350 // generation phase om-the-fly when a source-of-truth collection
1351 // is required.
1352 // In practice, this means a collection could be merged many, many
1353 // times during the lifetime of a generation.
1354 // To avoid repeating the merging process each time, which could
1355 // be time consuming, we use a small flag that is set directly on
1356 // the collection to bail-out early.
1357 //
1358 // The merging process is only meaningful for collections when the
1359 // collection references are spread troughout multiple projects.
1360 // The part of information that exists in other project is read
1361 // before the generation phase, such that when the generation
1362 // phase comes, we already have all the information we need for
1363 // merging such that we can consider all version of a certain
1364 // collection node immutable, making the caching inherently
1365 // correct at any point of the generation.
1366 //
1367 // This implies that this operation is unsafe if it is performed
1368 // before all the index files are loaded.
1369 // Indeed, this is a prerequisite, with the current structure, to
1370 // perform this optmization.
1371 //
1372 // At the current time, this is true and is expected not to
1373 // change.
1374 //
1375 // Do note that this is not applied to the other overload of
1376 // mergeCollections as we cannot as safely ensure its consistency
1377 // and, as the result of the merging depends on multiple
1378 // parameters, it would require an actual memoization of the call.
1379 //
1380 // Note that this is a defensive optimization and we are assuming
1381 // that it is effective based on heuristical data. As this is
1382 // expected to disappear, at least in its current form, in the
1383 // future, a more thorough analysis was not performed.
1384 if (c->isMerged()) {
1385 return;
1386 }
1387
1388 for (auto *tree : searchOrder()) {
1389 CollectionNode *cn = tree->getCollection(c->name(), c->nodeType());
1390 if (cn && cn != c) {
1391 if ((cn->isQmlModule())
1392 && cn->logicalModuleIdentifier() != c->logicalModuleIdentifier())
1393 continue;
1394
1395 for (auto *node : cn->members())
1396 c->addMember(node);
1397
1398 // REMARK: The merging process is performed to ensure that
1399 // references to the collection in external projects are
1400 // taken into account before consuming the collection.
1401 //
1402 // This works by having QDoc construct empty collections
1403 // as soon as a reference to a collection is encountered
1404 // and filling details later on when its definition is
1405 // found.
1406 //
1407 // This initially-empty collection is always saved to the
1408 // primaryTree and it is the collection that is directly
1409 // accessible to consumers during the generation process.
1410 //
1411 // Nonetheless, when the definition for the collection is
1412 // not in the same project as the one that is being
1413 // compiled, its details will never be filled in.
1414 //
1415 // Indeed, the details will live in the index file for the
1416 // project where the collection is defined, if any, and
1417 // the node for it, which has complete information, will
1418 // live in some non-primaryTree.
1419 //
1420 // The merging process itself is used by consumers during
1421 // the generation process because they access the
1422 // primaryTree version of the collection expecting a
1423 // source-of-truth.
1424 // To ensure that this is the case for usages that
1425 // requires linking, we need to merge not only the members
1426 // of the collection that reside in external versions of
1427 // the collection; but some of the data that reside in the
1428 // definition of the collection intself, namely the title
1429 // and the url.
1430 //
1431 // A collection that contains the data of a definition is
1432 // always marked as seen, hence we use that to discern
1433 // whether we are working with a placeholder node or not,
1434 // and fill in the data if we encounter a node that
1435 // represents a definition.
1436 //
1437 // The way in which QDoc works implies that collection are
1438 // globally scoped between projects.
1439 // The repetition of the definition for the same
1440 // collection is warned for as a duplicate documentation,
1441 // such that we can expect a single valid source of truth
1442 // for a given collection in each project.
1443 // It is currently unknown if this warning is applicable
1444 // when the repeated collection is defined in two
1445 // different projects.
1446 //
1447 // As QDoc implicitly would not correctly support this
1448 // case, we assume that only one declaration exists for
1449 // each collection, such that the first encoutered one
1450 // must be the source of truth and that there is no need
1451 // to copy any data after the first copy is performed.
1452 // KLUDGE: Note that this process is done as a hackish
1453 // solution to QTBUG-104237 and should not be considered
1454 // final or dependable.
1455 if (!c->wasSeen() && cn->wasSeen()) {
1456 c->markSeen();
1457 c->setTitle(cn->title());
1458 c->setUrl(cn->url());
1459 }
1460 }
1461 }
1462
1464}
1465
1466/*!
1467 Searches for the node that matches the path in \a atom and the
1468 specified \a genus. The \a relative node is used if the first
1469 leg of the path is empty, i.e. if the path begins with '#'.
1470 The function also sets \a ref if there remains an unused leg
1471 in the path after the node is found. The node is returned as
1472 well as the \a ref. If the returned node pointer is null,
1473 \a ref is also not valid.
1474 */
1475const Node *QDocDatabase::findNodeForAtom(const Atom *a, const Node *relative, QString &ref,
1476 Genus genus)
1477{
1478 const Node *node = nullptr;
1479
1480 Atom *atom = const_cast<Atom *>(a);
1481 QStringList targetPath = atom->string().split(QLatin1Char('#'));
1482 QString first = targetPath.first().trimmed();
1483
1484 Tree *domain = nullptr;
1485
1486 if (atom->isLinkAtom()) {
1487 domain = atom->domain();
1488 genus = atom->genus();
1489 }
1490
1491 if (first.isEmpty())
1492 node = relative; // search for a target on the current page.
1493 else if (domain) {
1494 if (first.endsWith(".html"))
1495 node = domain->findNodeByNameAndType(QStringList(first), &Node::isPageNode);
1496 else if (first.endsWith(QChar(')'))) {
1497 QString signature;
1498 QString function = first;
1499 qsizetype length = first.size();
1500 if (function.endsWith("()"))
1501 function.chop(2);
1502 if (function.endsWith(QChar(')'))) {
1503 qsizetype position = function.lastIndexOf(QChar('('));
1504 signature = function.mid(position + 1, length - position - 2);
1505 function = function.left(position);
1506 }
1507 QStringList path = function.split("::");
1508 node = domain->findFunctionNode(path, Parameters(signature), nullptr, genus);
1509 }
1510 if (node == nullptr) {
1511 int flags = SearchBaseClasses | SearchEnumValues;
1512 QStringList nodePath = first.split("::");
1513 QString target;
1514 targetPath.removeFirst();
1515 if (!targetPath.isEmpty())
1516 target = targetPath.takeFirst();
1517 if (relative && relative->tree()->physicalModuleName() != domain->physicalModuleName())
1518 relative = nullptr;
1519 return domain->findNodeForTarget(nodePath, target, relative, flags, genus, ref);
1520 }
1521 } else {
1522 if (first.endsWith(".html"))
1523 node = findNodeByNameAndType(QStringList(first), &Node::isPageNode);
1524 else if (first.endsWith(QChar(')')))
1525 node = findFunctionNode(first, relative, genus);
1526 if (node == nullptr) {
1527 // For QML contexts with qualified names containing ".", use the same logic as findTypeNode
1528 if (genus == Genus::QML && first.contains('.') && !first.contains("::")) {
1529 // Try import-aware lookup using findTypeNode logic
1530 node = findTypeNode(first, relative, genus);
1531 if (node) {
1532 // Handle any fragment reference
1533 targetPath.removeFirst();
1534 if (!targetPath.isEmpty()) {
1535 ref = node->root()->tree()->getRef(targetPath.first(), node);
1536 if (ref.isEmpty())
1537 node = nullptr;
1538 }
1539 return node;
1540 }
1541 }
1542 return findNodeForTarget(targetPath, relative, genus, ref);
1543 }
1544 }
1545
1546 if (node != nullptr && ref.isEmpty()) {
1547 if (!node->url().isEmpty())
1548 return node;
1549 targetPath.removeFirst();
1550 if (!targetPath.isEmpty()) {
1551 ref = node->root()->tree()->getRef(targetPath.first(), node);
1552 if (ref.isEmpty())
1553 node = nullptr;
1554 }
1555 }
1556 return node;
1557}
1558
1559/*!
1560 Updates navigation (previous/next page links and the navigation parent)
1561 for pages listed in the TOC, specified by the \c navigation.toctitles
1562 configuration variable.
1563
1564 if \c navigation.toctitles.inclusive is \c true, include also the TOC
1565 page(s) themselves as a 'root' item in the navigation bar (breadcrumbs)
1566 that are generated for HTML output.
1567*/
1569{
1570 // Restrict searching only to the local (primary) tree
1571 QList<Tree *> searchOrder = this->searchOrder();
1573
1574 const QString configVar = CONFIG_NAVIGATION +
1575 Config::dot +
1577
1578 // TODO: [direct-configuration-access]
1579 // The configuration is currently a singleton with some generally
1580 // global mutable state.
1581 //
1582 // Accessing the data in this form complicates testing and
1583 // requires tests that inhibit any test parallelization, as the
1584 // tests are not self contained.
1585 //
1586 // This should be generally avoived. Possibly, we should strive
1587 // for Config to be a POD type that generally is scoped to
1588 // main and whose data is destructured into dependencies when
1589 // the dependencies are constructed.
1590 bool inclusive{Config::instance().get(
1591 configVar + Config::dot + CONFIG_INCLUSIVE).asBool()};
1592
1593 // TODO: [direct-configuration-access]
1594 const auto tocTitles{Config::instance().get(configVar).asStringList()};
1595
1596 for (const auto &tocTitle : tocTitles) {
1597 if (const auto candidateTarget = findNodeForTarget(tocTitle, nullptr); candidateTarget && candidateTarget->isPageNode()) {
1598 auto tocPage{static_cast<const PageNode*>(candidateTarget)};
1599
1600 Text body = tocPage->doc().body();
1601
1602 auto *atom = body.firstAtom();
1603
1604 std::pair<PageNode *, QString> prev { nullptr, QString() };
1605
1606 std::stack<const PageNode *> tocStack;
1607 tocStack.push(inclusive ? tocPage : nullptr);
1608
1609 bool inItem = false;
1610
1611 // TODO: Understand how much we use this form of looping over atoms.
1612 // If it is used a few times we might consider providing
1613 // an iterator for Text to make use of a simpler
1614 // range-for loop.
1615 while (atom) {
1616 switch (atom->type()) {
1617 case Atom::ListItemLeft:
1618 // Not known if we're going to have a link, push a temporary
1619 tocStack.push(nullptr);
1620 inItem = true;
1621 break;
1622 case Atom::ListItemRight:
1623 tocStack.pop();
1624 inItem = false;
1625 break;
1626 case Atom::Link: {
1627 if (!inItem)
1628 break;
1629
1630 // TODO: [unnecessary-output-parameter]
1631 // We currently need an lvalue string to
1632 // pass to findNodeForAtom, as the
1633 // outparameter ref.
1634 //
1635 // Apart from the general problems with output
1636 // parameters, we shouldn't be forced to
1637 // instanciate an unnecessary object at call
1638 // site.
1639 //
1640 // Understand what the correct way to avoid this is.
1641 // This requires changes to findNodeForAtom
1642 // and should be addressed in the context of
1643 // revising that method.
1644 QString unused{};
1645 // TODO: Having to const cast is really a code
1646 // smell and could result in undefined
1647 // behavior in some specific cases (e.g point
1648 // to something that is actually const).
1649 //
1650 // We should understand how to sequence the
1651 // code so that we have access to mutable data
1652 // when we need it and "freeze" the data
1653 // afterwards.
1654 //
1655 // If it we expect this form of mutability at
1656 // this point we should expose a non-const API
1657 // for the database, possibly limited to a
1658 // very specific scope of execution.
1659 //
1660 // Understand what the correct sequencing for
1661 // this processing is and revise this part.
1662 auto candidatePage = const_cast<Node *>(findNodeForAtom(atom, nullptr, unused));
1663 if (!candidatePage || !candidatePage->isPageNode()) break;
1664
1665 auto page{static_cast<PageNode*>(candidatePage)};
1666
1667 // ignore self-references
1668 if (page == prev.first) break;
1669
1670 if (prev.first) {
1671 prev.first->setLink(
1672 Node::NextLink,
1673 page->title(),
1674 // TODO: [possible-assertion-failure][imprecise-types][atoms-link]
1675 // As with other structures in QDoc we
1676 // are able to call methods that are
1677 // valid only on very specific states.
1678 //
1679 // For some of those calls we have
1680 // some defensive programming measures
1681 // that allow us to at least identify
1682 // the error during debugging, while
1683 // for others this may currently hide
1684 // some logic error.
1685 //
1686 // To avoid those cases, we should
1687 // strive to move those cases to a
1688 // compilation error, requiring a
1689 // statically analyzable state that
1690 // represents the current model.
1691 //
1692 // This would ensure that those
1693 // lingering class of bugs are
1694 // eliminated completely, forces a
1695 // more explicit codebase where the
1696 // current capabilities do not depend
1697 // on runtime values might not be
1698 // generally visible, and does not
1699 // require us to incur into the
1700 // required state, which may be rare,
1701 // simplifying our abilities to
1702 // evaluate all possible states.
1703 //
1704 // For linking atoms, LinkAtom is
1705 // available and might be a good
1706 // enough solution to move linkText
1707 // to.
1708 atom->linkText()
1709 );
1710 page->setLink(
1711 Node::PreviousLink,
1712 prev.first->title(),
1713 prev.second
1714 );
1715 }
1716
1717 if (page == tocPage)
1718 break;
1719
1720 // Find the navigation parent from the stack; we may have null pointers
1721 // for non-link list items, so skip those.
1722 qsizetype popped = 0;
1723 while (tocStack.size() > 1 && !tocStack.top()) {
1724 tocStack.pop();
1725 ++popped;
1726 }
1727
1728 page->setNavigationParent(tocStack.empty() ? nullptr : tocStack.top());
1729
1730 while (--popped > 0)
1731 tocStack.push(nullptr);
1732
1733 tocStack.push(page);
1734 // TODO: [possible-assertion-failure][imprecise-types][atoms-link]
1735 prev = { page, atom->linkText() };
1736 }
1737 break;
1738
1739 case Atom::AnnotatedList:
1740 case Atom::GeneratedList: {
1741 if (const auto *cn = getCollectionNode(atom->string(), NodeType::Group)) {
1742 const auto sortOrder{Generator::sortOrder(atom->strings().last())};
1743 NodeList members{cn->members()};
1744 // Drop non-page nodes and index nodes so that we do not generate navigational
1745 // links pointing outside of this documentation set.
1746 members.erase(std::remove_if(members.begin(), members.end(),
1747 [](const Node *n) {
1748 return n->isIndexNode() || !n->isPageNode() || n->isExternalPage();
1749 }), members.end());
1750 if (members.isEmpty())
1751 break;
1752
1753 if (sortOrder == Qt::DescendingOrder)
1754 std::sort(members.rbegin(), members.rend(), Node::nodeSortKeyOrNameLessThan);
1755 else
1756 std::sort(members.begin(), members.end(), Node::nodeSortKeyOrNameLessThan);
1757
1758 // `members` now has local PageNode pointers, adjust prev/next links for each.
1759 // Do not set a navigation parent node as group members use the group node as
1760 // their nav. parent.
1761 for (auto *m : members) {
1762 auto *page = static_cast<PageNode *>(m);
1763 prev.first->setLink(Node::NextLink, page->title(), page->fullName());
1764 page->setLink(Node::PreviousLink, prev.first->title(), prev.second);
1765 prev = { page, page->fullName() };
1766 }
1767 }
1768 }
1769 break;
1770
1771 default:
1772 break;
1773 }
1774
1775 atom = atom->next();
1776 }
1777 } else {
1778 Config::instance().get(configVar).location()
1779 .warning(QStringLiteral("Failed to find table of contents with title '%1'")
1780 .arg(tocTitle));
1781 }
1782 }
1783
1784 // Restore search order
1785 setSearchOrder(searchOrder);
1786}
1787
1788QT_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.
NodeMultiMap & getAttributions()
Returns a reference to the multimap of attribution nodes.
static void destroyQdocDB()
Destroys the singleton.
NodeMultiMap & getQmlTypesWithObsoleteMembers()
Returns a reference to the map of QML types with obsolete members.
NodeMultiMap & getObsoleteQmlTypes()
Returns a reference to the map of obsolete QML types.
QmlTypeNode * findQmlType(const QString &qmid, const QString &name, const Node *relative=nullptr)
Returns the QML type node identified by the QML module id qmid and QML type name, or nullptr if no ty...
QmlTypeNode * findQmlType(const ImportRec &import, const QString &name, const Node *relative=nullptr)
const FunctionNode * findFunctionNode(const QString &target, const Node *relative, Genus genus)
Finds the function node for the qualified function path in target and returns a pointer to it.
static QDocDatabase * qdocDB()
Creates the singleton.
void resolveBaseClasses()
NodeMultiMap & getQmlTypes()
Returns a reference to the multimap of QML types.
NodeMultiMap & getClassesWithObsoleteMembers()
Returns a reference to the map of C++ classes with obsolete members.
NodeMultiMap & getQmlValueTypes()
Returns a reference to the map of QML basic types.
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:58
void markDontDocumentNodes()
The {don't document} map has been loaded with the names of classes and structs in the current module ...
Definition tree.cpp:1515
NodeList & proxies()
Definition tree.h:78
#define CONFIG_TOCTITLES
Definition config.h:438
#define CONFIG_NOLINKERRORS
Definition config.h:413
#define CONFIG_NAVIGATION
Definition config.h:412
#define CONFIG_INCLUSIVE
Definition config.h:399
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:875
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:27
TargetType
A type of a linkable target record.
Definition tree.h:29
@ Unknown
Definition tree.h:29