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 "sections.h" |
5 | |
6 | #include "aggregate.h" |
7 | #include "classnode.h" |
8 | #include "config.h" |
9 | #include "enumnode.h" |
10 | #include "functionnode.h" |
11 | #include "generator.h" |
12 | #include "utilities.h" |
13 | #include "namespacenode.h" |
14 | #include "qmlpropertynode.h" |
15 | #include "qmltypenode.h" |
16 | #include "sharedcommentnode.h" |
17 | #include "typedefnode.h" |
18 | #include "variablenode.h" |
19 | |
20 | #include <QtCore/qobjectdefs.h> |
21 | |
22 | QT_BEGIN_NAMESPACE |
23 | |
24 | QList<Section> Sections::s_stdSummarySections { |
25 | { "Namespaces" , "namespace" , "namespaces" , "" , Section::Summary }, |
26 | { "Classes" , "class" , "classes" , "" , Section::Summary }, |
27 | { "Types" , "type" , "types" , "" , Section::Summary }, |
28 | { "Variables" , "variable" , "variables" , "" , Section::Summary }, |
29 | { "Static Variables" , "static variable" , "static variables" , "" , Section::Summary }, |
30 | { "Functions" , "function" , "functions" , "" , Section::Summary }, |
31 | { "Macros" , "macro" , "macros" , "" , Section::Summary }, |
32 | }; |
33 | |
34 | QList<Section> Sections::s_stdDetailsSections { |
35 | { "Namespaces" , "namespace" , "namespaces" , "nmspace" , Section::Details }, |
36 | { "Classes" , "class" , "classes" , "classes" , Section::Details }, |
37 | { "Type Documentation" , "type" , "types" , "types" , Section::Details }, |
38 | { "Variable Documentation" , "variable" , "variables" , "vars" , Section::Details }, |
39 | { "Static Variables" , "static variable" , "static variables" , QString(), Section::Details }, |
40 | { "Function Documentation" , "function" , "functions" , "func" , Section::Details }, |
41 | { "Macro Documentation" , "macro" , "macros" , "macros" , Section::Details }, |
42 | }; |
43 | |
44 | QList<Section> Sections::s_stdCppClassSummarySections { |
45 | { "Public Types" , "public type" , "public types" , "" , Section::Summary }, |
46 | { "Properties" , "property" , "properties" , "" , Section::Summary }, |
47 | { "Public Functions" , "public function" , "public functions" , "" , Section::Summary }, |
48 | { "Public Slots" , "public slot" , "public slots" , "" , Section::Summary }, |
49 | { "Signals" , "signal" , "signals" , "" , Section::Summary }, |
50 | { "Public Variables" , "public variable" , "public variables" , "" , Section::Summary }, |
51 | { "Static Public Members" , "static public member" , "static public members" , "" , Section::Summary }, |
52 | { "Protected Types" , "protected type" , "protected types" , "" , Section::Summary }, |
53 | { "Protected Functions" , "protected function" , "protected functions" , "" , Section::Summary }, |
54 | { "Protected Slots" , "protected slot" , "protected slots" , "" , Section::Summary }, |
55 | { "Protected Variables" , "protected type" , "protected variables" , "" , Section::Summary }, |
56 | { "Static Protected Members" , "static protected member" , "static protected members" , "" , Section::Summary }, |
57 | { "Private Types" , "private type" , "private types" , "" , Section::Summary }, |
58 | { "Private Functions" , "private function" , "private functions" , "" , Section::Summary }, |
59 | { "Private Slots" , "private slot" , "private slots" , "" , Section::Summary }, |
60 | { "Static Private Members" , "static private member" , "static private members" , "" , Section::Summary }, |
61 | { "Related Non-Members" , "related non-member" , "related non-members" , "" , Section::Summary }, |
62 | { "Macros" , "macro" , "macros" , "" , Section::Summary }, |
63 | }; |
64 | |
65 | QList<Section> Sections::s_stdCppClassDetailsSections { |
66 | { "Member Type Documentation" , "member" , "members" , "types" , Section::Details }, |
67 | { "Property Documentation" , "member" , "members" , "prop" , Section::Details }, |
68 | { "Member Function Documentation" , "member" , "members" , "func" , Section::Details }, |
69 | { "Member Variable Documentation" , "member" , "members" , "vars" , Section::Details }, |
70 | { "Related Non-Members" , "member" , "members" , "relnonmem" , Section::Details }, |
71 | { "Macro Documentation" , "member" , "members" , "macros" , Section::Details }, |
72 | }; |
73 | |
74 | QList<Section> Sections::s_stdQmlTypeSummarySections { |
75 | { "Properties" , "property" , "properties" , "" , Section::Summary }, |
76 | { "Attached Properties" , "attached property" , "attached properties" , "" , Section::Summary }, |
77 | { "Signals" , "signal" , "signals" , "" , Section::Summary }, |
78 | { "Signal Handlers" , "signal handler" , "signal handlers" , "" , Section::Summary }, |
79 | { "Attached Signals" , "attached signal" , "attached signals" , "" , Section::Summary }, |
80 | { "Methods" , "method" , "methods" , "" , Section::Summary }, |
81 | { "Attached Methods" , "attached method" , "attached methods" , "" , Section::Summary }, |
82 | }; |
83 | |
84 | QList<Section> Sections::s_stdQmlTypeDetailsSections { |
85 | { "Property Documentation" , "member" , "members" , "qmlprop" , Section::Details }, |
86 | { "Attached Property Documentation" , "member" , "members" , "qmlattprop" , Section::Details }, |
87 | { "Signal Documentation" , "signal" , "signals" , "qmlsig" , Section::Details }, |
88 | { "Signal Handler Documentation" , "signal handler" , "signal handlers" , "qmlsighan" , Section::Details }, |
89 | { "Attached Signal Documentation" , "signal" , "signals" , "qmlattsig" , Section::Details }, |
90 | { "Method Documentation" , "member" , "members" , "qmlmeth" , Section::Details }, |
91 | { "Attached Method Documentation" , "member" , "members" , "qmlattmeth" , Section::Details }, |
92 | }; |
93 | |
94 | QList<Section> Sections::s_sinceSections { |
95 | { "New Namespaces" , "" , "" , "" , Section::Details }, |
96 | { "New Classes" , "" , "" , "" , Section::Details }, |
97 | { "New Member Functions" , "" , "" , "" , Section::Details }, |
98 | { "New Functions in Namespaces" , "" , "" , "" , Section::Details }, |
99 | { "New Global Functions" , "" , "" , "" , Section::Details }, |
100 | { "New Macros" , "" , "" , "" , Section::Details }, |
101 | { "New Enum Types" , "" , "" , "" , Section::Details }, |
102 | { "New Enum Values" , "" , "" , "" , Section::Details }, |
103 | { "New Type Aliases" , "" , "" , "" , Section::Details }, |
104 | { "New Properties" , "" , "" , "" , Section::Details }, |
105 | { "New Variables" , "" , "" , "" , Section::Details }, |
106 | { "New QML Types" , "" , "" , "" , Section::Details }, |
107 | { "New QML Properties" , "" , "" , "" , Section::Details }, |
108 | { "New QML Signals" , "" , "" , "" , Section::Details }, |
109 | { "New QML Signal Handlers" , "" , "" , "" , Section::Details }, |
110 | { "New QML Methods" , "" , "" , "" , Section::Details }, |
111 | }; |
112 | |
113 | QList<Section> Sections::s_allMembers{ { "" , "member" , "members" , "" , Section::AllMembers } }; |
114 | |
115 | /*! |
116 | \class Section |
117 | \brief A class for containing the elements of one documentation section |
118 | */ |
119 | |
120 | /*! |
121 | The destructor must delete the members of collections |
122 | when the members are allocated on the heap. |
123 | */ |
124 | Section::~Section() |
125 | { |
126 | clear(); |
127 | } |
128 | |
129 | /*! |
130 | A Section is now an element in a static vector, so we |
131 | don't have to repeatedly construct and destroy them. But |
132 | we do need to clear them before each call to build the |
133 | sections for a C++ or QML entity. |
134 | */ |
135 | void Section::clear() |
136 | { |
137 | m_reimplementedMemberMap.clear(); |
138 | m_members.clear(); |
139 | m_obsoleteMembers.clear(); |
140 | m_reimplementedMembers.clear(); |
141 | m_inheritedMembers.clear(); |
142 | m_classNodesList.clear(); |
143 | m_aggregate = nullptr; |
144 | } |
145 | |
146 | /*! |
147 | Construct a name for the \a node that can be used for sorting |
148 | a set of nodes into equivalence classes. |
149 | */ |
150 | QString sortName(const Node *node) |
151 | { |
152 | QString nodeName{node->name()}; |
153 | |
154 | int numDigits = 0; |
155 | for (qsizetype i = nodeName.size() - 1; i > 0; --i) { |
156 | if (nodeName.at(i).digitValue() == -1) |
157 | break; |
158 | ++numDigits; |
159 | } |
160 | |
161 | // we want 'qint8' to appear before 'qint16' |
162 | if (numDigits > 0) { |
163 | for (int i = 0; i < 4 - numDigits; ++i) |
164 | nodeName.insert(i: nodeName.size() - numDigits - 1, c: QLatin1Char('0')); |
165 | } |
166 | |
167 | if (node->isClassNode()) |
168 | return QLatin1Char('A') + nodeName; |
169 | |
170 | if (node->isFunction(g: Node::CPP)) { |
171 | const auto *fn = static_cast<const FunctionNode *>(node); |
172 | |
173 | QString sortNo; |
174 | if (fn->isCtor()) |
175 | sortNo = QLatin1String("C" ); |
176 | else if (fn->isCCtor()) |
177 | sortNo = QLatin1String("D" ); |
178 | else if (fn->isMCtor()) |
179 | sortNo = QLatin1String("E" ); |
180 | else if (fn->isDtor()) |
181 | sortNo = QLatin1String("F" ); |
182 | else if (nodeName.startsWith(s: QLatin1String("operator" )) && nodeName.size() > 8 |
183 | && !nodeName[8].isLetterOrNumber()) |
184 | sortNo = QLatin1String("H" ); |
185 | else |
186 | sortNo = QLatin1String("G" ); |
187 | |
188 | return sortNo + nodeName + QLatin1Char(' ') + QString::number(fn->overloadNumber(), base: 36); |
189 | } |
190 | |
191 | if (node->isFunction(g: Node::QML)) |
192 | return QLatin1Char('E') + nodeName + QLatin1Char(' ') + |
193 | QString::number(static_cast<const FunctionNode*>(node)->overloadNumber(), base: 36); |
194 | |
195 | if (node->isProperty() || node->isVariable()) |
196 | return QLatin1Char('G') + nodeName; |
197 | |
198 | return QLatin1Char('B') + nodeName; |
199 | } |
200 | |
201 | /*! |
202 | Inserts the \a node into this section if it is appropriate |
203 | for this section. |
204 | */ |
205 | void Section::insert(Node *node) |
206 | { |
207 | bool irrelevant = false; |
208 | bool inherited = false; |
209 | if (!node->isRelatedNonmember()) { |
210 | Aggregate *p = node->parent(); |
211 | if (!p->isNamespace() && p != m_aggregate) { |
212 | if (!p->isQmlType() || !p->isAbstract()) |
213 | inherited = true; |
214 | } |
215 | } |
216 | |
217 | if (node->isPrivate() || node->isInternal()) { |
218 | irrelevant = true; |
219 | } else if (node->isFunction()) { |
220 | auto *func = static_cast<FunctionNode *>(node); |
221 | irrelevant = (inherited && (func->isSomeCtor() || func->isDtor())); |
222 | } else if (node->isClassNode() || node->isEnumType() || node->isTypedef() |
223 | || node->isVariable()) { |
224 | irrelevant = (inherited && m_style != AllMembers); |
225 | if (!irrelevant && m_style == Details && node->isTypedef()) { |
226 | const auto *tdn = static_cast<const TypedefNode *>(node); |
227 | if (tdn->associatedEnum()) |
228 | irrelevant = true; |
229 | } |
230 | } |
231 | |
232 | if (!irrelevant) { |
233 | QString key = sortName(node); |
234 | if (node->isDeprecated()) { |
235 | m_obsoleteMembers.push_back(t: node); |
236 | } else { |
237 | if (!inherited || m_style == AllMembers) |
238 | m_members.push_back(t: node); |
239 | |
240 | if (inherited && (node->parent()->isClassNode() || node->parent()->isNamespace())) { |
241 | if (m_inheritedMembers.isEmpty() |
242 | || m_inheritedMembers.last().first != node->parent()) { |
243 | std::pair<Aggregate *, int> p(node->parent(), 0); |
244 | m_inheritedMembers.append(t: p); |
245 | } |
246 | m_inheritedMembers.last().second++; |
247 | } |
248 | } |
249 | } |
250 | } |
251 | |
252 | /*! |
253 | Returns \c true if the \a node is a reimplemented member |
254 | function of the current class. If true, the \a node is |
255 | inserted into the reimplemented member map. True |
256 | is returned only if \a node is inserted into the map. |
257 | That is, false is returned if the \a node is already in |
258 | the map. |
259 | */ |
260 | bool Section::insertReimplementedMember(Node *node) |
261 | { |
262 | if (!node->isPrivate() && !node->isRelatedNonmember()) { |
263 | const auto *fn = static_cast<const FunctionNode *>(node); |
264 | if (!fn->overridesThis().isEmpty()) { |
265 | if (fn->parent() == m_aggregate) { |
266 | QString key = sortName(node: fn); |
267 | if (!m_reimplementedMemberMap.contains(key)) { |
268 | m_reimplementedMemberMap.insert(key, value: node); |
269 | return true; |
270 | } |
271 | } |
272 | } |
273 | } |
274 | return false; |
275 | } |
276 | |
277 | /*! |
278 | If this section is not empty, convert its maps to sequential |
279 | structures for better traversal during doc generation. |
280 | */ |
281 | void Section::reduce() |
282 | { |
283 | // TODO:TEMPORARY:INTERMEDITATE: Section uses a series of maps |
284 | // to internally manage the categorization of the various members |
285 | // of an aggregate. It further uses a secondary "flattened" |
286 | // (usually vector) version that is later used by consumers of a |
287 | // Section content. |
288 | // |
289 | // One of the uses of those maps is that of ordering, by using |
290 | // keys generated with `sortName`. |
291 | // Nonetheless, this is the only usage that comes from the keys, |
292 | // as they are neither necessary nor used outside of the internal |
293 | // code for Section. |
294 | // |
295 | // Hence, the codebase is moving towards removing the maps in |
296 | // favor of building a flattened, consumer ready, version of the |
297 | // categorization directly, cutting the intermediate conversion |
298 | // step. |
299 | // |
300 | // To do so while keeping as much of the old behavior as possible, |
301 | // we provide a sorting for the flattened version that is based on |
302 | // `sortName`, as the previous ordering was. |
303 | // |
304 | // This acts as a relatively heavy pessimization, as `sortName`, |
305 | // used as a comparator, can be called multiple times for each |
306 | // Node, while before it would have been called almost-once. |
307 | // |
308 | // Instead of fixing this issue, by for example caching the |
309 | // sortName of each Node instance, we temporarily keep the |
310 | // pessimization while the various maps are removed. |
311 | // |
312 | // When all the maps are removed, we can remove `sortName`, which |
313 | // produces strings to use as key requiring a few allocations and |
314 | // expensive operations, with an actual comparator function, which |
315 | // should be more lightweight and more than offset the |
316 | // multiple-calls. |
317 | static auto node_less_than = [](const Node* left, const Node* right) { |
318 | return sortName(node: left) < sortName(node: right); |
319 | }; |
320 | |
321 | std::stable_sort(first: m_members.begin(), last: m_members.end(), comp: node_less_than); |
322 | std::stable_sort(first: m_obsoleteMembers.begin(), last: m_obsoleteMembers.end(), comp: node_less_than); |
323 | |
324 | m_reimplementedMembers = m_reimplementedMemberMap.values().toVector(); |
325 | |
326 | for (auto &cn : m_classNodesList) { |
327 | std::stable_sort(first: cn.second.begin(), last: cn.second.end(), comp: node_less_than); |
328 | } |
329 | } |
330 | |
331 | /*! |
332 | \class Sections |
333 | \brief A class for creating vectors of collections for documentation |
334 | |
335 | Each element in a vector is an instance of Section, which |
336 | contains all the elements that will be documented in one |
337 | section of a reference documentation page. |
338 | */ |
339 | |
340 | /*! |
341 | This constructor builds the vectors of sections based on the |
342 | type of the \a aggregate node. |
343 | */ |
344 | Sections::Sections(Aggregate *aggregate) : m_aggregate(aggregate) |
345 | { |
346 | initAggregate(v&: s_allMembers, aggregate: m_aggregate); |
347 | switch (m_aggregate->nodeType()) { |
348 | case Node::Class: |
349 | case Node::Struct: |
350 | case Node::Union: |
351 | initAggregate(v&: s_stdCppClassSummarySections, aggregate: m_aggregate); |
352 | initAggregate(v&: s_stdCppClassDetailsSections, aggregate: m_aggregate); |
353 | buildStdCppClassRefPageSections(); |
354 | break; |
355 | case Node::QmlType: |
356 | case Node::QmlValueType: |
357 | initAggregate(v&: s_stdQmlTypeSummarySections, aggregate: m_aggregate); |
358 | initAggregate(v&: s_stdQmlTypeDetailsSections, aggregate: m_aggregate); |
359 | buildStdQmlTypeRefPageSections(); |
360 | break; |
361 | case Node::Namespace: |
362 | case Node::HeaderFile: |
363 | case Node::Proxy: |
364 | default: |
365 | initAggregate(v&: s_stdSummarySections, aggregate: m_aggregate); |
366 | initAggregate(v&: s_stdDetailsSections, aggregate: m_aggregate); |
367 | buildStdRefPageSections(); |
368 | break; |
369 | } |
370 | } |
371 | |
372 | /*! |
373 | This constructor builds a vector of sections from the \e since |
374 | node map, \a nsmap |
375 | */ |
376 | Sections::Sections(const NodeMultiMap &nsmap) : m_aggregate(nullptr) |
377 | { |
378 | if (nsmap.isEmpty()) |
379 | return; |
380 | SectionVector §ions = sinceSections(); |
381 | for (auto it = nsmap.constBegin(); it != nsmap.constEnd(); ++it) { |
382 | Node *node = it.value(); |
383 | switch (node->nodeType()) { |
384 | case Node::QmlType: |
385 | sections[SinceQmlTypes].appendMember(node); |
386 | break; |
387 | case Node::Namespace: |
388 | sections[SinceNamespaces].appendMember(node); |
389 | break; |
390 | case Node::Class: |
391 | case Node::Struct: |
392 | case Node::Union: |
393 | sections[SinceClasses].appendMember(node); |
394 | break; |
395 | case Node::Enum: { |
396 | // The map can contain an enum node with \since, or an enum node |
397 | // with \value containing a since-clause. In the latter case, |
398 | // key() is an empty string. |
399 | if (!it.key().isEmpty()) |
400 | sections[SinceEnumTypes].appendMember(node); |
401 | else |
402 | sections[SinceEnumValues].appendMember(node); |
403 | break; |
404 | } |
405 | case Node::Typedef: |
406 | case Node::TypeAlias: |
407 | sections[SinceTypeAliases].appendMember(node); |
408 | break; |
409 | case Node::Function: { |
410 | const auto *fn = static_cast<const FunctionNode *>(node); |
411 | switch (fn->metaness()) { |
412 | case FunctionNode::QmlSignal: |
413 | sections[SinceQmlSignals].appendMember(node); |
414 | break; |
415 | case FunctionNode::QmlSignalHandler: |
416 | sections[SinceQmlSignalHandlers].appendMember(node); |
417 | break; |
418 | case FunctionNode::QmlMethod: |
419 | sections[SinceQmlMethods].appendMember(node); |
420 | break; |
421 | default: |
422 | if (fn->isMacro()) |
423 | sections[SinceMacros].appendMember(node); |
424 | else { |
425 | Node *p = fn->parent(); |
426 | if (p) { |
427 | if (p->isClassNode()) |
428 | sections[SinceMemberFunctions].appendMember(node); |
429 | else if (p->isNamespace()) { |
430 | if (p->name().isEmpty()) |
431 | sections[SinceGlobalFunctions].appendMember(node); |
432 | else |
433 | sections[SinceNamespaceFunctions].appendMember(node); |
434 | } else |
435 | sections[SinceGlobalFunctions].appendMember(node); |
436 | } else |
437 | sections[SinceGlobalFunctions].appendMember(node); |
438 | } |
439 | break; |
440 | } |
441 | break; |
442 | } |
443 | case Node::Property: |
444 | sections[SinceProperties].appendMember(node); |
445 | break; |
446 | case Node::Variable: |
447 | sections[SinceVariables].appendMember(node); |
448 | break; |
449 | case Node::QmlProperty: |
450 | sections[SinceQmlProperties].appendMember(node); |
451 | break; |
452 | default: |
453 | break; |
454 | } |
455 | } |
456 | } |
457 | |
458 | /*! |
459 | The behavior of the destructor depends on the type of the |
460 | Aggregate node that was passed to the constructor. If the |
461 | constructor was passed a multimap, the destruction is a |
462 | bit different because there was no Aggregate node. |
463 | */ |
464 | Sections::~Sections() |
465 | { |
466 | if (m_aggregate) { |
467 | switch (m_aggregate->nodeType()) { |
468 | case Node::Class: |
469 | case Node::Struct: |
470 | case Node::Union: |
471 | clear(v&: stdCppClassSummarySections()); |
472 | clear(v&: stdCppClassDetailsSections()); |
473 | allMembersSection().clear(); |
474 | break; |
475 | case Node::QmlType: |
476 | case Node::QmlValueType: |
477 | clear(v&: stdQmlTypeSummarySections()); |
478 | clear(v&: stdQmlTypeDetailsSections()); |
479 | allMembersSection().clear(); |
480 | break; |
481 | default: |
482 | clear(v&: stdSummarySections()); |
483 | clear(v&: stdDetailsSections()); |
484 | allMembersSection().clear(); |
485 | break; |
486 | } |
487 | m_aggregate = nullptr; |
488 | } else { |
489 | clear(v&: sinceSections()); |
490 | } |
491 | } |
492 | |
493 | /*! |
494 | Initialize the Aggregate in each Section of vector \a v with \a aggregate. |
495 | */ |
496 | void Sections::initAggregate(SectionVector &v, Aggregate *aggregate) |
497 | { |
498 | for (Section §ion : v) |
499 | section.setAggregate(aggregate); |
500 | } |
501 | |
502 | /*! |
503 | Reset each Section in vector \a v to its initialized state. |
504 | */ |
505 | void Sections::clear(QList<Section> &v) |
506 | { |
507 | for (Section §ion : v) |
508 | section.clear(); |
509 | } |
510 | |
511 | /*! |
512 | Linearize the maps in each Section in \a v. |
513 | */ |
514 | void Sections::reduce(QList<Section> &v) |
515 | { |
516 | for (Section §ion : v) |
517 | section.reduce(); |
518 | } |
519 | |
520 | /*! |
521 | This is a private helper function for buildStdRefPageSections(). |
522 | */ |
523 | void Sections::stdRefPageSwitch(SectionVector &v, Node *n, Node *t) |
524 | { |
525 | // t is the reference node to be tested, n is the node to be distributed. |
526 | // t differs from n only for shared comment nodes. |
527 | if (!t) |
528 | t = n; |
529 | |
530 | switch (t->nodeType()) { |
531 | case Node::Namespace: |
532 | v[StdNamespaces].insert(node: n); |
533 | return; |
534 | case Node::Class: |
535 | case Node::Struct: |
536 | case Node::Union: |
537 | v[StdClasses].insert(node: n); |
538 | return; |
539 | case Node::Enum: |
540 | case Node::Typedef: |
541 | case Node::TypeAlias: |
542 | v[StdTypes].insert(node: n); |
543 | return; |
544 | case Node::Function: { |
545 | auto *func = static_cast<FunctionNode *>(t); |
546 | if (func->isMacro()) |
547 | v[StdMacros].insert(node: n); |
548 | else |
549 | v[StdFunctions].insert(node: n); |
550 | } |
551 | return; |
552 | case Node::Variable: { |
553 | const auto *var = static_cast<const VariableNode *>(t); |
554 | if (!var->doc().isEmpty()) { |
555 | if (var->isStatic()) |
556 | v[StdStaticVariables].insert(node: n); |
557 | else |
558 | v[StdVariables].insert(node: n); |
559 | } |
560 | } |
561 | return; |
562 | case Node::SharedComment: { |
563 | auto *scn = static_cast<SharedCommentNode *>(t); |
564 | if (!scn->doc().isEmpty() && scn->collective().size()) |
565 | stdRefPageSwitch( |
566 | v, n: scn, |
567 | t: scn->collective().first()); // TODO: warn about mixed node types in collective? |
568 | } |
569 | return; |
570 | default: |
571 | return; |
572 | } |
573 | } |
574 | |
575 | /*! |
576 | Build the section vectors for a standard reference page, |
577 | when the aggregate node is not a C++ class or a QML type. |
578 | |
579 | If this is for a namespace page then if the namespace node |
580 | itself does not have documentation, only its children that |
581 | have documentation should be documented. In other words, |
582 | there are cases where a namespace is declared but does not |
583 | have documentation, but some of the elements declared in |
584 | that namespace do have documentation. |
585 | |
586 | This special processing of namespaces that do not have a |
587 | documentation comment is meant to allow documenting its |
588 | members that do have documentation while avoiding posting |
589 | error messages for its members that are not documented. |
590 | */ |
591 | void Sections::buildStdRefPageSections() |
592 | { |
593 | const NamespaceNode *ns = nullptr; |
594 | bool documentAll = true; // document all the children |
595 | if (m_aggregate->isNamespace()) { |
596 | ns = static_cast<const NamespaceNode *>(m_aggregate); |
597 | if (!ns->hasDoc()) |
598 | documentAll = false; // only document children that have documentation |
599 | } |
600 | for (auto it = m_aggregate->constBegin(); it != m_aggregate->constEnd(); ++it) { |
601 | Node *n = *it; |
602 | if (documentAll || n->hasDoc()) { |
603 | stdRefPageSwitch(v&: stdSummarySections(), n); |
604 | stdRefPageSwitch(v&: stdDetailsSections(), n); |
605 | } |
606 | } |
607 | if (!m_aggregate->relatedByProxy().isEmpty()) { |
608 | const QList<Node *> &relatedBy = m_aggregate->relatedByProxy(); |
609 | for (const auto &node : relatedBy) |
610 | stdRefPageSwitch(v&: stdSummarySections(), n: node); |
611 | } |
612 | /* |
613 | If we are building the sections for the reference page |
614 | for a namespace node, include all the namespace node's |
615 | included children in the sections. |
616 | */ |
617 | if (ns && !ns->includedChildren().isEmpty()) { |
618 | const QList<Node *> &children = ns->includedChildren(); |
619 | for (const auto &child : children) { |
620 | if (documentAll || child->hasDoc()) |
621 | stdRefPageSwitch(v&: stdSummarySections(), n: child); |
622 | } |
623 | } |
624 | reduce(v&: stdSummarySections()); |
625 | reduce(v&: stdDetailsSections()); |
626 | allMembersSection().reduce(); |
627 | } |
628 | |
629 | /*! |
630 | Inserts the node \a n in one of the entries in the vector \a v |
631 | depending on the node's type, access attribute, and a few other |
632 | attributes if the node is a signal, slot, or function. |
633 | */ |
634 | void Sections::distributeNodeInSummaryVector(SectionVector &sv, Node *n) |
635 | { |
636 | if (n->isSharedCommentNode()) |
637 | return; |
638 | if (n->isFunction()) { |
639 | auto *fn = static_cast<FunctionNode *>(n); |
640 | if (fn->isRelatedNonmember()) { |
641 | if (fn->isMacro()) |
642 | sv[Macros].insert(node: n); |
643 | else |
644 | sv[RelatedNonmembers].insert(node: n); |
645 | return; |
646 | } |
647 | if (fn->isIgnored()) |
648 | return; |
649 | if (fn->isSlot()) { |
650 | if (fn->isPublic()) |
651 | sv[PublicSlots].insert(node: fn); |
652 | else if (fn->isPrivate()) |
653 | sv[PrivateSlots].insert(node: fn); |
654 | else |
655 | sv[ProtectedSlots].insert(node: fn); |
656 | } else if (fn->isSignal()) { |
657 | if (fn->isPublic()) |
658 | sv[Signals].insert(node: fn); |
659 | } else if (fn->isPublic()) { |
660 | if (fn->isStatic()) |
661 | sv[StaticPublicMembers].insert(node: fn); |
662 | else if (!sv[PublicFunctions].insertReimplementedMember(node: fn)) |
663 | sv[PublicFunctions].insert(node: fn); |
664 | } else if (fn->isPrivate()) { |
665 | if (fn->isStatic()) |
666 | sv[StaticPrivateMembers].insert(node: fn); |
667 | else if (!sv[PrivateFunctions].insertReimplementedMember(node: fn)) |
668 | sv[PrivateFunctions].insert(node: fn); |
669 | } else { // protected |
670 | if (fn->isStatic()) |
671 | sv[StaticProtectedMembers].insert(node: fn); |
672 | else if (!sv[ProtectedFunctions].insertReimplementedMember(node: fn)) |
673 | sv[ProtectedFunctions].insert(node: fn); |
674 | } |
675 | return; |
676 | } |
677 | if (n->isRelatedNonmember()) { |
678 | sv[RelatedNonmembers].insert(node: n); |
679 | return; |
680 | } |
681 | if (n->isVariable()) { |
682 | if (n->isStatic()) { |
683 | if (n->isPublic()) |
684 | sv[StaticPublicMembers].insert(node: n); |
685 | else if (n->isPrivate()) |
686 | sv[StaticPrivateMembers].insert(node: n); |
687 | else |
688 | sv[StaticProtectedMembers].insert(node: n); |
689 | } else { |
690 | if (n->isPublic()) |
691 | sv[PublicVariables].insert(node: n); |
692 | else if (!n->isPrivate()) |
693 | sv[ProtectedVariables].insert(node: n); |
694 | } |
695 | return; |
696 | } |
697 | /* |
698 | Getting this far means the node is either a property |
699 | or some kind of type, like an enum or a typedef. |
700 | */ |
701 | if (n->isTypedef() && (n->name() == QLatin1String("QtGadgetHelper" ))) |
702 | return; |
703 | if (n->isProperty()) |
704 | sv[Properties].insert(node: n); |
705 | else if (n->isPublic()) |
706 | sv[PublicTypes].insert(node: n); |
707 | else if (n->isPrivate()) |
708 | sv[PrivateTypes].insert(node: n); |
709 | else |
710 | sv[ProtectedTypes].insert(node: n); |
711 | } |
712 | |
713 | /*! |
714 | Inserts the node \a n in one of the entries in the vector \a v |
715 | depending on the node's type, access attribute, and a few other |
716 | attributes if the node is a signal, slot, or function. |
717 | */ |
718 | void Sections::distributeNodeInDetailsVector(SectionVector &dv, Node *n) |
719 | { |
720 | if (n->isSharingComment()) |
721 | return; |
722 | |
723 | // t is the reference node to be tested - typically it's this node (n), but for |
724 | // shared comment nodes we need to distribute based on the nodes in its collective. |
725 | Node *t = n; |
726 | |
727 | if (n->isSharedCommentNode() && n->hasDoc()) { |
728 | auto *scn = static_cast<SharedCommentNode *>(n); |
729 | if (scn->collective().size()) |
730 | t = scn->collective().first(); // TODO: warn about mixed node types in collective? |
731 | } |
732 | |
733 | if (t->isFunction()) { |
734 | auto *fn = static_cast<FunctionNode *>(t); |
735 | if (fn->isRelatedNonmember()) { |
736 | if (fn->isMacro()) |
737 | dv[DetailsMacros].insert(node: n); |
738 | else |
739 | dv[DetailsRelatedNonmembers].insert(node: n); |
740 | return; |
741 | } |
742 | if (fn->isIgnored()) |
743 | return; |
744 | if (!fn->hasAssociatedProperties() || !fn->doc().isEmpty()) |
745 | dv[DetailsMemberFunctions].insert(node: n); |
746 | return; |
747 | } |
748 | if (t->isRelatedNonmember()) { |
749 | dv[DetailsRelatedNonmembers].insert(node: n); |
750 | return; |
751 | } |
752 | if (t->isEnumType() || t->isTypedef()) { |
753 | if (t->name() != QLatin1String("QtGadgetHelper" )) |
754 | dv[DetailsMemberTypes].insert(node: n); |
755 | return; |
756 | } |
757 | if (t->isProperty()) |
758 | dv[DetailsProperties].insert(node: n); |
759 | else if (t->isVariable() && !t->doc().isEmpty()) |
760 | dv[DetailsMemberVariables].insert(node: n); |
761 | } |
762 | |
763 | void Sections::distributeQmlNodeInDetailsVector(SectionVector &dv, Node *n) |
764 | { |
765 | if (n->isSharingComment()) |
766 | return; |
767 | |
768 | // t is the reference node to be tested - typically it's this node (n), but for |
769 | // shared comment nodes we need to distribute based on the nodes in its collective. |
770 | Node *t = n; |
771 | |
772 | if (n->isSharedCommentNode() && n->hasDoc()) { |
773 | if (n->isPropertyGroup()) { |
774 | dv[QmlProperties].insert(node: n); |
775 | return; |
776 | } |
777 | auto *scn = static_cast<SharedCommentNode *>(n); |
778 | if (scn->collective().size()) |
779 | t = scn->collective().first(); // TODO: warn about mixed node types in collective? |
780 | } |
781 | |
782 | if (t->isQmlProperty()) { |
783 | auto *pn = static_cast<QmlPropertyNode *>(t); |
784 | if (pn->isAttached()) |
785 | dv[QmlAttachedProperties].insert(node: n); |
786 | else |
787 | dv[QmlProperties].insert(node: n); |
788 | } else if (t->isFunction()) { |
789 | auto *fn = static_cast<FunctionNode *>(t); |
790 | if (fn->isQmlSignal()) { |
791 | if (fn->isAttached()) |
792 | dv[QmlAttachedSignals].insert(node: n); |
793 | else |
794 | dv[QmlSignals].insert(node: n); |
795 | } else if (fn->isQmlSignalHandler()) { |
796 | dv[QmlSignalHandlers].insert(node: n); |
797 | } else if (fn->isQmlMethod()) { |
798 | if (fn->isAttached()) |
799 | dv[QmlAttachedMethods].insert(node: n); |
800 | else |
801 | dv[QmlMethods].insert(node: n); |
802 | } |
803 | } |
804 | } |
805 | |
806 | /*! |
807 | Distributes a node \a n into the correct place in the summary section vector \a sv. |
808 | Nodes that are sharing a comment are handled recursively - for recursion, the \a |
809 | sharing parameter is set to \c true. |
810 | */ |
811 | void Sections::distributeQmlNodeInSummaryVector(SectionVector &sv, Node *n, bool sharing) |
812 | { |
813 | if (n->isSharingComment() && !sharing) |
814 | return; |
815 | if (n->isQmlProperty()) { |
816 | auto *pn = static_cast<QmlPropertyNode *>(n); |
817 | if (pn->isAttached()) |
818 | sv[QmlAttachedProperties].insert(node: pn); |
819 | else |
820 | sv[QmlProperties].insert(node: pn); |
821 | } else if (n->isFunction()) { |
822 | auto *fn = static_cast<FunctionNode *>(n); |
823 | if (fn->isQmlSignal()) { |
824 | if (fn->isAttached()) |
825 | sv[QmlAttachedSignals].insert(node: fn); |
826 | else |
827 | sv[QmlSignals].insert(node: fn); |
828 | } else if (fn->isQmlSignalHandler()) { |
829 | sv[QmlSignalHandlers].insert(node: fn); |
830 | } else if (fn->isQmlMethod()) { |
831 | if (fn->isAttached()) |
832 | sv[QmlAttachedMethods].insert(node: fn); |
833 | else |
834 | sv[QmlMethods].insert(node: fn); |
835 | } |
836 | } else if (n->isSharedCommentNode()) { |
837 | auto *scn = static_cast<SharedCommentNode *>(n); |
838 | if (scn->isPropertyGroup()) { |
839 | sv[QmlProperties].insert(node: scn); |
840 | } else { |
841 | for (const auto &child : scn->collective()) |
842 | distributeQmlNodeInSummaryVector(sv, n: child, sharing: true); |
843 | } |
844 | } |
845 | } |
846 | |
847 | static void pushBaseClasses(QStack<ClassNode *> &stack, ClassNode *cn) |
848 | { |
849 | const QList<RelatedClass> baseClasses = cn->baseClasses(); |
850 | for (const auto &cls : baseClasses) { |
851 | if (cls.m_node) |
852 | stack.prepend(t: cls.m_node); |
853 | } |
854 | } |
855 | |
856 | /*! |
857 | Build the section vectors for a standard reference page, |
858 | when the aggregate node is a C++. |
859 | */ |
860 | void Sections::buildStdCppClassRefPageSections() |
861 | { |
862 | SectionVector &summarySections = stdCppClassSummarySections(); |
863 | SectionVector &detailsSections = stdCppClassDetailsSections(); |
864 | Section &allMembers = allMembersSection(); |
865 | |
866 | for (auto it = m_aggregate->constBegin(); it != m_aggregate->constEnd(); ++it) { |
867 | Node *n = *it; |
868 | if (!n->isPrivate() && !n->isProperty() && !n->isRelatedNonmember() |
869 | && !n->isSharedCommentNode()) |
870 | allMembers.insert(node: n); |
871 | |
872 | distributeNodeInSummaryVector(sv&: summarySections, n); |
873 | distributeNodeInDetailsVector(dv&: detailsSections, n); |
874 | } |
875 | if (!m_aggregate->relatedByProxy().isEmpty()) { |
876 | const QList<Node *> relatedBy = m_aggregate->relatedByProxy(); |
877 | for (const auto &node : relatedBy) |
878 | distributeNodeInSummaryVector(sv&: summarySections, n: node); |
879 | } |
880 | |
881 | QStack<ClassNode *> stack; |
882 | auto *cn = static_cast<ClassNode *>(m_aggregate); |
883 | pushBaseClasses(stack, cn); |
884 | while (!stack.isEmpty()) { |
885 | ClassNode *cn = stack.pop(); |
886 | for (auto it = cn->constBegin(); it != cn->constEnd(); ++it) { |
887 | Node *n = *it; |
888 | if (!n->isPrivate() && !n->isProperty() && !n->isRelatedNonmember() |
889 | && !n->isSharedCommentNode()) |
890 | allMembers.insert(node: n); |
891 | } |
892 | pushBaseClasses(stack, cn); |
893 | } |
894 | reduce(v&: summarySections); |
895 | reduce(v&: detailsSections); |
896 | allMembers.reduce(); |
897 | } |
898 | |
899 | /*! |
900 | Build the section vectors for a standard reference page, |
901 | when the aggregate node is a QML type. |
902 | */ |
903 | void Sections::buildStdQmlTypeRefPageSections() |
904 | { |
905 | ClassNodes *classNodes = nullptr; |
906 | SectionVector &summarySections = stdQmlTypeSummarySections(); |
907 | SectionVector &detailsSections = stdQmlTypeDetailsSections(); |
908 | Section &allMembers = allMembersSection(); |
909 | |
910 | const Aggregate *qtn = m_aggregate; |
911 | while (qtn) { |
912 | if (!qtn->isAbstract() || !classNodes) |
913 | classNodes = &allMembers.classNodesList().emplace_back(args: static_cast<const QmlTypeNode*>(qtn), args: NodeVector{}); |
914 | for (const auto n : qtn->childNodes()) { |
915 | if (n->isInternal()) |
916 | continue; |
917 | |
918 | // Skip overridden property/function documentation from abstract base type |
919 | if (qtn != m_aggregate && qtn->isAbstract()) { |
920 | NodeList candidates; |
921 | m_aggregate->findChildren(name: n->name(), nodes&: candidates); |
922 | if (std::any_of(first: candidates.cbegin(), last: candidates.cend(), pred: [&n](const Node *c) { |
923 | if (c->nodeType() == n->nodeType()) { |
924 | if (!n->isFunction() || static_cast<const FunctionNode*>(n)->compare(node: c, sameParent: false)) |
925 | return true; |
926 | } |
927 | return false; |
928 | })) { |
929 | continue; |
930 | } |
931 | } |
932 | |
933 | if (!n->isSharedCommentNode() || n->isPropertyGroup()) { |
934 | allMembers.insert(node: n); |
935 | classNodes->second.push_back(t: n); |
936 | } |
937 | |
938 | |
939 | if (qtn == m_aggregate || qtn->isAbstract()) { |
940 | distributeQmlNodeInSummaryVector(sv&: summarySections, n); |
941 | distributeQmlNodeInDetailsVector(dv&: detailsSections, n); |
942 | } |
943 | } |
944 | if (qtn->qmlBaseNode() == qtn) { |
945 | qCDebug(lcQdoc, "error: circular type definition: '%s' inherits itself" , |
946 | qPrintable(qtn->name())); |
947 | break; |
948 | } |
949 | qtn = static_cast<QmlTypeNode *>(qtn->qmlBaseNode()); |
950 | } |
951 | |
952 | reduce(v&: summarySections); |
953 | reduce(v&: detailsSections); |
954 | allMembers.reduce(); |
955 | } |
956 | |
957 | /*! |
958 | Returns true if any sections in this object contain obsolete |
959 | members. If it returns false, then \a summary_spv and \a details_spv |
960 | have not been modified. Otherwise, both vectors will contain pointers |
961 | to the sections that contain obsolete members. |
962 | */ |
963 | bool Sections::hasObsoleteMembers(SectionPtrVector *summary_spv, |
964 | SectionPtrVector *details_spv) const |
965 | { |
966 | const SectionVector *sections = nullptr; |
967 | if (m_aggregate->isClassNode()) |
968 | sections = &stdCppClassSummarySections(); |
969 | else if (m_aggregate->isQmlType()) |
970 | sections = &stdQmlTypeSummarySections(); |
971 | else |
972 | sections = &stdSummarySections(); |
973 | for (const auto §ion : *sections) { |
974 | if (!section.obsoleteMembers().isEmpty()) |
975 | summary_spv->append(t: §ion); |
976 | } |
977 | if (m_aggregate->isClassNode()) |
978 | sections = &stdCppClassDetailsSections(); |
979 | else if (m_aggregate->isQmlType()) |
980 | sections = &stdQmlTypeDetailsSections(); |
981 | else |
982 | sections = &stdDetailsSections(); |
983 | for (const auto &it : *sections) { |
984 | if (!it.obsoleteMembers().isEmpty()) |
985 | details_spv->append(t: &it); |
986 | } |
987 | return !summary_spv->isEmpty(); |
988 | } |
989 | |
990 | QT_END_NAMESPACE |
991 | |