// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "sections.h" #include "aggregate.h" #include "classnode.h" #include "config.h" #include "enumnode.h" #include "functionnode.h" #include "generator.h" #include "utilities.h" #include "namespacenode.h" #include "qmlpropertynode.h" #include "qmltypenode.h" #include "sharedcommentnode.h" #include "typedefnode.h" #include "variablenode.h" #include QT_BEGIN_NAMESPACE QList
Sections::s_stdSummarySections { { "Namespaces", "namespace", "namespaces", "", Section::Summary }, { "Classes", "class", "classes", "", Section::Summary }, { "Types", "type", "types", "", Section::Summary }, { "Variables", "variable", "variables", "", Section::Summary }, { "Static Variables", "static variable", "static variables", "", Section::Summary }, { "Functions", "function", "functions", "", Section::Summary }, { "Macros", "macro", "macros", "", Section::Summary }, }; QList
Sections::s_stdDetailsSections { { "Namespaces", "namespace", "namespaces", "nmspace", Section::Details }, { "Classes", "class", "classes", "classes", Section::Details }, { "Type Documentation", "type", "types", "types", Section::Details }, { "Variable Documentation", "variable", "variables", "vars", Section::Details }, { "Static Variables", "static variable", "static variables", QString(), Section::Details }, { "Function Documentation", "function", "functions", "func", Section::Details }, { "Macro Documentation", "macro", "macros", "macros", Section::Details }, }; QList
Sections::s_stdCppClassSummarySections { { "Public Types", "public type", "public types", "", Section::Summary }, { "Properties", "property", "properties", "", Section::Summary }, { "Public Functions", "public function", "public functions", "", Section::Summary }, { "Public Slots", "public slot", "public slots", "", Section::Summary }, { "Signals", "signal", "signals", "", Section::Summary }, { "Public Variables", "public variable", "public variables", "", Section::Summary }, { "Static Public Members", "static public member", "static public members", "", Section::Summary }, { "Protected Types", "protected type", "protected types", "", Section::Summary }, { "Protected Functions", "protected function", "protected functions", "", Section::Summary }, { "Protected Slots", "protected slot", "protected slots", "", Section::Summary }, { "Protected Variables", "protected type", "protected variables", "", Section::Summary }, { "Static Protected Members", "static protected member", "static protected members", "", Section::Summary }, { "Private Types", "private type", "private types", "", Section::Summary }, { "Private Functions", "private function", "private functions", "", Section::Summary }, { "Private Slots", "private slot", "private slots", "", Section::Summary }, { "Static Private Members", "static private member", "static private members", "", Section::Summary }, { "Related Non-Members", "related non-member", "related non-members", "", Section::Summary }, { "Macros", "macro", "macros", "", Section::Summary }, }; QList
Sections::s_stdCppClassDetailsSections { { "Member Type Documentation", "member", "members", "types", Section::Details }, { "Property Documentation", "member", "members", "prop", Section::Details }, { "Member Function Documentation", "member", "members", "func", Section::Details }, { "Member Variable Documentation", "member", "members", "vars", Section::Details }, { "Related Non-Members", "member", "members", "relnonmem", Section::Details }, { "Macro Documentation", "member", "members", "macros", Section::Details }, }; QList
Sections::s_stdQmlTypeSummarySections { { "Properties", "property", "properties", "", Section::Summary }, { "Attached Properties", "attached property", "attached properties", "", Section::Summary }, { "Signals", "signal", "signals", "", Section::Summary }, { "Signal Handlers", "signal handler", "signal handlers", "", Section::Summary }, { "Attached Signals", "attached signal", "attached signals", "", Section::Summary }, { "Methods", "method", "methods", "", Section::Summary }, { "Attached Methods", "attached method", "attached methods", "", Section::Summary }, }; QList
Sections::s_stdQmlTypeDetailsSections { { "Property Documentation", "member", "members", "qmlprop", Section::Details }, { "Attached Property Documentation", "member", "members", "qmlattprop", Section::Details }, { "Signal Documentation", "signal", "signals", "qmlsig", Section::Details }, { "Signal Handler Documentation", "signal handler", "signal handlers", "qmlsighan", Section::Details }, { "Attached Signal Documentation", "signal", "signals", "qmlattsig", Section::Details }, { "Method Documentation", "member", "members", "qmlmeth", Section::Details }, { "Attached Method Documentation", "member", "members", "qmlattmeth", Section::Details }, }; QList
Sections::s_sinceSections { { " New Namespaces", "", "", "", Section::Details }, { " New Classes", "", "", "", Section::Details }, { " New Member Functions", "", "", "", Section::Details }, { " New Functions in Namespaces", "", "", "", Section::Details }, { " New Global Functions", "", "", "", Section::Details }, { " New Macros", "", "", "", Section::Details }, { " New Enum Types", "", "", "", Section::Details }, { " New Type Aliases", "", "", "", Section::Details }, { " New Properties", "", "", "", Section::Details }, { " New Variables", "", "", "", Section::Details }, { " New QML Types", "", "", "", Section::Details }, { " New QML Properties", "", "", "", Section::Details }, { " New QML Signals", "", "", "", Section::Details }, { " New QML Signal Handlers", "", "", "", Section::Details }, { " New QML Methods", "", "", "", Section::Details }, }; QList
Sections::s_allMembers{ { "", "member", "members", "", Section::AllMembers } }; /*! \class Section \brief A class for containing the elements of one documentation section */ /*! The destructor must delete the members of collections when the members are allocated on the heap. */ Section::~Section() { clear(); } /*! A Section is now an element in a static vector, so we don't have to repeatedly construct and destroy them. But we do need to clear them before each call to build the sections for a C++ or QML entity. */ void Section::clear() { m_reimplementedMemberMap.clear(); m_members.clear(); m_obsoleteMembers.clear(); m_reimplementedMembers.clear(); m_inheritedMembers.clear(); m_classNodesList.clear(); m_aggregate = nullptr; } /*! Construct a name for the \a node that can be used for sorting a set of nodes into equivalence classes. */ QString sortName(const Node *node) { QString nodeName{node->name()}; int numDigits = 0; for (qsizetype i = nodeName.size() - 1; i > 0; --i) { if (nodeName.at(i).digitValue() == -1) break; ++numDigits; } // we want 'qint8' to appear before 'qint16' if (numDigits > 0) { for (int i = 0; i < 4 - numDigits; ++i) nodeName.insert(nodeName.size() - numDigits - 1, QLatin1Char('0')); } if (node->isClassNode()) return QLatin1Char('A') + nodeName; if (node->isFunction(Node::CPP)) { const auto *fn = static_cast(node); QString sortNo; if (fn->isCtor()) sortNo = QLatin1String("C"); else if (fn->isCCtor()) sortNo = QLatin1String("D"); else if (fn->isMCtor()) sortNo = QLatin1String("E"); else if (fn->isDtor()) sortNo = QLatin1String("F"); else if (nodeName.startsWith(QLatin1String("operator")) && nodeName.size() > 8 && !nodeName[8].isLetterOrNumber()) sortNo = QLatin1String("H"); else sortNo = QLatin1String("G"); return sortNo + nodeName + QLatin1Char(' ') + QString::number(fn->overloadNumber(), 36); } if (node->isFunction(Node::QML)) return QLatin1Char('E') + nodeName + QLatin1Char(' ') + QString::number(static_cast(node)->overloadNumber(), 36); if (node->isProperty() || node->isVariable()) return QLatin1Char('G') + nodeName; return QLatin1Char('B') + nodeName; } /*! Inserts the \a node into this section if it is appropriate for this section. */ void Section::insert(Node *node) { bool irrelevant = false; bool inherited = false; if (!node->isRelatedNonmember()) { Aggregate *p = node->parent(); if (!p->isNamespace() && p != m_aggregate) { if (!p->isQmlType() || !p->isAbstract()) inherited = true; } } if (node->isPrivate() || node->isInternal()) { irrelevant = true; } else if (node->isFunction()) { auto *func = static_cast(node); irrelevant = (inherited && (func->isSomeCtor() || func->isDtor())); } else if (node->isClassNode() || node->isEnumType() || node->isTypedef() || node->isVariable()) { irrelevant = (inherited && m_style != AllMembers); if (!irrelevant && m_style == Details && node->isTypedef()) { const auto *tdn = static_cast(node); if (tdn->associatedEnum()) irrelevant = true; } } if (!irrelevant) { QString key = sortName(node); if (node->isDeprecated()) { m_obsoleteMembers.push_back(node); } else { if (!inherited || m_style == AllMembers) m_members.push_back(node); if (inherited && (node->parent()->isClassNode() || node->parent()->isNamespace())) { if (m_inheritedMembers.isEmpty() || m_inheritedMembers.last().first != node->parent()) { std::pair p(node->parent(), 0); m_inheritedMembers.append(p); } m_inheritedMembers.last().second++; } } } } /*! Returns \c true if the \a node is a reimplemented member function of the current class. If true, the \a node is inserted into the reimplemented member map. True is returned only if \a node is inserted into the map. That is, false is returned if the \a node is already in the map. */ bool Section::insertReimplementedMember(Node *node) { if (!node->isPrivate() && !node->isRelatedNonmember()) { const auto *fn = static_cast(node); if (!fn->overridesThis().isEmpty()) { if (fn->parent() == m_aggregate) { QString key = sortName(fn); if (!m_reimplementedMemberMap.contains(key)) { m_reimplementedMemberMap.insert(key, node); return true; } } } } return false; } /*! If this section is not empty, convert its maps to sequential structures for better traversal during doc generation. */ void Section::reduce() { // TODO:TEMPORARY:INTERMEDITATE: Section uses a series of maps // to internally manage the categorization of the various members // of an aggregate. It further uses a secondary "flattened" // (usually vector) version that is later used by consumers of a // Section content. // // One of the uses of those maps is that of ordering, by using // keys generated with `sortName`. // Nonetheless, this is the only usage that comes from the keys, // as they are neither necessary nor used outside of the internal // code for Section. // // Hence, the codebase is moving towards removing the maps in // favor of building a flattened, consumer ready, version of the // categorization directly, cutting the intermediate conversion // step. // // To do so while keeping as much of the old behavior as possible, // we provide a sorting for the flattened version that is based on // `sortName`, as the previous ordering was. // // This acts as a relatively heavy pessimization, as `sortName`, // used as a comparator, can be called multiple times for each // Node, while before it would have been called almost-once. // // Instead of fixing this issue, by for example caching the // sortName of each Node instance, we temporarily keep the // pessimization while the various maps are removed. // // When all the maps are removed, we can remove `sortName`, which // produces strings to use as key requiring a few allocations and // expensive operations, with an actual comparator function, which // should be more lightweight and more than offset the // multiple-calls. static auto node_less_than = [](const Node* left, const Node* right) { return sortName(left) < sortName(right); }; std::stable_sort(m_members.begin(), m_members.end(), node_less_than); std::stable_sort(m_obsoleteMembers.begin(), m_obsoleteMembers.end(), node_less_than); m_reimplementedMembers = m_reimplementedMemberMap.values().toVector(); for (auto &cn : m_classNodesList) { std::stable_sort(cn.second.begin(), cn.second.end(), node_less_than); } } /*! \class Sections \brief A class for creating vectors of collections for documentation Each element in a vector is an instance of Section, which contains all the elements that will be documented in one section of a reference documentation page. */ /*! This constructor builds the vectors of sections based on the type of the \a aggregate node. */ Sections::Sections(Aggregate *aggregate) : m_aggregate(aggregate) { initAggregate(s_allMembers, m_aggregate); switch (m_aggregate->nodeType()) { case Node::Class: case Node::Struct: case Node::Union: initAggregate(s_stdCppClassSummarySections, m_aggregate); initAggregate(s_stdCppClassDetailsSections, m_aggregate); buildStdCppClassRefPageSections(); break; case Node::QmlType: case Node::QmlValueType: initAggregate(s_stdQmlTypeSummarySections, m_aggregate); initAggregate(s_stdQmlTypeDetailsSections, m_aggregate); buildStdQmlTypeRefPageSections(); break; case Node::Namespace: case Node::HeaderFile: case Node::Proxy: default: initAggregate(s_stdSummarySections, m_aggregate); initAggregate(s_stdDetailsSections, m_aggregate); buildStdRefPageSections(); break; } } /*! This constructor builds a vector of sections from the \e since node map, \a nsmap */ Sections::Sections(const NodeMultiMap &nsmap) : m_aggregate(nullptr) { if (nsmap.isEmpty()) return; SectionVector §ions = sinceSections(); for (auto it = nsmap.constBegin(); it != nsmap.constEnd(); ++it) { Node *node = it.value(); switch (node->nodeType()) { case Node::QmlType: sections[SinceQmlTypes].appendMember(node); break; case Node::Namespace: sections[SinceNamespaces].appendMember(node); break; case Node::Class: case Node::Struct: case Node::Union: sections[SinceClasses].appendMember(node); break; case Node::Enum: sections[SinceEnumTypes].appendMember(node); break; case Node::Typedef: case Node::TypeAlias: sections[SinceTypeAliases].appendMember(node); break; case Node::Function: { const auto *fn = static_cast(node); switch (fn->metaness()) { case FunctionNode::QmlSignal: sections[SinceQmlSignals].appendMember(node); break; case FunctionNode::QmlSignalHandler: sections[SinceQmlSignalHandlers].appendMember(node); break; case FunctionNode::QmlMethod: sections[SinceQmlMethods].appendMember(node); break; default: if (fn->isMacro()) sections[SinceMacros].appendMember(node); else { Node *p = fn->parent(); if (p) { if (p->isClassNode()) sections[SinceMemberFunctions].appendMember(node); else if (p->isNamespace()) { if (p->name().isEmpty()) sections[SinceGlobalFunctions].appendMember(node); else sections[SinceNamespaceFunctions].appendMember(node); } else sections[SinceGlobalFunctions].appendMember(node); } else sections[SinceGlobalFunctions].appendMember(node); } break; } break; } case Node::Property: sections[SinceProperties].appendMember(node); break; case Node::Variable: sections[SinceVariables].appendMember(node); break; case Node::QmlProperty: sections[SinceQmlProperties].appendMember(node); break; default: break; } } } /*! The behavior of the destructor depends on the type of the Aggregate node that was passed to the constructor. If the constructor was passed a multimap, the destruction is a bit different because there was no Aggregate node. */ Sections::~Sections() { if (m_aggregate) { switch (m_aggregate->nodeType()) { case Node::Class: case Node::Struct: case Node::Union: clear(stdCppClassSummarySections()); clear(stdCppClassDetailsSections()); allMembersSection().clear(); break; case Node::QmlType: case Node::QmlValueType: clear(stdQmlTypeSummarySections()); clear(stdQmlTypeDetailsSections()); allMembersSection().clear(); break; default: clear(stdSummarySections()); clear(stdDetailsSections()); allMembersSection().clear(); break; } m_aggregate = nullptr; } else { clear(sinceSections()); } } /*! Initialize the Aggregate in each Section of vector \a v with \a aggregate. */ void Sections::initAggregate(SectionVector &v, Aggregate *aggregate) { for (Section §ion : v) section.setAggregate(aggregate); } /*! Reset each Section in vector \a v to its initialized state. */ void Sections::clear(QList
&v) { for (Section §ion : v) section.clear(); } /*! Linearize the maps in each Section in \a v. */ void Sections::reduce(QList
&v) { for (Section §ion : v) section.reduce(); } /*! This is a private helper function for buildStdRefPageSections(). */ void Sections::stdRefPageSwitch(SectionVector &v, Node *n, Node *t) { // t is the reference node to be tested, n is the node to be distributed. // t differs from n only for shared comment nodes. if (!t) t = n; switch (t->nodeType()) { case Node::Namespace: v[StdNamespaces].insert(n); return; case Node::Class: case Node::Struct: case Node::Union: v[StdClasses].insert(n); return; case Node::Enum: case Node::Typedef: case Node::TypeAlias: v[StdTypes].insert(n); return; case Node::Function: { auto *func = static_cast(t); if (func->isMacro()) v[StdMacros].insert(n); else v[StdFunctions].insert(n); } return; case Node::Variable: { const auto *var = static_cast(t); if (!var->doc().isEmpty()) { if (var->isStatic()) v[StdStaticVariables].insert(n); else v[StdVariables].insert(n); } } return; case Node::SharedComment: { auto *scn = static_cast(t); if (!scn->doc().isEmpty() && scn->collective().size()) stdRefPageSwitch( v, scn, scn->collective().first()); // TODO: warn about mixed node types in collective? } return; default: return; } } /*! Build the section vectors for a standard reference page, when the aggregate node is not a C++ class or a QML type. If this is for a namespace page then if the namespace node itself does not have documentation, only its children that have documentation should be documented. In other words, there are cases where a namespace is declared but does not have documentation, but some of the elements declared in that namespace do have documentation. This special processing of namespaces that do not have a documentation comment is meant to allow documenting its members that do have documentation while avoiding posting error messages for its members that are not documented. */ void Sections::buildStdRefPageSections() { const NamespaceNode *ns = nullptr; bool documentAll = true; // document all the children if (m_aggregate->isNamespace()) { ns = static_cast(m_aggregate); if (!ns->hasDoc()) documentAll = false; // only document children that have documentation } for (auto it = m_aggregate->constBegin(); it != m_aggregate->constEnd(); ++it) { Node *n = *it; if (documentAll || n->hasDoc()) { stdRefPageSwitch(stdSummarySections(), n); stdRefPageSwitch(stdDetailsSections(), n); } } if (!m_aggregate->relatedByProxy().isEmpty()) { const QList &relatedBy = m_aggregate->relatedByProxy(); for (const auto &node : relatedBy) stdRefPageSwitch(stdSummarySections(), node); } /* If we are building the sections for the reference page for a namespace node, include all the namespace node's included children in the sections. */ if (ns && !ns->includedChildren().isEmpty()) { const QList &children = ns->includedChildren(); for (const auto &child : children) { if (documentAll || child->hasDoc()) stdRefPageSwitch(stdSummarySections(), child); } } reduce(stdSummarySections()); reduce(stdDetailsSections()); allMembersSection().reduce(); } /*! Inserts the node \a n in one of the entries in the vector \a v depending on the node's type, access attribute, and a few other attributes if the node is a signal, slot, or function. */ void Sections::distributeNodeInSummaryVector(SectionVector &sv, Node *n) { if (n->isSharedCommentNode()) return; if (n->isFunction()) { auto *fn = static_cast(n); if (fn->isRelatedNonmember()) { if (fn->isMacro()) sv[Macros].insert(n); else sv[RelatedNonmembers].insert(n); return; } if (fn->isIgnored()) return; if (fn->isSlot()) { if (fn->isPublic()) sv[PublicSlots].insert(fn); else if (fn->isPrivate()) sv[PrivateSlots].insert(fn); else sv[ProtectedSlots].insert(fn); } else if (fn->isSignal()) { if (fn->isPublic()) sv[Signals].insert(fn); } else if (fn->isPublic()) { if (fn->isStatic()) sv[StaticPublicMembers].insert(fn); else if (!sv[PublicFunctions].insertReimplementedMember(fn)) sv[PublicFunctions].insert(fn); } else if (fn->isPrivate()) { if (fn->isStatic()) sv[StaticPrivateMembers].insert(fn); else if (!sv[PrivateFunctions].insertReimplementedMember(fn)) sv[PrivateFunctions].insert(fn); } else { // protected if (fn->isStatic()) sv[StaticProtectedMembers].insert(fn); else if (!sv[ProtectedFunctions].insertReimplementedMember(fn)) sv[ProtectedFunctions].insert(fn); } return; } if (n->isRelatedNonmember()) { sv[RelatedNonmembers].insert(n); return; } if (n->isVariable()) { if (n->isStatic()) { if (n->isPublic()) sv[StaticPublicMembers].insert(n); else if (n->isPrivate()) sv[StaticPrivateMembers].insert(n); else sv[StaticProtectedMembers].insert(n); } else { if (n->isPublic()) sv[PublicVariables].insert(n); else if (!n->isPrivate()) sv[ProtectedVariables].insert(n); } return; } /* Getting this far means the node is either a property or some kind of type, like an enum or a typedef. */ if (n->isTypedef() && (n->name() == QLatin1String("QtGadgetHelper"))) return; if (n->isProperty()) sv[Properties].insert(n); else if (n->isPublic()) sv[PublicTypes].insert(n); else if (n->isPrivate()) sv[PrivateTypes].insert(n); else sv[ProtectedTypes].insert(n); } /*! Inserts the node \a n in one of the entries in the vector \a v depending on the node's type, access attribute, and a few other attributes if the node is a signal, slot, or function. */ void Sections::distributeNodeInDetailsVector(SectionVector &dv, Node *n) { if (n->isSharingComment()) return; // t is the reference node to be tested - typically it's this node (n), but for // shared comment nodes we need to distribute based on the nodes in its collective. Node *t = n; if (n->isSharedCommentNode() && n->hasDoc()) { auto *scn = static_cast(n); if (scn->collective().size()) t = scn->collective().first(); // TODO: warn about mixed node types in collective? } if (t->isFunction()) { auto *fn = static_cast(t); if (fn->isRelatedNonmember()) { if (fn->isMacro()) dv[DetailsMacros].insert(n); else dv[DetailsRelatedNonmembers].insert(n); return; } if (fn->isIgnored()) return; if (!fn->hasAssociatedProperties() || !fn->doc().isEmpty()) dv[DetailsMemberFunctions].insert(n); return; } if (t->isRelatedNonmember()) { dv[DetailsRelatedNonmembers].insert(n); return; } if (t->isEnumType() || t->isTypedef()) { if (t->name() != QLatin1String("QtGadgetHelper")) dv[DetailsMemberTypes].insert(n); return; } if (t->isProperty()) dv[DetailsProperties].insert(n); else if (t->isVariable() && !t->doc().isEmpty()) dv[DetailsMemberVariables].insert(n); } void Sections::distributeQmlNodeInDetailsVector(SectionVector &dv, Node *n) { if (n->isSharingComment()) return; // t is the reference node to be tested - typically it's this node (n), but for // shared comment nodes we need to distribute based on the nodes in its collective. Node *t = n; if (n->isSharedCommentNode() && n->hasDoc()) { if (n->isPropertyGroup()) { dv[QmlProperties].insert(n); return; } auto *scn = static_cast(n); if (scn->collective().size()) t = scn->collective().first(); // TODO: warn about mixed node types in collective? } if (t->isQmlProperty()) { auto *pn = static_cast(t); if (pn->isAttached()) dv[QmlAttachedProperties].insert(n); else dv[QmlProperties].insert(n); } else if (t->isFunction()) { auto *fn = static_cast(t); if (fn->isQmlSignal()) { if (fn->isAttached()) dv[QmlAttachedSignals].insert(n); else dv[QmlSignals].insert(n); } else if (fn->isQmlSignalHandler()) { dv[QmlSignalHandlers].insert(n); } else if (fn->isQmlMethod()) { if (fn->isAttached()) dv[QmlAttachedMethods].insert(n); else dv[QmlMethods].insert(n); } } } /*! Distributes a node \a n into the correct place in the summary section vector \a sv. Nodes that are sharing a comment are handled recursively - for recursion, the \a sharing parameter is set to \c true. */ void Sections::distributeQmlNodeInSummaryVector(SectionVector &sv, Node *n, bool sharing) { if (n->isSharingComment() && !sharing) return; if (n->isQmlProperty()) { auto *pn = static_cast(n); if (pn->isAttached()) sv[QmlAttachedProperties].insert(pn); else sv[QmlProperties].insert(pn); } else if (n->isFunction()) { auto *fn = static_cast(n); if (fn->isQmlSignal()) { if (fn->isAttached()) sv[QmlAttachedSignals].insert(fn); else sv[QmlSignals].insert(fn); } else if (fn->isQmlSignalHandler()) { sv[QmlSignalHandlers].insert(fn); } else if (fn->isQmlMethod()) { if (fn->isAttached()) sv[QmlAttachedMethods].insert(fn); else sv[QmlMethods].insert(fn); } } else if (n->isSharedCommentNode()) { auto *scn = static_cast(n); if (scn->isPropertyGroup()) { sv[QmlProperties].insert(scn); } else { for (const auto &child : scn->collective()) distributeQmlNodeInSummaryVector(sv, child, true); } } } static void pushBaseClasses(QStack &stack, ClassNode *cn) { const QList baseClasses = cn->baseClasses(); for (const auto &cls : baseClasses) { if (cls.m_node) stack.prepend(cls.m_node); } } /*! Build the section vectors for a standard reference page, when the aggregate node is a C++. */ void Sections::buildStdCppClassRefPageSections() { SectionVector &summarySections = stdCppClassSummarySections(); SectionVector &detailsSections = stdCppClassDetailsSections(); Section &allMembers = allMembersSection(); for (auto it = m_aggregate->constBegin(); it != m_aggregate->constEnd(); ++it) { Node *n = *it; if (!n->isPrivate() && !n->isProperty() && !n->isRelatedNonmember() && !n->isSharedCommentNode()) allMembers.insert(n); distributeNodeInSummaryVector(summarySections, n); distributeNodeInDetailsVector(detailsSections, n); } if (!m_aggregate->relatedByProxy().isEmpty()) { const QList relatedBy = m_aggregate->relatedByProxy(); for (const auto &node : relatedBy) distributeNodeInSummaryVector(summarySections, node); } QStack stack; auto *cn = static_cast(m_aggregate); pushBaseClasses(stack, cn); while (!stack.isEmpty()) { ClassNode *cn = stack.pop(); for (auto it = cn->constBegin(); it != cn->constEnd(); ++it) { Node *n = *it; if (!n->isPrivate() && !n->isProperty() && !n->isRelatedNonmember() && !n->isSharedCommentNode()) allMembers.insert(n); } pushBaseClasses(stack, cn); } reduce(summarySections); reduce(detailsSections); allMembers.reduce(); } /*! Build the section vectors for a standard reference page, when the aggregate node is a QML type. */ void Sections::buildStdQmlTypeRefPageSections() { ClassNodes *classNodes = nullptr; SectionVector &summarySections = stdQmlTypeSummarySections(); SectionVector &detailsSections = stdQmlTypeDetailsSections(); Section &allMembers = allMembersSection(); const Aggregate *qtn = m_aggregate; while (qtn) { if (!qtn->isAbstract() || !classNodes) classNodes = &allMembers.classNodesList().emplace_back(static_cast(qtn), NodeVector{}); for (const auto n : qtn->childNodes()) { if (n->isInternal()) continue; // Skip overridden property/function documentation from abstract base type if (qtn != m_aggregate && qtn->isAbstract()) { NodeList candidates; m_aggregate->findChildren(n->name(), candidates); if (std::any_of(candidates.cbegin(), candidates.cend(), [&n](const Node *c) { if (c->nodeType() == n->nodeType()) { if (!n->isFunction() || static_cast(n)->compare(c, false)) return true; } return false; })) { continue; } } if (!n->isSharedCommentNode() || n->isPropertyGroup()) { allMembers.insert(n); classNodes->second.push_back(n); } if (qtn == m_aggregate || qtn->isAbstract()) { distributeQmlNodeInSummaryVector(summarySections, n); distributeQmlNodeInDetailsVector(detailsSections, n); } } if (qtn->qmlBaseNode() == qtn) { qCDebug(lcQdoc, "error: circular type definition: '%s' inherits itself", qPrintable(qtn->name())); break; } qtn = static_cast(qtn->qmlBaseNode()); } reduce(summarySections); reduce(detailsSections); allMembers.reduce(); } /*! Returns true if any sections in this object contain obsolete members. If it returns false, then \a summary_spv and \a details_spv have not been modified. Otherwise, both vectors will contain pointers to the sections that contain obsolete members. */ bool Sections::hasObsoleteMembers(SectionPtrVector *summary_spv, SectionPtrVector *details_spv) const { const SectionVector *sections = nullptr; if (m_aggregate->isClassNode()) sections = &stdCppClassSummarySections(); else if (m_aggregate->isQmlType()) sections = &stdQmlTypeSummarySections(); else sections = &stdSummarySections(); for (const auto §ion : *sections) { if (!section.obsoleteMembers().isEmpty()) summary_spv->append(§ion); } if (m_aggregate->isClassNode()) sections = &stdCppClassDetailsSections(); else if (m_aggregate->isQmlType()) sections = &stdQmlTypeDetailsSections(); else sections = &stdDetailsSections(); for (const auto &it : *sections) { if (!it.obsoleteMembers().isEmpty()) details_spv->append(&it); } return !summary_spv->isEmpty(); } QT_END_NAMESPACE