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 "variablenode.h"
36
37#include "location.h"
38
39#include <QRegularExpression>
40
42
43using namespace Qt::Literals;
44
46{
47 const Location &loc = node->doc().location();
48 return [loc](QtMsgType type, const QString &message) {
49 switch (type) {
50 case QtWarningMsg:
51 loc.warning(message);
52 break;
53 default:
54 loc.warning(message);
55 break;
56 }
57 };
58}
59
60static QString resolveHref(const HrefResolver *resolver, const Node *target, const Node *relative)
61{
62 if (!resolver)
63 return target->url();
64 auto result = resolver->hrefForNode(target, relative);
65 if (const auto *href = std::get_if<QString>(&result))
66 return *href;
67 return {};
68}
69
70namespace NodeExtractor {
71
72/*!
73 \internal
74 Extract page-level metadata from a PageNode into a value-type struct.
75
76 This function reads classification, identity, brief, and body fields
77 from the given PageNode and returns them as an IR::PageMetadata value.
78 Body content is populated via ContentBuilder, which transforms the
79 atom chain into structured content blocks. Format-conditional atoms
80 are skipped unconditionally since the template generator builds a
81 format-agnostic IR.
82
83 For aggregate pages (classes, QML types, namespaces), member listings
84 are extracted via the Sections infrastructure and stored as frozen
85 SectionIR values.
86
87 The caller (TemplateGenerator) invokes this before passing the
88 result to IR::Builder, ensuring Builder never includes PageNode
89 or other Node subclass headers.
90*/
91IR::PageMetadata extractPageMetadata(const PageNode *pn, const HrefResolver *hrefResolver)
92{
93 Q_ASSERT_X(pn, "NodeExtractor::extractPageMetadata",
94 "PageNode pointer must be non-null");
95 IR::PageMetadata pm;
96
97 pm.nodeType = pn->nodeType();
98 pm.genus = pn->genus();
99 pm.status = pn->status();
100 pm.access = pn->access();
101
102 if (pn->isQmlType()) {
103 const auto *qcn = static_cast<const QmlTypeNode *>(pn);
104 QString suffix = qcn->isQmlBasicType() ? " QML Value Type"_L1 : " QML Type"_L1;
105 pm.title = pn->name() + suffix;
106 pm.fullTitle = pm.title;
107 } else if (pn->isClassNode() || pn->isNamespace() || pn->isHeader()) {
108 // plainFullName() produces qualified names for nested aggregates
109 // (e.g. "Outer::Inner"), matching the legacy generator behavior.
110 // For top-level types and headers the result is the same as name().
111 const auto *aggregate = static_cast<const Aggregate *>(pn);
112 pm.fullTitle = aggregate->plainFullName();
113 pm.title = pm.fullTitle;
114 } else {
115 pm.title = pn->title();
116 pm.fullTitle = pn->fullTitle();
117 }
118
119 pm.url = pn->url();
120 pm.since = pn->since();
121 pm.deprecatedSince = pn->deprecatedSince();
122 pm.brief = pn->doc().briefText().toString();
123
124 const Text &bodyText = pn->doc().body();
125 if (const Atom *firstAtom = bodyText.firstAtom()) {
126 // Offset section heading levels to account for page structure.
127 // QDoc's \section1 maps to level 1, but pages already use <h1>
128 // for the title. The legacy generators apply a node-type-dependent
129 // offset; we replicate the same mapping here.
130 const int headingOffset = [&] {
131 switch (pn->nodeType()) {
133 case NodeType::Class:
134 case NodeType::Struct:
135 case NodeType::Union:
136 case NodeType::Module:
137 return 2;
141 case NodeType::Page:
142 case NodeType::Group:
143 return 1;
144 default:
145 return 3;
146 }
147 }();
148 IR::ContentBuilder contentBuilder(IR::BriefHandling::Skip, headingOffset,
149 diagnosticHandlerFor(pn));
150 pm.body = contentBuilder.build(firstAtom);
151 }
152
153 if (pn->isAggregate()) {
154 const auto *aggregate = static_cast<const Aggregate *>(pn);
155 pm.summarySections = extractSummarySections(aggregate, hrefResolver);
156 pm.detailSections = extractDetailSections(aggregate, hrefResolver);
157 }
158
159 if (pn->isQmlType()) {
160 const auto *qcn = static_cast<const QmlTypeNode *>(pn);
161 pm.qmlTypeData = extractQmlTypeData(qcn, hrefResolver);
162 }
163
164 if (pn->isCollectionNode()) {
165 const auto *cn = static_cast<const CollectionNode *>(pn);
166 pm.collectionData = extractCollectionData(cn, hrefResolver);
167 }
168
169 if (pn->isClassNode() || pn->isNamespace() || pn->isHeader()) {
170 const auto *aggregate = static_cast<const Aggregate *>(pn);
171 pm.cppReferenceData = extractCppReferenceData(aggregate, hrefResolver);
172 }
173
174 return pm;
175}
176
177/*!
178 \internal
179 Extract QML type metadata from a QmlTypeNode.
180
181 Populates import statement, inheritance chain, inherited-by list,
182 native C++ type link, and singleton/value-type flags. InclusionFilter
183 is applied to match the legacy generator's visibility filtering.
184*/
185IR::QmlTypeData extractQmlTypeData(const QmlTypeNode *qcn, const HrefResolver *hrefResolver)
186{
187 IR::QmlTypeData data;
188 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
189
190 if (!qcn->logicalModuleName().isEmpty()) {
191 bool includeImport = true;
192 const CollectionNode *collection = qcn->logicalModule();
193 if (collection) {
194 const NodeContext context = collection->createContext();
195 includeImport = InclusionFilter::isIncluded(policy, context);
196 }
197 if (includeImport) {
198 QStringList parts = QStringList()
199 << "import"_L1 << qcn->logicalModuleName() << qcn->logicalModuleVersion();
200 data.importStatement = parts.join(' '_L1).trimmed();
201 }
202 }
203
206
207 QmlTypeNode *base = qcn->qmlBaseNode();
208 while (base) {
209 const NodeContext context = base->createContext();
210 if (InclusionFilter::isIncluded(policy, context))
211 break;
212 base = base->qmlBaseNode();
213 }
214
215 NodeList subs;
216 QmlTypeNode::subclasses(qcn, subs, true);
217
218 if (base) {
219 IR::QmlTypeData::InheritsInfo inheritsInfo;
220 inheritsInfo.name = base->name();
221 inheritsInfo.href = resolveHref(hrefResolver, base, qcn);
222 const CollectionNode *baseModule = base->logicalModule();
223 if (baseModule) {
224 const NodeContext moduleContext = baseModule->createContext();
225 if (InclusionFilter::isIncluded(policy, moduleContext))
226 inheritsInfo.moduleName = base->logicalModuleName();
227 }
228 data.inherits = inheritsInfo;
229 }
230
231 if (!subs.isEmpty()) {
232 QList<IR::QmlTypeData::InheritedByEntry> filteredSubs;
233 for (const auto *sub : std::as_const(subs)) {
234 const NodeContext context = sub->createContext();
235 if (InclusionFilter::isIncluded(policy, context))
236 filteredSubs.append({sub->name(), resolveHref(hrefResolver, sub, qcn)});
237 }
238 std::sort(filteredSubs.begin(), filteredSubs.end(),
239 [](const IR::QmlTypeData::InheritedByEntry &a,
240 const IR::QmlTypeData::InheritedByEntry &b) {
241 return a.name < b.name;
242 });
243 data.inheritedBy = filteredSubs;
244 }
245
246 ClassNode *cn = qcn->classNode();
247 if (cn && cn->isQmlNativeType()) {
248 const NodeContext context = cn->createContext();
249 if (InclusionFilter::isIncluded(policy, context))
250 data.nativeType = IR::QmlTypeData::NativeTypeInfo{cn->name(), resolveHref(hrefResolver, cn, qcn)};
251 }
252
253 return data;
254}
255
256/*!
257 \internal
258 Extract collection metadata from a CollectionNode.
259
260 Populates module identity, CMake/qmake build variables, technology
261 preview state, and pre-sorted member listings. For C++ modules,
262 members are categorized into separate namespace and class lists.
263 For groups and QML modules, a single flat member list is produced.
264
265 All member lists are filtered through InclusionFilter (excluding
266 internal entries) and exclude deprecated nodes, then sorted
267 alphabetically by name (case-insensitive).
268*/
269IR::CollectionData extractCollectionData(const CollectionNode *cn, const HrefResolver *hrefResolver)
270{
271 IR::CollectionData data;
272
273 data.logicalModuleName = cn->logicalModuleName();
274 data.logicalModuleVersion = cn->logicalModuleVersion();
275 data.qtVariable = cn->qtVariable();
276 data.cmakePackage = cn->cmakePackage();
277 data.cmakeComponent = cn->cmakeComponent();
278 data.cmakeTargetItem = cn->cmakeTargetItem();
279 data.state = cn->state();
280
281 data.isModule = cn->isModule();
282 data.isQmlModule = cn->isQmlModule();
283 data.isGroup = cn->isGroup();
284 data.noAutoList = cn->noAutoList();
285
286 if (cn->noAutoList())
287 return data;
288
289 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
290
291 auto makeMemberEntry = [hrefResolver, cn](const Node *node) -> IR::CollectionData::MemberEntry {
292 return { node->name(), resolveHref(hrefResolver, node, cn), node->doc().briefText().toString() };
293 };
294
295 auto sortEntries = [](QList<IR::CollectionData::MemberEntry> &entries) {
296 std::sort(entries.begin(), entries.end(),
297 [](const IR::CollectionData::MemberEntry &a,
298 const IR::CollectionData::MemberEntry &b) {
299 return a.name.compare(b.name, Qt::CaseInsensitive) < 0;
300 });
301 };
302
303 if (cn->isModule()) {
305 for (auto *node : nsMap.values()) {
306 const NodeContext context = node->createContext();
307 if (InclusionFilter::isIncluded(policy, context) && !node->isDeprecated())
308 data.namespaces.append(makeMemberEntry(node));
309 }
310 sortEntries(data.namespaces);
311
312 const NodeMap classMap = cn->getMembers([](const Node *n) { return n->isClassNode(); });
313 for (auto *node : classMap.values()) {
314 const NodeContext context = node->createContext();
315 if (InclusionFilter::isIncluded(policy, context) && !node->isDeprecated())
316 data.classes.append(makeMemberEntry(node));
317 }
318 sortEntries(data.classes);
319 } else {
320 for (const auto *node : cn->members()) {
321 if (!node->isInAPI())
322 continue;
323 const NodeContext context = node->createContext();
324 if (InclusionFilter::isIncluded(policy, context) && !node->isDeprecated())
325 data.members.append(makeMemberEntry(node));
326 }
327 sortEntries(data.members);
328 }
329
330 return data;
331}
332
334
335/*!
336 \internal
337 Extract C++ reference page metadata from a class, namespace, or header.
338
339 Reads requisite table fields (header include, build-system snippets,
340 status), inheritance hierarchies, template declarations, comparison
341 operators, thread-safeness, and group associations. The result is a
342 value-type struct that captures everything the template generator needs
343 to render the requisites table and secondary sections without touching
344 the Node tree at render time.
345
346 All three aggregate page types (ClassNode, NamespaceNode, HeaderNode)
347 are handled, with ClassNode-specific sections gated on isClassNode().
348*/
349IR::CppReferenceData extractCppReferenceData(const Aggregate *aggregate, const HrefResolver *hrefResolver)
350{
351 IR::CppReferenceData data;
353
354 data.isNamespace = aggregate->isNamespace();
355 data.isHeader = aggregate->isHeader();
356 data.isInnerClass = aggregate->parent() && aggregate->parent()->isClassNode();
357 data.typeWord = aggregate->typeWord(false);
359
360 auto ancestors = aggregate->plainFullName().split("::"_L1);
361 ancestors.pop_back();
362 data.ancestorNames = ancestors;
363
364 if (aggregate->includeFile())
365 data.headerInclude = *aggregate->includeFile();
366
367 if (!aggregate->physicalModuleName().isEmpty()) {
368 const CollectionNode *cn =
369 qdb->getCollectionNode(aggregate->physicalModuleName(), NodeType::Module);
370 if (cn && (!cn->cmakeComponent().isEmpty() || !cn->cmakePackage().isEmpty())) {
371 const QString package = cn->cmakePackage().isEmpty()
372 ? "Qt"_L1 + QString::number(QT_VERSION_MAJOR)
373 : cn->cmakePackage();
374 QString findPkg;
375 if (cn->cmakeComponent().isEmpty())
376 findPkg = "find_package("_L1 + package + " REQUIRED)"_L1;
377 else
378 findPkg = "find_package("_L1 + package + " REQUIRED COMPONENTS "_L1
379 + cn->cmakeComponent() + ")"_L1;
380
381 QString target;
382 if (!cn->cmakeTargetItem().isEmpty()) {
383 target = cn->cmakeTargetItem();
384 } else if (cn->cmakeComponent().isEmpty()) {
385 target = package + "::"_L1 + package;
386 } else {
387 target = package + "::"_L1 + cn->cmakeComponent();
388 }
389
390 data.cmakeFindPackage = findPkg;
391 data.cmakeTargetLinkLibraries =
392 "target_link_libraries(mytarget PRIVATE "_L1 + target + ")"_L1;
393 }
394 if (cn && !cn->qtVariable().isEmpty())
395 data.qmakeVariable = "QT += "_L1 + cn->qtVariable();
396 }
397
398 auto statusOpt = formatStatus(aggregate, qdb);
399 if (statusOpt) {
400 data.statusText = *statusOpt;
401 if (aggregate->status() == Status::Deprecated)
402 data.statusCssClass = "deprecated"_L1;
403 else if (!aggregate->deprecatedSince().isEmpty())
404 data.statusCssClass = "pending-deprecation"_L1;
405 else if (aggregate->status() == Status::Preliminary)
406 data.statusCssClass = "preliminary"_L1;
407 else
408 data.statusCssClass = "status"_L1;
409 }
410
411 if (aggregate->isClassNode()) {
412 auto *classNode = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate));
413
414 if (classNode->isQmlNativeType()) {
415 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
416 const NodeContext context = classNode->createContext();
417 if (InclusionFilter::isIncluded(policy, context)) {
418 QList<QmlTypeNode *> nativeTypes{classNode->qmlNativeTypes().cbegin(),
419 classNode->qmlNativeTypes().cend()};
420 if (!nativeTypes.isEmpty()) {
421 std::sort(nativeTypes.begin(), nativeTypes.end(), Node::nodeNameLessThan);
422 data.qmlNativeType = IR::CppReferenceData::QmlNativeTypeLink{
423 nativeTypes.first()->name(),
424 resolveHref(hrefResolver, nativeTypes.first(), aggregate)
425 };
426 }
427 }
428 }
429
430 const auto *metaTags = classNode->doc().metaTagMap();
431 if (metaTags && metaTags->contains(u"qdoc-suppress-inheritance"_s))
432 data.suppressInheritance = true;
433
434 if (!data.suppressInheritance) {
435 const auto &baseClasses = classNode->baseClasses();
436 for (const auto &bc : baseClasses) {
437 if (bc.m_node) {
438 data.baseClasses.append({
439 bc.m_node->plainFullName(),
440 resolveHref(hrefResolver, bc.m_node, aggregate),
441 bc.m_access
442 });
443 }
444 }
445
446 const auto &derivedClasses = classNode->derivedClasses();
447 for (const auto &dc : derivedClasses) {
448 if (dc.m_node) {
449 data.derivedClasses.append({
450 dc.m_node->plainFullName(),
451 resolveHref(hrefResolver, dc.m_node, aggregate)
452 });
453 }
454 }
455 std::sort(data.derivedClasses.begin(), data.derivedClasses.end(),
458 return a.name.compare(b.name, Qt::CaseInsensitive) < 0;
459 });
460 }
461 }
462
463 if (aggregate->templateDecl()) {
464 data.templateDeclSpans = buildTemplateDeclSpans(&*aggregate->templateDecl());
465 }
466
467 const auto selfCategory = aggregate->comparisonCategory();
468 if (selfCategory != ComparisonCategory::None)
469 data.selfComparisonCategory = QString::fromStdString(comparisonCategoryAsString(selfCategory));
470
471 const auto *comparesMap = aggregate->doc().comparesWithMap();
472 if (comparesMap && !comparesMap->isEmpty()) {
473 for (auto [key, description] : comparesMap->asKeyValueRange()) {
474 IR::CppReferenceData::ComparisonEntry entry;
475 entry.category = QString::fromStdString(comparisonCategoryAsString(key));
476
477 const QStringList types{description.firstAtom()->string().split(';'_L1)};
478 entry.comparableTypes = types;
479
480 if (description.firstAtom()->next() != description.lastAtom()) {
481 Text descText = Text::subText(description.firstAtom()->next(),
482 description.lastAtom());
483 entry.description = descText.toString();
484 }
485 data.comparisonEntries.append(entry);
486 }
487 }
488
489 Node::ThreadSafeness ts = aggregate->threadSafeness();
490 if (ts != Node::UnspecifiedSafeness) {
492 switch (ts) {
493 case Node::NonReentrant:
494 tsInfo.level = "non-reentrant"_L1;
495 break;
496 case Node::Reentrant:
497 tsInfo.level = "reentrant"_L1;
498 break;
499 case Node::ThreadSafe:
500 tsInfo.level = "thread-safe"_L1;
501 break;
502 default:
503 break;
504 }
505
506 NodeList reentrant, threadsafe, nonreentrant;
507 bool hasExceptions = false;
508 for (const auto *child : aggregate->childNodes()) {
509 if (!child->isDeprecated()) {
510 switch (child->threadSafeness()) {
511 case Node::Reentrant:
512 reentrant.append(const_cast<Node *>(child));
513 if (ts == Node::ThreadSafe) hasExceptions = true;
514 break;
515 case Node::ThreadSafe:
516 threadsafe.append(const_cast<Node *>(child));
517 if (ts == Node::Reentrant) hasExceptions = true;
518 break;
519 case Node::NonReentrant:
520 nonreentrant.append(const_cast<Node *>(child));
521 hasExceptions = true;
522 break;
523 default:
524 break;
525 }
526 }
527 }
528 if (hasExceptions) {
529 for (const auto *node : std::as_const(reentrant)) {
530 tsInfo.reentrantExceptions.append({
531 node->plainFullName(),
532 resolveHref(hrefResolver, node, aggregate)
533 });
534 }
535 for (const auto *node : std::as_const(threadsafe)) {
536 tsInfo.threadSafeExceptions.append({
537 node->plainFullName(),
538 resolveHref(hrefResolver, node, aggregate)
539 });
540 }
541 for (const auto *node : std::as_const(nonreentrant)) {
542 tsInfo.nonReentrantExceptions.append({
543 node->plainFullName(),
544 resolveHref(hrefResolver, node, aggregate)
545 });
546 }
547 }
548 data.threadSafety = std::move(tsInfo);
549 }
550
551 const QStringList &groupNames = aggregate->groupNames();
552 if (!groupNames.isEmpty()) {
553 const auto &groupMap = qdb->groups();
554 for (const auto &groupName : groupNames) {
555 auto it = groupMap.find(groupName);
556 if (it == groupMap.end() || !*it)
557 continue;
558 CollectionNode *group = *it;
559 // TODO: mergeCollections() mutates the node tree during
560 // extraction, violating the principle that the new pipeline
561 // reads without side effects. Replace with an eager merge
562 // pass that runs before generation begins.
563 qdb->mergeCollections(group);
564 if (group->wasSeen()) {
565 data.groups.append({
566 group->fullTitle(),
567 resolveHref(hrefResolver, group, aggregate)
568 });
569 }
570 }
571 }
572
573 if (aggregate->isNamespace()) {
574 const auto *ns = static_cast<const NamespaceNode *>(aggregate);
575 if (!ns->hasDoc() && ns->docNode()) {
576 data.isPartialNamespace = true;
577 data.fullNamespaceHref = resolveHref(hrefResolver, ns->docNode(), aggregate);
578 data.fullNamespaceModuleName = ns->docNode()->tree()->camelCaseModuleName();
579 }
580 }
581
582 return data;
583}
584
585/*!
586 \internal
587 Build categorized summary sections for an aggregate node.
588
589 Delegates to the Sections class for member distribution, then extracts
590 results into frozen SectionIR values. The section variant (C++ class,
591 QML type, or generic) is chosen based on the aggregate's node type.
592*/
593QList<IR::SectionIR> extractSummarySections(const Aggregate *aggregate, const HrefResolver *hrefResolver)
594{
595 Sections sections(aggregate);
596
597 const auto &sv = sections.summarySections();
598
599 QList<IR::SectionIR> result;
600 for (const auto &section : sv) {
601 if (section.isEmpty())
602 continue;
603
604 IR::SectionIR irSection;
605 irSection.title = section.title();
606 irSection.id = Utilities::asAsciiPrintable(section.title());
607 irSection.singular = section.singular();
608 irSection.plural = section.plural();
609
610 // SharedCommentNode groups several declarations under one doc comment.
611 // Expand them into individual MemberIR entries so each function appears
612 // in the member table. This means the IR member count may exceed the
613 // Section::members() count.
614 for (const auto *member : section.members()) {
615 if (member->isSharedCommentNode()) {
616 const auto *scn = static_cast<const SharedCommentNode *>(member);
617 for (const auto *child : scn->collective()) {
618 IR::MemberIR irMember = extractMemberIR(child, hrefResolver, aggregate);
619 irMember.href = "#"_L1 + hrefResolver->anchorForNode(child);
620 irSection.members.append(irMember);
621 }
622 } else {
623 IR::MemberIR irMember = extractMemberIR(member, hrefResolver, aggregate);
624 irMember.href = "#"_L1 + hrefResolver->anchorForNode(member);
625 irSection.members.append(irMember);
626 }
627 }
628
629 for (const auto *reimpl : section.reimplementedMembers())
630 irSection.reimplementedMembers.append(extractMemberIR(reimpl, hrefResolver, aggregate));
631
632 for (const auto &[base, count] : section.inheritedMembers()) {
633 IR::InheritedMembersIR inherited;
634 inherited.className = base->plainFullName();
635 inherited.count = count;
636 inherited.href = resolveHref(hrefResolver, base, aggregate);
637 irSection.inheritedMembers.append(inherited);
638 }
639
640 result.append(irSection);
641 }
642 return result;
643}
644
645/*!
646 \internal
647 Build categorized detail sections for an aggregate node.
648
649 Iterates Sections::detailsSections() and extracts full member
650 documentation including body content, anchor IDs, and metadata.
651 SharedCommentNode groups share a single documentation body across
652 their children, with each child getting its own anchorId and synopsis.
653*/
654QList<IR::SectionIR> extractDetailSections(const Aggregate *aggregate, const HrefResolver *hrefResolver)
655{
656 Sections sections(aggregate);
657 const auto &sv = sections.detailsSections();
658
659 QList<IR::SectionIR> result;
660 for (const auto &section : sv) {
661 if (section.isEmpty())
662 continue;
663
664 IR::SectionIR irSection;
665 irSection.title = section.title();
666 irSection.id = Utilities::asAsciiPrintable(section.title());
667 irSection.singular = section.singular();
668 irSection.plural = section.plural();
669
670 for (const auto *member : section.members()) {
671 if (member->isSharedCommentNode()) {
672 const auto *scn = static_cast<const SharedCommentNode *>(member);
673
674 QList<IR::ContentBlock> sharedBody;
675 const Text &bodyText = scn->doc().body();
676 if (const Atom *firstAtom = bodyText.firstAtom()) {
677 IR::ContentBuilder contentBuilder(IR::BriefHandling::Include, 0,
678 diagnosticHandlerFor(scn));
679 sharedBody = contentBuilder.build(firstAtom);
680 }
681
682 QList<IR::ContentBlock> sharedAlso;
683 const QList<Text> &alsoTexts = scn->doc().alsoList();
684 for (const Text &alsoText : alsoTexts) {
685 if (const Atom *firstAtom = alsoText.firstAtom()) {
686 IR::ContentBuilder contentBuilder(IR::BriefHandling::Include, 0,
687 diagnosticHandlerFor(scn));
688 sharedAlso.append(contentBuilder.build(firstAtom));
689 }
690 }
691
692 for (const auto *child : scn->collective()) {
693 IR::MemberIR irMember = extractMemberIR(child, hrefResolver, aggregate, MemberExtractionLevel::Detail);
694 irMember.body = sharedBody;
695 irMember.alsoList = sharedAlso;
696 irSection.members.append(irMember);
697 }
698 } else {
699 irSection.members.append(extractMemberIR(member, hrefResolver, aggregate, MemberExtractionLevel::Detail));
700 }
701 }
702
703 result.append(irSection);
704 }
705 return result;
706}
707
709{
710 switch (ts) {
711 case Node::Reentrant:
712 return "reentrant"_L1;
713 case Node::ThreadSafe:
714 return "thread-safe"_L1;
715 default:
716 return {};
717 }
718}
719
720/*!
721 \internal
722 Build a MemberIR from a single Node.
723
724 Extracts identity, classification, and type-specific data from the node.
725 FunctionNode provides signatures, parameters, and overload metadata.
726 EnumNode provides scoped/unscoped signature and enum value listings.
727 PropertyNode provides a qualified data type signature.
728
729 When \a level is MemberExtractionLevel::Detail, also populates
730 detail documentation fields: anchorId, synopsis, since,
731 threadSafety, comparisonCategory, noexcept metadata, body (via
732 ContentBuilder), and alsoList.
733*/
734IR::MemberIR extractMemberIR(const Node *node, const HrefResolver *hrefResolver, const Node *relative, MemberExtractionLevel level)
735{
736 const bool includeDetail = (level == MemberExtractionLevel::Detail);
737 IR::MemberIR member;
738
739 member.name = node->name();
740 member.fullName = node->plainFullName();
741 member.href = resolveHref(hrefResolver, node, relative);
742 member.brief = node->doc().briefText().toString();
743
744 member.nodeType = node->nodeType();
745 member.access = node->access();
746 member.status = node->status();
747
748 if (node->isFunction()) {
749 const auto *fn = static_cast<const FunctionNode *>(node);
750 member.signature = fn->signature(
752 member.isStatic = fn->isStatic();
753 member.isConst = fn->isConst();
754 member.isVirtual = !fn->isNonvirtual();
755 member.isSignal = fn->isSignal();
756 member.isSlot = fn->isSlot();
757 member.overloadNumber = fn->overloadNumber();
758 member.isPrimaryOverload = fn->isPrimaryOverload();
759
760 const Parameters &params = fn->parameters();
761 for (int i = 0; i < params.count(); ++i) {
762 IR::ParameterIR param;
763 param.type = params.at(i).type();
764 param.name = params.at(i).name();
765 param.defaultValue = params.at(i).defaultValue();
766 member.parameters.append(param);
767 }
768 } else if (node->isEnumType()) {
769 const auto *en = static_cast<const EnumNode *>(node);
770 member.signature = en->isScoped()
771 ? QStringLiteral("enum class %1").arg(en->name())
772 : QStringLiteral("enum %1").arg(en->name());
773
774 for (const auto &item : en->items()) {
775 IR::EnumValueIR ev;
776 ev.name = item.name();
777 ev.value = item.value();
778 ev.since = item.since();
779 member.enumValues.append(ev);
780 }
781 } else if (node->isQmlProperty()) {
782 const auto *qpn = static_cast<const QmlPropertyNode *>(node);
783 member.signature = qpn->name() + " : "_L1 + qpn->dataType();
784 member.dataType = qpn->dataType();
785 member.isAttached = qpn->isAttached();
786 member.isDefault = qpn->isDefault();
787 member.isReadOnly = qpn->isReadOnly();
788 member.isRequired = qpn->isRequired();
789 } else if (node->isProperty()) {
790 const auto *pn = static_cast<const PropertyNode *>(node);
791 member.signature = pn->name() + " : "_L1 + pn->qualifiedDataType();
792 } else if (node->isTypedef()) {
793 const auto *td = static_cast<const TypedefNode *>(node);
794 member.signature = td->associatedEnum()
795 ? "flags "_L1 + td->name()
796 : td->name();
797 } else if (node->nodeType() == NodeType::Variable) {
798 const auto *vn = static_cast<const VariableNode *>(node);
799 member.signature = vn->leftType() + vn->name() + vn->rightType();
800 } else {
801 member.signature = node->name();
802 }
803
804 if (includeDetail) {
805 member.anchorId = hrefResolver->anchorForNode(node);
806 member.synopsis = member.signature;
807 member.since = node->since();
808 member.threadSafety = threadSafenessString(node->threadSafeness());
809
811 if (!catStr.empty())
812 member.comparisonCategory = QString::fromStdString(catStr);
813
814 if (node->isFunction()) {
815 const auto *fn = static_cast<const FunctionNode *>(node);
816 const auto &noexcept_ = fn->getNoexcept();
817 if (noexcept_) {
818 member.isNoexcept = true;
819 member.noexceptNote = *noexcept_;
820 }
821 }
822
823 const Text &bodyText = node->doc().body();
824 if (const Atom *firstAtom = bodyText.firstAtom()) {
825 IR::ContentBuilder contentBuilder(IR::BriefHandling::Include, 0,
826 diagnosticHandlerFor(node));
827 member.body = contentBuilder.build(firstAtom);
828 }
829
830 const QList<Text> &alsoTexts = node->doc().alsoList();
831 for (const Text &alsoText : alsoTexts) {
832 if (const Atom *firstAtom = alsoText.firstAtom()) {
833 IR::ContentBuilder contentBuilder(IR::BriefHandling::Include, 0,
834 diagnosticHandlerFor(node));
835 QList<IR::ContentBlock> blocks = contentBuilder.build(firstAtom);
836 member.alsoList.append(blocks);
837 }
838 }
839 }
840
841 Section::Style spanStyle = includeDetail ? Section::Details : Section::Summary;
842 member.signatureSpans = buildSignatureSpans(node, hrefResolver, relative, spanStyle);
843
844 return member;
845}
846
847static QList<IR::SignatureSpan> buildTypeSpans(const QString &typeString)
848{
849 QList<IR::SignatureSpan> spans;
850 QString pendingWord;
851
852 for (int i = 0; i <= typeString.size(); ++i) {
853 QChar ch;
854 if (i != typeString.size())
855 ch = typeString.at(i);
856
857 QChar lower = ch.toLower();
858 if ((lower >= 'a'_L1 && lower <= 'z'_L1) || ch.digitValue() >= 0
859 || ch == '_'_L1 || ch == ':'_L1) {
860 pendingWord += ch;
861 } else {
862 if (!pendingWord.isEmpty()) {
863 bool isProbablyType = (pendingWord != "const"_L1);
864 IR::SignatureSpan span;
865 span.role = isProbablyType ? IR::SpanRole::Type : IR::SpanRole::Text;
866 span.text = pendingWord;
867 spans.append(span);
868 }
869 pendingWord.clear();
870
871 if (!ch.isNull()) {
872 IR::SignatureSpan span;
874 span.text = QString(ch);
875 spans.append(span);
876 }
877 }
878 }
879 return spans;
880}
881
883{
884 QString extraStr = CodeMarker::extraSynopsis(node, style);
885 if (extraStr.isEmpty())
886 return {};
887
888 // extraSynopsis may contain <@extref target="...">text</@extref> tags for
889 // cppreference links. Parse those into ExternalRef spans; everything else
890 // becomes Extra spans.
891 static const QRegularExpression extrefRegex(
892 u"<@extref target=\"([^\"]+)\">([^<]+)</@extref>"_s);
893
894 QList<IR::SignatureSpan> spans;
895 IR::SignatureSpan wrapper;
896 wrapper.role = IR::SpanRole::Extra;
897
898 qsizetype pos = 0;
899 auto it = extrefRegex.globalMatch(extraStr);
900 while (it.hasNext()) {
901 auto match = it.next();
902 if (match.capturedStart() > pos) {
903 IR::SignatureSpan textSpan;
904 textSpan.role = IR::SpanRole::Text;
905 textSpan.text = extraStr.mid(pos, match.capturedStart() - pos);
906 wrapper.children.append(textSpan);
907 }
908 IR::SignatureSpan ref;
910 ref.text = match.captured(2);
911 ref.href = "https://en.cppreference.com/w/cpp/language/"_L1 + match.captured(1);
912 wrapper.children.append(ref);
913 pos = match.capturedEnd();
914 }
915 if (pos < extraStr.size()) {
916 IR::SignatureSpan textSpan;
917 textSpan.role = IR::SpanRole::Text;
918 textSpan.text = extraStr.mid(pos);
919 wrapper.children.append(textSpan);
920 }
921
922 if (wrapper.children.isEmpty()) {
923 wrapper.text = extraStr;
924 }
925 spans.append(wrapper);
926 return spans;
927}
928
930{
931 if (!templateDecl)
932 return {};
933
934 IR::SignatureSpan declSpan;
936 declSpan.text = "template"_L1;
937
938 IR::SignatureSpan open;
940 open.text = "<"_L1;
941 declSpan.children.append(open);
942
943 bool first = true;
944 for (const auto &param : templateDecl->parameters) {
945 if (param.sfinae_constraint)
946 continue;
947 if (!first) {
948 IR::SignatureSpan comma;
949 comma.role = IR::SpanRole::Text;
950 comma.text = ", "_L1;
951 declSpan.children.append(comma);
952 }
953
954 switch (param.kind) {
955 case RelaxedTemplateParameter::Kind::TypeTemplateParameter:
956 case RelaxedTemplateParameter::Kind::TemplateTemplateParameter: {
957 IR::SignatureSpan kw;
958 kw.role = IR::SpanRole::Text;
959 kw.text = "typename"_L1;
960 declSpan.children.append(kw);
961 break;
962 }
963 case RelaxedTemplateParameter::Kind::NonTypeTemplateParameter: {
964 if (!param.valued_declaration.type.empty()) {
965 auto typeSpans = buildTypeSpans(QString::fromStdString(param.valued_declaration.type));
966 declSpan.children.append(typeSpans);
967 }
968 break;
969 }
970 }
971
972 if (param.is_parameter_pack) {
973 IR::SignatureSpan dots;
974 dots.role = IR::SpanRole::Text;
975 dots.text = "..."_L1;
976 declSpan.children.append(dots);
977 }
978
979 if (!param.valued_declaration.name.empty()) {
980 IR::SignatureSpan space;
981 space.role = IR::SpanRole::Text;
982 space.text = " "_L1;
983 declSpan.children.append(space);
984
985 IR::SignatureSpan nameSpan;
986 nameSpan.role = IR::SpanRole::Parameter;
987 nameSpan.text = QString::fromStdString(param.valued_declaration.name);
988 declSpan.children.append(nameSpan);
989 }
990
991 if (!param.valued_declaration.initializer.empty()) {
992 IR::SignatureSpan eq;
993 eq.role = IR::SpanRole::Text;
994 eq.text = " = "_L1;
995 declSpan.children.append(eq);
996
997 if (param.kind == RelaxedTemplateParameter::Kind::TypeTemplateParameter
998 || param.kind == RelaxedTemplateParameter::Kind::TemplateTemplateParameter) {
999 auto typeSpans = buildTypeSpans(QString::fromStdString(param.valued_declaration.initializer));
1000 declSpan.children.append(typeSpans);
1001 } else {
1002 IR::SignatureSpan val;
1003 val.role = IR::SpanRole::Text;
1004 val.text = QString::fromStdString(param.valued_declaration.initializer);
1005 declSpan.children.append(val);
1006 }
1007 }
1008
1009 first = false;
1010 }
1011
1012 IR::SignatureSpan close;
1014 close.text = ">"_L1;
1015 declSpan.children.append(close);
1016
1017 if (templateDecl->requires_clause && !templateDecl->requires_clause->empty()) {
1018 IR::SignatureSpan req;
1020 req.text = " requires "_L1 + QString::fromStdString(*templateDecl->requires_clause);
1021 declSpan.children.append(req);
1022 }
1023
1024 return { declSpan };
1025}
1026
1028 const HrefResolver *hrefResolver,
1029 const Node *relative,
1030 Section::Style style)
1031{
1032 Q_UNUSED(hrefResolver);
1033 QList<IR::SignatureSpan> spans;
1034
1035 auto appendText = [&spans](const QString &text) {
1036 IR::SignatureSpan span;
1038 span.text = text;
1039 spans.append(span);
1040 };
1041
1042 auto appendName = [&spans, node, hrefResolver, relative](const QString &name) {
1043 IR::SignatureSpan span;
1045 span.text = name;
1046 span.href = resolveHref(hrefResolver, node, relative);
1047 spans.append(span);
1048 };
1049
1050 auto appendTypeSpans = [&spans](const QString &type, bool trailingSpace) {
1051 auto typeSpans = buildTypeSpans(type);
1052 spans.append(typeSpans);
1053 if (trailingSpace && !type.isEmpty()
1054 && !type.endsWith('*'_L1) && !type.endsWith('&'_L1)) {
1055 IR::SignatureSpan space;
1057 space.text = " "_L1;
1058 spans.append(space);
1059 }
1060 };
1061
1062 // Extra qualifiers go first (prepended in CppCodeMarker)
1063 if (style != Section::AllMembers) {
1064 auto extras = buildExtraSpans(node, style);
1065 if (!extras.isEmpty()) {
1066 spans.append(extras);
1067 appendText(" "_L1);
1068 }
1069 }
1070
1071 // Name with parent prefix for Details style
1072 QString nameText = node->name();
1073 bool linkName = (style != Section::Details);
1074
1075 if (style == Section::Details) {
1077 && !node->parent()->name().isEmpty()
1078 && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()) {
1079 nameText = node->parent()->name() + "::"_L1 + nameText;
1080 }
1081 }
1082
1083 switch (node->nodeType()) {
1085 case NodeType::Class:
1086 case NodeType::Struct:
1087 case NodeType::Union:
1088 appendText(Node::nodeTypeString(node->nodeType()) + " "_L1);
1089 if (linkName) {
1090 appendName(nameText);
1091 } else {
1092 IR::SignatureSpan span;
1094 span.text = nameText;
1095 spans.append(span);
1096 }
1097 break;
1098 case NodeType::Function: {
1099 const auto *func = static_cast<const FunctionNode *>(node);
1100
1101 if (style == Section::Details) {
1102 if (auto templateDecl = node->templateDecl()) {
1103 auto tmplSpans = buildTemplateDeclSpans(&*templateDecl);
1104 spans.append(tmplSpans);
1105 appendText(" "_L1);
1106 }
1107 }
1108
1109 if (style == Section::Summary || style == Section::Accessors) {
1110 if (!func->isNonvirtual())
1111 appendText("virtual "_L1);
1112 }
1113
1114 if (style != Section::AllMembers && !func->returnType().isEmpty())
1115 appendTypeSpans(func->returnTypeString(), true);
1116
1117 if (linkName) {
1118 appendName(nameText);
1119 } else {
1120 IR::SignatureSpan span;
1122 span.text = nameText;
1123 spans.append(span);
1124 }
1125
1126 if (!func->isMacroWithoutParams()) {
1127 appendText("("_L1);
1128 if (!func->parameters().isEmpty()) {
1129 const Parameters &parameters = func->parameters();
1130 for (int i = 0; i < parameters.count(); ++i) {
1131 if (i > 0)
1132 appendText(", "_L1);
1133 const Parameter &param = parameters.at(i);
1134 QString pName = param.name();
1135 QString type = param.type();
1136 QString value = param.defaultValue();
1137 qsizetype insertPos = param.nameInsertionPoint();
1138 if (insertPos >= 0 && style != Section::AllMembers && !pName.isEmpty()) {
1139 appendTypeSpans(type.left(insertPos), false);
1140 IR::SignatureSpan paramSpan;
1141 paramSpan.role = IR::SpanRole::Parameter;
1142 paramSpan.text = pName;
1143 spans.append(paramSpan);
1144 appendTypeSpans(type.mid(insertPos), false);
1145 } else {
1146 bool trailingSpace = style != Section::AllMembers && !pName.isEmpty();
1147 appendTypeSpans(type, trailingSpace);
1148 if (style != Section::AllMembers && !pName.isEmpty()) {
1149 IR::SignatureSpan paramSpan;
1150 paramSpan.role = IR::SpanRole::Parameter;
1151 paramSpan.text = pName;
1152 spans.append(paramSpan);
1153 }
1154 }
1155 if (style != Section::AllMembers && !value.isEmpty())
1156 appendText(" = "_L1 + value);
1157 }
1158 }
1159 appendText(")"_L1);
1160 }
1161
1162 if (func->isConst())
1163 appendText(" const"_L1);
1164
1165 if (style == Section::Summary || style == Section::Accessors) {
1166 if (func->isFinal())
1167 appendText(" final"_L1);
1168 if (func->isOverride())
1169 appendText(" override"_L1);
1170 if (func->isPureVirtual())
1171 appendText(" = 0"_L1);
1172 if (func->isRef())
1173 appendText(" &"_L1);
1174 else if (func->isRefRef())
1175 appendText(" &&"_L1);
1176 } else if (style == Section::AllMembers) {
1177 if (!func->returnType().isEmpty() && func->returnType() != "void"_L1) {
1178 appendText(" : "_L1);
1179 appendTypeSpans(func->returnTypeString(), false);
1180 }
1181 } else {
1182 if (func->isRef())
1183 appendText(" &"_L1);
1184 else if (func->isRefRef())
1185 appendText(" &&"_L1);
1186 if (const auto &req = func->trailingRequiresClause(); req && !req->isEmpty())
1187 appendText(" requires "_L1 + *req);
1188 }
1189 break;
1190 }
1191 case NodeType::Enum: {
1192 const auto *enume = static_cast<const EnumNode *>(node);
1193 appendText("enum"_L1);
1194 if (enume->isScoped())
1195 appendText(" class"_L1);
1196 if (!enume->isAnonymous()) {
1197 appendText(" "_L1);
1198 if (linkName) {
1199 appendName(nameText);
1200 } else {
1201 IR::SignatureSpan span;
1203 span.text = nameText;
1204 spans.append(span);
1205 }
1206 }
1207 if (style == Section::Summary) {
1208 appendText(" { "_L1);
1209 const int MaxEnumValues = 6;
1210 QStringList documentedItems = enume->doc().enumItemNames();
1211 if (documentedItems.isEmpty()) {
1212 const auto &enumItems = enume->items();
1213 for (const auto &item : enumItems)
1214 documentedItems << item.name();
1215 }
1216 const QStringList omitItems = enume->doc().omitEnumItemNames();
1217 for (const auto &item : omitItems)
1218 documentedItems.removeAll(item);
1219
1220 if (documentedItems.size() > MaxEnumValues) {
1221 const QString last = documentedItems.last();
1222 documentedItems = documentedItems.mid(0, MaxEnumValues - 1);
1223 documentedItems += "..."_L1;
1224 documentedItems += last;
1225 }
1226 appendText(documentedItems.join(", "_L1));
1227 if (!documentedItems.isEmpty())
1228 appendText(" "_L1);
1229 appendText("}"_L1);
1230 }
1231 break;
1232 }
1233 case NodeType::TypeAlias: {
1234 if (style == Section::Details) {
1235 if (auto templateDecl = node->templateDecl()) {
1236 auto tmplSpans = buildTemplateDeclSpans(&*templateDecl);
1237 spans.append(tmplSpans);
1238 appendText(" "_L1);
1239 }
1240 }
1241 if (linkName) {
1242 appendName(nameText);
1243 } else {
1244 IR::SignatureSpan span;
1246 span.text = nameText;
1247 spans.append(span);
1248 }
1249 break;
1250 }
1251 case NodeType::Typedef: {
1252 if (static_cast<const TypedefNode *>(node)->associatedEnum())
1253 appendText("flags "_L1);
1254 if (linkName) {
1255 appendName(nameText);
1256 } else {
1257 IR::SignatureSpan span;
1259 span.text = nameText;
1260 spans.append(span);
1261 }
1262 break;
1263 }
1264 case NodeType::Property: {
1265 const auto *property = static_cast<const PropertyNode *>(node);
1266 if (linkName) {
1267 appendName(nameText);
1268 } else {
1269 IR::SignatureSpan span;
1271 span.text = nameText;
1272 spans.append(span);
1273 }
1274 appendText(" : "_L1);
1275 appendTypeSpans(property->qualifiedDataType(), false);
1276 break;
1277 }
1278 case NodeType::QmlProperty: {
1279 const auto *property = static_cast<const QmlPropertyNode *>(node);
1280 if (linkName) {
1281 appendName(nameText);
1282 } else {
1283 IR::SignatureSpan span;
1285 span.text = nameText;
1286 spans.append(span);
1287 }
1288 appendText(" : "_L1);
1289 appendTypeSpans(property->dataType(), false);
1290 break;
1291 }
1292 case NodeType::Variable: {
1293 const auto *variable = static_cast<const VariableNode *>(node);
1294 if (style == Section::AllMembers) {
1295 if (linkName) {
1296 appendName(nameText);
1297 } else {
1298 IR::SignatureSpan span;
1300 span.text = nameText;
1301 spans.append(span);
1302 }
1303 appendText(" : "_L1);
1304 appendTypeSpans(variable->dataType(), false);
1305 } else {
1306 appendTypeSpans(variable->leftType(), true);
1307 if (linkName) {
1308 appendName(nameText);
1309 } else {
1310 IR::SignatureSpan span;
1312 span.text = nameText;
1313 spans.append(span);
1314 }
1315 appendText(variable->rightType());
1316 }
1317 break;
1318 }
1319 default:
1320 if (linkName) {
1321 appendName(nameText);
1322 } else {
1323 IR::SignatureSpan span;
1325 span.text = nameText;
1326 spans.append(span);
1327 }
1328 break;
1329 }
1330
1331 return spans;
1332}
1333
1335 const HrefResolver *hrefResolver)
1336{
1337 QList<IR::SignatureSpan> spans;
1338
1339 auto appendText = [&spans](const QString &text) {
1340 IR::SignatureSpan span;
1342 span.text = text;
1343 spans.append(span);
1344 };
1345
1346 auto appendTypeSpans = [&spans](const QString &type, bool trailingSpace) {
1347 auto typeSpans = buildTypeSpans(type);
1348 spans.append(typeSpans);
1349 if (trailingSpace && !type.isEmpty()
1350 && !type.endsWith('*'_L1) && !type.endsWith('&'_L1)) {
1351 IR::SignatureSpan space;
1353 space.text = " "_L1;
1354 spans.append(space);
1355 }
1356 };
1357
1358 IR::SignatureSpan nameSpan;
1359 nameSpan.role = IR::SpanRole::Name;
1360 nameSpan.text = node->name();
1361 nameSpan.href = resolveHref(hrefResolver, node, node->parent());
1362
1363 if (node->isQmlProperty()) {
1364 const auto *pn = static_cast<const QmlPropertyNode *>(node);
1365 spans.append(nameSpan);
1366 appendText(" : "_L1);
1367 appendTypeSpans(pn->dataType(), false);
1368 } else if (node->isFunction(Genus::QML)) {
1369 const auto *func = static_cast<const FunctionNode *>(node);
1370 if (!func->returnType().isEmpty())
1371 appendTypeSpans(func->returnTypeString(), true);
1372 spans.append(nameSpan);
1373 appendText("("_L1);
1374 if (!func->parameters().isEmpty()) {
1375 const Parameters &parameters = func->parameters();
1376 for (int i = 0; i < parameters.count(); ++i) {
1377 if (i > 0)
1378 appendText(", "_L1);
1379 QString pName = parameters.at(i).name();
1380 QString type = parameters.at(i).type();
1381 if (!pName.isEmpty()) {
1382 appendTypeSpans(type, true);
1383 IR::SignatureSpan paramSpan;
1384 paramSpan.role = IR::SpanRole::Parameter;
1385 paramSpan.text = pName;
1386 spans.append(paramSpan);
1387 } else {
1388 IR::SignatureSpan paramSpan;
1389 paramSpan.role = IR::SpanRole::Parameter;
1390 paramSpan.text = type;
1391 spans.append(paramSpan);
1392 }
1393 }
1394 }
1395 appendText(")"_L1);
1396 } else {
1397 spans.append(nameSpan);
1398 }
1399
1400 auto extras = buildExtraSpans(node, Section::Summary);
1401 if (!extras.isEmpty()) {
1402 appendText(" "_L1);
1403 spans.append(extras);
1404 }
1405
1406 return spans;
1407}
1408
1409static QString plainTextFromSpans(const QList<IR::SignatureSpan> &spans)
1410{
1411 QString result;
1412 for (const auto &span : spans)
1413 result += span.plainText();
1414 return result;
1415}
1416
1417/*!
1418 \internal
1419 Build structured signature spans from Node data.
1420
1421 This function produces a QList of SignatureSpan values that carry
1422 semantic roles (Type, Name, Parameter, Extra, and so on) for each
1423 element of a member's synopsis. It parallels what CppCodeMarker's
1424 markedUpSynopsis() and markedUpQmlItem() produce as tagged strings,
1425 but outputs structured IR spans instead.
1426
1427 The \a style parameter controls level of detail: Summary includes
1428 virtual/override qualifiers, Details adds template declarations and
1429 parent prefixes, AllMembers uses a condensed format.
1430*/
1432 const HrefResolver *hrefResolver,
1433 const Node *relative,
1434 Section::Style style)
1435{
1436 if (node->isQmlNode() && !node->isEnumType())
1437 return buildQmlItemSpans(node, hrefResolver);
1438 return buildCppSynopsisSpans(node, hrefResolver, relative, style);
1439}
1440
1441/*!
1442 \internal
1443 Extract a grouped all-members listing for a QML type.
1444
1445 Constructs a Sections object from the QmlTypeNode, extracts
1446 allMembersSection().classNodesList() to group members by
1447 originating QML type, and builds AllMemberEntry items with
1448 QML-specific hints and property group nesting.
1449*/
1450static IR::AllMembersIR extractQmlAllMembersIR(const QmlTypeNode *qcn, const HrefResolver *hrefResolver)
1451{
1452 IR::AllMembersIR result;
1453 result.typeName = qcn->name();
1454 result.typeHref = resolveHref(hrefResolver, qcn, qcn);
1455 result.isQmlType = true;
1456
1457 Sections sections(qcn);
1458 ClassNodesList &groupedMembers = sections.allMembersSection().classNodesList();
1459 if (groupedMembers.isEmpty())
1460 return result;
1461
1462 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
1463
1464 std::function<IR::AllMemberEntry(Node *)> buildEntry = [&](Node *node) -> IR::AllMemberEntry {
1465 IR::AllMemberEntry entry;
1466 entry.signatureSpans = buildQmlItemSpans(node, hrefResolver);
1467 entry.signature = plainTextFromSpans(entry.signatureSpans);
1468 entry.href = resolveHref(hrefResolver, node, qcn);
1469
1470 if (node->isQmlProperty()) {
1471 auto *qpn = static_cast<QmlPropertyNode *>(node);
1472 QStringList qmlHints = qpn->hints();
1473 if (qpn->isAttached() && !qmlHints.contains("attached"_L1))
1474 qmlHints << "attached"_L1;
1475 for (const auto &h : std::as_const(qmlHints))
1476 entry.hints.append(h);
1477 } else if (node->isAttached()) {
1478 entry.hints.append("attached"_L1);
1479 }
1480
1481 if (node->isPropertyGroup()) {
1482 entry.isPropertyGroup = true;
1483 const auto *scn = static_cast<SharedCommentNode *>(node);
1484 for (auto *child : scn->collective()) {
1485 const NodeContext childContext = child->createContext();
1486 if (!InclusionFilter::isIncluded(policy, childContext))
1487 continue;
1488 entry.children.append(buildEntry(child));
1489 }
1490 }
1491
1492 return entry;
1493 };
1494
1495 auto isVisible = [&policy](Node *node) {
1496 const NodeContext context = node->createContext();
1497 return InclusionFilter::isIncluded(policy, context)
1498 && !(node->isSharingComment() && node->sharedCommentNode()->isPropertyGroup());
1499 };
1500
1501 for (const auto &[originType, nodes] : groupedMembers) {
1502 Q_ASSERT(originType);
1503 if (nodes.isEmpty())
1504 continue;
1505
1506 IR::MemberGroup group;
1507 if (originType != qcn) {
1508 group.typeName = originType->name();
1509 group.typeHref = resolveHref(hrefResolver, originType, qcn);
1510 }
1511
1512 for (auto *node : nodes) {
1513 if (isVisible(node))
1514 group.members.append(buildEntry(node));
1515 }
1516
1517 result.memberGroups.append(group);
1518 }
1519
1520 return result;
1521}
1522
1523/*!
1524 \internal
1525 Extract a flat all-members listing for a C++ class or namespace.
1526
1527 Constructs a Sections object from the aggregate, extracts
1528 allMembersSection().members(), builds AllMemberEntry for each
1529 visible member, and returns an AllMembersIR with isQmlType=false.
1530*/
1531static IR::AllMembersIR extractCppAllMembersIR(const Aggregate *aggregate, const HrefResolver *hrefResolver)
1532{
1533 IR::AllMembersIR result;
1534 result.typeName = aggregate->plainFullName();
1535 result.typeHref = resolveHref(hrefResolver, aggregate, aggregate);
1536 result.isQmlType = false;
1537
1538 Sections sections(aggregate);
1539 const Section &allMembers = sections.allMembersSection();
1540 if (allMembers.isEmpty())
1541 return result;
1542
1543 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
1544
1545 for (const auto *node : allMembers.members()) {
1546 if (node->name().isEmpty())
1547 continue;
1548 const NodeContext context = node->createContext();
1549 if (!InclusionFilter::isIncluded(policy, context))
1550 continue;
1551
1552 IR::AllMemberEntry entry;
1553 entry.signatureSpans = buildSignatureSpans(node, hrefResolver, aggregate, Section::AllMembers);
1554 entry.signature = plainTextFromSpans(entry.signatureSpans);
1555 entry.href = resolveHref(hrefResolver, node, aggregate);
1556 result.members.append(entry);
1557 }
1558
1559 return result;
1560}
1561
1562/*!
1563 \internal
1564 Extract all-members IR for a page node.
1565
1566 Dispatches to the QML or C++ extraction function based on the page
1567 type. Returns std::nullopt for page types that don't have member
1568 listing pages (generic pages, QML basic types) or when the listing
1569 would be empty.
1570*/
1571std::optional<IR::AllMembersIR> extractAllMembersIR(const PageNode *pn, const HrefResolver *hrefResolver)
1572{
1573 if (pn->isQmlType()) {
1574 const auto *qcn = static_cast<const QmlTypeNode *>(pn);
1575 if (qcn->isQmlBasicType())
1576 return std::nullopt;
1577 auto result = extractQmlAllMembersIR(qcn, hrefResolver);
1578 bool hasMember = false;
1579 for (const auto &group : std::as_const(result.memberGroups)) {
1580 if (!group.members.isEmpty()) {
1581 hasMember = true;
1582 break;
1583 }
1584 }
1585 if (!hasMember)
1586 return std::nullopt;
1587 return result;
1588 }
1589
1590 if (pn->isAggregate() && (pn->isClassNode() || pn->isNamespace())) {
1591 const auto *aggregate = static_cast<const Aggregate *>(pn);
1592 auto result = extractCppAllMembersIR(aggregate, hrefResolver);
1593 if (result.members.isEmpty())
1594 return std::nullopt;
1595 return result;
1596 }
1597
1598 return std::nullopt;
1599}
1600
1601} // namespace NodeExtractor
1602
1603QT_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
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
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 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:47
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:40
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:150
Sections(const Aggregate *aggregate)
This constructor builds the section vectors based on the type of the aggregate node.
Definition sections.cpp:371
SectionVector & detailsSections()
Definition sections.h:151
Section & allMembersSection()
Definition sections.h:152
Definition text.h:12
const Atom * firstAtom() const
Definition text.h:34
static std::string comparisonCategoryAsString(ComparisonCategory category)
NodeType
Definition genustypes.h:150
Definition builder.cpp:14
static QList< IR::SignatureSpan > buildTypeSpans(const QString &typeString)
static QList< IR::SignatureSpan > buildQmlItemSpans(const Node *node, const HrefResolver *hrefResolver)
static QList< IR::SignatureSpan > buildTemplateDeclSpans(const RelaxedTemplateDeclaration *templateDecl)
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)
static QList< IR::SignatureSpan > buildExtraSpans(const Node *node, Section::Style style)
Combined button and popup list for selecting options.
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:235
bool isQmlNode() const
Returns true if this node's Genus value is QML.
Definition node.h:119
bool isGroup() const
Returns true if the node type is Group.
Definition node.h:104
bool isNamespace() const
Returns true if the node type is Namespace.
Definition node.h:108
bool isTypedef() const
Returns true if the node type is Typedef.
Definition node.h:126
bool isQmlBasicType() const
Returns true if the node type is QmlBasicType.
Definition node.h:117
ComparisonCategory comparisonCategory() const
Definition node.h:184
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
Definition node.h:121
bool isHeader() const
Returns true if the node type is HeaderFile.
Definition node.h:105
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:93
virtual Status status() const
Returns the node's status value.
Definition node.h:239
Aggregate * parent() const
Returns the node's parent pointer.
Definition node.h:208
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:136
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:113
const std::optional< RelaxedTemplateDeclaration > & templateDecl() const
Definition node.h:243
Access access() const
Returns the node's Access setting, which can be Public, Protected, or Private.
Definition node.h:228
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
Definition node.h:100
ThreadSafeness threadSafeness() const
Returns the thread safeness value for whatever this node represents.
Definition node.cpp:840
bool isProperty() const
Returns true if the node type is Property.
Definition node.h:112
NodeContext createContext() const
Definition node.cpp:175
bool isModule() const
Returns true if the node type is Module.
Definition node.h:107
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:122
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
Definition node.h:143
virtual bool isCollectionNode() const
Returns true if this is an instance of CollectionNode.
Definition node.h:144
bool isQmlModule() const
Returns true if the node type is QmlModule.
Definition node.h:118
@ SignatureReturnType
Definition node.h:68
@ SignatureDefaultValues
Definition node.h:67
bool isQmlProperty() const
Returns true if the node type is QmlProperty.
Definition node.h:120
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