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