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
tree.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 "tree.h"
5
6#include "anchorid.h"
7#include "classnode.h"
9#include "config.h"
10#include "doc.h"
11#include "enumnode.h"
12#include "functionnode.h"
13#include "htmlgenerator.h"
16#include "location.h"
17#include "node.h"
18#include "qdocdatabase.h"
20#include "text.h"
21#include "typedefnode.h"
22#include "utilities.h"
23#include "textutils.h"
24
25#include <utility>
26
28
29using namespace Qt::StringLiterals;
30
31/*!
32 \class Tree
33
34 This class constructs and maintains a tree of instances of
35 the subclasses of Node.
36
37 This class is now private. Only class QDocDatabase has access.
38 Please don't change this. If you must access class Tree, do it
39 though the pointer to the singleton QDocDatabase.
40
41 Tree is being converted to a forest. A static member provides a
42 map of Tree *values with the module names as the keys. There is
43 one Tree in the map for each index file read, and there is one
44 tree that is not in the map for the module whose documentation
45 is being generated.
46 */
47
48/*!
49 \class TargetRec
50 \brief A record of a linkable target within the documentation.
51*/
52
53/*!
54 \enum TargetRec::TargetType
55
56 A type of a linkable target record.
57
58 \value Unknown
59 Unknown/target not set.
60 \value Target
61 A location marked with a \\target command.
62 \value Keyword
63 A location marked with a \\keyword command.
64 \value Contents
65 A table of contents item (section title).
66 \value ContentsKeyword
67 A \\keyword tied to a section title.
68*/
69
70/*!
71 Constructs a Tree. \a qdb is the pointer to the singleton
72 qdoc database that is constructing the tree. This might not
73 be necessary, and it might be removed later.
74
75 \a camelCaseModuleName is the project name for this tree
76 as it appears in the qdocconf file.
77 */
78Tree::Tree(const QString &camelCaseModuleName, QDocDatabase *qdb)
79 : m_camelCaseModuleName(camelCaseModuleName),
80 m_physicalModuleName(camelCaseModuleName.toLower()),
81 m_qdb(qdb),
82 m_root(nullptr, QString())
83{
84 m_root.setPhysicalModuleName(m_physicalModuleName);
85 m_root.setTree(this);
86}
87
88/*!
89 Destroys the Tree.
90
91 There are two maps of targets, keywords, and contents.
92 One map is indexed by ref, the other by title. Both maps
93 use the same set of TargetRec objects as the values,
94 so we only need to delete the values from one of them.
95
96 The Node instances themselves are destroyed by the root
97 node's (\c m_root) destructor.
98 */
99Tree::~Tree()
100{
101 qDeleteAll(m_nodesByTargetRef);
102 m_nodesByTargetRef.clear();
103 m_nodesByTargetTitle.clear();
104}
105
106/* API members */
107
108/*!
109 Calls findClassNode() first with \a path and \a start. If
110 it finds a node, the node is returned. If not, it calls
111 findNamespaceNode() with the same parameters. The result
112 is returned.
113 */
114Node *Tree::findNodeForInclude(const QStringList &path) const
115{
116 Node *n = findClassNode(path);
117 if (n == nullptr)
118 n = findNamespaceNode(path);
119 return n;
120}
121
122/*!
123 This function searches this tree for an Aggregate node with
124 the specified \a name. It returns the pointer to that node
125 or nullptr.
126
127 We might need to split the name on '::' but we assume the
128 name is a single word at the moment.
129 */
130Aggregate *Tree::findAggregate(const QString &name)
131{
132 QStringList path = name.split(QLatin1String("::"));
133 return static_cast<Aggregate *>(findNodeRecursive(path, 0, const_cast<NamespaceNode *>(root()),
134 &Node::isFirstClassAggregate));
135}
136
137/*!
138 Find the C++ class node named \a path. Begin the search at the
139 \a start node. If the \a start node is 0, begin the search
140 at the root of the tree. Only a C++ class node named \a path is
141 acceptible. If one is not found, 0 is returned.
142 */
143ClassNode *Tree::findClassNode(const QStringList &path, const Node *start) const
144{
145 if (start == nullptr)
146 start = const_cast<NamespaceNode *>(root());
147 return static_cast<ClassNode *>(findNodeRecursive(path, 0, start, &Node::isClassNode));
148}
149
150/*!
151 Find the Namespace node named \a path. Begin the search at
152 the root of the tree. Only a Namespace node named \a path
153 is acceptible. If one is not found, 0 is returned.
154 */
155NamespaceNode *Tree::findNamespaceNode(const QStringList &path) const
156{
157 Node *start = const_cast<NamespaceNode *>(root());
158 return static_cast<NamespaceNode *>(findNodeRecursive(path, 0, start, &Node::isNamespace));
159}
160
161/*!
162 This function searches for the node specified by \a path.
163 The matching node can be one of several different types
164 including a C++ class, a C++ namespace, or a C++ header
165 file.
166
167 I'm not sure if it can be a QML type, but if that is a
168 possibility, the code can easily accommodate it.
169
170 If a matching node is found, a pointer to it is returned.
171 Otherwise 0 is returned.
172 */
173Aggregate *Tree::findRelatesNode(const QStringList &path)
174{
175 Node *n = findNodeRecursive(path, 0, root(), &Node::isRelatableType);
176 return (((n != nullptr) && n->isAggregate()) ? static_cast<Aggregate *>(n) : nullptr);
177}
178
179/*!
180 Inserts function name \a funcName and function role \a funcRole into
181 the property function map for the specified \a property.
182 */
183void Tree::addPropertyFunction(PropertyNode *property, const QString &funcName,
184 PropertyNode::FunctionRole funcRole)
185{
186 m_unresolvedPropertyMap[property].insert(funcRole, funcName);
187}
188
189/*!
190 This function resolves C++ inheritance and reimplementation
191 settings for each C++ class node found in the tree beginning
192 at \a n. It also calls itself recursively for each C++ class
193 node or namespace node it encounters.
194
195 This function does not resolve QML inheritance.
196 */
197void Tree::resolveBaseClasses(Aggregate *n)
198{
199 for (auto it = n->constBegin(); it != n->constEnd(); ++it) {
200 if ((*it)->isClassNode()) {
201 auto *cn = static_cast<ClassNode *>(*it);
202 QList<RelatedClass> &bases = cn->baseClasses_mutable();
203 for (auto &base : bases) {
204 if (base.m_node == nullptr) {
205 Node *n = m_qdb->findClassNode(base.m_path);
206 /*
207 If the node for the base class was not found,
208 the reason might be that the subclass is in a
209 namespace and the base class is in the same
210 namespace, but the base class name was not
211 qualified with the namespace name. That is the
212 case most of the time. Then restart the search
213 at the parent of the subclass node (the namespace
214 node) using the unqualified base class name.
215 */
216 if (n == nullptr) {
217 Aggregate *parent = cn->parent();
218 if (parent != nullptr)
219 // Exclude the root namespace
220 if (parent->isNamespace() && !parent->name().isEmpty())
221 n = findClassNode(base.m_path, parent);
222 }
223 if (n != nullptr) {
224 auto *bcn = static_cast<ClassNode *>(n);
225 base.m_node = bcn;
226 bcn->addDerivedClass(base.m_access, cn);
227 }
228 }
229 }
230 resolveBaseClasses(cn);
231 } else if ((*it)->isNamespace()) {
232 resolveBaseClasses(static_cast<NamespaceNode *>(*it));
233 }
234 }
235}
236
237/*!
238 */
239void Tree::resolvePropertyOverriddenFromPtrs(Aggregate *n)
240{
241 for (auto node = n->constBegin(); node != n->constEnd(); ++node) {
242 if ((*node)->isClassNode()) {
243 auto *cn = static_cast<ClassNode *>(*node);
244 for (auto property = cn->constBegin(); property != cn->constEnd(); ++property) {
245 if ((*property)->isProperty())
246 cn->resolvePropertyOverriddenFromPtrs(static_cast<PropertyNode *>(*property));
247 }
248 resolvePropertyOverriddenFromPtrs(cn);
249 } else if ((*node)->isNamespace()) {
250 resolvePropertyOverriddenFromPtrs(static_cast<NamespaceNode *>(*node));
251 }
252 }
253}
254
255/*!
256 Resolves access functions associated with each PropertyNode stored
257 in \c m_unresolvedPropertyMap, and adds them into the property node.
258 This allows the property node to list the access functions when
259 generating their documentation.
260 */
262{
263 for (auto propEntry = m_unresolvedPropertyMap.constBegin();
264 propEntry != m_unresolvedPropertyMap.constEnd(); ++propEntry) {
265 PropertyNode *property = propEntry.key();
266 Aggregate *parent = property->parent();
267 QString getterName = (*propEntry)[PropertyNode::FunctionRole::Getter];
268 QString setterName = (*propEntry)[PropertyNode::FunctionRole::Setter];
269 QString resetterName = (*propEntry)[PropertyNode::FunctionRole::Resetter];
270 QString notifierName = (*propEntry)[PropertyNode::FunctionRole::Notifier];
271 QString bindableName = (*propEntry)[PropertyNode::FunctionRole::Bindable];
272
273 for (auto it = parent->constBegin(); it != parent->constEnd(); ++it) {
274 if ((*it)->isFunction()) {
275 auto *function = static_cast<FunctionNode *>(*it);
276 if (function->access() == property->access()
277 && (function->status() == property->status() || function->doc().isEmpty())) {
278 if (function->name() == getterName) {
280 } else if (function->name() == setterName) {
282 } else if (function->name() == resetterName) {
284 } else if (function->name() == notifierName) {
286 } else if (function->name() == bindableName) {
288 }
289 }
290 }
291 }
292 }
293
294 for (auto propEntry = m_unresolvedPropertyMap.constBegin();
295 propEntry != m_unresolvedPropertyMap.constEnd(); ++propEntry) {
296 PropertyNode *property = propEntry.key();
297 // redo it to set the property functions
298 if (property->overriddenFrom())
300 }
301
302 m_unresolvedPropertyMap.clear();
303}
304
305/*!
306 Validates that all properties in the documentation tree that require
307 documentation according to the inclusion policy have documentation.
308 Generates warnings for undocumented properties.
309
310 This method recursively traverses the tree starting from \a aggregate,
311 checking each PropertyNode for documentation. Properties without
312 documentation that require it according to the inclusion policy will
313 generate a warning.
314
315 \sa InclusionFilter::requiresDocumentation()
316 */
317void Tree::validatePropertyDocumentation(const Aggregate *aggregate) const
318{
319 const auto &config = Config::instance();
320 const InclusionPolicy policy = config.createInclusionPolicy();
321 validatePropertyDocumentation(aggregate, policy);
322}
323
324/*!
325 \internal
326 \overload
327
328 Private helper that takes \a policy by const reference to avoid
329 recreating it on each recursive call while traversing \a aggregate.
330 */
331void Tree::validatePropertyDocumentation(const Aggregate *aggregate, const InclusionPolicy &policy) const
332{
333 for (auto it = aggregate->constBegin(); it != aggregate->constEnd(); ++it) {
334 Node *node = *it;
335
336 if (node->isProperty() && !node->hasDoc() && !node->isWrapper()) {
337 const NodeContext context = node->createContext();
339 node->location().warning(u"Undocumented property '%1'"_s.arg(node->plainFullName()));
340 }
341
342 if (node->isAggregate())
343 validatePropertyDocumentation(static_cast<Aggregate *>(node), policy);
344 }
345}
346
347/*!
348 For each QML class node that points to a C++ class node,
349 follow its C++ class node pointer and set the C++ class
350 node's QML class node pointer back to the QML class node.
351 */
352void Tree::resolveCppToQmlLinks()
353{
354
355 const NodeList &children = m_root.childNodes();
356 for (auto *child : children) {
357 if (child->isQmlType()) {
358 auto *qcn = static_cast<QmlTypeNode *>(child);
359 auto *cn = const_cast<ClassNode *>(qcn->classNode());
360 if (cn)
361 cn->insertQmlNativeType(qcn);
362 }
363 }
364}
365
366/*!
367 For each \a aggregate, recursively set the \\since version based on
368 \\since information from the associated physical or logical module.
369 That is, C++ and QML types inherit the \\since of their module,
370 unless that command is explicitly used in the type documentation.
371
372 In addition, resolve the since information for individual enum
373 values.
374*/
375void Tree::resolveSince(Aggregate &aggregate)
376{
377 for (auto *child : aggregate.childNodes()) {
378 // Order matters; resolve since-clauses in enum values
379 // first as EnumNode is not an Aggregate
380 if (child->isEnumType())
381 resolveEnumValueSince(static_cast<EnumNode&>(*child));
382 if (!child->isAggregate())
383 continue;
384 if (!child->since().isEmpty())
385 continue;
386
387 if (const auto collectionNode = m_qdb->getModuleNode(child))
388 child->setSince(collectionNode->since());
389
390 resolveSince(static_cast<Aggregate&>(*child));
391 }
392}
393
394/*!
395 Resolve since information for values of enum node \a en.
396
397 Enum values are not derived from Node, but they can have
398 'since' information associated with them. Since-strings
399 for each enum item are initially stored in the Doc
400 instance of EnumNode as SinceTag atoms; parse the doc
401 and store them into each EnumItem.
402*/
403void Tree::resolveEnumValueSince(EnumNode &en)
404{
405 const QStringList enumItems{en.doc().enumItemNames()};
406 const Atom *atom = en.doc().body().firstAtom();
407 if (!atom)
408 return;
409 while ((atom = atom->find(Atom::ListTagLeft))) {
410 if (atom = atom->next(); !atom)
411 break;
412 if (const auto &val = atom->string(); enumItems.contains(val)) {
413 if (atom = atom->next(); atom && atom->next(Atom::SinceTagLeft))
414 en.setSince(val, atom->next()->next()->string());
415 }
416 }
417}
418
419/*!
420 Traverse this Tree and for each ClassNode found, remove
421 from its list of base classes any that are marked private
422 or internal. When a class is removed from a base class
423 list, promote its public pase classes to be base classes
424 of the class where the base class was removed. This is
425 done for documentation purposes. The function is recursive
426 on namespace nodes.
427 */
428void Tree::removePrivateAndInternalBases(NamespaceNode *rootNode)
429{
430 if (rootNode == nullptr)
431 rootNode = root();
432
433 for (auto node = rootNode->constBegin(); node != rootNode->constEnd(); ++node) {
434 if ((*node)->isClassNode())
435 static_cast<ClassNode *>(*node)->removePrivateAndInternalBases();
436 else if ((*node)->isNamespace())
437 removePrivateAndInternalBases(static_cast<NamespaceNode *>(*node));
438 }
439}
440
441/*!
442 */
443ClassList Tree::allBaseClasses(const ClassNode *classNode) const
444{
445 ClassList result;
446 const auto &baseClasses = classNode->baseClasses();
447 for (const auto &relatedClass : baseClasses) {
448 if (relatedClass.m_node != nullptr) {
449 result += relatedClass.m_node;
450 result += allBaseClasses(relatedClass.m_node);
451 }
452 }
453 return result;
454}
455
456/*!
457 Find the node with the specified \a path name that is of
458 the specified \a type and \a subtype. Begin the search at
459 the \a start node. If the \a start node is 0, begin the
460 search at the tree root. \a subtype is not used unless
461 \a type is \c{Page}.
462 */
463Node *Tree::findNodeByNameAndType(const QStringList &path, bool (Node::*isMatch)() const) const
464{
465 return findNodeRecursive(path, 0, root(), isMatch);
466}
467
468/*!
469 Recursive search for a node identified by \a path. Each
470 path element is a name. \a pathIndex specifies the index
471 of the name in \a path to try to match. \a start is the
472 node whose children shoulod be searched for one that has
473 that name. Each time a match is found, increment the
474 \a pathIndex and call this function recursively.
475
476 If the end of the path is reached (i.e. if a matching
477 node is found for each name in the \a path), the \a type
478 must match the type of the last matching node, and if the
479 type is \e{Page}, the \a subtype must match as well.
480
481 If the algorithm is successful, the pointer to the final
482 node is returned. Otherwise 0 is returned.
483 */
484Node *Tree::findNodeRecursive(const QStringList &path, int pathIndex, const Node *start,
485 bool (Node::*isMatch)() const) const
486{
487 if (start == nullptr || path.isEmpty())
488 return nullptr;
489 Node *node = const_cast<Node *>(start);
490 if (!node->isAggregate())
491 return ((pathIndex >= path.size()) ? node : nullptr);
492 auto *current = static_cast<Aggregate *>(node);
493 const NodeList &children = current->childNodes();
494 const QString &name = path.at(pathIndex);
495 for (auto *node : children) {
496 if (node == nullptr)
497 continue;
498 if (node->name() == name) {
499 if (pathIndex + 1 >= path.size()) {
500 if ((node->*(isMatch))())
501 return node;
502 continue;
503 } else { // Search the children of n for the next name in the path.
504 node = findNodeRecursive(path, pathIndex + 1, node, isMatch);
505 if (node != nullptr)
506 return node;
507 }
508 }
509 }
510 return nullptr;
511}
512
513/*!
514 Searches the tree for a node that matches the \a path plus
515 the \a target. The search begins at \a start and moves up
516 the parent chain from there, or, if \a start is 0, the search
517 begins at the root.
518
519 The \a flags can indicate whether to search base classes and/or
520 the enum values in enum types. \a genus further restricts
521 the type of nodes to match, i.e. CPP or QML.
522
523 If a matching node is found, \a ref is set to the HTML fragment
524 identifier to use for the link. On return, the optional
525 \a targetType parameter contains the type of the resolved
526 target; section title (Contents), \\target, \\keyword, or other
527 (Unknown).
528 */
529const Node *Tree::findNodeForTarget(const QStringList &path, const QString &target,
530 const Node *start, int flags, Genus genus,
531 QString &ref, TargetRec::TargetType *targetType) const
532{
533 const Node *node = nullptr;
534
535 // Retrieves and sets ref from target for Node n.
536 // Returns n on valid (or empty) target, or nullptr on an invalid target.
537 auto set_ref_from_target = [this, &ref, &target](const Node *n) -> const Node* {
538 if (!target.isEmpty()) {
539 if (ref = getRef(target, n); ref.isEmpty())
540 return nullptr;
541 }
542 return n;
543 };
544
545 if (genus == Genus::DontCare || genus == Genus::DOC) {
546 if (node = findPageNodeByTitle(path.at(0)); node) {
547 if (node = set_ref_from_target(node); node)
548 return node;
549 }
550 }
551
552 /*
553 For C++ class and QML type contexts, prioritize hierarchical search (including
554 base classes/types) over global target maps. This allows inherited members to
555 take precedence over unrelated global targets such as section titles in other
556 documentation pages. See QTBUG-72107 and QTBUG-141606.
557
558 Documentation authored on a member of a class or QML type, such as a function
559 body or a shared comment, is in the same context as the type's own body, so
560 the member's parent decides.
561 */
562 const Node *scope = (start && !start->isAggregate()) ? start->parent() : start;
563 const bool prioritizeHierarchy = scope &&
564 ((scope->isClassNode() && (genus == Genus::CPP || genus == Genus::DontCare)) ||
565 (scope->isQmlType() && (genus == Genus::QML || genus == Genus::DontCare)));
566
567 const TargetRec *result = nullptr;
568 if (!prioritizeHierarchy) {
569 result = findUnambiguousTarget(path.join(QLatin1String("::")), genus, start);
570 if (result) {
571 ref = result->m_ref;
572 if (node = set_ref_from_target(result->m_node); node) {
573 // Delay returning references to section titles as we
574 // may find a better match below
575 if (result->m_type != TargetRec::Contents) {
576 if (targetType)
577 *targetType = result->m_type;
578 return node;
579 }
580 ref.clear();
581 }
582 }
583 }
584
585 const Node *current = start ? start : root();
586 /*
587 If the path contains one or two double colons ("::"),
588 check if the first two path elements refer to a QML type.
589 If so, path[0] is QML module identifier, and path[1] is
590 the type.
591 */
592 int path_idx = 0;
593 if ((genus == Genus::QML || genus == Genus::DontCare)
594 && path.size() >= 2 && !path[0].isEmpty()) {
595 if (auto *qcn = lookupQmlType(path.sliced(0, 2).join(QLatin1String("::")), start); qcn) {
596 current = qcn;
597 // No further elements in the path, return the type
598 if (path.size() == 2)
599 return set_ref_from_target(qcn);
600 path_idx = 2;
601 }
602 }
603
604 while (current) {
605 if (current->isAggregate()) {
606 if (const Node *match = matchPathAndTarget(
607 path, path_idx, target, current, flags, genus, ref);
608 match != nullptr)
609 return match;
610 }
611 current = current->parent();
612 path_idx = 0;
613 }
614
615 // If we prioritized hierarchy but found nothing, try global targets as fallback
616 if (prioritizeHierarchy) {
617 result = findUnambiguousTarget(path.join(QLatin1String("::")), genus, start);
618 if (result) {
619 ref = result->m_ref;
620 if (node = set_ref_from_target(result->m_node); node) {
621 if (targetType)
622 *targetType = result->m_type;
623 return node;
624 }
625 }
626 }
627
628 if (node && result) {
629 // Fall back to previously found section title
630 ref = result->m_ref;
631 if (targetType)
632 *targetType = result->m_type;
633 }
634 return node;
635}
636
637/*!
638 First, the \a path is used to find a node. The \a path
639 matches some part of the node's fully quallified name.
640 If the \a target is not empty, it must match a target
641 in the matching node. If the matching of the \a path
642 and the \a target (if present) is successful, \a ref
643 is set from the \a target, and the pointer to the
644 matching node is returned. \a idx is the index into the
645 \a path where to begin the matching. The function is
646 recursive with idx being incremented for each recursive
647 call.
648
649 The matching node must be of the correct \a genus, i.e.
650 either QML or C++, but \a genus can be set to \c DontCare.
651 \a flags indicates whether to search base classes and
652 whether to search for an enum value. \a node points to
653 the node where the search should begin, assuming the
654 \a path is a not a fully-qualified name. \a node is
655 most often the root of this Tree.
656 */
657const Node *Tree::matchPathAndTarget(const QStringList &path, int idx, const QString &target,
658 const Node *node, int flags, Genus genus,
659 QString &ref, int duplicates) const
660{
661 /*
662 If the path has been matched, then if there is a target,
663 try to match the target. If there is a target, but you
664 can't match it at the end of the path, give up; return
665 \nullptr.
666 */
667 if (idx == path.size()) {
668 if (!target.isEmpty()) {
669 ref = getRef(target, node);
670 if (ref.isEmpty())
671 return nullptr;
672 }
673 if (node->isFunction() && node->name() == node->parent()->name())
674 node = node->parent();
675
676 // If attached properties are requested, only match attached properties.
677 if (flags & QmlAttachedProperties) {
678 if (node->isAttached())
679 return node;
680 else
681 return nullptr;
682 }
683 // Match regular properties if attached properties are not specified.
684 // Match attached properties if they do not shadow regular properties.
685 if (node->isQmlProperty()) {
686 if (!node->isAttached() || duplicates == 0)
687 return node;
688 else
689 return nullptr;
690 } else
691 return node;
692 }
693
694 QString name = path.at(idx);
695 if (node->isAggregate()) {
696 NodeVector nodes;
697 static_cast<const Aggregate *>(node)->findChildren(name, nodes);
698 for (const auto *child : std::as_const(nodes)) {
699 if (genus != Genus::DontCare && !(hasCommonGenusType(genus, child->genus())))
700 continue;
701 const Node *t = matchPathAndTarget(path, idx + 1, target, child, flags, genus, ref, nodes.count() - 1);
702 if (t && !t->isPrivate() && !t->isInternal())
703 return t;
704 }
705 }
706 if (target.isEmpty() && (flags & SearchEnumValues)) {
707 const auto *enumNode = node->isAggregate() ?
708 findEnumNode(nullptr, node, path, idx) :
709 findEnumNode(node, nullptr, path, idx);
710 if (enumNode)
711 return enumNode;
712 }
713 if (((genus == Genus::CPP) || (genus == Genus::DontCare)) && node->isClassNode()
714 && (flags & SearchBaseClasses)) {
715 const ClassList bases = allBaseClasses(static_cast<const ClassNode *>(node));
716 for (const auto *base : bases) {
717 const Node *t = matchPathAndTarget(path, idx, target, base, flags, genus, ref);
718 if (t && !t->isPrivate() && !t->isInternal())
719 return t;
720 if (target.isEmpty() && (flags & SearchEnumValues)) {
721 if ((t = findEnumNode(base->findChildNode(path.at(idx), genus, flags), base, path, idx)))
722 return t;
723 }
724 }
725 }
726 if (((genus == Genus::QML) || (genus == Genus::DontCare)) && node->isQmlType()
727 && (flags & SearchBaseClasses)) {
728 const QmlTypeNode *qtn = static_cast<const QmlTypeNode *>(node);
729 while (qtn && qtn->qmlBaseNode()) {
730 qtn = qtn->qmlBaseNode();
731 const Node *t = matchPathAndTarget(path, idx, target, qtn, flags, genus, ref);
732 if (t && !t->isPrivate() && !t->isInternal())
733 return t;
734 }
735 }
736 return nullptr;
737}
738
739/*!
740 Searches the tree for a node that matches the \a path. The
741 search begins at \a start but can move up the parent chain
742 recursively if no match is found. The \a flags are used to
743 restrict the search.
744 */
745const Node *Tree::findNode(const QStringList &path, const Node *start, int flags,
746 Genus genus) const
747{
748 const Node *current = start;
749 if (current == nullptr)
750 current = root();
751
752 do {
753 const Node *node = current;
754 int i;
755 int start_idx = 0;
756
757 /*
758 If the path contains one or two double colons ("::"),
759 check first to see if the first two path strings refer
760 to a QML element. If they do, path[0] will be the QML
761 module identifier, and path[1] will be the QML type.
762 If the answer is yes, the reference identifies a QML
763 type node.
764 */
765 if (((genus == Genus::QML) || (genus == Genus::DontCare)) && (path.size() >= 2)
766 && !path[0].isEmpty()) {
767 QmlTypeNode *qcn = lookupQmlType(QString(path[0] + "::" + path[1]), start);
768 if (qcn != nullptr) {
769 node = qcn;
770 if (path.size() == 2)
771 return node;
772 start_idx = 2;
773 }
774 }
775
776 for (i = start_idx; i < path.size(); ++i) {
777 if (node == nullptr || !node->isAggregate())
778 break;
779
780 // Clear the TypesOnly flag until the last path segment, as e.g. namespaces are not
781 // types. We also ignore module nodes as they are not aggregates and thus have no
782 // children.
783 int tmpFlags = (i < path.size() - 1) ? (flags & ~TypesOnly) | IgnoreModules : flags;
784
785 const Node *next = static_cast<const Aggregate *>(node)->findChildNode(path.at(i),
786 genus, tmpFlags);
787 const Node *enumNode = (flags & SearchEnumValues) ?
788 findEnumNode(next, node, path, i) : nullptr;
789
790 if (enumNode)
791 return enumNode;
792
793
794 if (!next && ((genus == Genus::CPP) || (genus == Genus::DontCare))
795 && node->isClassNode() && (flags & SearchBaseClasses)) {
796 const ClassList bases = allBaseClasses(static_cast<const ClassNode *>(node));
797 for (const auto *base : bases) {
798 next = base->findChildNode(path.at(i), genus, tmpFlags);
799 if (flags & SearchEnumValues)
800 if ((enumNode = findEnumNode(next, base, path, i)))
801 return enumNode;
802 if (next)
803 break;
804 }
805 }
806 if (!next && ((genus == Genus::QML) || (genus == Genus::DontCare))
807 && node->isQmlType() && (flags & SearchBaseClasses)) {
808 const QmlTypeNode *qtn = static_cast<const QmlTypeNode *>(node);
809 while (qtn && qtn->qmlBaseNode() && !next) {
810 qtn = qtn->qmlBaseNode();
811 next = qtn->findChildNode(path.at(i), genus, tmpFlags);
812 }
813 }
814 node = next;
815 }
816 if ((node != nullptr) && i == path.size())
817 return node;
818 current = current->parent();
819 } while (current != nullptr);
820
821 return nullptr;
822}
823
824
825/*!
826 \internal
827
828 Helper function to return an enum that matches the \a path at a specified \a offset.
829 If \a node is a valid enum node, the enum name is assumed to be included in the path
830 (i.e, a scoped enum). Otherwise, query the \a aggregate (typically, the class node)
831 for enum node that includes the value at the last position in \a path.
832 */
833const Node *Tree::findEnumNode(const Node *node, const Node *aggregate, const QStringList &path, int offset) const
834{
835 // Scoped enum (path ends in enum_name :: enum_value)
836 if (node && node->isEnumType() && offset == path.size() - 1) {
837 const auto *en = static_cast<const EnumNode*>(node);
838 if (en->hasItem(path.last()))
839 return en;
840 }
841
842 // Standard enum (path ends in class_name :: enum_value)
843 return (!node && aggregate && offset == path.size() - 1) ?
844 static_cast<const Aggregate *>(aggregate)->findEnumNodeForValue(path.last()) :
845 nullptr;
846}
847
848/*!
849 This function searches for a node with a canonical title
850 constructed from \a target. If the node it finds is \a node,
851 it returns the ref from that node. Otherwise it returns an
852 empty string.
853 */
854QString Tree::getRef(const QString &target, const Node *node) const
855{
856 auto it = m_nodesByTargetTitle.constFind(target);
857 if (it != m_nodesByTargetTitle.constEnd()) {
858 do {
859 if (it.value()->m_node == node)
860 return it.value()->m_ref;
861 ++it;
862 } while (it != m_nodesByTargetTitle.constEnd() && it.key() == target);
863 }
864 QString key = TextUtils::asAsciiPrintable(target);
865 it = m_nodesByTargetRef.constFind(key);
866 if (it != m_nodesByTargetRef.constEnd()) {
867 do {
868 if (it.value()->m_node == node)
869 return it.value()->m_ref;
870 ++it;
871 } while (it != m_nodesByTargetRef.constEnd() && it.key() == key);
872 }
873 return QString();
874}
875
876/*!
877 Inserts a new target into the target table. \a name is the
878 key. The target record contains the \a type, a pointer to
879 the \a node, the \a priority. and a canonicalized form of
880 the \a name, which is later used.
881 */
882void Tree::insertTarget(const QString &name, const QString &title, TargetRec::TargetType type,
883 Node *node, int priority)
884{
885 auto *target = new TargetRec(name, type, node, priority);
886 m_nodesByTargetRef.insert(name, target);
887 m_nodesByTargetTitle.insert(title, target);
888}
889
890/*!
891 \internal
892
893 \a root is the root node of the tree to resolve targets for. This function
894 traverses the tree starting from the root node and processes each child
895 node. If the child node is an aggregate node, this function is called
896 recursively on the child node.
897 */
898void Tree::resolveTargets(Aggregate *root)
899{
900 for (auto *child : root->childNodes()) {
901 addToPageNodeByTitleMap(child);
902 populateTocSectionTargetMap(child);
903 addKeywordsToTargetMaps(child);
904 addTargetsToTargetMap(child);
905
906 if (child->isAggregate())
907 resolveTargets(static_cast<Aggregate *>(child));
908 }
909}
910
911static const Atom *nextSection(const Atom *a);
912
913/*!
914 \internal
915
916 Updates the target maps for targets associated with the given \a node.
917 */
918void Tree::addTargetsToTargetMap(Node *node) {
919 if (!node || !node->doc().hasTargets())
920 return;
921
922 QSet<QString> usedRefs;
923 for (const Atom *heading : node->doc().tableOfContents())
924 usedRefs.insert(refForAtom(heading));
925 if (node->isPageNode())
926 usedRefs.insert("details"_L1);
927
928 for (Atom *i : std::as_const(node->doc().targets())) {
929 QString ref = refForAtom(i);
930 const QString title = i->string();
931 if (!ref.isEmpty() && !title.isEmpty()) {
932 const QString rawRef = TextUtils::asAsciiPrintable(title);
933 // A target elsewhere on the page must not reuse a section's id.
934 // A target tied to the following section already shares its anchor.
935 if (i->count() == 1 && usedRefs.contains(rawRef)
936 && (!nextSection(i) || ref != rawRef)) {
937 const QString base = rawRef + "-target"_L1;
938 ref = base;
939 for (int n = 2; usedRefs.contains(ref); ++n)
940 ref = base + '-'_L1 + QString::number(n);
941 i->append(ref);
942 }
943 usedRefs.insert(ref);
944 QString key = TextUtils::asAsciiPrintable(title);
945 auto *target = new TargetRec(std::move(ref), TargetRec::Target, node, 2);
946 m_nodesByTargetRef.insert(key, target);
947 m_nodesByTargetTitle.insert(title, target);
948 }
949 }
950}
951
952/*
953 If atom \a a is immediately followed by a
954 section title (\section1..\section4 command),
955 returns the SectionLeft atom; otherwise nullptr.
956*/
957static const Atom *nextSection(const Atom *a)
958{
959 while (a && a->next(Atom::SectionRight))
960 a = a->next(); // skip closing section atoms
961 return a ? a->next(Atom::SectionLeft) : nullptr;
962}
963
964/*!
965 \internal
966
967 Updates the target maps for keywords associated with the given \a node.
968 */
969void Tree::addKeywordsToTargetMaps(Node *node) {
970 if (!node->doc().hasKeywords())
971 return;
972
973 for (Atom *i : std::as_const(node->doc().keywords())) {
974 QString ref = refForAtom(i);
975 QString title = i->string();
976 if (!ref.isEmpty() && !title.isEmpty()) {
977 auto *target = new TargetRec(ref, nextSection(i) ? TargetRec::ContentsKeyword : TargetRec::Keyword, node, 1);
978 m_nodesByTargetRef.insert(TextUtils::asAsciiPrintable(title), target);
979 m_nodesByTargetTitle.insert(title, target);
980 if (!target->isEmpty())
981 i->append(target->m_ref);
982 }
983 }
984}
985
986/*!
987 \internal
988
989 Populates the map of targets for each section in the table of contents for
990 the given \a node while ensuring that each target has a unique reference.
991 */
992void Tree::populateTocSectionTargetMap(Node *node) {
993 if (!node || !node->doc().hasTableOfContents())
994 return;
995
996 QStack<Atom *> tocLevels;
997 QSet<QString> anchors;
998
999 // Member documentation is rendered on its parent's page.
1000 QString memberRef = node->isPageNode() ? QString() : computeAnchorId(node);
1001 if (node->isSharedCommentNode() && memberRef.isEmpty()) {
1002 const auto *shared = static_cast<const SharedCommentNode *>(node);
1003 if (!shared->collective().isEmpty())
1004 memberRef = computeAnchorId(shared->collective().first());
1005 }
1006
1007 qsizetype index = 0;
1008
1009 for (Atom *atom: std::as_const(node->doc().tableOfContents())) {
1010 while (!tocLevels.isEmpty() && tocLevels.top()->string().toInt() >= atom->string().toInt())
1011 tocLevels.pop();
1012
1013 tocLevels.push(atom);
1014
1015 QString ref = refForAtom(atom);
1016 const QString &title = Text::sectionHeading(atom).toString();
1017 if (ref.isEmpty() || title.isEmpty())
1018 continue;
1019
1020 if (!memberRef.isEmpty() && node->parent() && node->parent()->isAggregate()) {
1021 const auto *page = static_cast<const Aggregate *>(node->parent());
1022 bool collides = false;
1023 for (const Node *sibling : page->childNodes()) {
1024 if (sibling == node)
1025 continue;
1026 for (const Atom *heading : sibling->doc().tableOfContents()) {
1027 if (TextUtils::asAsciiPrintable(Text::sectionHeading(heading).toString()) == ref) {
1028 collides = true;
1029 break;
1030 }
1031 }
1032 if (collides)
1033 break;
1034 }
1035 if (collides)
1036 ref = memberRef + '-'_L1 + ref;
1037 } else if (node->isAggregate()) {
1038 // A class section can otherwise shadow a member's link target.
1039 const auto *aggregate = static_cast<const Aggregate *>(node);
1040 for (const Node *child : aggregate->childNodes()) {
1041 if (computeAnchorId(child) == ref) {
1042 ref += "-section"_L1;
1043 break;
1044 }
1045 }
1046 }
1047
1048 // Legacy HTML reserves these ids for its page details and signal
1049 // summary, independently of the section titles in the source text.
1050 if (ref == "details"_L1 && node->isPageNode()) {
1051 ref += "-section"_L1;
1052 } else if (ref == "signals"_L1 && node->isAggregate()) {
1053 const auto *aggregate = static_cast<const Aggregate *>(node);
1054 for (const Node *child : aggregate->childNodes()) {
1055 if (child->isFunction() && static_cast<const FunctionNode *>(child)->isSignal()) {
1056 ref += "-section"_L1;
1057 break;
1058 }
1059 }
1060 }
1061
1062 if (anchors.contains(ref)) {
1063 QStringList refParts;
1064 for (const auto tocLevel : tocLevels)
1065 refParts << refForAtom(tocLevel);
1066
1067 refParts << QString::number(index);
1068 ref = refParts.join(QLatin1Char('-'));
1069 }
1070
1071 anchors.insert(ref);
1072 if (atom->next(Atom::SectionHeadingLeft) && atom->next()->count() == 1)
1073 atom->next()->append(ref);
1074 ++index;
1075
1076 const QString &key = TextUtils::asAsciiPrintable(title);
1077 auto *target = new TargetRec(ref, TargetRec::Contents, node, 3);
1078 m_nodesByTargetRef.insert(key, target);
1079 m_nodesByTargetTitle.insert(title, target);
1080 }
1081}
1082
1083/*!
1084 \internal
1085
1086 Checks if the \a node's title is registered in the page nodes by title map.
1087 If not, it stores the page node in the map.
1088 */
1089void Tree::addToPageNodeByTitleMap(Node *node) {
1090 if (!node || !node->isTextPageNode())
1091 return;
1092
1093 auto *pageNode = static_cast<PageNode *>(node);
1094 QString key = pageNode->title();
1095 if (key.isEmpty())
1096 return;
1097
1098 if (key.contains(QChar(' ')))
1099 key = TextUtils::asAsciiPrintable(key);
1100 const QList<PageNode *> nodes = m_pageNodesByTitle.values(key);
1101
1102 bool alreadyThere = std::any_of(nodes.cbegin(), nodes.cend(), [&](const auto &knownNode) {
1103 return knownNode->isExternalPage() && knownNode->name() == pageNode->name();
1104 });
1105
1106 if (!alreadyThere)
1107 m_pageNodesByTitle.insert(key, pageNode);
1108}
1109
1110/*!
1111 Finds a target anchor that matches \a target and \a genus. Returns the
1112 associated TargetRec instance, or nullptr if no match is found.
1113
1114 Searches target titles first. If no matching title is found, searches
1115 target references.
1116
1117 For each search, selects a matching target in the following order:
1118 \list 1
1119 \li Keyword targets.
1120 \li Targets whose node is \a start, its parent, or one of its children.
1121 \li Other targets.
1122 \endlist
1123
1124 Within each category, prefers targets with lower numeric priority values.
1125 If multiple targets have the same rank, returns the first one encountered.
1126 */
1127const TargetRec *Tree::findUnambiguousTarget(const QString &target, Genus genus, const Node *start) const
1128{
1129 // Nodes that are the same, children or parents are closely related.
1130 auto closelyRelated = [](const Node *n1, const Node *n2) {
1131 if (!n1 || !n2)
1132 return false;
1133 return (n1 == n2) || (n1->parent() == n2) || (n1 == n2->parent());
1134 };
1135
1136 enum class TargetCategory { Keyword, Nearby, Other };
1137
1138 auto rank = [&](const TargetRec *candidate) {
1139 auto category = TargetCategory::Other;
1140 if (candidate->m_type == TargetRec::Keyword) {
1141 category = TargetCategory::Keyword;
1142 } else if (closelyRelated(candidate->m_node, start)) {
1143 category = TargetCategory::Nearby;
1144 }
1145
1146 return std::pair{category, candidate->m_priority};
1147 };
1148
1149 auto findBestCandidate = [&](const TargetMap &tgtMap, const QString &key) {
1150 TargetRec *best = nullptr;
1151 auto [it, end] = tgtMap.equal_range(key);
1152 while (it != end) {
1153 TargetRec *candidate = it.value();
1154 if ((genus == Genus::DontCare) || hasCommonGenusType(genus, candidate->genus())) {
1155 if (!best || rank(candidate) < rank(best))
1156 best = candidate;
1157 }
1158 ++it;
1159 }
1160 return best;
1161 };
1162
1163 TargetRec *bestTarget = findBestCandidate(m_nodesByTargetTitle, target);
1164 if (!bestTarget)
1165 bestTarget = findBestCandidate(m_nodesByTargetRef, TextUtils::asAsciiPrintable(target));
1166
1167 return bestTarget;
1168}
1169
1170/*!
1171 This function searches for a node with the specified \a title.
1172 */
1173const PageNode *Tree::findPageNodeByTitle(const QString &title) const
1174{
1175 PageNodeMultiMap::const_iterator it;
1176 if (title.contains(QChar(' ')))
1177 it = m_pageNodesByTitle.constFind(TextUtils::asAsciiPrintable(title));
1178 else
1179 it = m_pageNodesByTitle.constFind(title);
1180 if (it != m_pageNodesByTitle.constEnd()) {
1181 /*
1182 Reporting all these duplicate section titles is probably
1183 overkill. We should report the duplicate file and let
1184 that suffice.
1185 */
1186 PageNodeMultiMap::const_iterator j = it;
1187 ++j;
1188 if (j != m_pageNodesByTitle.constEnd() && j.key() == it.key()) {
1189 while (j != m_pageNodesByTitle.constEnd()) {
1190 if (j.key() == it.key() && j.value()->url().isEmpty()) {
1191 break; // Just report one duplicate for now.
1192 }
1193 ++j;
1194 }
1195 if (j != m_pageNodesByTitle.cend()) {
1196 it.value()->location().warning("This page title exists in more than one file: "
1197 + title);
1198 j.value()->location().warning("[It also exists here]");
1199 }
1200 }
1201 return it.value();
1202 }
1203 return nullptr;
1204}
1205
1206/*!
1207 Returns a canonical title for the \a atom, if the \a atom
1208 is a SectionLeft, SectionHeadingLeft, Keyword, or Target.
1209
1210 If a target or a keyword is immediately followed by a
1211 section, the former adopts the title (ref) of the latter.
1212 */
1213QString Tree::refForAtom(const Atom *atom)
1214{
1215 Q_ASSERT(atom);
1216
1217 switch (atom->type()) {
1218 case Atom::SectionLeft:
1219 atom = atom->next();
1220 [[fallthrough]];
1222 if (atom->count() >= 2)
1223 return atom->string(1);
1224 return TextUtils::asAsciiPrintable(Text::sectionHeading(atom).toString());
1225 case Atom::Target:
1226 [[fallthrough]];
1227 case Atom::Keyword:
1228 if (atom->count() >= 2)
1229 return atom->string(1);
1230 if (const auto *section = nextSection(atom))
1231 return refForAtom(section);
1232 return TextUtils::asAsciiPrintable(atom->string());
1233 default:
1234 return {};
1235 }
1236}
1237
1238/*!
1239 \fn const CNMap &Tree::groups() const
1240 Returns a const reference to the collection of all
1241 group nodes.
1242*/
1243
1244/*!
1245 \fn const ModuleMap &Tree::modules() const
1246 Returns a const reference to the collection of all
1247 module nodes.
1248*/
1249
1250/*!
1251 \fn const QmlModuleMap &Tree::qmlModules() const
1252 Returns a const reference to the collection of all
1253 QML module nodes.
1254*/
1255
1256/*!
1257 Returns a pointer to the collection map specified by \a type.
1258 Returns null if \a type is not specified.
1259 */
1260CNMap *Tree::getCollectionMap(NodeType type)
1261{
1262 switch (type) {
1263 case NodeType::Group:
1264 return &m_groups;
1265 case NodeType::Module:
1266 return &m_modules;
1268 return &m_qmlModules;
1269 case NodeType::Concept:
1270 return &m_concepts;
1271 default:
1272 break;
1273 }
1274 return nullptr;
1275}
1276
1277/*!
1278 Searches this tree for a collection named \a name with the
1279 specified \a type. If the collection is found, a pointer
1280 to it is returned. If a collection is not found, null is
1281 returned.
1282 */
1283CollectionNode *Tree::getCollection(const QString &name, NodeType type)
1284{
1285 CNMap *map = getCollectionMap(type);
1286 if (map) {
1287 auto it = map->constFind(name);
1288 if (it != map->cend())
1289 return it.value();
1290 }
1291 return nullptr;
1292}
1293
1294/*!
1295 Find the group, module, or QML module named \a name and return a
1296 pointer to that collection node. \a type specifies which kind of
1297 collection node you want. If a collection node with the specified \a
1298 name and \a type is not found, a new one is created, and the pointer
1299 to the new one is returned.
1300
1301 If a new collection node is created, its parent is the tree
1302 root, and the new collection node is marked \e{not seen}.
1303
1304 \a genus must be specified, i.e. it must not be \c{DontCare}.
1305 If it is \c{DontCare}, 0 is returned, which is a programming
1306 error.
1307 */
1308CollectionNode *Tree::findCollection(const QString &name, NodeType type)
1309{
1310 CNMap *m = getCollectionMap(type);
1311 if (!m) // error
1312 return nullptr;
1313 auto it = m->constFind(name);
1314 if (it != m->cend())
1315 return it.value();
1316 CollectionNode *cn = new CollectionNode(type, root(), name);
1318 m->insert(name, cn);
1319 return cn;
1320}
1321
1322/*! \fn CollectionNode *Tree::findGroup(const QString &name)
1323 Find the group node named \a name and return a pointer
1324 to it. If the group node is not found, add a new group
1325 node named \a name and return a pointer to the new one.
1326
1327 If a new group node is added, its parent is the tree root,
1328 and the new group node is marked \e{not seen}.
1329 */
1330
1331/*! \fn CollectionNode *Tree::findModule(const QString &name)
1332 Find the module node named \a name and return a pointer
1333 to it. If a matching node is not found, add a new module
1334 node named \a name and return a pointer to that one.
1335
1336 If a new module node is added, its parent is the tree root,
1337 and the new module node is marked \e{not seen}.
1338 */
1339
1340/*! \fn CollectionNode *Tree::findQmlModule(const QString &name)
1341 Find the QML module node named \a name and return a pointer
1342 to it. If a matching node is not found, add a new QML module
1343 node named \a name and return a pointer to that one.
1344
1345 If a new QML module node is added, its parent is the tree root,
1346 and the new node is marked \e{not seen}.
1347 */
1348
1349/*! \fn CollectionNode *Tree::addGroup(const QString &name)
1350 Looks up the group node named \a name in the collection
1351 of all group nodes. If a match is found, a pointer to the
1352 node is returned. Otherwise, a new group node named \a name
1353 is created and inserted into the collection, and the pointer
1354 to that node is returned.
1355 */
1356
1357/*! \fn CollectionNode *Tree::addModule(const QString &name)
1358 Looks up the module node named \a name in the collection
1359 of all module nodes. If a match is found, a pointer to the
1360 node is returned. Otherwise, a new module node named \a name
1361 is created and inserted into the collection, and the pointer
1362 to that node is returned.
1363 */
1364
1365/*! \fn CollectionNode *Tree::addQmlModule(const QString &name)
1366 Looks up the QML module node named \a name in the collection
1367 of all QML module nodes. If a match is found, a pointer to the
1368 node is returned. Otherwise, a new QML module node named \a name
1369 is created and inserted into the collection, and the pointer
1370 to that node is returned.
1371 */
1372
1373/*!
1374 Looks up the group node named \a name in the collection
1375 of all group nodes. If a match is not found, a new group
1376 node named \a name is created and inserted into the collection.
1377 Then append \a node to the group's members list, and append the
1378 group name to the list of group names in \a node. The parent of
1379 \a node is not changed by this function. Returns a pointer to
1380 the group node.
1381 */
1382CollectionNode *Tree::addToGroup(const QString &name, Node *node)
1383{
1384 CollectionNode *cn = findGroup(name);
1385 if (!node->isInternal()) {
1386 cn->addMember(node);
1387 node->appendGroupName(name);
1388 }
1389 return cn;
1390}
1391
1392/*!
1393 Looks up the module node named \a name in the collection
1394 of all module nodes. If a match is not found, a new module
1395 node named \a name is created and inserted into the collection.
1396 Then append \a node to the module's members list. The parent of
1397 \a node is not changed by this function. Returns the module node.
1398 */
1399CollectionNode *Tree::addToModule(const QString &name, Node *node)
1400{
1401 CollectionNode *cn = findModule(name);
1402 cn->addMember(node);
1403 node->setPhysicalModuleName(name);
1404 return cn;
1405}
1406
1407/*!
1408 Looks up the QML module named \a name. If it isn't there,
1409 create it. Then append \a node to the QML module's member
1410 list. The parent of \a node is not changed by this function.
1411 Returns the pointer to the QML module node.
1412 */
1413CollectionNode *Tree::addToQmlModule(const QString &name, Node *node)
1414{
1415 QStringList qmid;
1416 QStringList dotSplit;
1417 QStringList blankSplit = name.split(QLatin1Char(' '));
1418 qmid.append(blankSplit[0]);
1419 if (blankSplit.size() > 1) {
1420 qmid.append(blankSplit[0] + blankSplit[1]);
1421 dotSplit = blankSplit[1].split(QLatin1Char('.'));
1422 qmid.append(blankSplit[0] + dotSplit[0]);
1423 }
1424
1425 CollectionNode *cn = findQmlModule(blankSplit[0]);
1426 cn->addMember(node);
1427 node->setQmlModule(cn);
1428 if (node->isQmlType()) {
1429 QmlTypeNode *n = static_cast<QmlTypeNode *>(node);
1430 for (int i = 0; i < qmid.size(); ++i) {
1431 QString key = qmid[i] + "::" + node->name();
1432 insertQmlType(key, n);
1433 }
1434 // Also insert with unqualified name for context-aware disambiguation
1435 insertQmlType(node->name(), n);
1436 }
1437 return cn;
1438}
1439
1440/*!
1441 Inserts QML type node \a n with the specified \a key into the type map.
1442 Since the map is a QMultiMap, multiple types with the same name can coexist
1443 (e.g., Shape from different modules).
1444 */
1445void Tree::insertQmlType(const QString &key, QmlTypeNode *n)
1446{
1447 m_qmlTypeMap.insert(key, n);
1448}
1449
1450/*!
1451 Looks up and returns the QML type node identified by \a name. When multiple
1452 types with the same name exist (e.g., Shape from different modules), prefers
1453 the type from the same module as \a relative if provided. Returns the first
1454 match if no module relationship exists, or nullptr if not found.
1455 */
1456QmlTypeNode *Tree::lookupQmlType(const QString &name, const Node *relative) const
1457{
1458 auto values = m_qmlTypeMap.values(name);
1459 if (values.isEmpty())
1460 return nullptr;
1461
1462 // If no context or only one match, return first result
1463 if (!relative || values.size() == 1)
1464 return values.first();
1465
1466 // Prefer types from the same module as the relative context
1467 if (relative->isQmlType()) {
1468 const auto *relativeQmlType = static_cast<const QmlTypeNode *>(relative);
1469 const CollectionNode *relativeModule = relativeQmlType->logicalModule();
1470
1471 for (auto *candidate : values) {
1472 if (candidate->logicalModule() == relativeModule)
1473 return candidate;
1474 }
1475 }
1476
1477 // Fallback to first match
1478 return values.first();
1479}
1480
1481/*!
1482 Finds the function node with the specifried name \a path that
1483 also has the specified \a parameters and returns a pointer to
1484 the first matching function node if one is found.
1485
1486 This function begins searching the tree at \a relative for
1487 the \l {FunctionNode} {function node} identified by \a path
1488 that has the specified \a parameters. The \a flags are
1489 used to restrict the search. If a matching node is found, a
1490 pointer to it is returned. Otherwise, nullis returned. If
1491 \a relative is ull, the search begins at the tree root.
1492 */
1493const FunctionNode *Tree::findFunctionNode(const QStringList &path, const Parameters &parameters,
1494 const Node *relative, Genus genus) const
1495{
1496 if (path.size() == 3 && !path[0].isEmpty()
1497 && ((genus == Genus::QML) || (genus == Genus::DontCare))) {
1498 QmlTypeNode *qcn = lookupQmlType(QString(path[0] + "::" + path[1]), relative);
1499 if (qcn == nullptr) {
1500 QStringList p(path[1]);
1501 Node *n = findNodeByNameAndType(p, &Node::isQmlType);
1502 if ((n != nullptr) && n->isQmlType())
1503 qcn = static_cast<QmlTypeNode *>(n);
1504 }
1505 if (qcn != nullptr)
1506 return static_cast<const FunctionNode *>(qcn->findFunctionChild(path[2], parameters));
1507 }
1508
1509 if (relative == nullptr)
1510 relative = root();
1511 else if (genus != Genus::DontCare) {
1512 if (!(hasCommonGenusType(genus, relative->genus())))
1513 relative = root();
1514 }
1515
1516 do {
1517 Node *node = const_cast<Node *>(relative);
1518 int i;
1519
1520 for (i = 0; i < path.size(); ++i) {
1521 if (node == nullptr || !node->isAggregate())
1522 break;
1523
1524 Aggregate *aggregate = static_cast<Aggregate *>(node);
1525 Node *next = nullptr;
1526 if (i == path.size() - 1)
1527 next = aggregate->findFunctionChild(path.at(i), parameters);
1528 else
1529 next = aggregate->findChildNode(path.at(i), genus);
1530
1531 if ((next == nullptr) && aggregate->isClassNode()) {
1532 const ClassList bases = allBaseClasses(static_cast<const ClassNode *>(aggregate));
1533 for (auto *base : bases) {
1534 if (i == path.size() - 1)
1535 next = base->findFunctionChild(path.at(i), parameters);
1536 else
1537 next = base->findChildNode(path.at(i), genus);
1538
1539 if (next != nullptr)
1540 break;
1541 }
1542 }
1543
1544 node = next;
1545 } // for (i = 0; i < path.size(); ++i)
1546
1547 if (node && i == path.size() && node->isFunction()) {
1548 // A function node was found at the end of the path.
1549 // If it is not marked private, return it. If it is
1550 // marked private, then if it overrides a function,
1551 // find that function instead because it might not
1552 // be marked private. If all the overloads are
1553 // marked private, return the original function node.
1554 // This should be replace with findOverriddenFunctionNode().
1555 const FunctionNode *fn = static_cast<const FunctionNode *>(node);
1556 const FunctionNode *FN = fn;
1557 while (FN->isPrivate() && !FN->overridesThis().isEmpty()) {
1558 QStringList path = FN->overridesThis().split("::");
1559 FN = m_qdb->findFunctionNode(path, parameters, relative, genus);
1560 if (FN == nullptr)
1561 break;
1562 if (!FN->isPrivate())
1563 return FN;
1564 }
1565 return fn;
1566 }
1567 relative = relative->parent();
1568 } while (relative);
1569 return nullptr;
1570}
1571
1572/*!
1573 Search this tree recursively from \a parent to find a function
1574 node with the specified \a tag. If no function node is found
1575 with the required \a tag, return 0.
1576 */
1577FunctionNode *Tree::findFunctionNodeForTag(const QString &tag, Aggregate *parent)
1578{
1579 if (parent == nullptr)
1580 parent = root();
1581 const NodeList &children = parent->childNodes();
1582 for (Node *n : children) {
1583 if (n != nullptr && n->isFunction() && n->hasTag(tag))
1584 return static_cast<FunctionNode *>(n);
1585 }
1586 for (Node *n : children) {
1587 if (n != nullptr && n->isAggregate()) {
1588 n = findFunctionNodeForTag(tag, static_cast<Aggregate *>(n));
1589 if (n != nullptr)
1590 return static_cast<FunctionNode *>(n);
1591 }
1592 }
1593 return nullptr;
1594}
1595
1596/*!
1597 There should only be one macro node for macro name \a t.
1598 The macro node is not built until the \macro command is seen.
1599 */
1600FunctionNode *Tree::findMacroNode(const QString &t, const Aggregate *parent)
1601{
1602 if (parent == nullptr)
1603 parent = root();
1604 const NodeList &children = parent->childNodes();
1605 for (Node *n : children) {
1606 if (n != nullptr && (n->isMacro() || n->isFunction()) && n->name() == t)
1607 return static_cast<FunctionNode *>(n);
1608 }
1609 for (Node *n : children) {
1610 if (n != nullptr && n->isAggregate()) {
1611 FunctionNode *fn = findMacroNode(t, static_cast<Aggregate *>(n));
1612 if (fn != nullptr)
1613 return fn;
1614 }
1615 }
1616 return nullptr;
1617}
1618
1619/*!
1620 Add the class and struct names in \a arg to the \e {don't document}
1621 map.
1622 */
1623void Tree::addToDontDocumentMap(QString &arg)
1624{
1625 arg.remove(QChar('('));
1626 arg.remove(QChar(')'));
1627 QString t = arg.simplified();
1628 QStringList sl = t.split(QChar(' '));
1629 if (sl.isEmpty())
1630 return;
1631 for (const QString &s : sl) {
1632 if (!m_dontDocumentMap.contains(s))
1633 m_dontDocumentMap.insert(s, nullptr);
1634 }
1635}
1636
1637/*!
1638 The \e {don't document} map has been loaded with the names
1639 of classes and structs in the current module that are not
1640 documented and should not be documented. Now traverse the
1641 map, and for each class or struct name, find the class node
1642 that represents that class or struct and mark it with the
1643 \C DontDocument status.
1644
1645 This results in a map of the class and struct nodes in the
1646 module that are in the public API but are not meant to be
1647 used by anyone. They are only used internally, but for one
1648 reason or another, they must have public visibility.
1649 */
1651{
1652 for (auto it = m_dontDocumentMap.begin(); it != m_dontDocumentMap.end(); ++it) {
1653 Aggregate *node = findAggregate(it.key());
1654 if (node != nullptr)
1656 }
1657}
1658
1659QT_END_NAMESPACE
FunctionNode * findFunctionChild(const FunctionNode *clone)
Returns the function node that is a child of this node, such that the function described has the same...
const NodeList & childNodes() const
Returns a const reference to the child list.
Definition aggregate.h:48
The Atom class is the fundamental unit for representing documents internally.
Definition atom.h:19
AtomType type() const
Return the type of this atom.
Definition atom.h:144
@ ListTagLeft
Definition atom.h:67
@ Keyword
Definition atom.h:59
@ Target
Definition atom.h:106
@ SectionRight
Definition atom.h:84
@ SectionHeadingLeft
Definition atom.h:85
@ SinceTagLeft
Definition atom.h:90
@ SectionLeft
Definition atom.h:83
const Atom * next() const
Return the next atom in the atom list.
Definition atom.h:141
const Atom * next(AtomType t) const
Return the next Atom in the list if it is of AtomType t.
Definition atom.cpp:298
The ClassNode represents a C++ class.
Definition classnode.h:23
void removePrivateAndInternalBases()
Remove private and internal bases classes from this class's list of base classes.
A class for holding the members of a collection of doc pages.
void addMember(Node *node) override
Appends node to the collection node's member list and updates the new member's status.
bool hasTableOfContents() const
Definition doc.cpp:287
bool hasKeywords() const
Definition doc.cpp:292
bool hasTargets() const
Definition doc.cpp:297
This node is used to represent any kind of function being documented.
static bool requiresDocumentation(const InclusionPolicy &policy, const NodeContext &context)
This class represents a C++ namespace.
A PageNode is a Node that generates a documentation page.
Definition pagenode.h:19
This class describes one instance of using the Q_PROPERTY macro.
const PropertyNode * overriddenFrom() const
void addFunction(FunctionNode *function, FunctionRole role)
void setOverriddenFrom(const PropertyNode *baseProperty)
Sets this property's {overridden from} property to baseProperty, which indicates that this property o...
void addSignal(FunctionNode *function, FunctionRole role)
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
Status
Specifies the status of the QQmlIncubator.
QmlTypeNode * qmlBaseNode() const override
If this Aggregate is a QmlTypeNode, this function returns a pointer to the QmlTypeNode that is its ba...
Definition qmltypenode.h:54
Definition text.h:12
static Text sectionHeading(const Atom *sectionBegin)
Definition text.cpp:157
This class constructs and maintains a tree of instances of the subclasses of Node.
Definition tree.h:58
void markDontDocumentNodes()
The {don't document} map has been loaded with the names of classes and structs in the current module ...
Definition tree.cpp:1650
void addToDontDocumentMap(QString &arg)
Add the class and struct names in arg to the {don't document} map.
Definition tree.cpp:1623
Node * findNodeByNameAndType(const QStringList &path, bool(Node::*isMatch)() const) const
Find the node with the specified path name that is of the specified type and subtype.
Definition tree.cpp:463
void resolveProperties()
Resolves access functions associated with each PropertyNode stored in m_unresolvedPropertyMap,...
Definition tree.cpp:261
void addPropertyFunction(PropertyNode *property, const QString &funcName, PropertyNode::FunctionRole funcRole)
Inserts function name funcName and function role funcRole into the property function map for the spec...
Definition tree.cpp:183
NodeType
Definition genustypes.h:165
Combined button and popup list for selecting options.
QList< Node * > NodeList
Definition node.h:45
QList< ClassNode * > ClassList
Definition node.h:46
QList< Node * > NodeVector
Definition node.h:47
QMap< QString, CollectionNode * > CNMap
Definition node.h:52
@ QmlAttachedProperties
@ SearchBaseClasses
@ SearchEnumValues
@ IgnoreModules
@ TypesOnly
@ DontDocument
Definition status.h:17
The Node class is the base class for all the nodes in QDoc's parse tree.
const Doc & doc() const
Returns a reference to the node's Doc data member.
Definition node.h:239
virtual bool isWrapper() const
Returns true if the node is a class node or a QML type node that is marked as being a wrapper class o...
Definition node.cpp:989
bool isPrivate() const
Returns true if this node's access is Private.
Definition node.h:113
virtual void setQmlModule(CollectionNode *)
If this is a QmlTypeNode, this function sets the QML type's QML module pointer to the CollectionNode ...
Definition node.h:263
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
Definition node.h:123
bool isSharedCommentNode() const
Returns true if the node type is SharedComment.
Definition node.h:126
virtual bool isInternal() const
Returns true if the node's status is Internal, or if its parent is a class with Internal status.
Definition node.cpp:868
Genus genus() const override
Returns this node's Genus.
Definition node.h:85
virtual bool isPageNode() const
Returns true if this node represents something that generates a documentation page.
Definition node.h:150
bool isEnumType() const
Returns true if the node type is Enum.
Definition node.h:94
virtual Status status() const
Returns the node's status value.
Definition node.h:243
virtual bool isTextPageNode() const
Returns true if the node is a PageNode but not an Aggregate.
Definition node.h:155
virtual bool isAttached() const
Returns true if the QML property or QML method node is marked as attached.
Definition node.h:144
Aggregate * parent() const
Returns the node's parent pointer, that is, the aggregate under which the node is documented.
Definition node.h:211
virtual bool isAggregate() const
Returns true if this node is an aggregate, which means it inherits Aggregate and can therefore have c...
Definition node.h:138
const Location & location() const
If this node's definition location is empty, this function returns this node's declaration location.
Definition node.h:235
Access access() const
Returns the node's Access setting, which can be Public, Protected, or Private.
Definition node.h:232
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
Definition node.h:101
bool isProperty() const
Returns true if the node type is Property.
Definition node.h:114
NodeContext createContext() const
Definition node.cpp:175
bool hasDoc() const
Returns true if this node is documented, or it represents a documented node read from the index ('had...
Definition node.cpp:942
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
Definition node.h:145
virtual void setStatus(Status t)
Sets the node's status to t.
Definition node.cpp:574
bool isQmlProperty() const
Returns true if the node type is QmlProperty.
Definition node.h:122
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
Node * m_node
Definition tree.h:46
Genus genus() const
Definition tree.h:44
TargetType
A type of a linkable target record.
Definition tree.h:29
@ Keyword
Definition tree.h:29
@ Contents
Definition tree.h:29
TargetType m_type
Definition tree.h:48
static const Atom * nextSection(const Atom *a)
Definition tree.cpp:957
QMultiMap< QString, TargetRec * > TargetMap
Definition tree.h:52