| 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 "aggregate.h" |
| 5 | |
| 6 | #include "functionnode.h" |
| 7 | #include "parameters.h" |
| 8 | #include "typedefnode.h" |
| 9 | #include "qdocdatabase.h" |
| 10 | #include "qmlpropertynode.h" |
| 11 | #include "qmltypenode.h" |
| 12 | #include "sharedcommentnode.h" |
| 13 | #include <vector> |
| 14 | |
| 15 | using namespace Qt::Literals::StringLiterals; |
| 16 | |
| 17 | QT_BEGIN_NAMESPACE |
| 18 | |
| 19 | /*! |
| 20 | \class Aggregate |
| 21 | */ |
| 22 | |
| 23 | /*! \fn Aggregate::Aggregate(NodeType type, Aggregate *parent, const QString &name) |
| 24 | The constructor should never be called directly. It is only called |
| 25 | by the constructors of subclasses of Aggregate. Those constructors |
| 26 | pass the node \a type they want to create, the \a parent of the new |
| 27 | node, and its \a name. |
| 28 | */ |
| 29 | |
| 30 | /*! |
| 31 | Recursively set all non-related members in the list of children to |
| 32 | \nullptr, after which each aggregate can safely delete all children |
| 33 | in their list. Aggregate's destructor calls this only on the root |
| 34 | namespace node. |
| 35 | */ |
| 36 | void Aggregate::dropNonRelatedMembers() |
| 37 | { |
| 38 | for (auto &child : m_children) { |
| 39 | if (!child) |
| 40 | continue; |
| 41 | if (child->parent() != this) |
| 42 | child = nullptr; |
| 43 | else if (child->isAggregate()) |
| 44 | static_cast<Aggregate*>(child)->dropNonRelatedMembers(); |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | /*! |
| 49 | Destroys this Aggregate; deletes each child. |
| 50 | */ |
| 51 | Aggregate::~Aggregate() |
| 52 | { |
| 53 | // If this is the root, clear non-related children first |
| 54 | if (isNamespace() && name().isEmpty()) |
| 55 | dropNonRelatedMembers(); |
| 56 | |
| 57 | m_enumChildren.clear(); |
| 58 | m_nonfunctionMap.clear(); |
| 59 | m_functionMap.clear(); |
| 60 | qDeleteAll(begin: m_children.begin(), end: m_children.end()); |
| 61 | m_children.clear(); |
| 62 | } |
| 63 | |
| 64 | /*! |
| 65 | If \a genus is \c{Node::DontCare}, find the first node in |
| 66 | this node's child list that has the given \a name. If this |
| 67 | node is a QML type, be sure to also look in the children |
| 68 | of its property group nodes. Return the matching node or \c nullptr. |
| 69 | |
| 70 | If \a genus is either \c{Node::CPP} or \c {Node::QML}, then |
| 71 | find all this node's children that have the given \a name, |
| 72 | and return the one that satisfies the \a genus requirement. |
| 73 | */ |
| 74 | Node *Aggregate::findChildNode(const QString &name, Genus genus, int findFlags) const |
| 75 | { |
| 76 | if (genus == Genus::DontCare) { |
| 77 | Node *node = m_nonfunctionMap.value(key: name); |
| 78 | if (node) |
| 79 | return node; |
| 80 | } else { |
| 81 | const NodeList &nodes = m_nonfunctionMap.values(key: name); |
| 82 | for (auto *node : nodes) { |
| 83 | if (hasCommonGenusType(searchMask: genus, candidate: node->genus())) { |
| 84 | if (findFlags & TypesOnly) { |
| 85 | if (!node->isTypedef() && !node->isClassNode() |
| 86 | && !node->isQmlType() && !node->isEnumType()) |
| 87 | continue; |
| 88 | } else if (findFlags & IgnoreModules && node->isModule()) |
| 89 | continue; |
| 90 | return node; |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | if (genus != Genus::DontCare && !(hasCommonGenusType(searchMask: genus, candidate: this->genus()))) |
| 95 | return nullptr; |
| 96 | |
| 97 | if (findFlags & TypesOnly) |
| 98 | return nullptr; |
| 99 | |
| 100 | auto it = m_functionMap.find(key: name); |
| 101 | return it != m_functionMap.end() ? (*(*it).begin()) : nullptr; |
| 102 | } |
| 103 | |
| 104 | /*! |
| 105 | Find all the child nodes of this node that are named |
| 106 | \a name and return them in \a nodes. |
| 107 | */ |
| 108 | void Aggregate::findChildren(const QString &name, NodeVector &nodes) const |
| 109 | { |
| 110 | nodes.clear(); |
| 111 | const auto &functions = m_functionMap.value(key: name); |
| 112 | nodes.reserve(asize: functions.size() + m_nonfunctionMap.count(key: name)); |
| 113 | for (auto f : functions) |
| 114 | nodes.emplace_back(args&: f); |
| 115 | auto [it, end] = m_nonfunctionMap.equal_range(akey: name); |
| 116 | while (it != end) { |
| 117 | nodes.emplace_back(args: *it); |
| 118 | ++it; |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | /*! |
| 123 | This function searches for a child node of this Aggregate, |
| 124 | such that the child node has the spacified \a name and the |
| 125 | function \a isMatch returns true for the node. The function |
| 126 | passed must be one of the isXxx() functions in class Node |
| 127 | that tests the node type. |
| 128 | */ |
| 129 | Node *Aggregate::findNonfunctionChild(const QString &name, bool (Node::*isMatch)() const) |
| 130 | { |
| 131 | const NodeList &nodes = m_nonfunctionMap.values(key: name); |
| 132 | for (auto *node : nodes) { |
| 133 | if ((node->*(isMatch))()) |
| 134 | return node; |
| 135 | } |
| 136 | return nullptr; |
| 137 | } |
| 138 | |
| 139 | /*! |
| 140 | Find a function node that is a child of this node, such that |
| 141 | the function node has the specified \a name and \a parameters. |
| 142 | If \a parameters is empty but no matching function is found |
| 143 | that has no parameters, return the first non-internal primary |
| 144 | function or overload, whether it has parameters or not. |
| 145 | |
| 146 | \sa normalizeOverloads() |
| 147 | */ |
| 148 | FunctionNode *Aggregate::findFunctionChild(const QString &name, const Parameters ¶meters) |
| 149 | { |
| 150 | auto map_it = m_functionMap.find(key: name); |
| 151 | if (map_it == m_functionMap.end()) |
| 152 | return nullptr; |
| 153 | |
| 154 | auto match_it = std::find_if(first: (*map_it).begin(), last: (*map_it).end(), |
| 155 | pred: [¶meters](const FunctionNode *fn) { |
| 156 | if (fn->isInternal()) |
| 157 | return false; |
| 158 | if (parameters.count() != fn->parameters().count()) |
| 159 | return false; |
| 160 | for (int i = 0; i < parameters.count(); ++i) |
| 161 | if (parameters.at(i).type() != fn->parameters().at(i).type()) |
| 162 | return false; |
| 163 | return true; |
| 164 | }); |
| 165 | |
| 166 | if (match_it != (*map_it).end()) |
| 167 | return *match_it; |
| 168 | |
| 169 | // If no exact match was found and parameters are empty (e.g., from \overload command), |
| 170 | // try to find the best available function to link to. |
| 171 | if (parameters.isEmpty()) { |
| 172 | // First, try to find a non-deprecated, non-internal function |
| 173 | auto best_it = std::find_if(first: (*map_it).begin(), last: (*map_it).end(), |
| 174 | pred: [](const FunctionNode *fn) { |
| 175 | return !fn->isInternal() && !fn->isDeprecated(); |
| 176 | }); |
| 177 | |
| 178 | if (best_it != (*map_it).end()) |
| 179 | return *best_it; |
| 180 | |
| 181 | // If no non-deprecated function found, fall back to any non-internal function |
| 182 | auto fallback_it = std::find_if(first: (*map_it).begin(), last: (*map_it).end(), |
| 183 | pred: [](const FunctionNode *fn) { |
| 184 | return !fn->isInternal(); |
| 185 | }); |
| 186 | |
| 187 | if (fallback_it != (*map_it).end()) |
| 188 | return *fallback_it; |
| 189 | } |
| 190 | |
| 191 | return nullptr; |
| 192 | } |
| 193 | |
| 194 | /*! |
| 195 | Returns the function node that is a child of this node, such |
| 196 | that the function described has the same name and signature |
| 197 | as the function described by the function node \a clone. |
| 198 | |
| 199 | Returns \nullptr if no matching function was found. |
| 200 | */ |
| 201 | FunctionNode *Aggregate::findFunctionChild(const FunctionNode *clone) |
| 202 | { |
| 203 | auto funcs_it = m_functionMap.find(key: clone->name()); |
| 204 | if (funcs_it == m_functionMap.end()) |
| 205 | return nullptr; |
| 206 | |
| 207 | auto func_it = std::find_if(first: (*funcs_it).begin(), last: (*funcs_it).end(), |
| 208 | pred: [clone](const FunctionNode *fn) { |
| 209 | return compare(f1: clone, f2: fn) == 0; |
| 210 | }); |
| 211 | |
| 212 | return func_it != (*funcs_it).end() ? *func_it : nullptr; |
| 213 | } |
| 214 | |
| 215 | /*! |
| 216 | Mark all child nodes that have no documentation as having |
| 217 | private access and internal status. qdoc will then ignore |
| 218 | them for documentation purposes. |
| 219 | */ |
| 220 | void Aggregate::markUndocumentedChildrenInternal() |
| 221 | { |
| 222 | for (auto *child : std::as_const(t&: m_children)) { |
| 223 | if (!child->hasDoc() && !child->isDontDocument()) { |
| 224 | if (!child->docMustBeGenerated()) { |
| 225 | if (child->isFunction()) { |
| 226 | if (static_cast<FunctionNode *>(child)->hasAssociatedProperties()) |
| 227 | continue; |
| 228 | } else if (child->isTypedef()) { |
| 229 | if (static_cast<TypedefNode *>(child)->hasAssociatedEnum()) |
| 230 | continue; |
| 231 | } |
| 232 | child->setAccess(Access::Private); |
| 233 | child->setStatus(Node::Internal); |
| 234 | } |
| 235 | } |
| 236 | if (child->isAggregate()) { |
| 237 | static_cast<Aggregate *>(child)->markUndocumentedChildrenInternal(); |
| 238 | } |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | /*! |
| 243 | Adopts each non-aggregate C++ node (function/macro, typedef, enum, variable, |
| 244 | or a shared comment node with genus Node::CPP) to the aggregate specified in |
| 245 | the node's documentation using the \\relates command. |
| 246 | |
| 247 | If the target Aggregate is not found in the primary tree, creates a new |
| 248 | ProxyNode to use as the parent. |
| 249 | */ |
| 250 | void Aggregate::resolveRelates() |
| 251 | { |
| 252 | auto *database = QDocDatabase::qdocDB(); |
| 253 | |
| 254 | for (auto *node : m_children) { |
| 255 | if (node->isRelatedNonmember()) |
| 256 | continue; |
| 257 | if (node->genus() != Genus::CPP) |
| 258 | continue; |
| 259 | |
| 260 | if (!node->isAggregate()) { |
| 261 | const auto &relates_args = node->doc().metaCommandArgs(metaCommand: "relates"_L1 ); |
| 262 | if (relates_args.isEmpty()) |
| 263 | continue; |
| 264 | |
| 265 | auto *aggregate = database->findRelatesNode(path: relates_args[0].first.split(sep: "::"_L1 )); |
| 266 | if (!aggregate) |
| 267 | aggregate = new ProxyNode(this, relates_args[0].first); |
| 268 | else if (node->parent() == aggregate) |
| 269 | continue; |
| 270 | |
| 271 | aggregate->adoptChild(child: node); |
| 272 | node->setRelatedNonmember(true); |
| 273 | } else { |
| 274 | static_cast<Aggregate*>(node)->resolveRelates(); |
| 275 | } |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | /*! |
| 280 | Sorts the lists of overloads in the function map and assigns overload |
| 281 | numbers. |
| 282 | |
| 283 | For sorting, active functions take precedence over internal ones, as well |
| 284 | as ones marked as \\overload - the latter ones typically do not contain |
| 285 | full documentation, so selecting them as the \e primary function |
| 286 | would cause unnecessary warnings to be generated. |
| 287 | |
| 288 | Otherwise, the order is set as determined by FunctionNode::compare(). |
| 289 | */ |
| 290 | void Aggregate::normalizeOverloads() |
| 291 | { |
| 292 | for (auto &map_it : m_functionMap) { |
| 293 | if (map_it.size() == 1) { |
| 294 | map_it.front()->setOverloadNumber(0); |
| 295 | } else if (map_it.size() > 1) { |
| 296 | std::sort(first: map_it.begin(), last: map_it.end(), |
| 297 | comp: [](const FunctionNode *f1, const FunctionNode *f2) -> bool { |
| 298 | if (f1->isInternal() != f2->isInternal()) |
| 299 | return f2->isInternal(); |
| 300 | if (f1->isOverload() != f2->isOverload()) |
| 301 | return f2->isOverload(); |
| 302 | // Prioritize documented over undocumented |
| 303 | if (f1->hasDoc() != f2->hasDoc()) |
| 304 | return f1->hasDoc(); |
| 305 | return (compare(f1, f2) < 0); |
| 306 | }); |
| 307 | // Set overload numbers only if the functions are documented. |
| 308 | // They are not visible if undocumented. |
| 309 | signed short n{0}; |
| 310 | for (auto *fn : map_it) { |
| 311 | if (fn->hasDoc()) |
| 312 | fn->setOverloadNumber(n++); |
| 313 | } |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | for (auto *node : std::as_const(t&: m_children)) { |
| 318 | if (node->isAggregate()) |
| 319 | static_cast<Aggregate *>(node)->normalizeOverloads(); |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | /*! |
| 324 | Returns a const reference to the list of child nodes of this |
| 325 | aggregate that are not function nodes. Duplicate nodes are |
| 326 | removed from the list and the list is sorted. |
| 327 | */ |
| 328 | const NodeList &Aggregate::nonfunctionList() |
| 329 | { |
| 330 | m_nonfunctionList = m_nonfunctionMap.values(); |
| 331 | // Sort based on node name |
| 332 | std::sort(first: m_nonfunctionList.begin(), last: m_nonfunctionList.end(), comp: Node::nodeNameLessThan); |
| 333 | // Erase duplicates |
| 334 | m_nonfunctionList.erase(abegin: std::unique(first: m_nonfunctionList.begin(), last: m_nonfunctionList.end()), |
| 335 | aend: m_nonfunctionList.end()); |
| 336 | return m_nonfunctionList; |
| 337 | } |
| 338 | |
| 339 | /*! \fn bool Aggregate::isAggregate() const |
| 340 | Returns \c true because this node is an instance of Aggregate, |
| 341 | which means it can have children. |
| 342 | */ |
| 343 | |
| 344 | /*! |
| 345 | Finds the enum type node that has \a enumValue as one of |
| 346 | its enum values and returns a pointer to it. Returns 0 if |
| 347 | no enum type node is found that has \a enumValue as one |
| 348 | of its values. |
| 349 | */ |
| 350 | const EnumNode *Aggregate::findEnumNodeForValue(const QString &enumValue) const |
| 351 | { |
| 352 | for (const auto *node : m_enumChildren) { |
| 353 | const auto *en = static_cast<const EnumNode *>(node); |
| 354 | if (en->hasItem(name: enumValue)) |
| 355 | return en; |
| 356 | } |
| 357 | return nullptr; |
| 358 | } |
| 359 | |
| 360 | /*! |
| 361 | Adds the \a child to this node's child map using \a title |
| 362 | as the key. The \a child is not added to the child list |
| 363 | again, because it is presumed to already be there. We just |
| 364 | want to be able to find the child by its \a title. |
| 365 | */ |
| 366 | void Aggregate::addChildByTitle(Node *child, const QString &title) |
| 367 | { |
| 368 | m_nonfunctionMap.insert(key: title, value: child); |
| 369 | } |
| 370 | |
| 371 | /*! |
| 372 | Adds the \a child to this node's child list and sets the child's |
| 373 | parent pointer to this Aggregate. It then mounts the child with |
| 374 | mountChild(). |
| 375 | |
| 376 | The \a child is then added to this Aggregate's searchable maps |
| 377 | and lists. |
| 378 | |
| 379 | \note This function does not test the child's parent pointer |
| 380 | for null before changing it. If the child's parent pointer |
| 381 | is not null, then it is being reparented. The child becomes |
| 382 | a child of this Aggregate, but it also remains a child of |
| 383 | the Aggregate that is it's old parent. But the child will |
| 384 | only have one parent, and it will be this Aggregate. The is |
| 385 | because of the \c relates command. |
| 386 | |
| 387 | \sa mountChild(), dismountChild() |
| 388 | */ |
| 389 | void Aggregate::addChild(Node *child) |
| 390 | { |
| 391 | m_children.append(t: child); |
| 392 | child->setParent(this); |
| 393 | child->setUrl(QString()); |
| 394 | child->setIndexNodeFlag(isIndexNode()); |
| 395 | |
| 396 | if (child->isFunction()) { |
| 397 | m_functionMap[child->name()].emplace_back(args: static_cast<FunctionNode *>(child)); |
| 398 | } else if (!child->name().isEmpty()) { |
| 399 | m_nonfunctionMap.insert(key: child->name(), value: child); |
| 400 | if (child->isEnumType()) |
| 401 | m_enumChildren.append(t: child); |
| 402 | } |
| 403 | } |
| 404 | |
| 405 | /*! |
| 406 | This Aggregate becomes the adoptive parent of \a child. The |
| 407 | \a child knows this Aggregate as its parent, but its former |
| 408 | parent continues to have pointers to the child in its child |
| 409 | list and in its searchable data structures. But the child is |
| 410 | also added to the child list and searchable data structures |
| 411 | of this Aggregate. |
| 412 | */ |
| 413 | void Aggregate::adoptChild(Node *child) |
| 414 | { |
| 415 | if (child->parent() != this) { |
| 416 | m_children.append(t: child); |
| 417 | child->setParent(this); |
| 418 | if (child->isFunction()) { |
| 419 | m_functionMap[child->name()].emplace_back(args: static_cast<FunctionNode *>(child)); |
| 420 | } else if (!child->name().isEmpty()) { |
| 421 | m_nonfunctionMap.insert(key: child->name(), value: child); |
| 422 | if (child->isEnumType()) |
| 423 | m_enumChildren.append(t: child); |
| 424 | } |
| 425 | if (child->isSharedCommentNode()) { |
| 426 | auto *scn = static_cast<SharedCommentNode *>(child); |
| 427 | for (Node *n : scn->collective()) |
| 428 | adoptChild(child: n); |
| 429 | } |
| 430 | } |
| 431 | } |
| 432 | |
| 433 | /*! |
| 434 | If this node has a child that is a QML property named \a n, return a |
| 435 | pointer to that child. Otherwise, return \nullptr. |
| 436 | */ |
| 437 | QmlPropertyNode *Aggregate::hasQmlProperty(const QString &n) const |
| 438 | { |
| 439 | NodeType goal = NodeType::QmlProperty; |
| 440 | for (auto *child : std::as_const(t: m_children)) { |
| 441 | if (child->nodeType() == goal) { |
| 442 | if (child->name() == n) |
| 443 | return static_cast<QmlPropertyNode *>(child); |
| 444 | } |
| 445 | } |
| 446 | return nullptr; |
| 447 | } |
| 448 | |
| 449 | /*! |
| 450 | If this node has a child that is a QML property named \a n and that |
| 451 | also matches \a attached, return a pointer to that child. |
| 452 | */ |
| 453 | QmlPropertyNode *Aggregate::hasQmlProperty(const QString &n, bool attached) const |
| 454 | { |
| 455 | NodeType goal = NodeType::QmlProperty; |
| 456 | for (auto *child : std::as_const(t: m_children)) { |
| 457 | if (child->nodeType() == goal) { |
| 458 | if (child->name() == n && child->isAttached() == attached) |
| 459 | return static_cast<QmlPropertyNode *>(child); |
| 460 | } |
| 461 | } |
| 462 | return nullptr; |
| 463 | } |
| 464 | |
| 465 | /*! |
| 466 | Returns \c true if this aggregate has multiple function |
| 467 | overloads matching the name of \a fn. |
| 468 | |
| 469 | \note Assumes \a fn is a member of this aggregate. |
| 470 | */ |
| 471 | bool Aggregate::hasOverloads(const FunctionNode *fn) const |
| 472 | { |
| 473 | auto it = m_functionMap.find(key: fn->name()); |
| 474 | return !(it == m_functionMap.end()) && (it.value().size() > 1); |
| 475 | } |
| 476 | |
| 477 | /* |
| 478 | When deciding whether to include a function in the function |
| 479 | index, if the function is marked private, don't include it. |
| 480 | If the function is marked obsolete, don't include it. If the |
| 481 | function is marked internal, don't include it. Or if the |
| 482 | function is a destructor or any kind of constructor, don't |
| 483 | include it. Otherwise include it. |
| 484 | */ |
| 485 | static bool keep(FunctionNode *fn) |
| 486 | { |
| 487 | if (fn->isPrivate() || fn->isDeprecated() || fn->isInternal() || fn->isSomeCtor() || fn->isDtor()) |
| 488 | return false; |
| 489 | return true; |
| 490 | } |
| 491 | |
| 492 | /*! |
| 493 | Insert all functions declared in this aggregate into the |
| 494 | \a functionIndex. Call the function recursively for each |
| 495 | child that is an aggregate. |
| 496 | |
| 497 | Only include functions that are in the public API and |
| 498 | that are not constructors or destructors. |
| 499 | */ |
| 500 | void Aggregate::findAllFunctions(NodeMapMap &functionIndex) |
| 501 | { |
| 502 | for (auto functions : m_functionMap) { |
| 503 | std::for_each(first: functions.begin(), last: functions.end(), |
| 504 | f: [&functionIndex](FunctionNode *fn) { |
| 505 | if (keep(fn)) |
| 506 | functionIndex[fn->name()].insert(key: fn->parent()->fullDocumentName(), value: fn); |
| 507 | } |
| 508 | ); |
| 509 | } |
| 510 | |
| 511 | for (Node *node : std::as_const(t&: m_children)) { |
| 512 | if (node->isAggregate() && !node->isPrivate() && !node->isDontDocument()) |
| 513 | static_cast<Aggregate *>(node)->findAllFunctions(functionIndex); |
| 514 | } |
| 515 | } |
| 516 | |
| 517 | /*! |
| 518 | For each child of this node, if the child is a namespace node, |
| 519 | insert the child into the \a namespaces multimap. If the child |
| 520 | is an aggregate, call this function recursively for that child. |
| 521 | |
| 522 | When the function called with the root node of a tree, it finds |
| 523 | all the namespace nodes in that tree and inserts them into the |
| 524 | \a namespaces multimap. |
| 525 | |
| 526 | The root node of a tree is a namespace, but it has no name, so |
| 527 | it is not inserted into the map. So, if this function is called |
| 528 | for each tree in the qdoc database, it finds all the namespace |
| 529 | nodes in the database. |
| 530 | */ |
| 531 | void Aggregate::findAllNamespaces(NodeMultiMap &namespaces) |
| 532 | { |
| 533 | for (auto *node : std::as_const(t&: m_children)) { |
| 534 | if (node->isAggregate() && !node->isPrivate()) { |
| 535 | if (node->isNamespace() && !node->name().isEmpty()) |
| 536 | namespaces.insert(key: node->name(), value: node); |
| 537 | static_cast<Aggregate *>(node)->findAllNamespaces(namespaces); |
| 538 | } |
| 539 | } |
| 540 | } |
| 541 | |
| 542 | /*! |
| 543 | Returns true if this aggregate contains at least one child |
| 544 | that is marked obsolete. Otherwise returns false. |
| 545 | */ |
| 546 | bool Aggregate::hasObsoleteMembers() const |
| 547 | { |
| 548 | for (const auto *node : m_children) |
| 549 | if (!node->isPrivate() && node->isDeprecated()) { |
| 550 | if (node->isFunction() || node->isProperty() || node->isEnumType() || node->isTypedef() |
| 551 | || node->isTypeAlias() || node->isVariable() || node->isQmlProperty()) |
| 552 | return true; |
| 553 | } |
| 554 | return false; |
| 555 | } |
| 556 | |
| 557 | /*! |
| 558 | Finds all the obsolete C++ classes and QML types in this |
| 559 | aggregate and all the C++ classes and QML types with obsolete |
| 560 | members, and inserts them into maps used elsewhere for |
| 561 | generating documentation. |
| 562 | */ |
| 563 | void Aggregate::findAllObsoleteThings() |
| 564 | { |
| 565 | for (auto *node : std::as_const(t&: m_children)) { |
| 566 | if (!node->isPrivate()) { |
| 567 | if (node->isDeprecated()) { |
| 568 | if (node->isClassNode()) |
| 569 | QDocDatabase::obsoleteClasses().insert(key: node->qualifyCppName(), value: node); |
| 570 | else if (node->isQmlType()) |
| 571 | QDocDatabase::obsoleteQmlTypes().insert(key: node->qualifyQmlName(), value: node); |
| 572 | } else if (node->isClassNode()) { |
| 573 | auto *a = static_cast<Aggregate *>(node); |
| 574 | if (a->hasObsoleteMembers()) |
| 575 | QDocDatabase::classesWithObsoleteMembers().insert(key: node->qualifyCppName(), value: node); |
| 576 | } else if (node->isQmlType()) { |
| 577 | auto *a = static_cast<Aggregate *>(node); |
| 578 | if (a->hasObsoleteMembers()) |
| 579 | QDocDatabase::qmlTypesWithObsoleteMembers().insert(key: node->qualifyQmlName(), |
| 580 | value: node); |
| 581 | } else if (node->isAggregate()) { |
| 582 | static_cast<Aggregate *>(node)->findAllObsoleteThings(); |
| 583 | } |
| 584 | } |
| 585 | } |
| 586 | } |
| 587 | |
| 588 | /*! |
| 589 | Finds all the C++ classes, QML types, QML basic types, and examples |
| 590 | in this aggregate and inserts them into appropriate maps for later |
| 591 | use in generating documentation. |
| 592 | */ |
| 593 | void Aggregate::findAllClasses() |
| 594 | { |
| 595 | for (auto *node : std::as_const(t&: m_children)) { |
| 596 | if (!node->isPrivate() && !node->isInternal() && !node->isDontDocument() |
| 597 | && node->tree()->camelCaseModuleName() != QString("QDoc" )) { |
| 598 | if (node->isClassNode()) { |
| 599 | QDocDatabase::cppClasses().insert(key: node->qualifyCppName().toLower(), value: node); |
| 600 | } else if (node->isQmlType()) { |
| 601 | QString name = node->name().toLower(); |
| 602 | QDocDatabase::qmlTypes().insert(key: name, value: node); |
| 603 | // also add to the QML basic type map |
| 604 | if (node->isQmlBasicType()) |
| 605 | QDocDatabase::qmlBasicTypes().insert(key: name, value: node); |
| 606 | } else if (node->isExample()) { |
| 607 | // use the module index title as key for the example map |
| 608 | QString title = node->tree()->indexTitle(); |
| 609 | if (!QDocDatabase::examples().contains(key: title, value: node)) |
| 610 | QDocDatabase::examples().insert(key: title, value: node); |
| 611 | } else if (node->isAggregate()) { |
| 612 | static_cast<Aggregate *>(node)->findAllClasses(); |
| 613 | } |
| 614 | } |
| 615 | } |
| 616 | } |
| 617 | |
| 618 | /*! |
| 619 | Find all the attribution pages in this node and insert them |
| 620 | into \a attributions. |
| 621 | */ |
| 622 | void Aggregate::findAllAttributions(NodeMultiMap &attributions) |
| 623 | { |
| 624 | for (auto *node : std::as_const(t&: m_children)) { |
| 625 | if (!node->isPrivate()) { |
| 626 | if (node->isPageNode() && static_cast<PageNode*>(node)->isAttribution()) |
| 627 | attributions.insert(key: node->tree()->indexTitle(), value: node); |
| 628 | else if (node->isAggregate()) |
| 629 | static_cast<Aggregate *>(node)->findAllAttributions(attributions); |
| 630 | } |
| 631 | } |
| 632 | } |
| 633 | |
| 634 | /*! |
| 635 | Finds all the nodes in this node where a \e{since} command appeared |
| 636 | in the qdoc comment and sorts them into maps according to the kind |
| 637 | of node. |
| 638 | |
| 639 | This function is used for generating the "New Classes... in x.y" |
| 640 | section on the \e{What's New in Qt x.y} page. |
| 641 | */ |
| 642 | void Aggregate::findAllSince() |
| 643 | { |
| 644 | for (auto *node : std::as_const(t&: m_children)) { |
| 645 | if (node->isRelatedNonmember() && node->parent() != this) |
| 646 | continue; |
| 647 | QString sinceString = node->since(); |
| 648 | // Insert a new entry into each map for each new since string found. |
| 649 | if (node->isInAPI() && !sinceString.isEmpty()) { |
| 650 | // operator[] will insert a default-constructed value into the |
| 651 | // map if key is not found, which is what we want here. |
| 652 | auto &nsmap = QDocDatabase::newSinceMaps()[sinceString]; |
| 653 | auto &ncmap = QDocDatabase::newClassMaps()[sinceString]; |
| 654 | auto &nqcmap = QDocDatabase::newQmlTypeMaps()[sinceString]; |
| 655 | |
| 656 | if (node->isFunction()) { |
| 657 | // Insert functions into the general since map. |
| 658 | auto *fn = static_cast<FunctionNode *>(node); |
| 659 | if (!fn->isDeprecated() && !fn->isSomeCtor() && !fn->isDtor()) |
| 660 | nsmap.insert(key: fn->name(), value: fn); |
| 661 | } else if (node->isClassNode()) { |
| 662 | // Insert classes into the since and class maps. |
| 663 | QString name = node->qualifyWithParentName(); |
| 664 | nsmap.insert(key: name, value: node); |
| 665 | ncmap.insert(key: name, value: node); |
| 666 | } else if (node->isQmlType()) { |
| 667 | // Insert QML elements into the since and element maps. |
| 668 | QString name = node->qualifyWithParentName(); |
| 669 | nsmap.insert(key: name, value: node); |
| 670 | nqcmap.insert(key: name, value: node); |
| 671 | } else if (node->isQmlProperty()) { |
| 672 | // Insert QML properties into the since map. |
| 673 | nsmap.insert(key: node->name(), value: node); |
| 674 | } else { |
| 675 | // Insert external documents into the general since map. |
| 676 | QString name = node->qualifyWithParentName(); |
| 677 | nsmap.insert(key: name, value: node); |
| 678 | } |
| 679 | } |
| 680 | // Enum values - a special case as EnumItem is not a Node subclass |
| 681 | if (node->isInAPI() && node->isEnumType()) { |
| 682 | for (const auto &val : static_cast<EnumNode *>(node)->items()) { |
| 683 | sinceString = val.since(); |
| 684 | if (sinceString.isEmpty()) |
| 685 | continue; |
| 686 | // Insert to enum value map |
| 687 | QDocDatabase::newEnumValueMaps()[sinceString].insert( |
| 688 | key: node->name() + "::" + val.name(), value: node); |
| 689 | // Ugly hack: Insert into general map with an empty key - |
| 690 | // we need something in there to mark the corresponding |
| 691 | // section populated. See Sections class constructor. |
| 692 | QDocDatabase::newSinceMaps()[sinceString].replace(key: QString(), value: node); |
| 693 | } |
| 694 | } |
| 695 | |
| 696 | // Recursively find child nodes with since commands. |
| 697 | if (node->isAggregate()) |
| 698 | static_cast<Aggregate *>(node)->findAllSince(); |
| 699 | } |
| 700 | } |
| 701 | |
| 702 | /*! |
| 703 | Resolves the inheritance information for all QML type children |
| 704 | of this aggregate. |
| 705 | */ |
| 706 | void Aggregate::resolveQmlInheritance() |
| 707 | { |
| 708 | NodeMap previousSearches; |
| 709 | for (auto *child : std::as_const(t&: m_children)) { |
| 710 | if (!child->isQmlType()) |
| 711 | continue; |
| 712 | static_cast<QmlTypeNode *>(child)->resolveInheritance(previousSearches); |
| 713 | } |
| 714 | |
| 715 | // At this point we check for cycles in the inheritance of QML types. |
| 716 | for (auto *child : std::as_const(t&: m_children)) { |
| 717 | if (child->isQmlType()) |
| 718 | static_cast<QmlTypeNode *>(child)->checkInheritance(); |
| 719 | } |
| 720 | } |
| 721 | |
| 722 | /*! |
| 723 | Returns a word representing the kind of Aggregate this node is. |
| 724 | Currently recognizes class, struct, union, and namespace. |
| 725 | If \a cap is true, the word is capitalised. |
| 726 | */ |
| 727 | QString Aggregate::typeWord(bool cap) const |
| 728 | { |
| 729 | if (cap) { |
| 730 | switch (nodeType()) { |
| 731 | case NodeType::Class: |
| 732 | return "Class"_L1 ; |
| 733 | case NodeType::Struct: |
| 734 | return "Struct"_L1 ; |
| 735 | case NodeType::Union: |
| 736 | return "Union"_L1 ; |
| 737 | case NodeType::Namespace: |
| 738 | return "Namespace"_L1 ; |
| 739 | default: |
| 740 | break; |
| 741 | } |
| 742 | } else { |
| 743 | switch (nodeType()) { |
| 744 | case NodeType::Class: |
| 745 | return "class"_L1 ; |
| 746 | case NodeType::Struct: |
| 747 | return "struct"_L1 ; |
| 748 | case NodeType::Union: |
| 749 | return "union"_L1 ; |
| 750 | case NodeType::Namespace: |
| 751 | return "namespace"_L1 ; |
| 752 | default: |
| 753 | break; |
| 754 | } |
| 755 | } |
| 756 | return QString(); |
| 757 | } |
| 758 | |
| 759 | /*! \fn int Aggregate::count() const |
| 760 | Returns the number of children in the child list. |
| 761 | */ |
| 762 | |
| 763 | /*! \fn const NodeList &Aggregate::childNodes() const |
| 764 | Returns a const reference to the child list. |
| 765 | */ |
| 766 | |
| 767 | /*! \fn NodeList::ConstIterator Aggregate::constBegin() const |
| 768 | Returns a const iterator pointing at the beginning of the child list. |
| 769 | */ |
| 770 | |
| 771 | /*! \fn NodeList::ConstIterator Aggregate::constEnd() const |
| 772 | Returns a const iterator pointing at the end of the child list. |
| 773 | */ |
| 774 | |
| 775 | /*! \fn QmlTypeNode *Aggregate::qmlBaseNode() const |
| 776 | If this Aggregate is a QmlTypeNode, this function returns a pointer to |
| 777 | the QmlTypeNode that is its base type. Otherwise it returns \c nullptr. |
| 778 | A QmlTypeNode doesn't always have a base type, so even when this Aggregate |
| 779 | is aQmlTypeNode, the pointer returned can be \c nullptr. |
| 780 | */ |
| 781 | |
| 782 | /*! \fn FunctionMap &Aggregate::functionMap() |
| 783 | Returns a reference to this Aggregate's function map, which |
| 784 | is a map of all the children of this Aggregate that are |
| 785 | FunctionNodes. |
| 786 | */ |
| 787 | |
| 788 | /*! \fn void Aggregate::appendToRelatedByProxy(const NodeList &t) |
| 789 | Appends the list of node pointers to the list of elements that are |
| 790 | related to this Aggregate but are documented in a different module. |
| 791 | |
| 792 | \sa relatedByProxy() |
| 793 | */ |
| 794 | |
| 795 | /*! \fn NodeList &Aggregate::relatedByProxy() |
| 796 | Returns a reference to a list of node pointers where each element |
| 797 | points to a node in an index file for some other module, such that |
| 798 | whatever the node represents was documented in that other module, |
| 799 | but it is related to this Aggregate, so when the documentation for |
| 800 | this Aggregate is written, it will contain links to elements in the |
| 801 | other module. |
| 802 | */ |
| 803 | |
| 804 | QT_END_NAMESPACE |
| 805 | |