Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
nodeextractor.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
5
6#include "aggregate.h"
7#include "atom.h"
8#include "classnode.h"
9#include "codemarker.h"
10#include "collectionnode.h"
12#include "config.h"
13#include "doc.h"
14#include "enumnode.h"
15#include "functionnode.h"
16#include "generator.h"
17#include "hrefresolver.h"
19#include "namespacenode.h"
20#include "ir/contentbuilder.h"
21#include "ir/signaturespan.h"
22#include "pagenode.h"
23#include "parameters.h"
24#include "propertynode.h"
26#include "qdocdatabase.h"
27#include "qmltypenode.h"
28#include "sections.h"
31#include "text.h"
32#include "tree.h"
33#include "typedefnode.h"
34#include "utilities.h"
35#include "textutils.h"
36#include "variablenode.h"
37
38#include "location.h"
39
40#include <QRegularExpression>
41
42#include <deque>
43
44QT_BEGIN_NAMESPACE
45
46using namespace Qt::Literals;
47
49{
50 const Location &loc = node->doc().location();
51 return [loc](QtMsgType type, const QString &message) {
52 switch (type) {
53 case QtWarningMsg:
54 loc.warning(message);
55 break;
56 default:
57 loc.warning(message);
58 break;
59 }
60 };
61}
62
63static QString resolveHref(const HrefResolver *resolver, const Node *target, const Node *relative)
64{
65 if (!resolver)
66 return target->url();
67 auto result = resolver->hrefForNode(target, relative);
68 if (const auto *href = std::get_if<QString>(&result))
69 return *href;
70 return {};
71}
72
73namespace {
74
75// Locate a documented \concept node by name. Walks the QDocDatabase search
76// order so cross-module references resolve through dependency index trees.
77// The relative node is reserved for relative-node-aware scoping in a future
78// revision; today the lookup is global.
79const Node *findConceptNodeForRelative(const Node *relative, const QString &name)
80{
81 Q_UNUSED(relative);
83 if (!qdb)
84 return nullptr;
85 return qdb->findConceptNode(name);
86}
87
88// Tokenize a requires-clause text against a list of documented concept
89// names, emitting a Text/Link/Text/... span sequence into \a out. Mirrors
90// the Text+ExternalRef+Text span pattern used for external references, but
91// uses SpanRole::Link with an HrefResolver-resolved href instead of an
92// external cppreference URL. Used by both the template-head requires
93// clause site (inside buildTemplateDeclSpans) and the trailing requires
94// site (inside buildCppSynopsisSpans).
95//
96// Concept names are matched longest first, so a fully-qualified
97// "ns::Integral" wins against a bare "Integral" when both occur in the
98// same requires clause text. Each matched spelling maps back to its
99// fully-qualified name for lookup, so source text that spells a concept
100// either qualified or unqualified resolves to the same concept page.
101//
102// When no concept name appears in the text, the whole clause is emitted as
103// a single Text span. Concept identifiers are alphanumeric and underscore
104// per the C++ grammar, but the alternation is built through
105// QRegularExpression::escape() for defense in depth.
106void appendRequiresSpansWithLinks(QList<IR::SignatureSpan> &out,
107 const QString &requiresText,
108 const QStringList &conceptNames,
109 const HrefResolver *hrefResolver,
110 const Node *relative)
111{
112 auto emitFallback = [&out, &requiresText]() {
113 IR::SignatureSpan req;
115 req.text = " requires "_L1 + requiresText;
116 out.append(req);
117 };
118
119 if (conceptNames.isEmpty() || requiresText.isEmpty()) {
120 emitFallback();
121 return;
122 }
123
124 QStringList sorted = conceptNames;
125 std::sort(sorted.begin(), sorted.end(),
126 [](const QString &a, const QString &b) { return a.size() > b.size(); });
127
128 // Build a single alternation regex matching any concept name (longest
129 // first) anchored at word boundaries, so longer names take priority and
130 // overlapping matches don't double-emit. Each matched spelling — the
131 // fully-qualified name or its unqualified tail — maps back to the
132 // fully-qualified name the lookup needs.
133 QHash<QString, QString> targetForDisplay;
134 QStringList escapedAlternatives;
135 escapedAlternatives.reserve(sorted.size() * 2);
136 for (const QString &name : sorted) {
137 if (!targetForDisplay.contains(name)) {
138 targetForDisplay.insert(name, name);
139 escapedAlternatives.append(QRegularExpression::escape(name));
140 }
141 const QString tail = name.section("::"_L1, -1);
142 if (tail != name && !targetForDisplay.contains(tail)) {
143 targetForDisplay.insert(tail, name);
144 escapedAlternatives.append(QRegularExpression::escape(tail));
145 }
146 }
147 const QRegularExpression conceptRegex(
148 u"\\b("_s + escapedAlternatives.join(u"|"_s) + u")\\b"_s);
149 if (!conceptRegex.isValid()) {
150 emitFallback();
151 return;
152 }
153
154 auto it = conceptRegex.globalMatch(requiresText);
155 if (!it.hasNext()) {
156 emitFallback();
157 return;
158 }
159
160 // At least one concept matches: emit the " requires " lead-in, then
161 // alternate plain-text runs with concept spans.
162 IR::SignatureSpan lead;
164 lead.text = " requires "_L1;
165 out.append(lead);
166
167 qsizetype pos = 0;
168 while (it.hasNext()) {
169 const auto match = it.next();
170 if (match.capturedStart() > pos) {
171 IR::SignatureSpan textSpan;
172 textSpan.role = IR::SpanRole::Text;
173 textSpan.text = requiresText.mid(pos, match.capturedStart() - pos);
174 out.append(textSpan);
175 }
176 const QString matched = match.captured(1);
177 const QString target = targetForDisplay.value(matched, matched);
178 const Node *cn = findConceptNodeForRelative(relative, target);
179 const QString href = cn ? resolveHref(hrefResolver, cn, relative) : QString();
180
181 IR::SignatureSpan span;
182 span.text = matched;
183 if (href.isEmpty()) {
184 // An undocumented concept, or one whose page can't be located,
185 // renders as plain text rather than a dangling link.
187 } else {
189 span.href = href;
190 }
191 out.append(span);
192 pos = match.capturedEnd();
193 }
194 if (pos < requiresText.size()) {
195 IR::SignatureSpan tail;
197 tail.text = requiresText.mid(pos);
198 out.append(tail);
199 }
200}
201
202} // namespace
203
204namespace NodeExtractor {
205
206/*!
207 \internal
208 Extract page-level metadata from a PageNode into a value-type struct.
209
210 This function reads classification, identity, brief, and body fields
211 from the given PageNode and returns them as an IR::PageMetadata value.
212 Body content is populated via ContentBuilder, which transforms the
213 atom chain into structured content blocks. Format-conditional atoms
214 are skipped unconditionally since the template generator builds a
215 format-agnostic IR.
216
217 For aggregate pages (classes, QML types, namespaces), member listings
218 are extracted via the Sections infrastructure and stored as frozen
219 SectionIR values.
220
221 The caller (TemplateGenerator) invokes this before passing the
222 result to IR::Builder, ensuring Builder never includes PageNode
223 or other Node subclass headers.
224*/
225IR::PageMetadata extractPageMetadata(const PageNode *pn, const HrefResolver *hrefResolver)
226{
227 Q_ASSERT_X(pn, "NodeExtractor::extractPageMetadata",
228 "PageNode pointer must be non-null");
229 IR::PageMetadata pm;
230
231 pm.nodeType = pn->nodeType();
232 pm.genus = pn->genus();
233 pm.status = pn->status();
234 pm.access = pn->access();
235
236 if (pn->isQmlType()) {
237 const auto *qcn = static_cast<const QmlTypeNode *>(pn);
238 QString suffix = qcn->isQmlBasicType() ? " QML Value Type"_L1 : " QML Type"_L1;
239 pm.title = pn->name() + suffix;
240 pm.fullTitle = pm.title;
241 } else if (pn->isClassNode() || pn->isNamespace() || pn->isHeader()) {
242 // plainFullName() produces qualified names for nested aggregates
243 // (e.g. "Outer::Inner"), matching the legacy generator behavior.
244 // For top-level types and headers the result is the same as name().
245 const auto *aggregate = static_cast<const Aggregate *>(pn);
246 pm.fullTitle = aggregate->plainFullName();
247 pm.title = pm.fullTitle;
248 } else {
249 pm.title = pn->title();
250 pm.fullTitle = pn->fullTitle();
251 }
252
253 pm.url = hrefResolver->fileName(pn);
254 const QString baseUrl = Config::instance().get(CONFIG_URL).asString();
255 if (!baseUrl.isEmpty() && !pm.url.isEmpty() && !pm.url.contains("://"_L1)) {
256 pm.url = baseUrl
257 + (baseUrl.endsWith(u'/') ? QString{} : QStringLiteral("/"))
258 + pm.url;
259 }
260 pm.since = pn->since();
261 pm.deprecatedSince = pn->deprecatedSince();
262 pm.brief = pn->doc().briefText().toString();
263
264 const Text &bodyText = pn->doc().body();
265 if (const Atom *firstAtom = bodyText.firstAtom()) {
266 // Offset section heading levels to account for page structure.
267 // QDoc's \section1 maps to level 1, but pages already use <h1>
268 // for the title. The legacy generators apply a node-type-dependent
269 // offset; we replicate the same mapping here.
270 const int headingOffset = [&] {
271 switch (pn->nodeType()) {
273 case NodeType::Class:
274 case NodeType::Struct:
275 case NodeType::Union:
276 case NodeType::Module:
277 return 2;
281 case NodeType::Page:
282 case NodeType::Group:
283 return 1;
284 default:
285 return 3;
286 }
287 }();
288 IR::ContentBuilder contentBuilder(IR::BriefHandling::Skip, headingOffset,
289 diagnosticHandlerFor(pn));
290 pm.body = contentBuilder.build(firstAtom);
291 }
292
293 if (pn->isAggregate()) {
294 const auto *aggregate = static_cast<const Aggregate *>(pn);
295 pm.summarySections = extractSummarySections(aggregate, hrefResolver);
296 pm.detailSections = extractDetailSections(aggregate, hrefResolver);
297 }
298
299 if (pn->isQmlType()) {
300 const auto *qcn = static_cast<const QmlTypeNode *>(pn);
301 pm.qmlTypeData = extractQmlTypeData(qcn, hrefResolver);
302 }
303
304 if (pn->isCollectionNode()) {
305 const auto *cn = static_cast<const CollectionNode *>(pn);
306 pm.collectionData = extractCollectionData(cn, hrefResolver);
307 }
308
309 if (pn->isClassNode() || pn->isNamespace() || pn->isHeader()) {
310 const auto *aggregate = static_cast<const Aggregate *>(pn);
311 pm.cppReferenceData = extractCppReferenceData(aggregate, hrefResolver);
312 }
313
314 pm.navigationData = extractNavigationData(pn, hrefResolver);
315
316 return pm;
317}
318
319/*!
320 \internal
321 Extract QML type metadata from a QmlTypeNode.
322
323 Populates import statement, inheritance chain, inherited-by list,
324 native C++ type link, and singleton/value-type flags. InclusionFilter
325 is applied to match the legacy generator's visibility filtering.
326*/
327IR::QmlTypeData extractQmlTypeData(const QmlTypeNode *qcn, const HrefResolver *hrefResolver)
328{
329 IR::QmlTypeData data;
330 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
331
332 if (!qcn->logicalModuleName().isEmpty()) {
333 bool includeImport = true;
334 const CollectionNode *collection = qcn->logicalModule();
335 if (collection) {
336 const NodeContext context = collection->createContext();
337 includeImport = InclusionFilter::isIncluded(policy, context);
338 }
339 if (includeImport) {
340 QStringList parts = QStringList()
341 << "import"_L1 << qcn->logicalModuleName() << qcn->logicalModuleVersion();
342 data.importStatement = parts.join(' '_L1).trimmed();
343 }
344 }
345
348
349 QmlTypeNode *base = qcn->qmlBaseNode();
350 while (base) {
351 const NodeContext context = base->createContext();
352 if (InclusionFilter::isIncluded(policy, context))
353 break;
354 base = base->qmlBaseNode();
355 }
356
357 NodeList subs;
358 QmlTypeNode::subclasses(qcn, subs, true);
359
360 if (base) {
361 IR::QmlTypeData::InheritsInfo inheritsInfo;
362 inheritsInfo.name = base->name();
363 inheritsInfo.href = resolveHref(hrefResolver, base, qcn);
364 const CollectionNode *baseModule = base->logicalModule();
365 if (baseModule) {
366 const NodeContext moduleContext = baseModule->createContext();
367 if (InclusionFilter::isIncluded(policy, moduleContext))
368 inheritsInfo.moduleName = base->logicalModuleName();
369 }
370 data.inherits = inheritsInfo;
371 }
372
373 if (!subs.isEmpty()) {
374 QList<IR::QmlTypeData::InheritedByEntry> filteredSubs;
375 for (const auto *sub : std::as_const(subs)) {
376 const NodeContext context = sub->createContext();
377 if (InclusionFilter::isIncluded(policy, context))
378 filteredSubs.append({sub->name(), resolveHref(hrefResolver, sub, qcn)});
379 }
380 std::sort(filteredSubs.begin(), filteredSubs.end(),
381 [](const IR::QmlTypeData::InheritedByEntry &a,
382 const IR::QmlTypeData::InheritedByEntry &b) {
383 return a.name < b.name;
384 });
385 data.inheritedBy = filteredSubs;
386 }
387
388 ClassNode *cn = qcn->classNode();
389 if (cn && cn->isQmlNativeType()) {
390 const NodeContext context = cn->createContext();
391 if (InclusionFilter::isIncluded(policy, context))
392 data.nativeType = IR::QmlTypeData::NativeTypeInfo{cn->name(), resolveHref(hrefResolver, cn, qcn)};
393 }
394
395 return data;
396}
397
398/*!
399 \internal
400 Extract collection metadata from a CollectionNode.
401
402 Populates module identity, CMake/qmake build variables, technology
403 preview state, and pre-sorted member listings. For C++ modules,
404 members are categorized into separate namespace and class lists.
405 For groups and QML modules, a single flat member list is produced.
406
407 All member lists are filtered through InclusionFilter (excluding
408 internal entries) and exclude deprecated nodes, then sorted
409 alphabetically by name (case-insensitive).
410*/
411IR::CollectionData extractCollectionData(const CollectionNode *cn, const HrefResolver *hrefResolver)
412{
413 IR::CollectionData data;
414
415 data.logicalModuleName = cn->logicalModuleName();
416 data.logicalModuleVersion = cn->logicalModuleVersion();
417 data.qtVariable = cn->qtVariable();
418
419 // CMake metadata synthesis. Documenters can declare CMake info
420 // through several command shapes (\cmakepackage, \cmakecomponent,
421 // \cmaketargetitem and the Qt-specific \qtcmakepackage and
422 // \qtcmaketargetitem) that populate different combinations of
423 // the package, component and target fields on the CollectionNode.
424 // Synthesize the package as Qt<major-version> when only a
425 // component is declared, so module pages render coherent
426 // find_package snippets for both the generic and Qt-flavored
427 // command families. The target_link_libraries line is
428 // synthesized only when a component is also available — a bare
429 // package name does not imply a target named package::package,
430 // so packages without a component leave the target empty and
431 // the template suppresses the target_link_libraries clause.
432 // When no field is set, leave the data fields empty so the
433 // template suppresses the CMake row entirely.
434 const QString rawCmakePackage = cn->cmakePackage();
435 const QString rawCmakeComponent = cn->cmakeComponent();
436 const QString rawCmakeTargetItem = cn->cmakeTargetItem();
437 if (!rawCmakePackage.isEmpty() || !rawCmakeComponent.isEmpty()) {
438 const QString package = rawCmakePackage.isEmpty()
439 ? "Qt"_L1 + QString::number(QT_VERSION_MAJOR)
440 : rawCmakePackage;
441 data.cmakePackage = package;
442 data.cmakeComponent = rawCmakeComponent;
443 if (!rawCmakeTargetItem.isEmpty())
444 data.cmakeTargetItem = rawCmakeTargetItem;
445 else if (!rawCmakeComponent.isEmpty())
446 data.cmakeTargetItem = package + "::"_L1 + rawCmakeComponent;
447 }
448
449 data.state = cn->state();
450
451 data.isModule = cn->isModule();
452 data.isQmlModule = cn->isQmlModule();
453 data.isGroup = cn->isGroup();
454 data.isConcept = cn->isConcept();
455 data.noAutoList = cn->noAutoList();
456
457 if (cn->noAutoList())
458 return data;
459
460 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
461
462 auto makeMemberEntry = [hrefResolver, cn](const Node *node) -> IR::CollectionData::MemberEntry {
463 return { node->name(), resolveHref(hrefResolver, node, cn), node->doc().briefText().toString() };
464 };
465
466 auto sortEntries = [](QList<IR::CollectionData::MemberEntry> &entries) {
467 std::sort(entries.begin(), entries.end(),
468 [](const IR::CollectionData::MemberEntry &a,
469 const IR::CollectionData::MemberEntry &b) {
470 return a.name.compare(b.name, Qt::CaseInsensitive) < 0;
471 });
472 };
473
474 if (cn->isModule()) {
476 for (auto *node : nsMap.values()) {
477 const NodeContext context = node->createContext();
478 if (InclusionFilter::isIncluded(policy, context) && !node->isDeprecated())
479 data.namespaces.append(makeMemberEntry(node));
480 }
481 sortEntries(data.namespaces);
482
483 const NodeMap classMap = cn->getMembers([](const Node *n) { return n->isClassNode(); });
484 for (auto *node : classMap.values()) {
485 const NodeContext context = node->createContext();
486 if (InclusionFilter::isIncluded(policy, context) && !node->isDeprecated())
487 data.classes.append(makeMemberEntry(node));
488 }
489 sortEntries(data.classes);
490 } else {
491 for (const auto *node : cn->members()) {
492 if (!node->isInAPI())
493 continue;
494 const NodeContext context = node->createContext();
495 if (InclusionFilter::isIncluded(policy, context) && !node->isDeprecated())
496 data.members.append(makeMemberEntry(node));
497 }
498 sortEntries(data.members);
499 }
500
501 return data;
502}
503
505 const HrefResolver *hrefResolver,
506 const Node *relative);
507
508/*!
509 \internal
510 Extract C++ reference page metadata from a class, namespace, or header.
511
512 Reads requisite table fields (header include, build-system snippets,
513 status), inheritance hierarchies, template declarations, comparison
514 operators, thread-safeness, and group associations. The result is a
515 value-type struct that captures everything the template generator needs
516 to render the requisites table and secondary sections without touching
517 the Node tree at render time.
518
519 All three aggregate page types (ClassNode, NamespaceNode, HeaderNode)
520 are handled, with ClassNode-specific sections gated on isClassNode().
521*/
522IR::CppReferenceData extractCppReferenceData(const Aggregate *aggregate, const HrefResolver *hrefResolver)
523{
524 IR::CppReferenceData data;
526
527 data.isNamespace = aggregate->isNamespace();
528 data.isHeader = aggregate->isHeader();
529 data.isInnerClass = aggregate->parent() && aggregate->parent()->isClassNode();
530 data.typeWord = aggregate->typeWord(false);
532
533 auto ancestors = aggregate->plainFullName().split("::"_L1);
534 ancestors.pop_back();
535 data.ancestorNames = ancestors;
536
537 if (aggregate->includeFile())
538 data.headerInclude = *aggregate->includeFile();
539
540 if (!aggregate->physicalModuleName().isEmpty()) {
541 const CollectionNode *cn =
542 qdb->getCollectionNode(aggregate->physicalModuleName(), NodeType::Module);
543 if (cn && (!cn->cmakeComponent().isEmpty() || !cn->cmakePackage().isEmpty())) {
544 const QString package = cn->cmakePackage().isEmpty()
545 ? "Qt"_L1 + QString::number(QT_VERSION_MAJOR)
546 : cn->cmakePackage();
547 QString findPkg;
548 if (cn->cmakeComponent().isEmpty())
549 findPkg = "find_package("_L1 + package + " REQUIRED)"_L1;
550 else
551 findPkg = "find_package("_L1 + package + " REQUIRED COMPONENTS "_L1
552 + cn->cmakeComponent() + ")"_L1;
553
554 QString target;
555 if (!cn->cmakeTargetItem().isEmpty()) {
556 target = cn->cmakeTargetItem();
557 } else if (cn->cmakeComponent().isEmpty()) {
558 target = package + "::"_L1 + package;
559 } else {
560 target = package + "::"_L1 + cn->cmakeComponent();
561 }
562
563 data.cmakeFindPackage = findPkg;
564 data.cmakeTargetLinkLibraries =
565 "target_link_libraries(mytarget PRIVATE "_L1 + target + ")"_L1;
566 }
567 if (cn && !cn->qtVariable().isEmpty())
568 data.qmakeVariable = "QT += "_L1 + cn->qtVariable();
569 }
570
571 auto statusOpt = formatStatus(aggregate, qdb);
572 if (statusOpt) {
573 data.statusText = *statusOpt;
574 if (aggregate->status() == Status::Deprecated)
575 data.statusCssClass = "deprecated"_L1;
576 else if (!aggregate->deprecatedSince().isEmpty())
577 data.statusCssClass = "pending-deprecation"_L1;
578 else if (aggregate->status() == Status::Preliminary)
579 data.statusCssClass = "preliminary"_L1;
580 else
581 data.statusCssClass = "status"_L1;
582 }
583
584 if (aggregate->isClassNode()) {
585 auto *classNode = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate));
586
587 if (classNode->isQmlNativeType()) {
588 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
589 const NodeContext context = classNode->createContext();
590 if (InclusionFilter::isIncluded(policy, context)) {
591 QList<QmlTypeNode *> nativeTypes{classNode->qmlNativeTypes().cbegin(),
592 classNode->qmlNativeTypes().cend()};
593 if (!nativeTypes.isEmpty()) {
594 std::sort(nativeTypes.begin(), nativeTypes.end(), Node::nodeNameLessThan);
595 data.qmlNativeType = IR::CppReferenceData::QmlNativeTypeLink{
596 nativeTypes.first()->name(),
597 resolveHref(hrefResolver, nativeTypes.first(), aggregate)
598 };
599 }
600 }
601 }
602
603 const auto *metaTags = classNode->doc().metaTagMap();
604 if (metaTags && metaTags->contains(u"qdoc-suppress-inheritance"_s))
605 data.suppressInheritance = true;
606
607 if (!data.suppressInheritance) {
608 const auto &baseClasses = classNode->baseClasses();
609 for (const auto &bc : baseClasses) {
610 if (bc.m_node) {
611 data.baseClasses.append({
612 bc.m_node->plainFullName(),
613 resolveHref(hrefResolver, bc.m_node, aggregate),
614 bc.m_access
615 });
616 }
617 }
618
619 const auto &derivedClasses = classNode->derivedClasses();
620 for (const auto &dc : derivedClasses) {
621 if (dc.m_node) {
622 data.derivedClasses.append({
623 dc.m_node->plainFullName(),
624 resolveHref(hrefResolver, dc.m_node, aggregate)
625 });
626 }
627 }
628 std::sort(data.derivedClasses.begin(), data.derivedClasses.end(),
631 return a.name.compare(b.name, Qt::CaseInsensitive) < 0;
632 });
633 }
634 }
635
636 if (aggregate->templateDecl()) {
637 const auto &templateDecl = *aggregate->templateDecl();
638 data.templateDeclSpans = buildTemplateDeclSpans(&templateDecl, hrefResolver, aggregate);
639 data.referencedConcepts.reserve(int(templateDecl.referenced_concepts.size()));
640 for (const auto &s : templateDecl.referenced_concepts)
641 data.referencedConcepts.append(QString::fromStdString(s));
642 }
643
644 const auto selfCategory = aggregate->comparisonCategory();
645 if (selfCategory != ComparisonCategory::None)
646 data.selfComparisonCategory = QString::fromStdString(comparisonCategoryAsString(selfCategory));
647
648 const auto *comparesMap = aggregate->doc().comparesWithMap();
649 if (comparesMap && !comparesMap->isEmpty()) {
650 for (auto [key, description] : comparesMap->asKeyValueRange()) {
651 IR::CppReferenceData::ComparisonEntry entry;
652 entry.category = QString::fromStdString(comparisonCategoryAsString(key));
653
654 const QStringList types{description.firstAtom()->string().split(';'_L1)};
655 entry.comparableTypes = types;
656
657 if (description.firstAtom()->next() != description.lastAtom()) {
658 Text descText = Text::subText(description.firstAtom()->next(),
659 description.lastAtom());
660 entry.description = descText.toString();
661 }
662 data.comparisonEntries.append(entry);
663 }
664 }
665
666 Node::ThreadSafeness ts = aggregate->threadSafeness();
667 if (ts != Node::UnspecifiedSafeness) {
669 switch (ts) {
670 case Node::NonReentrant:
671 tsInfo.level = "non-reentrant"_L1;
672 break;
673 case Node::Reentrant:
674 tsInfo.level = "reentrant"_L1;
675 break;
676 case Node::ThreadSafe:
677 tsInfo.level = "thread-safe"_L1;
678 break;
679 default:
680 break;
681 }
682
683 NodeList reentrant, threadsafe, nonreentrant;
684 bool hasExceptions = false;
685 for (const auto *child : aggregate->childNodes()) {
686 if (!child->isDeprecated()) {
687 switch (child->threadSafeness()) {
688 case Node::Reentrant:
689 reentrant.append(const_cast<Node *>(child));
690 if (ts == Node::ThreadSafe) hasExceptions = true;
691 break;
692 case Node::ThreadSafe:
693 threadsafe.append(const_cast<Node *>(child));
694 if (ts == Node::Reentrant) hasExceptions = true;
695 break;
696 case Node::NonReentrant:
697 nonreentrant.append(const_cast<Node *>(child));
698 hasExceptions = true;
699 break;
700 default:
701 break;
702 }
703 }
704 }
705 if (hasExceptions) {
706 for (const auto *node : std::as_const(reentrant)) {
707 tsInfo.reentrantExceptions.append({
708 node->plainFullName(),
709 resolveHref(hrefResolver, node, aggregate)
710 });
711 }
712 for (const auto *node : std::as_const(threadsafe)) {
713 tsInfo.threadSafeExceptions.append({
714 node->plainFullName(),
715 resolveHref(hrefResolver, node, aggregate)
716 });
717 }
718 for (const auto *node : std::as_const(nonreentrant)) {
719 tsInfo.nonReentrantExceptions.append({
720 node->plainFullName(),
721 resolveHref(hrefResolver, node, aggregate)
722 });
723 }
724 }
725 data.threadSafety = std::move(tsInfo);
726 }
727
728 const QStringList &groupNames = aggregate->groupNames();
729 if (!groupNames.isEmpty()) {
730 const auto &groupMap = qdb->groups();
731 for (const auto &groupName : groupNames) {
732 auto it = groupMap.find(groupName);
733 if (it == groupMap.end() || !*it)
734 continue;
735 CollectionNode *group = *it;
736 // TODO: mergeCollections() mutates the node tree during
737 // extraction, violating the principle that the new pipeline
738 // reads without side effects. Replace with an eager merge
739 // pass that runs before generation begins.
740 qdb->mergeCollections(group);
741 if (group->wasSeen()) {
742 data.groups.append({
743 group->fullTitle(),
744 resolveHref(hrefResolver, group, aggregate)
745 });
746 }
747 }
748 }
749
750 if (aggregate->isNamespace()) {
751 const auto *ns = static_cast<const NamespaceNode *>(aggregate);
752 if (!ns->hasDoc() && ns->docNode()) {
753 data.isPartialNamespace = true;
754 data.fullNamespaceHref = resolveHref(hrefResolver, ns->docNode(), aggregate);
755 data.fullNamespaceModuleName = ns->docNode()->tree()->camelCaseModuleName();
756 }
757 }
758
759 return data;
760}
761
762/*!
763 \internal
764 Build categorized summary sections for an aggregate node.
765
766 Delegates to the Sections class for member distribution, then extracts
767 results into frozen SectionIR values. The section variant (C++ class,
768 QML type, or generic) is chosen based on the aggregate's node type.
769*/
770QList<IR::SectionIR> extractSummarySections(const Aggregate *aggregate, const HrefResolver *hrefResolver)
771{
772 Sections sections(aggregate);
773
774 const auto &sv = sections.summarySections();
775
776 QList<IR::SectionIR> result;
777 for (const auto &section : sv) {
778 if (section.isEmpty())
779 continue;
780
781 IR::SectionIR irSection;
782 irSection.title = section.title();
783 irSection.id = TextUtils::asAsciiPrintable(section.title());
784 irSection.singular = section.singular();
785 irSection.plural = section.plural();
786
787 // SharedCommentNode groups several declarations under one doc comment.
788 // Expand them into individual MemberIR entries so each function appears
789 // in the member table. This means the IR member count may exceed the
790 // Section::members() count.
791 for (const auto *member : section.members()) {
792 if (member->isSharedCommentNode()) {
793 const auto *scn = static_cast<const SharedCommentNode *>(member);
794 for (const auto *child : scn->collective()) {
795 IR::MemberIR irMember = extractMemberIR(child, hrefResolver, aggregate);
796 irMember.href = "#"_L1 + hrefResolver->anchorForNode(child);
797 irSection.members.append(irMember);
798 }
799 } else {
800 IR::MemberIR irMember = extractMemberIR(member, hrefResolver, aggregate);
801 irMember.href = "#"_L1 + hrefResolver->anchorForNode(member);
802 irSection.members.append(irMember);
803 }
804 }
805
806 for (const auto *reimpl : section.reimplementedMembers())
807 irSection.reimplementedMembers.append(extractMemberIR(reimpl, hrefResolver, aggregate));
808
809 for (const auto &[base, count] : section.inheritedMembers()) {
810 IR::InheritedMembersIR inherited;
811 inherited.className = base->plainFullName();
812 inherited.count = count;
813 inherited.href = resolveHref(hrefResolver, base, aggregate);
814 irSection.inheritedMembers.append(inherited);
815 }
816
817 result.append(irSection);
818 }
819 return result;
820}
821
822/*!
823 \internal
824 Build categorized detail sections for an aggregate node.
825
826 Iterates Sections::detailsSections() and extracts full member
827 documentation including body content, anchor IDs, and metadata.
828 SharedCommentNode groups share a single documentation body across
829 their children, with each child getting its own anchorId and synopsis.
830*/
831QList<IR::SectionIR> extractDetailSections(const Aggregate *aggregate, const HrefResolver *hrefResolver)
832{
833 Sections sections(aggregate);
834 const auto &sv = sections.detailsSections();
835
836 QList<IR::SectionIR> result;
837 for (const auto &section : sv) {
838 if (section.isEmpty())
839 continue;
840
841 IR::SectionIR irSection;
842 irSection.title = section.title();
843 irSection.id = TextUtils::asAsciiPrintable(section.title());
844 irSection.singular = section.singular();
845 irSection.plural = section.plural();
846
847 for (const auto *member : section.members()) {
848 if (member->isSharedCommentNode()) {
849 const auto *scn = static_cast<const SharedCommentNode *>(member);
850
851 QList<IR::ContentBlock> sharedBody;
852 const Text &bodyText = scn->doc().body();
853 if (const Atom *firstAtom = bodyText.firstAtom()) {
854 IR::ContentBuilder contentBuilder(IR::BriefHandling::Include, 0,
855 diagnosticHandlerFor(scn));
856 sharedBody = contentBuilder.build(firstAtom);
857 }
858
859 QList<IR::ContentBlock> sharedAlso;
860 const QList<Text> &alsoTexts = scn->doc().alsoList();
861 for (const Text &alsoText : alsoTexts) {
862 if (const Atom *firstAtom = alsoText.firstAtom()) {
863 IR::ContentBuilder contentBuilder(IR::BriefHandling::Include, 0,
864 diagnosticHandlerFor(scn));
865 sharedAlso.append(contentBuilder.build(firstAtom));
866 }
867 }
868
869 for (const auto *child : scn->collective()) {
870 IR::MemberIR irMember = extractMemberIR(child, hrefResolver, aggregate, MemberExtractionLevel::Detail);
871 irMember.body = sharedBody;
872 irMember.alsoList = sharedAlso;
873 irSection.members.append(irMember);
874 }
875 } else {
876 irSection.members.append(extractMemberIR(member, hrefResolver, aggregate, MemberExtractionLevel::Detail));
877 }
878 }
879
880 result.append(irSection);
881 }
882 return result;
883}
884
886{
887 switch (ts) {
888 case Node::Reentrant:
889 return "reentrant"_L1;
890 case Node::ThreadSafe:
891 return "thread-safe"_L1;
892 default:
893 return {};
894 }
895}
896
897/*!
898 \internal
899 Build a MemberIR from a single Node.
900
901 Extracts identity, classification, and type-specific data from the node.
902 FunctionNode provides signatures, parameters, and overload metadata.
903 EnumNode provides scoped/unscoped signature and enum value listings.
904 PropertyNode provides a qualified data type signature.
905
906 When \a level is MemberExtractionLevel::Detail, also populates
907 detail documentation fields: anchorId, synopsis, since,
908 threadSafety, comparisonCategory, noexcept metadata, body (via
909 ContentBuilder), and alsoList.
910*/
911IR::MemberIR extractMemberIR(const Node *node, const HrefResolver *hrefResolver, const Node *relative, MemberExtractionLevel level)
912{
913 const bool includeDetail = (level == MemberExtractionLevel::Detail);
914 IR::MemberIR member;
915
916 member.name = node->name();
917 member.fullName = node->plainFullName();
918 member.href = resolveHref(hrefResolver, node, relative);
919 member.brief = node->doc().briefText().toString();
920
921 member.nodeType = node->nodeType();
922 member.access = node->access();
923 member.status = node->status();
924
925 if (node->isFunction()) {
926 const auto *fn = static_cast<const FunctionNode *>(node);
927 member.signature = fn->signature(
929 member.isStatic = fn->isStatic();
930 member.isConst = fn->isConst();
931 member.isVirtual = !fn->isNonvirtual();
932 member.isSignal = fn->isSignal();
933 member.isSlot = fn->isSlot();
934 member.overloadNumber = fn->overloadNumber();
935 member.isPrimaryOverload = fn->isPrimaryOverload();
936
937 const Parameters &params = fn->parameters();
938 for (int i = 0; i < params.count(); ++i) {
939 IR::ParameterIR param;
940 param.type = params.at(i).type();
941 param.name = params.at(i).name();
942 param.defaultValue = params.at(i).defaultValue();
943 member.parameters.append(param);
944 }
945 } else if (node->isEnumType()) {
946 const auto *en = static_cast<const EnumNode *>(node);
947 member.signature = en->isScoped()
948 ? QStringLiteral("enum class %1").arg(en->name())
949 : QStringLiteral("enum %1").arg(en->name());
950
951 for (const auto &item : en->items()) {
952 IR::EnumValueIR ev;
953 ev.name = item.name();
954 ev.value = item.value();
955 ev.since = item.since();
956 member.enumValues.append(ev);
957 }
958 } else if (node->isQmlProperty()) {
959 const auto *qpn = static_cast<const QmlPropertyNode *>(node);
960 member.signature = qpn->name() + " : "_L1 + qpn->dataType();
961 member.dataType = qpn->dataType();
962 member.isAttached = qpn->isAttached();
963 member.isDefault = qpn->isDefault();
964 member.isReadOnly = qpn->isReadOnly();
965 member.isRequired = qpn->isRequired();
966 } else if (node->isProperty()) {
967 const auto *pn = static_cast<const PropertyNode *>(node);
968 member.signature = pn->name() + " : "_L1 + pn->qualifiedDataType();
969 } else if (node->isTypedef()) {
970 const auto *td = static_cast<const TypedefNode *>(node);
971 member.signature = td->associatedEnum()
972 ? "flags "_L1 + td->name()
973 : td->name();
974 } else if (node->nodeType() == NodeType::Variable) {
975 const auto *vn = static_cast<const VariableNode *>(node);
976 member.signature = vn->leftType() + vn->name() + vn->rightType();
977 } else {
978 member.signature = node->name();
979 }
980
981 if (includeDetail) {
982 member.anchorId = hrefResolver->anchorForNode(node);
983 member.synopsis = member.signature;
984 member.since = node->since();
985 member.threadSafety = threadSafenessString(node->threadSafeness());
986
988 if (!catStr.empty())
989 member.comparisonCategory = QString::fromStdString(catStr);
990
991 if (node->isFunction()) {
992 const auto *fn = static_cast<const FunctionNode *>(node);
993 const auto &noexcept_ = fn->getNoexcept();
994 if (noexcept_) {
995 member.isNoexcept = true;
996 member.noexceptNote = *noexcept_;
997 }
998 }
999
1000 const Text &bodyText = node->doc().body();
1001 if (const Atom *firstAtom = bodyText.firstAtom()) {
1002 IR::ContentBuilder contentBuilder(IR::BriefHandling::Include, 0,
1003 diagnosticHandlerFor(node));
1004 member.body = contentBuilder.build(firstAtom);
1005 }
1006
1007 const QList<Text> &alsoTexts = node->doc().alsoList();
1008 for (const Text &alsoText : alsoTexts) {
1009 if (const Atom *firstAtom = alsoText.firstAtom()) {
1010 IR::ContentBuilder contentBuilder(IR::BriefHandling::Include, 0,
1011 diagnosticHandlerFor(node));
1012 QList<IR::ContentBlock> blocks = contentBuilder.build(firstAtom);
1013 member.alsoList.append(blocks);
1014 }
1015 }
1016 }
1017
1018 Section::Style spanStyle = includeDetail ? Section::Details : Section::Summary;
1019 member.signatureSpans = buildSignatureSpans(node, hrefResolver, relative, spanStyle);
1020
1021 return member;
1022}
1023
1024static QList<IR::SignatureSpan> buildTypeSpans(const QString &typeString)
1025{
1026 QList<IR::SignatureSpan> spans;
1027 QString pendingWord;
1028
1029 for (int i = 0; i <= typeString.size(); ++i) {
1030 QChar ch;
1031 if (i != typeString.size())
1032 ch = typeString.at(i);
1033
1034 QChar lower = ch.toLower();
1035 if ((lower >= 'a'_L1 && lower <= 'z'_L1) || ch.digitValue() >= 0
1036 || ch == '_'_L1 || ch == ':'_L1) {
1037 pendingWord += ch;
1038 } else {
1039 if (!pendingWord.isEmpty()) {
1040 bool isProbablyType = (pendingWord != "const"_L1);
1041 IR::SignatureSpan span;
1042 span.role = isProbablyType ? IR::SpanRole::Type : IR::SpanRole::Text;
1043 span.text = pendingWord;
1044 spans.append(span);
1045 }
1046 pendingWord.clear();
1047
1048 if (!ch.isNull()) {
1049 IR::SignatureSpan span;
1051 span.text = QString(ch);
1052 spans.append(span);
1053 }
1054 }
1055 }
1056 return spans;
1057}
1058
1060{
1061 QString extraStr = CodeMarker::extraSynopsis(node, style);
1062 if (extraStr.isEmpty())
1063 return {};
1064
1065 // extraSynopsis may contain <@extref target="...">text</@extref> tags for
1066 // cppreference links. Parse those into ExternalRef spans; everything else
1067 // becomes Extra spans.
1068 static const QRegularExpression extrefRegex(
1069 u"<@extref target=\"([^\"]+)\">([^<]+)</@extref>"_s);
1070
1071 QList<IR::SignatureSpan> spans;
1072 IR::SignatureSpan wrapper;
1073 wrapper.role = IR::SpanRole::Extra;
1074
1075 qsizetype pos = 0;
1076 auto it = extrefRegex.globalMatch(extraStr);
1077 while (it.hasNext()) {
1078 auto match = it.next();
1079 if (match.capturedStart() > pos) {
1080 IR::SignatureSpan textSpan;
1081 textSpan.role = IR::SpanRole::Text;
1082 textSpan.text = extraStr.mid(pos, match.capturedStart() - pos);
1083 wrapper.children.append(textSpan);
1084 }
1085 IR::SignatureSpan ref;
1087 ref.text = match.captured(2);
1088 ref.href = "https://en.cppreference.com/w/cpp/language/"_L1 + match.captured(1);
1089 wrapper.children.append(ref);
1090 pos = match.capturedEnd();
1091 }
1092 if (pos < extraStr.size()) {
1093 IR::SignatureSpan textSpan;
1094 textSpan.role = IR::SpanRole::Text;
1095 textSpan.text = extraStr.mid(pos);
1096 wrapper.children.append(textSpan);
1097 }
1098
1099 if (wrapper.children.isEmpty()) {
1100 wrapper.text = extraStr;
1101 }
1102 spans.append(wrapper);
1103 return spans;
1104}
1105
1107 const HrefResolver *hrefResolver,
1108 const Node *relative)
1109{
1110 if (!templateDecl)
1111 return {};
1112
1113 IR::SignatureSpan declSpan;
1115 declSpan.text = "template"_L1;
1116
1117 IR::SignatureSpan open;
1119 open.text = "<"_L1;
1120 declSpan.children.append(open);
1121
1122 bool first = true;
1123 for (const auto &param : templateDecl->parameters) {
1124 if (param.sfinae_constraint)
1125 continue;
1126 if (!first) {
1127 IR::SignatureSpan comma;
1128 comma.role = IR::SpanRole::Text;
1129 comma.text = ", "_L1;
1130 declSpan.children.append(comma);
1131 }
1132
1133 switch (param.kind) {
1134 case RelaxedTemplateParameter::Kind::TypeTemplateParameter: {
1135 if (param.concept_name) {
1136 const QString fq = QString::fromStdString(*param.concept_name);
1137 const QString unqualified = fq.section("::"_L1, -1);
1138 // Resolve by the fully-qualified name the concept is registered
1139 // under; render the unqualified spelling the author wrote.
1140 const Node *conceptNode = findConceptNodeForRelative(relative, fq);
1141 IR::SignatureSpan link;
1142 link.role = IR::SpanRole::Link;
1143 link.text = unqualified;
1144 link.href = resolveHref(hrefResolver, conceptNode, relative);
1145 declSpan.children.append(link);
1146 } else {
1147 IR::SignatureSpan kw;
1148 kw.role = IR::SpanRole::Text;
1149 kw.text = "typename"_L1;
1150 declSpan.children.append(kw);
1151 }
1152 break;
1153 }
1154 case RelaxedTemplateParameter::Kind::TemplateTemplateParameter: {
1155 IR::SignatureSpan kw;
1156 kw.role = IR::SpanRole::Text;
1157 kw.text = "typename"_L1;
1158 declSpan.children.append(kw);
1159 break;
1160 }
1161 case RelaxedTemplateParameter::Kind::NonTypeTemplateParameter: {
1162 if (!param.valued_declaration.type.empty()) {
1163 auto typeSpans = buildTypeSpans(QString::fromStdString(param.valued_declaration.type));
1164 declSpan.children.append(typeSpans);
1165 }
1166 break;
1167 }
1168 }
1169
1170 if (param.is_parameter_pack) {
1171 IR::SignatureSpan dots;
1172 dots.role = IR::SpanRole::Text;
1173 dots.text = "..."_L1;
1174 declSpan.children.append(dots);
1175 }
1176
1177 if (!param.valued_declaration.name.empty()) {
1178 IR::SignatureSpan space;
1179 space.role = IR::SpanRole::Text;
1180 space.text = " "_L1;
1181 declSpan.children.append(space);
1182
1183 IR::SignatureSpan nameSpan;
1184 nameSpan.role = IR::SpanRole::Parameter;
1185 nameSpan.text = QString::fromStdString(param.valued_declaration.name);
1186 declSpan.children.append(nameSpan);
1187 }
1188
1189 if (!param.valued_declaration.initializer.empty()) {
1190 IR::SignatureSpan eq;
1191 eq.role = IR::SpanRole::Text;
1192 eq.text = " = "_L1;
1193 declSpan.children.append(eq);
1194
1195 if (param.kind == RelaxedTemplateParameter::Kind::TypeTemplateParameter
1196 || param.kind == RelaxedTemplateParameter::Kind::TemplateTemplateParameter) {
1197 auto typeSpans = buildTypeSpans(QString::fromStdString(param.valued_declaration.initializer));
1198 declSpan.children.append(typeSpans);
1199 } else {
1200 IR::SignatureSpan val;
1201 val.role = IR::SpanRole::Text;
1202 val.text = QString::fromStdString(param.valued_declaration.initializer);
1203 declSpan.children.append(val);
1204 }
1205 }
1206
1207 first = false;
1208 }
1209
1210 IR::SignatureSpan close;
1212 close.text = ">"_L1;
1213 declSpan.children.append(close);
1214
1215 if (templateDecl->requires_clause && !templateDecl->requires_clause->empty()) {
1216 const QString reqText = QString::fromStdString(*templateDecl->requires_clause);
1217 QStringList conceptNames;
1218 conceptNames.reserve(int(templateDecl->referenced_concepts.size()));
1219 for (const auto &s : templateDecl->referenced_concepts)
1220 conceptNames.append(QString::fromStdString(s));
1221 appendRequiresSpansWithLinks(declSpan.children, reqText, conceptNames,
1222 hrefResolver, relative);
1223 }
1224
1225 return { declSpan };
1226}
1227
1229 const HrefResolver *hrefResolver,
1230 const Node *relative,
1231 Section::Style style)
1232{
1233 QList<IR::SignatureSpan> spans;
1234
1235 auto appendText = [&spans](const QString &text) {
1236 IR::SignatureSpan span;
1238 span.text = text;
1239 spans.append(span);
1240 };
1241
1242 auto appendName = [&spans, node, hrefResolver, relative](const QString &name) {
1243 IR::SignatureSpan span;
1245 span.text = name;
1246 span.href = resolveHref(hrefResolver, node, relative);
1247 spans.append(span);
1248 };
1249
1250 auto appendTypeSpans = [&spans](const QString &type, bool trailingSpace) {
1251 auto typeSpans = buildTypeSpans(type);
1252 spans.append(typeSpans);
1253 if (trailingSpace && !type.isEmpty()
1254 && !type.endsWith('*'_L1) && !type.endsWith('&'_L1)) {
1255 IR::SignatureSpan space;
1257 space.text = " "_L1;
1258 spans.append(space);
1259 }
1260 };
1261
1262 // Extra qualifiers go first (prepended in CppCodeMarker)
1263 if (style != Section::AllMembers) {
1264 auto extras = buildExtraSpans(node, style);
1265 if (!extras.isEmpty()) {
1266 spans.append(extras);
1267 appendText(" "_L1);
1268 }
1269 }
1270
1271 // Name with parent prefix for Details style
1272 QString nameText = node->name();
1273 bool linkName = (style != Section::Details);
1274
1275 if (style == Section::Details) {
1277 && !node->parent()->name().isEmpty()
1278 && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()) {
1279 nameText = node->parent()->name() + "::"_L1 + nameText;
1280 }
1281 }
1282
1283 switch (node->nodeType()) {
1285 case NodeType::Class:
1286 case NodeType::Struct:
1287 case NodeType::Union:
1288 appendText(Node::nodeTypeString(node->nodeType()) + " "_L1);
1289 if (linkName) {
1290 appendName(nameText);
1291 } else {
1292 IR::SignatureSpan span;
1294 span.text = nameText;
1295 spans.append(span);
1296 }
1297 break;
1298 case NodeType::Function: {
1299 const auto *func = static_cast<const FunctionNode *>(node);
1300
1301 if (style == Section::Details) {
1302 if (auto templateDecl = node->templateDecl()) {
1303 auto tmplSpans = buildTemplateDeclSpans(&*templateDecl, hrefResolver, relative);
1304 spans.append(tmplSpans);
1305 appendText(" "_L1);
1306 }
1307 }
1308
1309 if (style == Section::Summary || style == Section::Accessors) {
1310 if (!func->isNonvirtual())
1311 appendText("virtual "_L1);
1312 }
1313
1314 if (style != Section::AllMembers && !func->returnType().isEmpty())
1315 appendTypeSpans(func->returnTypeString(), true);
1316
1317 if (linkName) {
1318 appendName(nameText);
1319 } else {
1320 IR::SignatureSpan span;
1322 span.text = nameText;
1323 spans.append(span);
1324 }
1325
1326 if (!func->isMacroWithoutParams()) {
1327 appendText("("_L1);
1328 if (!func->parameters().isEmpty()) {
1329 const Parameters &parameters = func->parameters();
1330 for (int i = 0; i < parameters.count(); ++i) {
1331 if (i > 0)
1332 appendText(", "_L1);
1333 const Parameter &param = parameters.at(i);
1334 QString pName = param.name();
1335 QString type = param.type();
1336 QString value = param.defaultValue();
1337 qsizetype insertPos = param.nameInsertionPoint();
1338 if (insertPos >= 0 && style != Section::AllMembers && !pName.isEmpty()) {
1339 appendTypeSpans(type.left(insertPos), false);
1340 IR::SignatureSpan paramSpan;
1341 paramSpan.role = IR::SpanRole::Parameter;
1342 paramSpan.text = pName;
1343 spans.append(paramSpan);
1344 appendTypeSpans(type.mid(insertPos), false);
1345 } else {
1346 bool trailingSpace = style != Section::AllMembers && !pName.isEmpty();
1347 appendTypeSpans(type, trailingSpace);
1348 if (style != Section::AllMembers && !pName.isEmpty()) {
1349 IR::SignatureSpan paramSpan;
1350 paramSpan.role = IR::SpanRole::Parameter;
1351 paramSpan.text = pName;
1352 spans.append(paramSpan);
1353 }
1354 }
1355 if (style != Section::AllMembers && !value.isEmpty())
1356 appendText(" = "_L1 + value);
1357 }
1358 }
1359 appendText(")"_L1);
1360 }
1361
1362 if (func->isConst())
1363 appendText(" const"_L1);
1364
1365 if (style == Section::Summary || style == Section::Accessors) {
1366 if (func->isFinal())
1367 appendText(" final"_L1);
1368 if (func->isOverride())
1369 appendText(" override"_L1);
1370 if (func->isPureVirtual())
1371 appendText(" = 0"_L1);
1372 if (func->isRef())
1373 appendText(" &"_L1);
1374 else if (func->isRefRef())
1375 appendText(" &&"_L1);
1376 } else if (style == Section::AllMembers) {
1377 if (!func->returnType().isEmpty() && func->returnType() != "void"_L1) {
1378 appendText(" : "_L1);
1379 appendTypeSpans(func->returnTypeString(), false);
1380 }
1381 } else {
1382 if (func->isRef())
1383 appendText(" &"_L1);
1384 else if (func->isRefRef())
1385 appendText(" &&"_L1);
1386 if (const auto &req = func->trailingRequiresClause(); req && !req->isEmpty())
1387 appendRequiresSpansWithLinks(spans, *req, func->referencedConcepts(),
1388 hrefResolver, relative);
1389 }
1390 break;
1391 }
1392 case NodeType::Enum: {
1393 const auto *enume = static_cast<const EnumNode *>(node);
1394 appendText("enum"_L1);
1395 if (enume->isScoped())
1396 appendText(" class"_L1);
1397 if (!enume->isAnonymous()) {
1398 appendText(" "_L1);
1399 if (linkName) {
1400 appendName(nameText);
1401 } else {
1402 IR::SignatureSpan span;
1404 span.text = nameText;
1405 spans.append(span);
1406 }
1407 }
1408 if (style == Section::Summary) {
1409 appendText(" { "_L1);
1410 const int MaxEnumValues = 6;
1411 QStringList documentedItems = enume->doc().enumItemNames();
1412 if (documentedItems.isEmpty()) {
1413 const auto &enumItems = enume->items();
1414 for (const auto &item : enumItems)
1415 documentedItems << item.name();
1416 }
1417 const QStringList omitItems = enume->doc().omitEnumItemNames();
1418 for (const auto &item : omitItems)
1419 documentedItems.removeAll(item);
1420
1421 if (documentedItems.size() > MaxEnumValues) {
1422 const QString last = documentedItems.last();
1423 documentedItems = documentedItems.mid(0, MaxEnumValues - 1);
1424 documentedItems += "..."_L1;
1425 documentedItems += last;
1426 }
1427 appendText(documentedItems.join(", "_L1));
1428 if (!documentedItems.isEmpty())
1429 appendText(" "_L1);
1430 appendText("}"_L1);
1431 }
1432 break;
1433 }
1434 case NodeType::TypeAlias: {
1435 if (style == Section::Details) {
1436 if (auto templateDecl = node->templateDecl()) {
1437 auto tmplSpans = buildTemplateDeclSpans(&*templateDecl, hrefResolver, relative);
1438 spans.append(tmplSpans);
1439 appendText(" "_L1);
1440 }
1441 }
1442 if (linkName) {
1443 appendName(nameText);
1444 } else {
1445 IR::SignatureSpan span;
1447 span.text = nameText;
1448 spans.append(span);
1449 }
1450 break;
1451 }
1452 case NodeType::Typedef: {
1453 if (static_cast<const TypedefNode *>(node)->associatedEnum())
1454 appendText("flags "_L1);
1455 if (linkName) {
1456 appendName(nameText);
1457 } else {
1458 IR::SignatureSpan span;
1460 span.text = nameText;
1461 spans.append(span);
1462 }
1463 break;
1464 }
1465 case NodeType::Property: {
1466 const auto *property = static_cast<const PropertyNode *>(node);
1467 if (linkName) {
1468 appendName(nameText);
1469 } else {
1470 IR::SignatureSpan span;
1472 span.text = nameText;
1473 spans.append(span);
1474 }
1475 appendText(" : "_L1);
1476 appendTypeSpans(property->qualifiedDataType(), false);
1477 break;
1478 }
1479 case NodeType::QmlProperty: {
1480 const auto *property = static_cast<const QmlPropertyNode *>(node);
1481 if (linkName) {
1482 appendName(nameText);
1483 } else {
1484 IR::SignatureSpan span;
1486 span.text = nameText;
1487 spans.append(span);
1488 }
1489 appendText(" : "_L1);
1490 appendTypeSpans(property->dataType(), false);
1491 break;
1492 }
1493 case NodeType::Variable: {
1494 const auto *variable = static_cast<const VariableNode *>(node);
1495 if (style == Section::AllMembers) {
1496 if (linkName) {
1497 appendName(nameText);
1498 } else {
1499 IR::SignatureSpan span;
1501 span.text = nameText;
1502 spans.append(span);
1503 }
1504 appendText(" : "_L1);
1505 appendTypeSpans(variable->dataType(), false);
1506 } else {
1507 appendTypeSpans(variable->leftType(), true);
1508 if (linkName) {
1509 appendName(nameText);
1510 } else {
1511 IR::SignatureSpan span;
1513 span.text = nameText;
1514 spans.append(span);
1515 }
1516 appendText(variable->rightType());
1517 }
1518 break;
1519 }
1520 default:
1521 if (linkName) {
1522 appendName(nameText);
1523 } else {
1524 IR::SignatureSpan span;
1526 span.text = nameText;
1527 spans.append(span);
1528 }
1529 break;
1530 }
1531
1532 return spans;
1533}
1534
1536 const HrefResolver *hrefResolver)
1537{
1538 QList<IR::SignatureSpan> spans;
1539
1540 auto appendText = [&spans](const QString &text) {
1541 IR::SignatureSpan span;
1543 span.text = text;
1544 spans.append(span);
1545 };
1546
1547 auto appendTypeSpans = [&spans](const QString &type, bool trailingSpace) {
1548 auto typeSpans = buildTypeSpans(type);
1549 spans.append(typeSpans);
1550 if (trailingSpace && !type.isEmpty()
1551 && !type.endsWith('*'_L1) && !type.endsWith('&'_L1)) {
1552 IR::SignatureSpan space;
1554 space.text = " "_L1;
1555 spans.append(space);
1556 }
1557 };
1558
1559 IR::SignatureSpan nameSpan;
1560 nameSpan.role = IR::SpanRole::Name;
1561 nameSpan.text = node->name();
1562 nameSpan.href = resolveHref(hrefResolver, node, node->parent());
1563
1564 if (node->isQmlProperty()) {
1565 const auto *pn = static_cast<const QmlPropertyNode *>(node);
1566 spans.append(nameSpan);
1567 appendText(" : "_L1);
1568 appendTypeSpans(pn->dataType(), false);
1569 } else if (node->isFunction(Genus::QML)) {
1570 const auto *func = static_cast<const FunctionNode *>(node);
1571 if (!func->returnType().isEmpty())
1572 appendTypeSpans(func->returnTypeString(), true);
1573 spans.append(nameSpan);
1574 appendText("("_L1);
1575 if (!func->parameters().isEmpty()) {
1576 const Parameters &parameters = func->parameters();
1577 for (int i = 0; i < parameters.count(); ++i) {
1578 if (i > 0)
1579 appendText(", "_L1);
1580 QString pName = parameters.at(i).name();
1581 QString type = parameters.at(i).type();
1582 if (!pName.isEmpty()) {
1583 appendTypeSpans(type, true);
1584 IR::SignatureSpan paramSpan;
1585 paramSpan.role = IR::SpanRole::Parameter;
1586 paramSpan.text = pName;
1587 spans.append(paramSpan);
1588 } else {
1589 IR::SignatureSpan paramSpan;
1590 paramSpan.role = IR::SpanRole::Parameter;
1591 paramSpan.text = type;
1592 spans.append(paramSpan);
1593 }
1594 }
1595 }
1596 appendText(")"_L1);
1597 } else {
1598 spans.append(nameSpan);
1599 }
1600
1601 auto extras = buildExtraSpans(node, Section::Summary);
1602 if (!extras.isEmpty()) {
1603 appendText(" "_L1);
1604 spans.append(extras);
1605 }
1606
1607 return spans;
1608}
1609
1610static QString plainTextFromSpans(const QList<IR::SignatureSpan> &spans)
1611{
1612 QString result;
1613 for (const auto &span : spans)
1614 result += span.plainText();
1615 return result;
1616}
1617
1618/*!
1619 \internal
1620 Build structured signature spans from Node data.
1621
1622 This function produces a QList of SignatureSpan values that carry
1623 semantic roles (Type, Name, Parameter, Extra, and so on) for each
1624 element of a member's synopsis. It parallels what CppCodeMarker's
1625 markedUpSynopsis() and markedUpQmlItem() produce as tagged strings,
1626 but outputs structured IR spans instead.
1627
1628 The \a style parameter controls level of detail: Summary includes
1629 virtual/override qualifiers, Details adds template declarations and
1630 parent prefixes, AllMembers uses a condensed format.
1631*/
1633 const HrefResolver *hrefResolver,
1634 const Node *relative,
1635 Section::Style style)
1636{
1637 if (node->isQmlNode() && !node->isEnumType())
1638 return buildQmlItemSpans(node, hrefResolver);
1639 return buildCppSynopsisSpans(node, hrefResolver, relative, style);
1640}
1641
1642/*!
1643 \internal
1644 Extract a grouped all-members listing for a QML type.
1645
1646 Constructs a Sections object from the QmlTypeNode, extracts
1647 allMembersSection().classNodesList() to group members by
1648 originating QML type, and builds AllMemberEntry items with
1649 QML-specific hints and property group nesting.
1650*/
1651static IR::AllMembersIR extractQmlAllMembersIR(const QmlTypeNode *qcn, const HrefResolver *hrefResolver)
1652{
1653 IR::AllMembersIR result;
1654 result.typeName = qcn->name();
1655 result.typeHref = resolveHref(hrefResolver, qcn, qcn);
1656 result.isQmlType = true;
1657
1658 Sections sections(qcn);
1659 ClassNodesList &groupedMembers = sections.allMembersSection().classNodesList();
1660 if (groupedMembers.isEmpty())
1661 return result;
1662
1663 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
1664
1665 std::function<IR::AllMemberEntry(Node *)> buildEntry = [&](Node *node) -> IR::AllMemberEntry {
1666 IR::AllMemberEntry entry;
1667 entry.signatureSpans = buildQmlItemSpans(node, hrefResolver);
1668 entry.signature = plainTextFromSpans(entry.signatureSpans);
1669 entry.href = resolveHref(hrefResolver, node, qcn);
1670
1671 if (node->isQmlProperty()) {
1672 auto *qpn = static_cast<QmlPropertyNode *>(node);
1673 QStringList qmlHints = qpn->hints();
1674 if (qpn->isAttached() && !qmlHints.contains("attached"_L1))
1675 qmlHints << "attached"_L1;
1676 for (const auto &h : std::as_const(qmlHints))
1677 entry.hints.append(h);
1678 } else if (node->isAttached()) {
1679 entry.hints.append("attached"_L1);
1680 }
1681
1682 if (node->isPropertyGroup()) {
1683 entry.isPropertyGroup = true;
1684 const auto *scn = static_cast<SharedCommentNode *>(node);
1685 for (auto *child : scn->collective()) {
1686 const NodeContext childContext = child->createContext();
1687 if (!InclusionFilter::isIncluded(policy, childContext))
1688 continue;
1689 entry.children.append(buildEntry(child));
1690 }
1691 }
1692
1693 return entry;
1694 };
1695
1696 auto isVisible = [&policy](Node *node) {
1697 const NodeContext context = node->createContext();
1698 return InclusionFilter::isIncluded(policy, context)
1699 && !(node->isSharingComment() && node->sharedCommentNode()->isPropertyGroup());
1700 };
1701
1702 for (const auto &[originType, nodes] : groupedMembers) {
1703 Q_ASSERT(originType);
1704 if (nodes.isEmpty())
1705 continue;
1706
1707 IR::MemberGroup group;
1708 if (originType != qcn) {
1709 group.typeName = originType->name();
1710 group.typeHref = resolveHref(hrefResolver, originType, qcn);
1711 }
1712
1713 for (auto *node : nodes) {
1714 if (isVisible(node))
1715 group.members.append(buildEntry(node));
1716 }
1717
1718 result.memberGroups.append(group);
1719 }
1720
1721 return result;
1722}
1723
1724/*!
1725 \internal
1726 Extract a flat all-members listing for a C++ class or namespace.
1727
1728 Constructs a Sections object from the aggregate, extracts
1729 allMembersSection().members(), builds AllMemberEntry for each
1730 visible member, and returns an AllMembersIR with isQmlType=false.
1731*/
1732static IR::AllMembersIR extractCppAllMembersIR(const Aggregate *aggregate, const HrefResolver *hrefResolver)
1733{
1734 IR::AllMembersIR result;
1735 result.typeName = aggregate->plainFullName();
1736 result.typeHref = resolveHref(hrefResolver, aggregate, aggregate);
1737 result.isQmlType = false;
1738
1739 Sections sections(aggregate);
1740 const Section &allMembers = sections.allMembersSection();
1741 if (allMembers.isEmpty())
1742 return result;
1743
1744 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
1745
1746 for (const auto *node : allMembers.members()) {
1747 if (node->name().isEmpty())
1748 continue;
1749 const NodeContext context = node->createContext();
1750 if (!InclusionFilter::isIncluded(policy, context))
1751 continue;
1752
1753 IR::AllMemberEntry entry;
1754 entry.signatureSpans = buildSignatureSpans(node, hrefResolver, aggregate, Section::AllMembers);
1755 entry.signature = plainTextFromSpans(entry.signatureSpans);
1756 entry.href = resolveHref(hrefResolver, node, aggregate);
1757 result.members.append(entry);
1758 }
1759
1760 return result;
1761}
1762
1763/*!
1764 \internal
1765 Extract all-members IR for a page node.
1766
1767 Dispatches to the QML or C++ extraction function based on the page
1768 type. Returns std::nullopt for page types that don't have member
1769 listing pages (generic pages, QML basic types) or when the listing
1770 would be empty.
1771*/
1772std::optional<IR::AllMembersIR> extractAllMembersIR(const PageNode *pn, const HrefResolver *hrefResolver)
1773{
1774 if (pn->isQmlType()) {
1775 const auto *qcn = static_cast<const QmlTypeNode *>(pn);
1776 if (qcn->isQmlBasicType())
1777 return std::nullopt;
1778 auto result = extractQmlAllMembersIR(qcn, hrefResolver);
1779 bool hasMember = false;
1780 for (const auto &group : std::as_const(result.memberGroups)) {
1781 if (!group.members.isEmpty()) {
1782 hasMember = true;
1783 break;
1784 }
1785 }
1786 if (!hasMember)
1787 return std::nullopt;
1788 return result;
1789 }
1790
1791 if (pn->isAggregate() && (pn->isClassNode() || pn->isNamespace())) {
1792 const auto *aggregate = static_cast<const Aggregate *>(pn);
1793 auto result = extractCppAllMembersIR(aggregate, hrefResolver);
1794 if (result.members.isEmpty())
1795 return std::nullopt;
1796 return result;
1797 }
1798
1799 return std::nullopt;
1800}
1801
1802/*!
1803 \internal
1804 Extract navigation metadata from a PageNode.
1805
1806 Reads navigation configuration values (homepage, landingpage,
1807 cppclassespage, qmltypespage) and the node's position in the
1808 documentation tree to produce a breadcrumb chain, sequential
1809 links (previous/next/start), and the configured TOC depth.
1810
1811 The breadcrumb chain follows page-type-specific logic ported
1812 from HtmlGenerator::generateNavigationBar(): static chain
1813 entries for API reference pages (class, QML type), a
1814 navigationParent() walk for generic pages with a 16-item
1815 circular reference cutoff, and a fallback to the first group
1816 page when no navigation parent exists.
1817
1818 Sequential links come from the node's pre-populated link map,
1819 set by QDocDatabase::updateNavigation() before generation runs.
1820*/
1821IR::NavigationData extractNavigationData(const PageNode *pn, const HrefResolver *hrefResolver)
1822{
1823 IR::NavigationData nav;
1824 const Config &config = Config::instance();
1826
1827 const QString navDot = CONFIG_NAVIGATION + Config::dot;
1828 const QString homepage = config.get(navDot + CONFIG_HOMEPAGE).asString();
1829 const QString hometitle = config.get(navDot + CONFIG_HOMETITLE).asString(homepage);
1830 const QString landingpage = config.get(navDot + CONFIG_LANDINGPAGE).asString();
1831 const QString landingtitle = config.get(navDot + CONFIG_LANDINGTITLE).asString(landingpage);
1832 const QString cppclassespage = config.get(navDot + CONFIG_CPPCLASSESPAGE).asString();
1833 const QString cppclassestitle = config.get(navDot + CONFIG_CPPCLASSESTITLE).asString("C++ Classes"_L1);
1834 const QString qmltypespage = config.get(navDot + CONFIG_QMLTYPESPAGE).asString();
1835 const QString qmltypestitle = config.get(navDot + CONFIG_QMLTYPESTITLE).asString("QML Types"_L1);
1836
1837 const QString pageTitle = pn->title();
1838 using CrumbState = IR::NavigationData::CrumbState;
1839
1840 auto resolveCrumb = [&](const QString &targetName)
1841 -> std::pair<QString, CrumbState> {
1842 const Node *target = qdb->findNodeForTarget(targetName, pn);
1843 if (!target)
1844 return {{}, CrumbState::Unresolved};
1845 if (target == pn)
1846 return {{}, CrumbState::Current};
1847 return {resolveHref(hrefResolver, target, pn), CrumbState::Link};
1848 };
1849
1850 if (!homepage.isEmpty()) {
1851 auto [href, state] = resolveCrumb(homepage);
1852 if (state == CrumbState::Current)
1853 return nav;
1854 nav.breadcrumbs.append({hometitle, std::move(href), state});
1855 }
1856
1857 if (!landingpage.isEmpty()) {
1858 auto [href, state] = resolveCrumb(landingpage);
1859 if (state != CrumbState::Current)
1860 nav.breadcrumbs.append({landingtitle, std::move(href), state});
1861 }
1862
1863 if (pn->isClassNode()) {
1864 if (!cppclassespage.isEmpty() && !cppclassestitle.isEmpty()) {
1865 auto [href, state] = resolveCrumb(cppclassespage);
1866 nav.breadcrumbs.append({cppclassestitle, std::move(href), state});
1867 }
1868
1869 const auto *moduleNode = qdb->getModuleNode(pn);
1870 QString moduleState;
1871 if (moduleNode && !moduleNode->state().isEmpty())
1872 moduleState = QStringLiteral(" (%1)").arg(moduleNode->state());
1873
1874 if (!pn->physicalModuleName().isEmpty() && moduleNode
1875 && (!moduleState.isEmpty() || moduleNode->title() != cppclassespage)) {
1876 nav.breadcrumbs.append({moduleNode->name() + moduleState,
1877 resolveHref(hrefResolver, moduleNode, pn),
1878 CrumbState::Link});
1879 }
1880 nav.breadcrumbs.append({pn->name(), {}, CrumbState::Current});
1881 } else if (pn->isQmlType()) {
1882 if (!qmltypespage.isEmpty() && !qmltypestitle.isEmpty()) {
1883 auto [href, state] = resolveCrumb(qmltypespage);
1884 nav.breadcrumbs.append({qmltypestitle, std::move(href), state});
1885 }
1886
1887 const auto *moduleNode = qdb->getModuleNode(pn);
1888 QString moduleState;
1889 if (moduleNode && !moduleNode->state().isEmpty())
1890 moduleState = QStringLiteral(" (%1)").arg(moduleNode->state());
1891
1892 if (moduleNode
1893 && (!moduleState.isEmpty() || moduleNode->title() != qmltypespage)) {
1894 nav.breadcrumbs.append({moduleNode->name() + moduleState,
1895 resolveHref(hrefResolver, moduleNode, pn),
1896 CrumbState::Link});
1897 }
1898 nav.breadcrumbs.append({pn->name(), {}, CrumbState::Current});
1899 } else {
1900 auto currentNode = pn;
1901 std::deque<const Node *> navNodes;
1902 qsizetype navItems = 0;
1903 while (currentNode->navigationParent() && ++navItems < 16) {
1904 if (std::find(navNodes.cbegin(), navNodes.cend(),
1905 currentNode->navigationParent()) == navNodes.cend())
1906 navNodes.push_front(currentNode->navigationParent());
1907 currentNode = currentNode->navigationParent();
1908 }
1909 if (navNodes.empty()) {
1910 const QStringList groups = pn->groupNames();
1911 for (const auto &groupName : groups) {
1912 const auto *groupNode = qdb->findNodeByNameAndType(
1913 QStringList{groupName}, &Node::isGroup);
1914 if (groupNode && !groupNode->title().isEmpty()) {
1915 navNodes.push_front(groupNode);
1916 break;
1917 }
1918 }
1919 }
1920 for (const auto *navNode : navNodes) {
1921 if (navNode->isPageNode())
1922 nav.breadcrumbs.append({navNode->title(),
1923 resolveHref(hrefResolver, navNode, pn),
1924 CrumbState::Link});
1925 }
1926 if (!nav.breadcrumbs.isEmpty())
1927 nav.breadcrumbs.append({pageTitle, {}, CrumbState::Current});
1928 }
1929
1930 const auto &linkMap = pn->links();
1931 if (linkMap.contains(Node::PreviousLink)) {
1932 const auto &linkPair = linkMap[Node::PreviousLink];
1933 const Node *target = qdb->findNodeForTarget(linkPair.first, pn);
1934 QString href;
1935 QString title;
1936 if (target && target != pn) {
1937 href = resolveHref(hrefResolver, target, pn);
1938 title = (linkPair.first == linkPair.second && !target->title().isEmpty())
1939 ? target->title() : linkPair.second;
1940 } else {
1941 href = linkPair.first;
1942 title = linkPair.second;
1943 }
1944 nav.previousLink = IR::NavigationData::LinkEntry{title, href};
1945 }
1946 if (linkMap.contains(Node::NextLink)) {
1947 const auto &linkPair = linkMap[Node::NextLink];
1948 const Node *target = qdb->findNodeForTarget(linkPair.first, pn);
1949 QString href;
1950 QString title;
1951 if (target && target != pn) {
1952 href = resolveHref(hrefResolver, target, pn);
1953 title = (linkPair.first == linkPair.second && !target->title().isEmpty())
1954 ? target->title() : linkPair.second;
1955 } else {
1956 href = linkPair.first;
1957 title = linkPair.second;
1958 }
1959 nav.nextLink = IR::NavigationData::LinkEntry{title, href};
1960 }
1961 if (linkMap.contains(Node::StartLink)) {
1962 const auto &linkPair = linkMap[Node::StartLink];
1963 const Node *target = qdb->findNodeForTarget(linkPair.first, pn);
1964 QString href;
1965 QString title;
1966 if (target && target != pn) {
1967 href = resolveHref(hrefResolver, target, pn);
1968 title = (linkPair.first == linkPair.second && !target->title().isEmpty())
1969 ? target->title() : linkPair.second;
1970 } else {
1971 href = linkPair.first;
1972 title = linkPair.second;
1973 }
1974 nav.startLink = IR::NavigationData::LinkEntry{title, href};
1975 }
1976
1977 const QString formatDot = "HTML"_L1 + Config::dot;
1978 nav.tocDepth = config.get(formatDot + "tocdepth"_L1).asInt();
1979
1980 return nav;
1981}
1982
1983} // namespace NodeExtractor
1984
1985QT_END_NAMESPACE
bool hasObsoleteMembers() const
Returns true if this aggregate contains at least one child that is marked obsolete.
The Atom class is the fundamental unit for representing documents internally.
Definition atom.h:19
The ClassNode represents a C++ class.
Definition classnode.h:23
bool isQmlNativeType()
Definition classnode.h:54
A class for holding the members of a collection of doc pages.
NodeMap getMembers(NodeType type) const
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:95
const Location & location() const
Returns the starting location of a qdoc comment.
Definition doc.cpp:89
const Text & body() const
Definition doc.cpp:114
Text briefText(bool inclusive=false) const
Definition doc.cpp:126
Converts Atom chains to QList<IR::ContentBlock> trees.
static bool isIncluded(const InclusionPolicy &policy, const NodeContext &context)
The Location class provides a way to mark a location in a file.
Definition location.h:20
A PageNode is a Node that generates a documentation page.
Definition pagenode.h:19
const PageNode * navigationParent() const
Definition pagenode.h:47
bool noAutoList() const
Returns the value of the no auto-list flag.
Definition pagenode.h:42
The Parameter class describes one function parameter.
Definition parameter.h:14
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
static QDocDatabase * qdocDB()
Creates the singleton.
const CollectionNode * getModuleNode(const Node *relative)
Returns the collection node representing the module that relative node belongs to,...
const CNMap & groups()
Returns a const reference to the collection of all group nodes in the primary tree.
ClassNode * classNode() const override
If this is a QmlTypeNode, this function returns the pointer to the C++ ClassNode that this QML type r...
Definition qmltypenode.h:27
static void subclasses(const Node *base, NodeList &subs, bool recurse=false)
Loads the list subs with the nodes of all the subclasses of base.
bool isSingleton() const
Definition qmltypenode.h:31
QmlTypeNode * qmlBaseNode() const override
If this Aggregate is a QmlTypeNode, this function returns a pointer to the QmlTypeNode that is its ba...
Definition qmltypenode.h:54
CollectionNode * logicalModule() const override
If this is a QmlTypeNode, a pointer to its QML module is returned, which is a pointer to a Collection...
Definition qmltypenode.h:47
A class for containing the elements of one documentation section.
Definition sections.h:17
ClassNodesList & classNodesList()
Definition sections.h:52
@ Summary
Definition sections.h:19
@ Details
Definition sections.h:19
@ Accessors
Definition sections.h:19
@ AllMembers
Definition sections.h:19
bool isEmpty() const
Definition sections.h:35
A class for creating vectors of collections for documentation.
Definition sections.h:80
SectionVector & summarySections()
Definition sections.h:151
Sections(const Aggregate *aggregate)
This constructor builds the section vectors based on the type of the aggregate node.
Definition sections.cpp:372
SectionVector & detailsSections()
Definition sections.h:152
Section & allMembersSection()
Definition sections.h:153
Definition text.h:12
const Atom * firstAtom() const
Definition text.h:34
static std::string comparisonCategoryAsString(ComparisonCategory category)
#define CONFIG_CPPCLASSESTITLE
Definition config.h:384
#define CONFIG_HOMETITLE
Definition config.h:404
#define CONFIG_HOMEPAGE
Definition config.h:403
#define CONFIG_URL
Definition config.h:457
#define CONFIG_QMLTYPESPAGE
Definition config.h:463
#define CONFIG_CPPCLASSESPAGE
Definition config.h:383
#define CONFIG_NAVIGATION
Definition config.h:427
#define CONFIG_LANDINGPAGE
Definition config.h:416
#define CONFIG_LANDINGTITLE
Definition config.h:417
#define CONFIG_QMLTYPESTITLE
Definition config.h:464
NodeType
Definition genustypes.h:154
Definition builder.cpp:14
static QList< IR::SignatureSpan > buildTemplateDeclSpans(const RelaxedTemplateDeclaration *templateDecl, const HrefResolver *hrefResolver, const Node *relative)
static QList< IR::SignatureSpan > buildTypeSpans(const QString &typeString)
static QList< IR::SignatureSpan > buildQmlItemSpans(const Node *node, const HrefResolver *hrefResolver)
QList< IR::SignatureSpan > buildSignatureSpans(const Node *node, const HrefResolver *hrefResolver, const Node *relative, Section::Style style)
IR::CollectionData extractCollectionData(const CollectionNode *cn, const HrefResolver *hrefResolver)
IR::MemberIR extractMemberIR(const Node *node, const HrefResolver *hrefResolver, const Node *relative, MemberExtractionLevel level)
std::optional< IR::AllMembersIR > extractAllMembersIR(const PageNode *pn, const HrefResolver *hrefResolver)
QList< IR::SectionIR > extractSummarySections(const Aggregate *aggregate, const HrefResolver *hrefResolver)
static QString plainTextFromSpans(const QList< IR::SignatureSpan > &spans)
static IR::AllMembersIR extractQmlAllMembersIR(const QmlTypeNode *qcn, const HrefResolver *hrefResolver)
IR::QmlTypeData extractQmlTypeData(const QmlTypeNode *qcn, const HrefResolver *hrefResolver)
QList< IR::SectionIR > extractDetailSections(const Aggregate *aggregate, const HrefResolver *hrefResolver)
IR::PageMetadata extractPageMetadata(const PageNode *pn, const HrefResolver *hrefResolver)
static QList< IR::SignatureSpan > buildCppSynopsisSpans(const Node *node, const HrefResolver *hrefResolver, const Node *relative, Section::Style style)
static QString threadSafenessString(Node::ThreadSafeness ts)
IR::CppReferenceData extractCppReferenceData(const Aggregate *aggregate, const HrefResolver *hrefResolver)
static IR::AllMembersIR extractCppAllMembersIR(const Aggregate *aggregate, const HrefResolver *hrefResolver)
IR::NavigationData extractNavigationData(const PageNode *pn, const HrefResolver *hrefResolver)
static QList< IR::SignatureSpan > buildExtraSpans(const Node *node, Section::Style style)
QList< Node * > NodeList
Definition node.h:45
QMap< QString, Node * > NodeMap
Definition node.h:48
static QString resolveHref(const HrefResolver *resolver, const Node *target, const Node *relative)
static IR::DiagnosticHandler diagnosticHandlerFor(const Node *node)
MemberExtractionLevel
QList< ClassNodes > ClassNodesList
Definition sections.h:14
A single entry in an all-members listing page.
Definition member.h:98
Intermediate representation of the all-members listing page.
Definition member.h:115
Intermediate representation of a single documentable member.
Definition member.h:35
bool isPrimaryOverload
Definition member.h:48
bool isConst
Definition member.h:53
int overloadNumber
Definition member.h:47
bool isAttached
Definition member.h:59
bool isDefault
Definition member.h:60
bool isSlot
Definition member.h:56
bool isRequired
Definition member.h:62
bool isStatic
Definition member.h:52
bool isNoexcept
Definition member.h:72
bool isSignal
Definition member.h:55
Access access
Definition member.h:43
NodeType nodeType
Definition member.h:42
bool isReadOnly
Definition member.h:61
bool isVirtual
Definition member.h:54
Status status
Definition member.h:44
Intermediate representation of a function parameter.
Definition member.h:21
Represents a single span within a structured signature.
The Node class is the base class for all the nodes in QDoc's parse tree.
const Doc & doc() const
Returns a reference to the node's Doc data member.
Definition node.h:237
bool isQmlNode() const
Returns true if this node's Genus value is QML.
Definition node.h:121
bool isGroup() const
Returns true if the node type is Group.
Definition node.h:105
bool isNamespace() const
Returns true if the node type is Namespace.
Definition node.h:110
bool isTypedef() const
Returns true if the node type is Typedef.
Definition node.h:128
bool isQmlBasicType() const
Returns true if the node type is QmlBasicType.
Definition node.h:119
ComparisonCategory comparisonCategory() const
Definition node.h:186
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
Definition node.h:123
bool isHeader() const
Returns true if the node type is HeaderFile.
Definition node.h:106
NodeType nodeType() const override
Returns this node's type.
Definition node.h:82
Genus genus() const override
Returns this node's Genus.
Definition node.h:85
bool isEnumType() const
Returns true if the node type is Enum.
Definition node.h:94
virtual Status status() const
Returns the node's status value.
Definition node.h:241
bool isConcept() const
Definition node.h:109
Aggregate * parent() const
Returns the node's parent pointer.
Definition node.h:210
virtual bool isAggregate() const
Returns true if this node is an aggregate, which means it inherits Aggregate and can therefore have c...
Definition node.h:138
static bool nodeNameLessThan(const Node *first, const Node *second)
Returns true if the node n1 is less than node n2.
Definition node.cpp:111
bool isProxyNode() const
Returns true if the node type is Proxy.
Definition node.h:115
const std::optional< RelaxedTemplateDeclaration > & templateDecl() const
Definition node.h:245
Access access() const
Returns the node's Access setting, which can be Public, Protected, or Private.
Definition node.h:230
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
Definition node.h:101
ThreadSafeness threadSafeness() const
Returns the thread safeness value for whatever this node represents.
Definition node.cpp:848
bool isProperty() const
Returns true if the node type is Property.
Definition node.h:114
NodeContext createContext() const
Definition node.cpp:175
bool isModule() const
Returns true if the node type is Module.
Definition node.h:108
ThreadSafeness
An unsigned char that specifies the degree of thread-safeness of the element.
Definition node.h:58
@ UnspecifiedSafeness
Definition node.h:59
bool isRelatedNonmember() const
Returns true if this is a related nonmember of something.
Definition node.h:124
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
Definition node.h:145
virtual bool isCollectionNode() const
Returns true if this is an instance of CollectionNode.
Definition node.h:146
bool isQmlModule() const
Returns true if the node type is QmlModule.
Definition node.h:120
@ SignatureReturnType
Definition node.h:68
@ SignatureDefaultValues
Definition node.h:67
bool isQmlProperty() const
Returns true if the node type is QmlProperty.
Definition node.h:122
A class for parsing and managing a function parameter list.
Definition main.cpp:28
const Parameter & at(int i) const
Definition parameters.h:36
int count() const
Definition parameters.h:34