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 | |
18 | QT_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 | */ |
45 | Tree::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 | */ |
66 | Tree::~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 | */ |
81 | Node *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 | */ |
97 | Aggregate *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 | */ |
110 | ClassNode *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 | */ |
122 | NamespaceNode *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 | */ |
140 | Aggregate *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 | */ |
150 | void 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 | */ |
164 | void 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 | */ |
206 | void 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 | */ |
228 | void 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 | */ |
277 | void 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 | */ |
300 | void 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 | */ |
328 | void 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 | */ |
357 | void 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 | */ |
372 | ClassList 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 | */ |
392 | Node *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 | */ |
413 | Node *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 | */ |
455 | const 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 | */ |
553 | const 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 | */ |
614 | const 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 | */ |
694 | const 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 | */ |
715 | QString 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 | */ |
743 | void 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 | */ |
753 | void 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 | */ |
826 | const 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 | */ |
852 | const 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 | */ |
889 | QString 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 | */ |
922 | CNMap *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 | */ |
943 | CollectionNode *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 | */ |
968 | CollectionNode *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 | */ |
1042 | CollectionNode *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 | */ |
1059 | CollectionNode *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 | */ |
1073 | CollectionNode *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 | */ |
1102 | void 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 | */ |
1120 | const FunctionNode *Tree::findFunctionNode(const QStringList &path, const Parameters ¶meters, |
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 | */ |
1204 | FunctionNode *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 | */ |
1227 | FunctionNode *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 | */ |
1250 | void 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 | */ |
1277 | void 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 | |
1286 | QT_END_NAMESPACE |
1287 | |