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 "classnode.h"
7#include "collectionnode.h"
8#include "doc.h"
9#include "enumnode.h"
10#include "functionnode.h"
11#include "htmlgenerator.h"
12#include "location.h"
13#include "node.h"
14#include "qdocdatabase.h"
15#include "text.h"
16#include "typedefnode.h"
17
18QT_BEGIN_NAMESPACE
19
20/*!
21 \class Tree
22
23 This class constructs and maintains a tree of instances of
24 the subclasses of Node.
25
26 This class is now private. Only class QDocDatabase has access.
27 Please don't change this. If you must access class Tree, do it
28 though the pointer to the singleton QDocDatabase.
29
30 Tree is being converted to a forest. A static member provides a
31 map of Tree *values with the module names as the keys. There is
32 one Tree in the map for each index file read, and there is one
33 tree that is not in the map for the module whose documentation
34 is being generated.
35 */
36
37/*!
38 Constructs a Tree. \a qdb is the pointer to the singleton
39 qdoc database that is constructing the tree. This might not
40 be necessary, and it might be removed later.
41
42 \a camelCaseModuleName is the project name for this tree
43 as it appears in the qdocconf file.
44 */
45Tree::Tree(const QString &camelCaseModuleName, QDocDatabase *qdb)
46 : m_camelCaseModuleName(camelCaseModuleName),
47 m_physicalModuleName(camelCaseModuleName.toLower()),
48 m_qdb(qdb),
49 m_root(nullptr, QString())
50{
51 m_root.setPhysicalModuleName(m_physicalModuleName);
52 m_root.setTree(this);
53}
54
55/*!
56 Destroys the Tree.
57
58 There are two maps of targets, keywords, and contents.
59 One map is indexed by ref, the other by title. Both maps
60 use the same set of TargetRec objects as the values,
61 so we only need to delete the values from one of them.
62
63 The Node instances themselves are destroyed by the root
64 node's (\c m_root) destructor.
65 */
66Tree::~Tree()
67{
68 qDeleteAll(c: m_nodesByTargetRef);
69 m_nodesByTargetRef.clear();
70 m_nodesByTargetTitle.clear();
71}
72
73/* API members */
74
75/*!
76 Calls findClassNode() first with \a path and \a start. If
77 it finds a node, the node is returned. If not, it calls
78 findNamespaceNode() with the same parameters. The result
79 is returned.
80 */
81Node *Tree::findNodeForInclude(const QStringList &path) const
82{
83 Node *n = findClassNode(path);
84 if (n == nullptr)
85 n = findNamespaceNode(path);
86 return n;
87}
88
89/*!
90 This function searches this tree for an Aggregate node with
91 the specified \a name. It returns the pointer to that node
92 or nullptr.
93
94 We might need to split the name on '::' but we assume the
95 name is a single word at the moment.
96 */
97Aggregate *Tree::findAggregate(const QString &name)
98{
99 QStringList path = name.split(sep: QLatin1String("::"));
100 return static_cast<Aggregate *>(findNodeRecursive(path, pathIndex: 0, start: const_cast<NamespaceNode *>(root()),
101 &Node::isFirstClassAggregate));
102}
103
104/*!
105 Find the C++ class node named \a path. Begin the search at the
106 \a start node. If the \a start node is 0, begin the search
107 at the root of the tree. Only a C++ class node named \a path is
108 acceptible. If one is not found, 0 is returned.
109 */
110ClassNode *Tree::findClassNode(const QStringList &path, const Node *start) const
111{
112 if (start == nullptr)
113 start = const_cast<NamespaceNode *>(root());
114 return static_cast<ClassNode *>(findNodeRecursive(path, pathIndex: 0, start, &Node::isClassNode));
115}
116
117/*!
118 Find the Namespace node named \a path. Begin the search at
119 the root of the tree. Only a Namespace node named \a path
120 is acceptible. If one is not found, 0 is returned.
121 */
122NamespaceNode *Tree::findNamespaceNode(const QStringList &path) const
123{
124 Node *start = const_cast<NamespaceNode *>(root());
125 return static_cast<NamespaceNode *>(findNodeRecursive(path, pathIndex: 0, start, &Node::isNamespace));
126}
127
128/*!
129 This function searches for the node specified by \a path.
130 The matching node can be one of several different types
131 including a C++ class, a C++ namespace, or a C++ header
132 file.
133
134 I'm not sure if it can be a QML type, but if that is a
135 possibility, the code can easily accommodate it.
136
137 If a matching node is found, a pointer to it is returned.
138 Otherwise 0 is returned.
139 */
140Aggregate *Tree::findRelatesNode(const QStringList &path)
141{
142 Node *n = findNodeRecursive(path, pathIndex: 0, start: root(), &Node::isRelatableType);
143 return (((n != nullptr) && n->isAggregate()) ? static_cast<Aggregate *>(n) : nullptr);
144}
145
146/*!
147 Inserts function name \a funcName and function role \a funcRole into
148 the property function map for the specified \a property.
149 */
150void Tree::addPropertyFunction(PropertyNode *property, const QString &funcName,
151 PropertyNode::FunctionRole funcRole)
152{
153 m_unresolvedPropertyMap[property].insert(key: funcRole, value: funcName);
154}
155
156/*!
157 This function resolves C++ inheritance and reimplementation
158 settings for each C++ class node found in the tree beginning
159 at \a n. It also calls itself recursively for each C++ class
160 node or namespace node it encounters.
161
162 This function does not resolve QML inheritance.
163 */
164void Tree::resolveBaseClasses(Aggregate *n)
165{
166 for (auto it = n->constBegin(); it != n->constEnd(); ++it) {
167 if ((*it)->isClassNode()) {
168 auto *cn = static_cast<ClassNode *>(*it);
169 QList<RelatedClass> &bases = cn->baseClasses();
170 for (auto &base : bases) {
171 if (base.m_node == nullptr) {
172 Node *n = m_qdb->findClassNode(path: base.m_path);
173 /*
174 If the node for the base class was not found,
175 the reason might be that the subclass is in a
176 namespace and the base class is in the same
177 namespace, but the base class name was not
178 qualified with the namespace name. That is the
179 case most of the time. Then restart the search
180 at the parent of the subclass node (the namespace
181 node) using the unqualified base class name.
182 */
183 if (n == nullptr) {
184 Aggregate *parent = cn->parent();
185 if (parent != nullptr)
186 // Exclude the root namespace
187 if (parent->isNamespace() && !parent->name().isEmpty())
188 n = findClassNode(path: base.m_path, start: parent);
189 }
190 if (n != nullptr) {
191 auto *bcn = static_cast<ClassNode *>(n);
192 base.m_node = bcn;
193 bcn->addDerivedClass(access: base.m_access, node: cn);
194 }
195 }
196 }
197 resolveBaseClasses(n: cn);
198 } else if ((*it)->isNamespace()) {
199 resolveBaseClasses(n: static_cast<NamespaceNode *>(*it));
200 }
201 }
202}
203
204/*!
205 */
206void Tree::resolvePropertyOverriddenFromPtrs(Aggregate *n)
207{
208 for (auto node = n->constBegin(); node != n->constEnd(); ++node) {
209 if ((*node)->isClassNode()) {
210 auto *cn = static_cast<ClassNode *>(*node);
211 for (auto property = cn->constBegin(); property != cn->constEnd(); ++property) {
212 if ((*property)->isProperty())
213 cn->resolvePropertyOverriddenFromPtrs(pn: static_cast<PropertyNode *>(*property));
214 }
215 resolvePropertyOverriddenFromPtrs(n: cn);
216 } else if ((*node)->isNamespace()) {
217 resolvePropertyOverriddenFromPtrs(n: static_cast<NamespaceNode *>(*node));
218 }
219 }
220}
221
222/*!
223 Resolves access functions associated with each PropertyNode stored
224 in \c m_unresolvedPropertyMap, and adds them into the property node.
225 This allows the property node to list the access functions when
226 generating their documentation.
227 */
228void Tree::resolveProperties()
229{
230 for (auto propEntry = m_unresolvedPropertyMap.constBegin();
231 propEntry != m_unresolvedPropertyMap.constEnd(); ++propEntry) {
232 PropertyNode *property = propEntry.key();
233 Aggregate *parent = property->parent();
234 QString getterName = (*propEntry)[PropertyNode::FunctionRole::Getter];
235 QString setterName = (*propEntry)[PropertyNode::FunctionRole::Setter];
236 QString resetterName = (*propEntry)[PropertyNode::FunctionRole::Resetter];
237 QString notifierName = (*propEntry)[PropertyNode::FunctionRole::Notifier];
238 QString bindableName = (*propEntry)[PropertyNode::FunctionRole::Bindable];
239
240 for (auto it = parent->constBegin(); it != parent->constEnd(); ++it) {
241 if ((*it)->isFunction()) {
242 auto *function = static_cast<FunctionNode *>(*it);
243 if (function->access() == property->access()
244 && (function->status() == property->status() || function->doc().isEmpty())) {
245 if (function->name() == getterName) {
246 property->addFunction(function, role: PropertyNode::FunctionRole::Getter);
247 } else if (function->name() == setterName) {
248 property->addFunction(function, role: PropertyNode::FunctionRole::Setter);
249 } else if (function->name() == resetterName) {
250 property->addFunction(function, role: PropertyNode::FunctionRole::Resetter);
251 } else if (function->name() == notifierName) {
252 property->addSignal(function, role: PropertyNode::FunctionRole::Notifier);
253 } else if (function->name() == bindableName) {
254 property->addFunction(function, role: PropertyNode::FunctionRole::Bindable);
255 }
256 }
257 }
258 }
259 }
260
261 for (auto propEntry = m_unresolvedPropertyMap.constBegin();
262 propEntry != m_unresolvedPropertyMap.constEnd(); ++propEntry) {
263 PropertyNode *property = propEntry.key();
264 // redo it to set the property functions
265 if (property->overriddenFrom())
266 property->setOverriddenFrom(property->overriddenFrom());
267 }
268
269 m_unresolvedPropertyMap.clear();
270}
271
272/*!
273 For each QML class node that points to a C++ class node,
274 follow its C++ class node pointer and set the C++ class
275 node's QML class node pointer back to the QML class node.
276 */
277void Tree::resolveCppToQmlLinks()
278{
279
280 const NodeList &children = m_root.childNodes();
281 for (auto *child : children) {
282 if (child->isQmlType()) {
283 auto *qcn = static_cast<QmlTypeNode *>(child);
284 auto *cn = const_cast<ClassNode *>(qcn->classNode());
285 if (cn)
286 cn->setQmlElement(qcn);
287 }
288 }
289}
290
291/*!
292 For each \a aggregate, recursively set the \\since version based on
293 \\since information from the associated physical or logical module.
294 That is, C++ and QML types inherit the \\since of their module,
295 unless that command is explicitly used in the type documentation.
296
297 In addition, resolve the since information for individual enum
298 values.
299*/
300void Tree::resolveSince(Aggregate &aggregate)
301{
302 for (auto *child : aggregate.childNodes()) {
303 // Order matters; resolve since-clauses in enum values
304 // first as EnumNode is not an Aggregate
305 if (child->isEnumType())
306 resolveEnumValueSince(en&: static_cast<EnumNode&>(*child));
307 if (!child->isAggregate())
308 continue;
309 if (!child->since().isEmpty())
310 continue;
311
312 if (const auto collectionNode = m_qdb->getModuleNode(relative: child))
313 child->setSince(collectionNode->since());
314
315 resolveSince(aggregate&: static_cast<Aggregate&>(*child));
316 }
317}
318
319/*!
320 Resolve since information for values of enum node \a en.
321
322 Enum values are not derived from Node, but they can have
323 'since' information associated with them. Since-strings
324 for each enum item are initially stored in the Doc
325 instance of EnumNode as SinceTag atoms; parse the doc
326 and store them into each EnumItem.
327*/
328void Tree::resolveEnumValueSince(EnumNode &en)
329{
330 auto findNextAtom = [](const Atom *a, Atom::AtomType t) {
331 while (a && a->type() != t)
332 a = a->next();
333 return a;
334 };
335
336 const QStringList enumItems{en.doc().enumItemNames()};
337 const Atom *atom = en.doc().body().firstAtom();
338 while ((atom = findNextAtom(atom, Atom::ListTagLeft))) {
339 if (atom = atom->next(); !atom)
340 break;
341 if (auto val = atom->string(); enumItems.contains(str: val)) {
342 if (atom = atom->next(); atom && atom->next(t: Atom::SinceTagLeft))
343 en.setSince(value: val, since: atom->next()->next()->string());
344 }
345 }
346}
347
348/*!
349 Traverse this Tree and for each ClassNode found, remove
350 from its list of base classes any that are marked private
351 or internal. When a class is removed from a base class
352 list, promote its public pase classes to be base classes
353 of the class where the base class was removed. This is
354 done for documentation purposes. The function is recursive
355 on namespace nodes.
356 */
357void Tree::removePrivateAndInternalBases(NamespaceNode *rootNode)
358{
359 if (rootNode == nullptr)
360 rootNode = root();
361
362 for (auto node = rootNode->constBegin(); node != rootNode->constEnd(); ++node) {
363 if ((*node)->isClassNode())
364 static_cast<ClassNode *>(*node)->removePrivateAndInternalBases();
365 else if ((*node)->isNamespace())
366 removePrivateAndInternalBases(rootNode: static_cast<NamespaceNode *>(*node));
367 }
368}
369
370/*!
371 */
372ClassList Tree::allBaseClasses(const ClassNode *classNode) const
373{
374 ClassList result;
375 const auto &baseClasses = classNode->baseClasses();
376 for (const auto &relatedClass : baseClasses) {
377 if (relatedClass.m_node != nullptr) {
378 result += relatedClass.m_node;
379 result += allBaseClasses(classNode: relatedClass.m_node);
380 }
381 }
382 return result;
383}
384
385/*!
386 Find the node with the specified \a path name that is of
387 the specified \a type and \a subtype. Begin the search at
388 the \a start node. If the \a start node is 0, begin the
389 search at the tree root. \a subtype is not used unless
390 \a type is \c{Page}.
391 */
392Node *Tree::findNodeByNameAndType(const QStringList &path, bool (Node::*isMatch)() const) const
393{
394 return findNodeRecursive(path, pathIndex: 0, start: root(), isMatch);
395}
396
397/*!
398 Recursive search for a node identified by \a path. Each
399 path element is a name. \a pathIndex specifies the index
400 of the name in \a path to try to match. \a start is the
401 node whose children shoulod be searched for one that has
402 that name. Each time a match is found, increment the
403 \a pathIndex and call this function recursively.
404
405 If the end of the path is reached (i.e. if a matching
406 node is found for each name in the \a path), the \a type
407 must match the type of the last matching node, and if the
408 type is \e{Page}, the \a subtype must match as well.
409
410 If the algorithm is successful, the pointer to the final
411 node is returned. Otherwise 0 is returned.
412 */
413Node *Tree::findNodeRecursive(const QStringList &path, int pathIndex, const Node *start,
414 bool (Node::*isMatch)() const) const
415{
416 if (start == nullptr || path.isEmpty())
417 return nullptr;
418 Node *node = const_cast<Node *>(start);
419 if (!node->isAggregate())
420 return ((pathIndex >= path.size()) ? node : nullptr);
421 auto *current = static_cast<Aggregate *>(node);
422 const NodeList &children = current->childNodes();
423 const QString &name = path.at(i: pathIndex);
424 for (auto *node : children) {
425 if (node == nullptr)
426 continue;
427 if (node->name() == name) {
428 if (pathIndex + 1 >= path.size()) {
429 if ((node->*(isMatch))())
430 return node;
431 continue;
432 } else { // Search the children of n for the next name in the path.
433 node = findNodeRecursive(path, pathIndex: pathIndex + 1, start: node, isMatch);
434 if (node != nullptr)
435 return node;
436 }
437 }
438 }
439 return nullptr;
440}
441
442/*!
443 Searches the tree for a node that matches the \a path plus
444 the \a target. The search begins at \a start and moves up
445 the parent chain from there, or, if \a start is 0, the search
446 begins at the root.
447
448 The \a flags can indicate whether to search base classes and/or
449 the enum values in enum types. \a genus can be a further restriction
450 on what kind of node is an acceptible match, i.e. CPP or QML.
451
452 If a matching node is found, \a ref is an output parameter that
453 is set to the HTML reference to use for the link.
454 */
455const Node *Tree::findNodeForTarget(const QStringList &path, const QString &target,
456 const Node *start, int flags, Node::Genus genus,
457 QString &ref, TargetRec::TargetType *targetType) const
458{
459 const Node *node = nullptr;
460
461 if ((genus == Node::DontCare) || (genus == Node::DOC)) {
462 node = findPageNodeByTitle(title: path.at(i: 0));
463 if (node) {
464 if (!target.isEmpty()) {
465 if (ref = getRef(target, node); ref.isEmpty())
466 node = nullptr;
467 }
468 if (node)
469 return node;
470 }
471 }
472
473 const TargetRec *result = findUnambiguousTarget(target: path.join(sep: QLatin1String("::")), genus);
474 if (result) {
475 node = result->m_node;
476 ref = result->m_ref;
477 if (!target.isEmpty()) {
478 if (ref = getRef(target, node); ref.isEmpty())
479 node = nullptr;
480 }
481 if (node) {
482 if (targetType)
483 *targetType = result->m_type;
484 // Delay returning references to section titles as we
485 // may find a better match below
486 if (!targetType || *targetType != TargetRec::Contents)
487 return node;
488 else
489 ref.clear();
490 }
491 }
492
493 const Node *current = start ? start : root();
494 /*
495 If the path contains one or two double colons ("::"),
496 check if the first two path elements refer to a QML type.
497 If so, path[0] is QML module identifier, and path[1] is
498 the type.
499 */
500 int path_idx = 0;
501 if (((genus == Node::QML) || (genus == Node::DontCare)) && (path.size() >= 2)
502 && !path[0].isEmpty()) {
503 QmlTypeNode *qcn = lookupQmlType(name: QString(path[0] + "::" + path[1]));
504 if (qcn) {
505 current = qcn;
506 if (path.size() == 2) {
507 if (!target.isEmpty()) {
508 ref = getRef(target, node: current);
509 return (!ref.isEmpty()) ? current : nullptr;
510 }
511 return current;
512 }
513 path_idx = 2;
514 }
515 }
516
517 while (current) {
518 if (current->isAggregate()) {
519 if (const Node *match = matchPathAndTarget(
520 path, idx: path_idx, target, node: current, flags, genus, ref);
521 match != nullptr)
522 return match;
523 }
524 current = current->parent();
525 path_idx = 0;
526 }
527
528 if (node && result)
529 ref = result->m_ref; // Restore section title's ref
530 return node;
531}
532
533/*!
534 First, the \a path is used to find a node. The \a path
535 matches some part of the node's fully quallified name.
536 If the \a target is not empty, it must match a target
537 in the matching node. If the matching of the \a path
538 and the \a target (if present) is successful, \a ref
539 is set from the \a target, and the pointer to the
540 matching node is returned. \a idx is the index into the
541 \a path where to begin the matching. The function is
542 recursive with idx being incremented for each recursive
543 call.
544
545 The matching node must be of the correct \a genus, i.e.
546 either QML or C++, but \a genus can be set to \c DontCare.
547 \a flags indicates whether to search base classes and
548 whether to search for an enum value. \a node points to
549 the node where the search should begin, assuming the
550 \a path is a not a fully-qualified name. \a node is
551 most often the root of this Tree.
552 */
553const Node *Tree::matchPathAndTarget(const QStringList &path, int idx, const QString &target,
554 const Node *node, int flags, Node::Genus genus,
555 QString &ref) const
556{
557 /*
558 If the path has been matched, then if there is a target,
559 try to match the target. If there is a target, but you
560 can't match it at the end of the path, give up; return 0.
561 */
562 if (idx == path.size()) {
563 if (!target.isEmpty()) {
564 ref = getRef(target, node);
565 if (ref.isEmpty())
566 return nullptr;
567 }
568 if (node->isFunction() && node->name() == node->parent()->name())
569 node = node->parent();
570 return node;
571 }
572
573 QString name = path.at(i: idx);
574 if (node->isAggregate()) {
575 NodeVector nodes;
576 static_cast<const Aggregate *>(node)->findChildren(name, nodes);
577 for (const auto *child : std::as_const(t&: nodes)) {
578 if (genus != Node::DontCare && !(genus & child->genus()))
579 continue;
580 const Node *t = matchPathAndTarget(path, idx: idx + 1, target, node: child, flags, genus, ref);
581 if (t && !t->isPrivate())
582 return t;
583 }
584 }
585 if (target.isEmpty() && (flags & SearchEnumValues)) {
586 const auto *enumNode = node->isAggregate() ?
587 findEnumNode(node: nullptr, aggregate: node, path, offset: idx) :
588 findEnumNode(node, aggregate: nullptr, path, offset: idx);
589 if (enumNode)
590 return enumNode;
591 }
592 if (((genus == Node::CPP) || (genus == Node::DontCare)) && node->isClassNode()
593 && (flags & SearchBaseClasses)) {
594 const ClassList bases = allBaseClasses(classNode: static_cast<const ClassNode *>(node));
595 for (const auto *base : bases) {
596 const Node *t = matchPathAndTarget(path, idx, target, node: base, flags, genus, ref);
597 if (t && !t->isPrivate())
598 return t;
599 if (target.isEmpty() && (flags & SearchEnumValues)) {
600 if ((t = findEnumNode(node: base->findChildNode(name: path.at(i: idx), genus, findFlags: flags), aggregate: base, path, offset: idx)))
601 return t;
602 }
603 }
604 }
605 return nullptr;
606}
607
608/*!
609 Searches the tree for a node that matches the \a path. The
610 search begins at \a start but can move up the parent chain
611 recursively if no match is found. The \a flags are used to
612 restrict the search.
613 */
614const Node *Tree::findNode(const QStringList &path, const Node *start, int flags,
615 Node::Genus genus) const
616{
617 const Node *current = start;
618 if (current == nullptr)
619 current = root();
620
621 do {
622 const Node *node = current;
623 int i;
624 int start_idx = 0;
625
626 /*
627 If the path contains one or two double colons ("::"),
628 check first to see if the first two path strings refer
629 to a QML element. If they do, path[0] will be the QML
630 module identifier, and path[1] will be the QML type.
631 If the answer is yes, the reference identifies a QML
632 type node.
633 */
634 if (((genus == Node::QML) || (genus == Node::DontCare)) && (path.size() >= 2)
635 && !path[0].isEmpty()) {
636 QmlTypeNode *qcn = lookupQmlType(name: QString(path[0] + "::" + path[1]));
637 if (qcn != nullptr) {
638 node = qcn;
639 if (path.size() == 2)
640 return node;
641 start_idx = 2;
642 }
643 }
644
645 for (i = start_idx; i < path.size(); ++i) {
646 if (node == nullptr || !node->isAggregate())
647 break;
648
649 // Clear the TypesOnly flag until the last path segment, as e.g. namespaces are not
650 // types. We also ignore module nodes as they are not aggregates and thus have no
651 // children.
652 int tmpFlags = (i < path.size() - 1) ? (flags & ~TypesOnly) | IgnoreModules : flags;
653
654 const Node *next = static_cast<const Aggregate *>(node)->findChildNode(name: path.at(i),
655 genus, findFlags: tmpFlags);
656 const Node *enumNode = (flags & SearchEnumValues) ?
657 findEnumNode(node: next, aggregate: node, path, offset: i) : nullptr;
658
659 if (enumNode)
660 return enumNode;
661
662
663 if (!next && ((genus == Node::CPP) || (genus == Node::DontCare))
664 && node->isClassNode() && (flags & SearchBaseClasses)) {
665 const ClassList bases = allBaseClasses(classNode: static_cast<const ClassNode *>(node));
666 for (const auto *base : bases) {
667 next = base->findChildNode(name: path.at(i), genus, findFlags: tmpFlags);
668 if (flags & SearchEnumValues)
669 if ((enumNode = findEnumNode(node: next, aggregate: base, path, offset: i)))
670 return enumNode;
671 if (next)
672 break;
673 }
674 }
675 node = next;
676 }
677 if ((node != nullptr) && i == path.size())
678 return node;
679 current = current->parent();
680 } while (current != nullptr);
681
682 return nullptr;
683}
684
685
686/*!
687 \internal
688
689 Helper function to return an enum that matches the \a path at a specified \a offset.
690 If \a node is a valid enum node, the enum name is assumed to be included in the path
691 (i.e, a scoped enum). Otherwise, query the \a aggregate (typically, the class node)
692 for enum node that includes the value at the last position in \a path.
693 */
694const Node *Tree::findEnumNode(const Node *node, const Node *aggregate, const QStringList &path, int offset) const
695{
696 // Scoped enum (path ends in enum_name :: enum_value)
697 if (node && node->isEnumType() && offset == path.size() - 1) {
698 const auto *en = static_cast<const EnumNode*>(node);
699 if (en->isScoped() && en->hasItem(name: path.last()))
700 return en;
701 }
702
703 // Standard enum (path ends in class_name :: enum_value)
704 return (!node && aggregate && offset == path.size() - 1) ?
705 static_cast<const Aggregate *>(aggregate)->findEnumNodeForValue(enumValue: path.last()) :
706 nullptr;
707}
708
709/*!
710 This function searches for a node with a canonical title
711 constructed from \a target. If the node it finds is \a node,
712 it returns the ref from that node. Otherwise it returns an
713 empty string.
714 */
715QString Tree::getRef(const QString &target, const Node *node) const
716{
717 auto it = m_nodesByTargetTitle.constFind(key: target);
718 if (it != m_nodesByTargetTitle.constEnd()) {
719 do {
720 if (it.value()->m_node == node)
721 return it.value()->m_ref;
722 ++it;
723 } while (it != m_nodesByTargetTitle.constEnd() && it.key() == target);
724 }
725 QString key = Utilities::asAsciiPrintable(name: target);
726 it = m_nodesByTargetRef.constFind(key);
727 if (it != m_nodesByTargetRef.constEnd()) {
728 do {
729 if (it.value()->m_node == node)
730 return it.value()->m_ref;
731 ++it;
732 } while (it != m_nodesByTargetRef.constEnd() && it.key() == key);
733 }
734 return QString();
735}
736
737/*!
738 Inserts a new target into the target table. \a name is the
739 key. The target record contains the \a type, a pointer to
740 the \a node, the \a priority. and a canonicalized form of
741 the \a name, which is later used.
742 */
743void Tree::insertTarget(const QString &name, const QString &title, TargetRec::TargetType type,
744 Node *node, int priority)
745{
746 auto *target = new TargetRec(name, type, node, priority);
747 m_nodesByTargetRef.insert(key: name, value: target);
748 m_nodesByTargetTitle.insert(key: title, value: target);
749}
750
751/*!
752 */
753void Tree::resolveTargets(Aggregate *root)
754{
755 for (auto *child : root->childNodes()) {
756 if (child->isTextPageNode()) {
757 auto *node = static_cast<PageNode *>(child);
758 QString key = node->title();
759 if (!key.isEmpty()) {
760 if (key.contains(c: QChar(' ')))
761 key = Utilities::asAsciiPrintable(name: key);
762 QList<PageNode *> nodes = m_pageNodesByTitle.values(key);
763 bool alreadyThere = false;
764 if (!nodes.empty()) {
765 for (const auto &node_ : nodes) {
766 if (node_->isExternalPage()) {
767 if (node->name() == node_->name()) {
768 alreadyThere = true;
769 break;
770 }
771 }
772 }
773 }
774 if (!alreadyThere)
775 m_pageNodesByTitle.insert(key, value: node);
776 }
777 }
778
779 if (child->doc().hasTableOfContents()) {
780 const QList<Atom *> &toc = child->doc().tableOfContents();
781 for (Atom *i : toc) {
782 QString ref = refForAtom(atom: i);
783 QString title = Text::sectionHeading(sectionBegin: i).toString();
784 if (!ref.isEmpty() && !title.isEmpty()) {
785 QString key = Utilities::asAsciiPrintable(name: title);
786 auto *target = new TargetRec(ref, TargetRec::Contents, child, 3);
787 m_nodesByTargetRef.insert(key, value: target);
788 m_nodesByTargetTitle.insert(key: title, value: target);
789 }
790 }
791 }
792 if (child->doc().hasKeywords()) {
793 const QList<Atom *> &keywords = child->doc().keywords();
794 for (Atom *i : keywords) {
795 QString ref = refForAtom(atom: i);
796 QString title = i->string();
797 if (!ref.isEmpty() && !title.isEmpty()) {
798 auto *target = new TargetRec(ref, TargetRec::Keyword, child, 1);
799 m_nodesByTargetRef.insert(key: Utilities::asAsciiPrintable(name: title), value: target);
800 m_nodesByTargetTitle.insert(key: title, value: target);
801 }
802 }
803 }
804 if (child->doc().hasTargets()) {
805 const QList<Atom *> &targets = child->doc().targets();
806 for (Atom *i : targets) {
807 QString ref = refForAtom(atom: i);
808 QString title = i->string();
809 if (!ref.isEmpty() && !title.isEmpty()) {
810 QString key = Utilities::asAsciiPrintable(name: title);
811 auto *target = new TargetRec(ref, TargetRec::Target, child, 2);
812 m_nodesByTargetRef.insert(key, value: target);
813 m_nodesByTargetTitle.insert(key: title, value: target);
814 }
815 }
816 }
817 if (child->isAggregate())
818 resolveTargets(root: static_cast<Aggregate *>(child));
819 }
820}
821
822/*!
823 Searches for a \a target anchor, matching the given \a genus, and returns
824 the associated TargetRec instance.
825 */
826const TargetRec *Tree::findUnambiguousTarget(const QString &target, Node::Genus genus) const
827{
828 auto findBestCandidate = [&](const TargetMap &tgtMap, const QString &key) {
829 TargetRec *best = nullptr;
830 auto [it, end] = tgtMap.equal_range(akey: key);
831 while (it != end) {
832 TargetRec *candidate = it.value();
833 if ((genus == Node::DontCare) || (genus & candidate->genus())) {
834 if (!best || (candidate->m_priority < best->m_priority))
835 best = candidate;
836 }
837 ++it;
838 }
839 return best;
840 };
841
842 TargetRec *bestTarget = findBestCandidate(m_nodesByTargetTitle, target);
843 if (!bestTarget)
844 bestTarget = findBestCandidate(m_nodesByTargetRef, Utilities::asAsciiPrintable(name: target));
845
846 return bestTarget;
847}
848
849/*!
850 This function searches for a node with the specified \a title.
851 */
852const PageNode *Tree::findPageNodeByTitle(const QString &title) const
853{
854 PageNodeMultiMap::const_iterator it;
855 if (title.contains(c: QChar(' ')))
856 it = m_pageNodesByTitle.constFind(key: Utilities::asAsciiPrintable(name: title));
857 else
858 it = m_pageNodesByTitle.constFind(key: title);
859 if (it != m_pageNodesByTitle.constEnd()) {
860 /*
861 Reporting all these duplicate section titles is probably
862 overkill. We should report the duplicate file and let
863 that suffice.
864 */
865 PageNodeMultiMap::const_iterator j = it;
866 ++j;
867 if (j != m_pageNodesByTitle.constEnd() && j.key() == it.key()) {
868 while (j != m_pageNodesByTitle.constEnd()) {
869 if (j.key() == it.key() && j.value()->url().isEmpty()) {
870 break; // Just report one duplicate for now.
871 }
872 ++j;
873 }
874 if (j != m_pageNodesByTitle.cend()) {
875 it.value()->location().warning(message: "This page title exists in more than one file: "
876 + title);
877 j.value()->location().warning(message: "[It also exists here]");
878 }
879 }
880 return it.value();
881 }
882 return nullptr;
883}
884
885/*!
886 Returns a canonical title for the \a atom, if the \a atom
887 is a SectionLeft or a Target.
888 */
889QString Tree::refForAtom(const Atom *atom)
890{
891 if (atom) {
892 if (atom->type() == Atom::SectionLeft)
893 return Utilities::asAsciiPrintable(name: Text::sectionHeading(sectionBegin: atom).toString());
894 if ((atom->type() == Atom::Target) || (atom->type() == Atom::Keyword))
895 return Utilities::asAsciiPrintable(name: atom->string());
896 }
897 return QString();
898}
899
900/*!
901 \fn const CNMap &Tree::groups() const
902 Returns a const reference to the collection of all
903 group nodes.
904*/
905
906/*!
907 \fn const ModuleMap &Tree::modules() const
908 Returns a const reference to the collection of all
909 module nodes.
910*/
911
912/*!
913 \fn const QmlModuleMap &Tree::qmlModules() const
914 Returns a const reference to the collection of all
915 QML module nodes.
916*/
917
918/*!
919 Returns a pointer to the collection map specified by \a type.
920 Returns null if \a type is not specified.
921 */
922CNMap *Tree::getCollectionMap(Node::NodeType type)
923{
924 switch (type) {
925 case Node::Group:
926 return &m_groups;
927 case Node::Module:
928 return &m_modules;
929 case Node::QmlModule:
930 return &m_qmlModules;
931 default:
932 break;
933 }
934 return nullptr;
935}
936
937/*!
938 Searches this tree for a collection named \a name with the
939 specified \a type. If the collection is found, a pointer
940 to it is returned. If a collection is not found, null is
941 returned.
942 */
943CollectionNode *Tree::getCollection(const QString &name, Node::NodeType type)
944{
945 CNMap *map = getCollectionMap(type);
946 if (map) {
947 auto it = map->constFind(key: name);
948 if (it != map->cend())
949 return it.value();
950 }
951 return nullptr;
952}
953
954/*!
955 Find the group, module, or QML module named \a name and return a
956 pointer to that collection node. \a type specifies which kind of
957 collection node you want. If a collection node with the specified \a
958 name and \a type is not found, a new one is created, and the pointer
959 to the new one is returned.
960
961 If a new collection node is created, its parent is the tree
962 root, and the new collection node is marked \e{not seen}.
963
964 \a genus must be specified, i.e. it must not be \c{DontCare}.
965 If it is \c{DontCare}, 0 is returned, which is a programming
966 error.
967 */
968CollectionNode *Tree::findCollection(const QString &name, Node::NodeType type)
969{
970 CNMap *m = getCollectionMap(type);
971 if (!m) // error
972 return nullptr;
973 auto it = m->constFind(key: name);
974 if (it != m->cend())
975 return it.value();
976 CollectionNode *cn = new CollectionNode(type, root(), name);
977 cn->markNotSeen();
978 m->insert(key: name, value: cn);
979 return cn;
980}
981
982/*! \fn CollectionNode *Tree::findGroup(const QString &name)
983 Find the group node named \a name and return a pointer
984 to it. If the group node is not found, add a new group
985 node named \a name and return a pointer to the new one.
986
987 If a new group node is added, its parent is the tree root,
988 and the new group node is marked \e{not seen}.
989 */
990
991/*! \fn CollectionNode *Tree::findModule(const QString &name)
992 Find the module node named \a name and return a pointer
993 to it. If a matching node is not found, add a new module
994 node named \a name and return a pointer to that one.
995
996 If a new module node is added, its parent is the tree root,
997 and the new module node is marked \e{not seen}.
998 */
999
1000/*! \fn CollectionNode *Tree::findQmlModule(const QString &name)
1001 Find the QML module node named \a name and return a pointer
1002 to it. If a matching node is not found, add a new QML module
1003 node named \a name and return a pointer to that one.
1004
1005 If a new QML module node is added, its parent is the tree root,
1006 and the new node is marked \e{not seen}.
1007 */
1008
1009/*! \fn CollectionNode *Tree::addGroup(const QString &name)
1010 Looks up the group node named \a name in the collection
1011 of all group nodes. If a match is found, a pointer to the
1012 node is returned. Otherwise, a new group node named \a name
1013 is created and inserted into the collection, and the pointer
1014 to that node is returned.
1015 */
1016
1017/*! \fn CollectionNode *Tree::addModule(const QString &name)
1018 Looks up the module node named \a name in the collection
1019 of all module nodes. If a match is found, a pointer to the
1020 node is returned. Otherwise, a new module node named \a name
1021 is created and inserted into the collection, and the pointer
1022 to that node is returned.
1023 */
1024
1025/*! \fn CollectionNode *Tree::addQmlModule(const QString &name)
1026 Looks up the QML module node named \a name in the collection
1027 of all QML module nodes. If a match is found, a pointer to the
1028 node is returned. Otherwise, a new QML module node named \a name
1029 is created and inserted into the collection, and the pointer
1030 to that node is returned.
1031 */
1032
1033/*!
1034 Looks up the group node named \a name in the collection
1035 of all group nodes. If a match is not found, a new group
1036 node named \a name is created and inserted into the collection.
1037 Then append \a node to the group's members list, and append the
1038 group name to the list of group names in \a node. The parent of
1039 \a node is not changed by this function. Returns a pointer to
1040 the group node.
1041 */
1042CollectionNode *Tree::addToGroup(const QString &name, Node *node)
1043{
1044 CollectionNode *cn = findGroup(name);
1045 if (!node->isInternal()) {
1046 cn->addMember(node);
1047 node->appendGroupName(name);
1048 }
1049 return cn;
1050}
1051
1052/*!
1053 Looks up the module node named \a name in the collection
1054 of all module nodes. If a match is not found, a new module
1055 node named \a name is created and inserted into the collection.
1056 Then append \a node to the module's members list. The parent of
1057 \a node is not changed by this function. Returns the module node.
1058 */
1059CollectionNode *Tree::addToModule(const QString &name, Node *node)
1060{
1061 CollectionNode *cn = findModule(name);
1062 cn->addMember(node);
1063 node->setPhysicalModuleName(name);
1064 return cn;
1065}
1066
1067/*!
1068 Looks up the QML module named \a name. If it isn't there,
1069 create it. Then append \a node to the QML module's member
1070 list. The parent of \a node is not changed by this function.
1071 Returns the pointer to the QML module node.
1072 */
1073CollectionNode *Tree::addToQmlModule(const QString &name, Node *node)
1074{
1075 QStringList qmid;
1076 QStringList dotSplit;
1077 QStringList blankSplit = name.split(sep: QLatin1Char(' '));
1078 qmid.append(t: blankSplit[0]);
1079 if (blankSplit.size() > 1) {
1080 qmid.append(t: blankSplit[0] + blankSplit[1]);
1081 dotSplit = blankSplit[1].split(sep: QLatin1Char('.'));
1082 qmid.append(t: blankSplit[0] + dotSplit[0]);
1083 }
1084
1085 CollectionNode *cn = findQmlModule(name: blankSplit[0]);
1086 cn->addMember(node);
1087 node->setQmlModule(cn);
1088 if (node->isQmlType()) {
1089 QmlTypeNode *n = static_cast<QmlTypeNode *>(node);
1090 for (int i = 0; i < qmid.size(); ++i) {
1091 QString key = qmid[i] + "::" + node->name();
1092 insertQmlType(key, n);
1093 }
1094 }
1095 return cn;
1096}
1097
1098/*!
1099 If the QML type map does not contain \a key, insert node
1100 \a n with the specified \a key.
1101 */
1102void Tree::insertQmlType(const QString &key, QmlTypeNode *n)
1103{
1104 if (!m_qmlTypeMap.contains(key))
1105 m_qmlTypeMap.insert(key, value: n);
1106}
1107
1108/*!
1109 Finds the function node with the specifried name \a path that
1110 also has the specified \a parameters and returns a pointer to
1111 the first matching function node if one is found.
1112
1113 This function begins searching the tree at \a relative for
1114 the \l {FunctionNode} {function node} identified by \a path
1115 that has the specified \a parameters. The \a flags are
1116 used to restrict the search. If a matching node is found, a
1117 pointer to it is returned. Otherwise, nullis returned. If
1118 \a relative is ull, the search begins at the tree root.
1119 */
1120const FunctionNode *Tree::findFunctionNode(const QStringList &path, const Parameters &parameters,
1121 const Node *relative, Node::Genus genus) const
1122{
1123 if (path.size() == 3 && !path[0].isEmpty()
1124 && ((genus == Node::QML) || (genus == Node::DontCare))) {
1125 QmlTypeNode *qcn = lookupQmlType(name: QString(path[0] + "::" + path[1]));
1126 if (qcn == nullptr) {
1127 QStringList p(path[1]);
1128 Node *n = findNodeByNameAndType(path: p, isMatch: &Node::isQmlType);
1129 if ((n != nullptr) && n->isQmlType())
1130 qcn = static_cast<QmlTypeNode *>(n);
1131 }
1132 if (qcn != nullptr)
1133 return static_cast<const FunctionNode *>(qcn->findFunctionChild(name: path[2], parameters));
1134 }
1135
1136 if (relative == nullptr)
1137 relative = root();
1138 else if (genus != Node::DontCare) {
1139 if (!(genus & relative->genus()))
1140 relative = root();
1141 }
1142
1143 do {
1144 Node *node = const_cast<Node *>(relative);
1145 int i;
1146
1147 for (i = 0; i < path.size(); ++i) {
1148 if (node == nullptr || !node->isAggregate())
1149 break;
1150
1151 Aggregate *aggregate = static_cast<Aggregate *>(node);
1152 Node *next = nullptr;
1153 if (i == path.size() - 1)
1154 next = aggregate->findFunctionChild(name: path.at(i), parameters);
1155 else
1156 next = aggregate->findChildNode(name: path.at(i), genus);
1157
1158 if ((next == nullptr) && aggregate->isClassNode()) {
1159 const ClassList bases = allBaseClasses(classNode: static_cast<const ClassNode *>(aggregate));
1160 for (auto *base : bases) {
1161 if (i == path.size() - 1)
1162 next = base->findFunctionChild(name: path.at(i), parameters);
1163 else
1164 next = base->findChildNode(name: path.at(i), genus);
1165
1166 if (next != nullptr)
1167 break;
1168 }
1169 }
1170
1171 node = next;
1172 } // for (i = 0; i < path.size(); ++i)
1173
1174 if (node && i == path.size() && node->isFunction()) {
1175 // A function node was found at the end of the path.
1176 // If it is not marked private, return it. If it is
1177 // marked private, then if it overrides a function,
1178 // find that function instead because it might not
1179 // be marked private. If all the overloads are
1180 // marked private, return the original function node.
1181 // This should be replace with findOverriddenFunctionNode().
1182 const FunctionNode *fn = static_cast<const FunctionNode *>(node);
1183 const FunctionNode *FN = fn;
1184 while (FN->isPrivate() && !FN->overridesThis().isEmpty()) {
1185 QStringList path = FN->overridesThis().split(sep: "::");
1186 FN = m_qdb->findFunctionNode(path, parameters, relative, genus);
1187 if (FN == nullptr)
1188 break;
1189 if (!FN->isPrivate())
1190 return FN;
1191 }
1192 return fn;
1193 }
1194 relative = relative->parent();
1195 } while (relative);
1196 return nullptr;
1197}
1198
1199/*!
1200 Search this tree recursively from \a parent to find a function
1201 node with the specified \a tag. If no function node is found
1202 with the required \a tag, return 0.
1203 */
1204FunctionNode *Tree::findFunctionNodeForTag(const QString &tag, Aggregate *parent)
1205{
1206 if (parent == nullptr)
1207 parent = root();
1208 const NodeList &children = parent->childNodes();
1209 for (Node *n : children) {
1210 if (n != nullptr && n->isFunction() && n->hasTag(tag))
1211 return static_cast<FunctionNode *>(n);
1212 }
1213 for (Node *n : children) {
1214 if (n != nullptr && n->isAggregate()) {
1215 n = findFunctionNodeForTag(tag, parent: static_cast<Aggregate *>(n));
1216 if (n != nullptr)
1217 return static_cast<FunctionNode *>(n);
1218 }
1219 }
1220 return nullptr;
1221}
1222
1223/*!
1224 There should only be one macro node for macro name \a t.
1225 The macro node is not built until the \macro command is seen.
1226 */
1227FunctionNode *Tree::findMacroNode(const QString &t, const Aggregate *parent)
1228{
1229 if (parent == nullptr)
1230 parent = root();
1231 const NodeList &children = parent->childNodes();
1232 for (Node *n : children) {
1233 if (n != nullptr && (n->isMacro() || n->isFunction()) && n->name() == t)
1234 return static_cast<FunctionNode *>(n);
1235 }
1236 for (Node *n : children) {
1237 if (n != nullptr && n->isAggregate()) {
1238 FunctionNode *fn = findMacroNode(t, parent: static_cast<Aggregate *>(n));
1239 if (fn != nullptr)
1240 return fn;
1241 }
1242 }
1243 return nullptr;
1244}
1245
1246/*!
1247 Add the class and struct names in \a arg to the \e {don't document}
1248 map.
1249 */
1250void Tree::addToDontDocumentMap(QString &arg)
1251{
1252 arg.remove(c: QChar('('));
1253 arg.remove(c: QChar(')'));
1254 QString t = arg.simplified();
1255 QStringList sl = t.split(sep: QChar(' '));
1256 if (sl.isEmpty())
1257 return;
1258 for (const QString &s : sl) {
1259 if (!m_dontDocumentMap.contains(key: s))
1260 m_dontDocumentMap.insert(key: s, value: nullptr);
1261 }
1262}
1263
1264/*!
1265 The \e {don't document} map has been loaded with the names
1266 of classes and structs in the current module that are not
1267 documented and should not be documented. Now traverse the
1268 map, and for each class or struct name, find the class node
1269 that represents that class or struct and mark it with the
1270 \C DontDocument status.
1271
1272 This results in a map of the class and struct nodes in the
1273 module that are in the public API but are not meant to be
1274 used by anyone. They are only used internally, but for one
1275 reason or another, they must have public visibility.
1276 */
1277void Tree::markDontDocumentNodes()
1278{
1279 for (auto it = m_dontDocumentMap.begin(); it != m_dontDocumentMap.end(); ++it) {
1280 Aggregate *node = findAggregate(name: it.key());
1281 if (node != nullptr)
1282 node->setStatus(Node::DontDocument);
1283 }
1284}
1285
1286QT_END_NAMESPACE
1287

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