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