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
generator.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "generator.h"
5
6#include "access.h"
7#include "aggregate.h"
8#include "classnode.h"
9#include "codemarker.h"
10#include "codeparser.h"
11#include "collectionnode.h"
13#include "config.h"
14#include "doc.h"
15#include "editdistance.h"
16#include "enumnode.h"
17#include "examplenode.h"
18#include "functionnode.h"
21#include "inode.h"
22#include "node.h"
23#include "openedlist.h"
24#include "propertynode.h"
25#include "qdocdatabase.h"
26#include "qmltypenode.h"
28#include "quoter.h"
30#include "tokenizer.h"
31#include "typedefnode.h"
32#include "utilities.h"
33
34#include <QtCore/qdebug.h>
35#include <QtCore/qdir.h>
36#include <QtCore/qregularexpression.h>
37
38#ifndef QT_BOOTSTRAPPED
39# include "QtCore/qurl.h"
40#endif
41
42#include <string>
43
44using namespace std::literals::string_literals;
45
46QT_BEGIN_NAMESPACE
47
48using namespace Qt::StringLiterals;
49
50Generator *Generator::s_currentGenerator;
51QMap<QString, QMap<QString, QString>> Generator::s_fmtLeftMaps;
52QMap<QString, QMap<QString, QString>> Generator::s_fmtRightMaps;
53QList<Generator *> Generator::s_generators;
54QString Generator::s_outDir;
55QString Generator::s_outSubdir;
56QStringList Generator::s_outFileNames;
57QSet<QString> Generator::s_trademarks;
58QSet<QString> Generator::s_outputFormats;
59QHash<QString, QString> Generator::s_outputPrefixes;
60QHash<QString, QString> Generator::s_outputSuffixes;
61QString Generator::s_project;
62bool Generator::s_noLinkErrors = false;
63bool Generator::s_autolinkErrors = false;
64bool Generator::s_redirectDocumentationToDevNull = false;
65bool Generator::s_useOutputSubdirs = true;
66QmlTypeNode *Generator::s_qmlTypeContext = nullptr;
67
68static QRegularExpression tag("</?@[^>]*>");
69static QLatin1String amp("&amp;");
70static QLatin1String gt("&gt;");
71static QLatin1String lt("&lt;");
72static QLatin1String quot("&quot;");
73
74/*!
75 Constructs the generator base class. Prepends the newly
76 constructed generator to the list of output generators.
77 Sets a pointer to the QDoc database singleton, which is
78 available to the generator subclasses.
79 */
81 : file_resolver{file_resolver}
82{
84 s_generators.prepend(this);
85}
86
87/*!
88 Destroys the generator after removing it from the list of
89 output generators.
90 */
92{
93 s_generators.removeAll(this);
94}
95
96void Generator::appendFullName(Text &text, const Node *apparentNode, const Node *relative,
97 const Node *actualNode)
99 if (actualNode == nullptr)
100 actualNode = apparentNode;
101
102 addNodeLink(text, actualNode, apparentNode->plainFullName(relative));
103}
104
105void Generator::appendFullName(Text &text, const Node *apparentNode, const QString &fullName,
106 const Node *actualNode)
107{
108 if (actualNode == nullptr)
109 actualNode = apparentNode;
110
111 addNodeLink(text, actualNode, fullName);
112}
113
114/*!
115 Append the signature for the function named in \a node to
116 \a text, so that is a link to the documentation for that
117 function.
118 */
119void Generator::appendSignature(Text &text, const Node *node)
120{
121 addNodeLink(text, node, node->signature(Node::SignaturePlain));
122}
123
124/*!
125 Generate a bullet list of function signatures. The function
126 nodes are in \a nodes. It uses the \a relative node and the
127 \a marker for the generation.
128 */
129void Generator::signatureList(const NodeList &nodes, const Node *relative, CodeMarker *marker)
130{
131 Text text;
132 int count = 0;
133 text << Atom(Atom::ListLeft, QString("bullet"));
134 for (const auto &node : nodes) {
135 text << Atom(Atom::ListItemNumber, QString::number(++count));
136 text << Atom(Atom::ListItemLeft, QString("bullet"));
137 appendSignature(text, node);
138 text << Atom(Atom::ListItemRight, QString("bullet"));
139 }
140 text << Atom(Atom::ListRight, QString("bullet"));
141 generateText(text, relative, marker);
142}
143
144int Generator::appendSortedNames(Text &text, const ClassNode *cn, const QList<RelatedClass> &rc)
145{
146 QMap<QString, Text> classMap;
147 for (const auto &relatedClass : rc) {
148 ClassNode *rcn = relatedClass.m_node;
149 if (rcn && rcn->isInAPI()) {
150 Text className;
151 appendFullName(className, rcn, cn);
152 classMap[className.toString().toLower()] = className;
153 }
154 }
155
156 int index = 0;
157 const QStringList classNames = classMap.keys();
158 for (const auto &className : classNames) {
159 text << classMap[className];
160 text << Utilities::comma(index++, classNames.size());
161 }
162 return index;
163}
164
165int Generator::appendSortedQmlNames(Text &text, const Node *base, const QStringList &knownTypes,
166 const NodeList &subs)
167{
168 QMap<QString, Text> classMap;
169
170 QStringList typeNames(knownTypes);
171 for (const auto sub : subs)
172 typeNames << sub->name();
173
174 for (const auto sub : subs) {
175 Text full_name;
176 appendFullName(full_name, sub, base);
177 // Disambiguate with '(<QML module name>)' if there are clashing type names
178 if (typeNames.count(sub->name()) > 1)
179 full_name << Atom(Atom::String, " (%1)"_L1.arg(sub->logicalModuleName()));
180 classMap[full_name.toString().toLower()] = full_name;
181 }
182
183 int index = 0;
184 const auto &names = classMap.keys();
185 for (const auto &name : names)
186 text << classMap[name] << Utilities::comma(index++, names.size());
187 return index;
188}
189
190/*!
191 Creates the file named \a fileName in the output directory
192 and returns a QFile pointing to this file. In particular,
193 this method deals with errors when opening the file:
194 the returned QFile is always valid and can be written to.
195
196 \sa beginSubPage()
197 */
198QFile *Generator::openSubPageFile(const PageNode *node, const QString &fileName)
199{
200 // Skip generating a warning for license attribution pages, as their source
201 // is generated by qtattributionsscanner and may potentially include duplicates.
202 // NOTE: Depending on the value of the `QtParts` field in qt_attribution.json files,
203 // qtattributionsscanner may not use the \attribution QDoc command for the page
204 // (by design). Therefore, check also filename.
205 if (s_outFileNames.contains(fileName) && !node->isAttribution() && !fileName.contains("-attribution-"_L1))
206 node->location().warning("Already generated %1 for this project"_L1.arg(fileName));
207
208 QString path = outputDir() + QLatin1Char('/') + fileName;
209
210 const auto &outPath = s_redirectDocumentationToDevNull ? QStringLiteral("/dev/null") : path;
211 auto outFile = new QFile(outPath);
212
213 if (!s_redirectDocumentationToDevNull && outFile->exists()) {
214 const QString warningText {"Output file already exists, overwriting %1"_L1.arg(outFile->fileName())};
215 if (qEnvironmentVariableIsSet("QDOC_ALL_OVERWRITES_ARE_WARNINGS"))
216 node->location().warning(warningText);
217 else
218 qCDebug(lcQdoc) << qUtf8Printable(warningText);
219 }
220
221 if (!outFile->open(QFile::WriteOnly | QFile::Text)) {
222 node->location().fatal(
223 QStringLiteral("Cannot open output file '%1'").arg(outFile->fileName()));
224 }
225
226 qCDebug(lcQdoc, "Writing: %s", qPrintable(path));
227 s_outFileNames << fileName;
228 s_trademarks.clear();
229 return outFile;
230}
231
232/*!
233 Creates the file named \a fileName in the output directory.
234 Attaches a QTextStream to the created file, which is written
235 to all over the place using out().
236 */
237void Generator::beginSubPage(const Node *node, const QString &fileName)
238{
239 Q_ASSERT(node->isPageNode());
240 QFile *outFile = openSubPageFile(static_cast<const PageNode*>(node), fileName);
241 auto *out = new QTextStream(outFile);
242 outStreamStack.push(out);
243}
244
245/*!
246 Flush the text stream associated with the subpage, and
247 then pop it off the text stream stack and delete it.
248 This terminates output of the subpage.
249 */
251{
252 outStreamStack.top()->flush();
253 delete outStreamStack.top()->device();
254 delete outStreamStack.pop();
255}
256
257QString Generator::fileBase(const Node *node) const
258{
259 if (!node->isPageNode() && !node->isCollectionNode())
260 node = node->parent();
261
262 if (node->hasFileNameBase())
263 return node->fileNameBase();
264
265 QString base{node->name()};
266 if (base.endsWith(".html"))
267 base.truncate(base.size() - 5);
268
269 if (node->isCollectionNode()) {
270 if (node->isQmlModule())
271 base.append("-qmlmodule");
272 else if (node->isModule())
273 base.append("-module");
274 base.append(outputSuffix(node));
275 } else if (node->isTextPageNode()) {
276 if (node->isExample()) {
277 base.prepend("%1-"_L1.arg(s_project.toLower()));
278 base.append("-example");
279 }
280 } else if (node->isQmlType()) {
281 /*
282 To avoid file name conflicts in the html directory,
283 we prepend a prefix (by default, "qml-") and an optional suffix
284 to the file name. The suffix, if one exists, is appended to the
285 module name.
286
287 For historical reasons, skip the module name qualifier for QML value types
288 in order to avoid excess redirects in the online docs. TODO: re-assess
289 */
290 if (!node->logicalModuleName().isEmpty() && !node->isQmlBasicType()) {
291 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
292 const NodeContext context = node->logicalModule()->createContext();
293 if (InclusionFilter::isIncluded(policy, context))
294 base.prepend("%1%2-"_L1.arg(node->logicalModuleName(), outputSuffix(node)));
295 }
296
297 } else if (node->isProxyNode()) {
298 base.append("-%1-proxy"_L1.arg(node->tree()->physicalModuleName()));
299 } else {
300 base.clear();
301 const Node *p = node;
302 forever {
303 const Node *pp = p->parent();
304 base.prepend(p->name());
305 if (pp == nullptr || pp->name().isEmpty() || pp->isTextPageNode())
306 break;
307 base.prepend('-'_L1);
308 p = pp;
309 }
310 if (node->isNamespace() && !node->name().isEmpty()) {
311 const auto *ns = static_cast<const NamespaceNode *>(node);
312 if (!ns->isDocumentedHere()) {
313 base.append(QLatin1String("-sub-"));
314 base.append(ns->tree()->camelCaseModuleName());
315 }
316 }
317 base.append(outputSuffix(node));
318 }
319
320 base.prepend(outputPrefix(node));
321 QString canonicalName{ Utilities::asAsciiPrintable(base) };
322 Node *n = const_cast<Node *>(node);
323 n->setFileNameBase(canonicalName);
324 return canonicalName;
325}
326
327/*!
328 Constructs an href link from an example file name, which
329 is a \a path to the example file. If \a fileExt is empty
330 (default value), retrieve the file extension from
331 the generator.
332 */
333QString Generator::linkForExampleFile(const QString &path, const QString &fileExt)
334{
335 QString link{path};
336 link.prepend(s_project.toLower() + QLatin1Char('-'));
337
338 QString canonicalName{ Utilities::asAsciiPrintable(link) };
339 canonicalName.append(QLatin1Char('.'));
340 canonicalName.append(fileExt.isEmpty() ? fileExtension() : fileExt);
341 return canonicalName;
342}
343
344/*!
345 Helper function to construct a title for a file or image page
346 included in an example.
347*/
348QString Generator::exampleFileTitle(const ExampleNode *relative, const QString &fileName)
349{
350 QString suffix;
351 if (relative->files().contains(fileName))
352 suffix = QLatin1String(" Example File");
353 else if (relative->images().contains(fileName))
354 suffix = QLatin1String(" Image File");
355 else
356 return suffix;
357
358 return fileName.mid(fileName.lastIndexOf(QLatin1Char('/')) + 1) + suffix;
359}
360
361/*!
362 If the \a node has a URL, return the URL as the file name.
363 Otherwise, construct the file name from the fileBase() and
364 either the provided \a extension or fileExtension(), and
365 return the constructed name.
366 */
367QString Generator::fileName(const Node *node, const QString &extension) const
368{
369 if (!node->url().isEmpty())
370 return node->url();
371
372 // Special case for simple page nodes (\page commands) with explicit
373 // non-.html extensions. Use the normalized fileBase() but preserve
374 // user specified extension
375 if (node->isTextPageNode() && !node->isCollectionNode() && extension.isNull()) {
376 QFileInfo originalName(node->name());
377 QString suffix = originalName.suffix();
378 if (!suffix.isEmpty() && suffix != "html") {
379 // User specified a non-.html extension - use normalized base + original extension
380 QString name = fileBase(node);
381 return name + QLatin1Char('.') + suffix;
382 }
383 }
384
385 QString name = fileBase(node) + QLatin1Char('.');
386 return name + (extension.isNull() ? fileExtension() : extension);
387}
388
389/*!
390 Clean the given \a ref to be used as an HTML anchor or an \c xml:id.
391 If \a xmlCompliant is set to \c true, a stricter process is used, as XML
392 is more rigorous in what it accepts. Otherwise, if \a xmlCompliant is set to
393 \c false, the basic HTML transformations are applied.
394
395 More specifically, only XML NCNames are allowed
396 (https://www.w3.org/TR/REC-xml-names/#NT-NCName).
397 */
398QString Generator::cleanRef(const QString &ref, bool xmlCompliant)
399{
400 // XML-compliance is ensured in two ways:
401 // - no digit (0-9) at the beginning of an ID (many IDs do not respect this property)
402 // - no colon (:) anywhere in the ID (occurs very rarely)
403
404 QString clean;
405
406 if (ref.isEmpty())
407 return clean;
408
409 clean.reserve(ref.size() + 20);
410 const QChar c = ref[0];
411 const uint u = c.unicode();
412
413 if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (!xmlCompliant && u >= '0' && u <= '9')) {
414 clean += c;
415 } else if (xmlCompliant && u >= '0' && u <= '9') {
416 clean += QLatin1Char('A') + c;
417 } else if (u == '~') {
418 clean += "dtor.";
419 } else if (u == '_') {
420 clean += "underscore.";
421 } else {
422 clean += QLatin1Char('A');
423 }
424
425 for (int i = 1; i < ref.size(); i++) {
426 const QChar c = ref[i];
427 const uint u = c.unicode();
428 if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9') || u == '-'
429 || u == '_' || (xmlCompliant && u == ':') || u == '.') {
430 clean += c;
431 } else if (c.isSpace()) {
432 clean += QLatin1Char('-');
433 } else if (u == '!') {
434 clean += "-not";
435 } else if (u == '&') {
436 clean += "-and";
437 } else if (u == '<') {
438 clean += "-lt";
439 } else if (u == '=') {
440 clean += "-eq";
441 } else if (u == '>') {
442 clean += "-gt";
443 } else if (u == '#') {
444 clean += QLatin1Char('#');
445 } else {
446 clean += QLatin1Char('-');
447 clean += QString::number(static_cast<int>(u), 16);
448 }
449 }
450 return clean;
451}
452
454{
455 return s_fmtLeftMaps[format()];
456}
457
459{
460 return s_fmtRightMaps[format()];
461}
462
463/*!
464 Returns the full document location.
465 */
467{
468 if (node == nullptr)
469 return QString();
470 if (!node->url().isEmpty())
471 return node->url();
472
473 QString parentName;
474 QString anchorRef;
475
476 if (node->isNamespace()) {
477 /*
478 The root namespace has no name - check for this before creating
479 an attribute containing the location of any documentation.
480 */
481 if (!fileBase(node).isEmpty())
482 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
483 else
484 return QString();
485 } else if (node->isQmlType()) {
486 return fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
487 } else if (node->isTextPageNode() || node->isCollectionNode()) {
488 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
489 } else if (fileBase(node).isEmpty())
490 return QString();
491
492 Node *parentNode = nullptr;
493
494 if ((parentNode = node->parent())) {
495 // use the parent's name unless the parent is the root namespace
496 if (!node->parent()->isNamespace() || !node->parent()->name().isEmpty())
497 parentName = fullDocumentLocation(node->parent());
498 }
499
500 switch (node->nodeType()) {
501 case NodeType::Class:
502 case NodeType::Struct:
503 case NodeType::Union:
504 case NodeType::Namespace:
505 case NodeType::Proxy:
506 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
507 break;
508 case NodeType::Function: {
509 const auto *fn = static_cast<const FunctionNode *>(node);
510 switch (fn->metaness()) {
512 anchorRef = QLatin1Char('#') + node->name() + "-signal";
513 break;
515 anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
516 break;
518 anchorRef = QLatin1Char('#') + node->name() + "-method";
519 break;
520 default:
521 if (fn->isDtor())
522 anchorRef = "#dtor." + fn->name().mid(1);
523 else if (const auto *p = fn->primaryAssociatedProperty(); p && fn->doc().isEmpty())
524 return fullDocumentLocation(p);
525 else if (fn->overloadNumber() > 0)
526 anchorRef = QLatin1Char('#') + cleanRef(fn->name()) + QLatin1Char('-')
527 + QString::number(fn->overloadNumber());
528 else
529 anchorRef = QLatin1Char('#') + cleanRef(fn->name());
530 break;
531 }
532 break;
533 }
534 /*
535 Use node->name() instead of fileBase(node) as
536 the latter returns the name in lower-case. For
537 HTML anchors, we need to preserve the case.
538 */
539 case NodeType::Enum:
541 anchorRef = QLatin1Char('#') + node->name() + "-enum";
542 break;
543 case NodeType::Typedef: {
544 const auto *tdef = static_cast<const TypedefNode *>(node);
545 if (tdef->associatedEnum())
546 return fullDocumentLocation(tdef->associatedEnum());
547 } Q_FALLTHROUGH();
549 anchorRef = QLatin1Char('#') + node->name() + "-typedef";
550 break;
552 anchorRef = QLatin1Char('#') + node->name() + "-prop";
553 break;
555 if (!node->isPropertyGroup())
556 break;
557 } Q_FALLTHROUGH();
559 if (node->isAttached())
560 anchorRef = QLatin1Char('#') + node->name() + "-attached-prop";
561 else
562 anchorRef = QLatin1Char('#') + node->name() + "-prop";
563 break;
565 anchorRef = QLatin1Char('#') + node->name() + "-var";
566 break;
568 case NodeType::Page:
569 case NodeType::Group:
571 case NodeType::Module:
572 case NodeType::QmlModule: {
573 parentName = fileBase(node);
574 parentName.replace(QLatin1Char('/'), QLatin1Char('-'))
575 .replace(QLatin1Char('.'), QLatin1Char('-'));
576 parentName += QLatin1Char('.') + currentGenerator()->fileExtension();
577 } break;
578 default:
579 break;
580 }
581
582 if (!node->isClassNode() && !node->isNamespace()) {
583 if (node->isDeprecated())
584 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
585 "-obsolete." + currentGenerator()->fileExtension());
586 }
587
588 return parentName.toLower() + anchorRef;
589}
590
591void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
592{
593 QList<Text> alsoList = node->doc().alsoList();
594 supplementAlsoList(node, alsoList);
595
596 if (!alsoList.isEmpty()) {
597 Text text;
598 text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << "See also "
600
601 QSet<QString> used;
602 QList<Text> items;
603 for (const auto &also : std::as_const(alsoList)) {
604 // Every item starts with a link atom.
605 QString link = also.firstAtom()->string();
606 if (!used.contains(link)) {
607 items.append(also);
608 used.insert(link);
609 }
610 }
611
612 int i = 0;
613 for (const auto &also : std::as_const(items))
614 text << also << Utilities::separator(i++, items.size());
615
616 text << Atom::ParaRight;
617 generateText(text, node, marker);
618 }
619}
620
621const Atom *Generator::generateAtomList(const Atom *atom, const Node *relative, CodeMarker *marker,
622 bool generate, int &numAtoms)
623{
624 while (atom != nullptr) {
625 if (atom->type() == Atom::FormatIf) {
626 int numAtoms0 = numAtoms;
627 bool rightFormat = canHandleFormat(atom->string());
628 atom = generateAtomList(atom->next(), relative, marker, generate && rightFormat,
629 numAtoms);
630 if (atom == nullptr)
631 return nullptr;
632
633 if (atom->type() == Atom::FormatElse) {
634 ++numAtoms;
635 atom = generateAtomList(atom->next(), relative, marker, generate && !rightFormat,
636 numAtoms);
637 if (atom == nullptr)
638 return nullptr;
639 }
640
641 if (atom->type() == Atom::FormatEndif) {
642 if (generate && numAtoms0 == numAtoms) {
643 relative->location().warning(QStringLiteral("Output format %1 not handled %2")
644 .arg(format(), outFileName()));
645 Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
646 generateAtomList(&unhandledFormatAtom, relative, marker, generate, numAtoms);
647 }
648 atom = atom->next();
649 }
650 } else if (atom->type() == Atom::FormatElse || atom->type() == Atom::FormatEndif) {
651 return atom;
652 } else {
653 int n = 1;
654 if (generate) {
655 n += generateAtom(atom, relative, marker);
656 numAtoms += n;
657 }
658 while (n-- > 0)
659 atom = atom->next();
660 }
661 }
662 return nullptr;
663}
664
665
666/*!
667 Generate the body of the documentation from the qdoc comment
668 found with the entity represented by the \a node.
669 */
670void Generator::generateBody(const Node *node, CodeMarker *marker)
671{
672 const FunctionNode *fn = node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
673 if (!node->hasDoc()) {
674 /*
675 Test for special function, like a destructor or copy constructor,
676 that has no documentation.
677 */
678 if (fn) {
679 if (fn->isDtor()) {
680 Text text;
681 text << "Destroys the instance of ";
682 text << fn->parent()->name() << ".";
683 if (fn->isVirtual())
684 text << " The destructor is virtual.";
685 out() << "<p>";
686 generateText(text, node, marker);
687 out() << "</p>";
688 } else if (fn->isCtor()) {
689 Text text;
690 text << "Default constructs an instance of ";
691 text << fn->parent()->name() << ".";
692 out() << "<p>";
693 generateText(text, node, marker);
694 out() << "</p>";
695 } else if (fn->isCCtor()) {
696 Text text;
697 text << "Copy constructor.";
698 out() << "<p>";
699 generateText(text, node, marker);
700 out() << "</p>";
701 } else if (fn->isMCtor()) {
702 Text text;
703 text << "Move-copy constructor.";
704 out() << "<p>";
705 generateText(text, node, marker);
706 out() << "</p>";
707 } else if (fn->isCAssign()) {
708 Text text;
709 text << "Copy-assignment operator.";
710 out() << "<p>";
711 generateText(text, node, marker);
712 out() << "</p>";
713 } else if (fn->isMAssign()) {
714 Text text;
715 text << "Move-assignment operator.";
716 out() << "<p>";
717 generateText(text, node, marker);
718 out() << "</p>";
719 } else if (!node->isWrapper() && !node->isMarkedReimp()) {
720 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
721 const NodeContext context = node->createContext();
722 if (!fn->isIgnored() && InclusionFilter::requiresDocumentation(policy, context)) // undocumented functions added by Q_OBJECT
723 node->location().warning(QStringLiteral("No documentation for '%1'")
724 .arg(node->plainSignature()));
725 }
726 } else if (!node->isWrapper() && !node->isMarkedReimp()) {
727 // Don't require documentation of things defined in Q_GADGET
728 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
729 const NodeContext context = node->createContext();
730 if (node->name() != QLatin1String("QtGadgetHelper") && InclusionFilter::requiresDocumentation(policy, context))
731 node->location().warning(
732 QStringLiteral("No documentation for '%1'").arg(node->plainSignature()));
733 }
734 } else if (!node->isSharingComment()) {
735 // Reimplements clause and type alias info precede body text
736 if (fn && !fn->overridesThis().isEmpty())
737 generateReimplementsClause(fn, marker);
738 else if (node->isProperty()) {
741 }
742
743 if (!generateText(node->doc().body(), node, marker)) {
744 if (node->isMarkedReimp())
745 return;
746 }
747
748 if (fn) {
749 if (fn->isQmlSignal())
753 if (fn->isInvokable())
757 if (fn->hasOverloads() && fn->doc().hasOverloadCommand())
759 }
760
761 // Generate warnings
762 if (node->isEnumType()) {
763 const auto *enume = static_cast<const EnumNode *>(node);
764
765 QSet<QString> definedItems;
766 const QList<EnumItem> &items = enume->items();
767 for (const auto &item : items)
768 definedItems.insert(item.name());
769
770 const auto &documentedItemList = enume->doc().enumItemNames();
771 QSet<QString> documentedItems(documentedItemList.cbegin(), documentedItemList.cend());
772 const QSet<QString> allItems = definedItems + documentedItems;
773 if (allItems.size() > definedItems.size()
774 || allItems.size() > documentedItems.size()) {
775 for (const auto &it : allItems) {
776 if (!definedItems.contains(it)) {
777 QString details;
778 QString best = nearestName(it, definedItems);
779 if (!best.isEmpty() && !documentedItems.contains(best))
780 details = QStringLiteral("Maybe you meant '%1'?").arg(best);
781
782 node->doc().location().warning(
783 QStringLiteral("No such enum item '%1' in %2")
784 .arg(it, node->plainFullName()),
785 details);
786 } else if (!documentedItems.contains(it)) {
787 node->doc().location().warning(
788 QStringLiteral("Undocumented enum item '%1' in %2")
789 .arg(it, node->plainFullName()));
790 }
791 }
792 }
793 } else if (fn) {
794 const QSet<QString> declaredNames = fn->parameters().getNames();
795 const QSet<QString> documentedNames = fn->doc().parameterNames();
796 if (declaredNames != documentedNames) {
797 for (const auto &name : declaredNames) {
798 if (!documentedNames.contains(name)) {
799 if (fn->isActive() || fn->isPreliminary()) {
800 // Require no parameter documentation for overrides and overloads,
801 // and only require it for non-overloaded constructors.
802 if (!fn->isMarkedReimp() && !fn->isOverload() &&
803 !(fn->isSomeCtor() && fn->hasOverloads())) {
804 fn->doc().location().warning(
805 QStringLiteral("Undocumented parameter '%1' in %2")
806 .arg(name, node->plainFullName()));
807 }
808 }
809 }
810 }
811 for (const auto &name : documentedNames) {
812 if (!declaredNames.contains(name) && CodeParser::isWorthWarningAbout(fn->doc())) {
813 QString best = nearestName(name, declaredNames);
814 QString details;
815 if (!best.isEmpty())
816 details = QStringLiteral("Maybe you meant '%1'?").arg(best);
817 fn->doc().location().warning(QStringLiteral("No such parameter '%1' in %2")
818 .arg(name, fn->plainFullName()),
819 details);
820 }
821 }
822 }
823 /*
824 This return value check should be implemented
825 for all functions with a return type.
826 mws 13/12/2018
827 */
829 && !fn->isOverload()) {
830 if (!fn->doc().body().contains("return"))
831 node->doc().location().warning(
832 QStringLiteral("Undocumented return value "
833 "(hint: use 'return' or 'returns' in the text"));
834 }
835 } else if (node->isQmlProperty()) {
836 if (auto *qpn = static_cast<const QmlPropertyNode *>(node); !qpn->validateDataType())
837 qpn->doc().location().warning("Invalid QML property type: %1"_L1.arg(qpn->dataType()));
838 }
839 }
841 generateRequiredLinks(node, marker);
842}
843
844/*!
845 Generates either a link to the project folder for example \a node, or a list
846 of links files/images if 'url.examples config' variable is not defined.
847
848 Does nothing for non-example nodes.
849*/
851{
852 if (!node->isExample())
853 return;
854
855 const auto *en = static_cast<const ExampleNode *>(node);
856 QString exampleUrl{Config::instance().get(CONFIG_URL + Config::dot + CONFIG_EXAMPLES).asString()};
857
858 if (exampleUrl.isEmpty()) {
859 if (!en->noAutoList()) {
860 generateFileList(en, marker, false); // files
861 generateFileList(en, marker, true); // images
862 }
863 } else {
864 generateLinkToExample(en, marker, exampleUrl);
865 }
866}
867
868/*!
869 Generates an external link to the project folder for example \a node.
870 The path to the example replaces a placeholder '\1' character if
871 one is found in the \a baseUrl string. If no such placeholder is found,
872 the path is appended to \a baseUrl, after a '/' character if \a baseUrl did
873 not already end in one.
874*/
876 const QString &baseUrl)
877{
878 QString exampleUrl(baseUrl);
879 QString link;
880#ifndef QT_BOOTSTRAPPED
881 link = QUrl(exampleUrl).host();
882#endif
883 if (!link.isEmpty())
884 link.prepend(" @ ");
885 link.prepend("Example project");
886
887 const QLatin1Char separator('/');
888 const QLatin1Char placeholder('\1');
889 if (!exampleUrl.contains(placeholder)) {
890 if (!exampleUrl.endsWith(separator))
891 exampleUrl += separator;
892 exampleUrl += placeholder;
893 }
894
895 // Construct a path to the example; <install path>/<example name>
896 QString pathRoot;
897 QStringMultiMap *metaTagMap = en->doc().metaTagMap();
898 if (metaTagMap)
899 pathRoot = metaTagMap->value(QLatin1String("installpath"));
900 if (pathRoot.isEmpty())
901 pathRoot = Config::instance().get(CONFIG_EXAMPLESINSTALLPATH).asString();
902 QStringList path = QStringList() << pathRoot << en->name();
903 path.removeAll(QString());
904
905 Text text;
906 text << Atom::ParaLeft
907 << Atom(Atom::Link, exampleUrl.replace(placeholder, path.join(separator)))
910
911 generateText(text, nullptr, marker);
912}
913
914void Generator::addImageToCopy(const ExampleNode *en, const ResolvedFile& resolved_file)
915{
916 QDir dirInfo;
917 // TODO: [uncentralized-output-directory-structure]
918 const QString prefix("/images/used-in-examples");
919
920 // TODO: Generators probably should not need to keep track of which files were generated.
921 // Understand if we really need this information and where it should
922 // belong, considering that it should be part of whichever system
923 // would actually store the file itself.
924 s_outFileNames << prefix.mid(1) + "/" + resolved_file.get_query();
925
926
927 // TODO: [uncentralized-output-directory-structure]
928 QString imgOutDir = s_outDir + prefix + "/" + QFileInfo{resolved_file.get_query()}.path();
929 if (!dirInfo.mkpath(imgOutDir))
930 en->location().fatal(QStringLiteral("Cannot create output directory '%1'").arg(imgOutDir));
931 Config::copyFile(en->location(), resolved_file.get_path(), QFileInfo{resolved_file.get_query()}.fileName(), imgOutDir);
932}
933
934// TODO: [multi-purpose-function-with-flag][generate-file-list]
935// Avoid the use of a boolean flag to dispatch to the correct
936// implementation trough branching.
937// We always have to process both images and files, such that we
938// should consider to remove the branching altogheter, performing both
939// operations in a single call.
940// Otherwise, if this turns out to be infeasible, complex or
941// possibly-confusing, consider extracting the processing code outside
942// the function and provide two higer-level dispathing functions for
943// files and images.
944
945/*!
946 This function is called when the documentation for an example is
947 being formatted. It outputs a list of files for the example, which
948 can be the example's source files or the list of images used by the
949 example. The images are copied into a subtree of
950 \c{...doc/html/images/used-in-examples/...}
951*/
952void Generator::generateFileList(const ExampleNode *en, CodeMarker *marker, bool images)
953{
954 Text text;
956 QString tag;
957 QStringList paths;
959
960 if (images) {
961 paths = en->images();
962 tag = "Images:";
963 atomType = Atom::ExampleImageLink;
964 } else { // files
965 paths = en->files();
966 tag = "Files:";
967 }
968 std::sort(paths.begin(), paths.end(), Generator::comparePaths);
969
970 text << Atom::ParaLeft << tag << Atom::ParaRight;
971 text << Atom(Atom::ListLeft, openedList.styleString());
972
973 for (const auto &path : std::as_const(paths)) {
974 auto maybe_resolved_file{file_resolver.resolve(path)};
975 if (!maybe_resolved_file) {
976 // TODO: [uncentralized-admonition][failed-resolve-file]
977 QString details = std::transform_reduce(
978 file_resolver.get_search_directories().cbegin(),
979 file_resolver.get_search_directories().cend(),
980 u"Searched directories:"_s,
981 std::plus(),
982 [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
983 );
984
985 en->location().warning(u"(Generator)Cannot find file to quote from: %1"_s.arg(path), details);
986
987 continue;
988 }
989
990 const auto &file{*maybe_resolved_file};
991 if (images)
992 addImageToCopy(en, file);
993 else
994 generateExampleFilePage(en, file, marker);
995
996 openedList.next();
997 text << Atom(Atom::ListItemNumber, openedList.numberString())
998 << Atom(Atom::ListItemLeft, openedList.styleString()) << Atom::ParaLeft
999 << Atom(atomType, file.get_query()) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << file.get_query()
1000 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight
1001 << Atom(Atom::ListItemRight, openedList.styleString());
1002 }
1003 text << Atom(Atom::ListRight, openedList.styleString());
1004 if (!paths.isEmpty())
1005 generateText(text, en, marker);
1006}
1007
1008/*!
1009 Recursive writing of HTML files from the root \a node.
1010 */
1012{
1013 if (!node->url().isNull())
1014 return;
1015 if (node->isIndexNode())
1016 return;
1017 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
1018 const NodeContext context = node->createContext();
1019 if (!InclusionFilter::isIncluded(policy, context))
1020 return;
1021 if (node->isExternalPage())
1022 return;
1023
1024 /*
1025 Obtain a code marker for the source file.
1026 */
1027 CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
1028
1029 if (node->parent() != nullptr) {
1030 if (node->isCollectionNode()) {
1031 /*
1032 A collection node collects: groups, C++ modules, or QML
1033 modules. Testing for a CollectionNode must be done
1034 before testing for a TextPageNode because a
1035 CollectionNode is a PageNode at this point.
1036
1037 Don't output an HTML page for the collection node unless
1038 the \group, \module, or \qmlmodule command was actually
1039 seen by qdoc in the qdoc comment for the node.
1040
1041 A key prerequisite in this case is the call to
1042 mergeCollections(cn). We must determine whether this
1043 group, module or QML module has members in other
1044 modules. We know at this point that cn's members list
1045 contains only members in the current module. Therefore,
1046 before outputting the page for cn, we must search for
1047 members of cn in the other modules and add them to the
1048 members list.
1049 */
1050 auto *cn = static_cast<CollectionNode *>(node);
1051 if (cn->wasSeen()) {
1053 beginSubPage(node, fileName(node));
1056 } else if (cn->isGenericCollection()) {
1057 // Currently used only for the module's related orphans page
1058 // but can be generalized for other kinds of collections if
1059 // other use cases pop up.
1060 QString name = cn->name().toLower();
1061 name.replace(QChar(' '), QString("-"));
1062 QString filename =
1063 cn->tree()->physicalModuleName() + "-" + name + "." + fileExtension();
1064 beginSubPage(node, filename);
1067 }
1068 } else if (node->isTextPageNode()) {
1069 beginSubPage(node, fileName(node));
1070 generatePageNode(static_cast<PageNode *>(node), marker);
1072 } else if (node->isAggregate()) {
1073 if ((node->isClassNode() || node->isHeader() || node->isNamespace())
1074 && node->docMustBeGenerated()) {
1075 beginSubPage(node, fileName(node));
1076 generateCppReferencePage(static_cast<Aggregate *>(node), marker);
1078 } else if (node->isQmlType()) {
1079 beginSubPage(node, fileName(node));
1080 auto *qcn = static_cast<QmlTypeNode *>(node);
1081 generateQmlTypePage(qcn, marker);
1083 } else if (node->isProxyNode()) {
1084 beginSubPage(node, fileName(node));
1085 generateProxyPage(static_cast<Aggregate *>(node), marker);
1087 }
1088 }
1089 }
1090
1091 if (node->isAggregate()) {
1092 auto *aggregate = static_cast<Aggregate *>(node);
1093 const NodeList &children = aggregate->childNodes();
1094 for (auto *child : children) {
1095 if (child->isPageNode() && !child->isPrivate()) {
1096 generateDocumentation(child);
1097 } else if (!node->parent() && child->isInAPI() && !child->isRelatedNonmember()) {
1098 // Warn if there are documented non-page-generating nodes in the root namespace
1099 child->location().warning(u"No documentation generated for %1 '%2' in global scope."_s
1100 .arg(typeString(child), child->name()),
1101 u"Maybe you forgot to use the '\\relates' command?"_s);
1102 child->setStatus(Node::DontDocument);
1103 } else if (child->isQmlModule() && !child->wasSeen()) {
1104 // An undocumented QML module that was constructed as a placeholder
1105 auto *qmlModule = static_cast<CollectionNode *>(child);
1106 for (const auto *member : qmlModule->members()) {
1107 member->location().warning(
1108 u"Undocumented QML module '%1' referred by type '%2' or its members"_s
1109 .arg(qmlModule->name(), member->name()),
1110 u"Maybe you forgot to document '\\qmlmodule %1'?"_s
1111 .arg(qmlModule->name()));
1112 }
1113 } else if (child->isQmlType() && !child->hasDoc()) {
1114 // A placeholder QML type with incorrect module identifier
1115 auto *qmlType = static_cast<QmlTypeNode *>(child);
1116 if (auto qmid = qmlType->logicalModuleName(); !qmid.isEmpty())
1117 qmlType->location().warning(u"No such type '%1' in QML module '%2'"_s
1118 .arg(qmlType->name(), qmid));
1119 }
1120 }
1121 }
1122}
1123
1124void Generator::generateReimplementsClause(const FunctionNode *fn, CodeMarker *marker)
1125{
1126 if (fn->overridesThis().isEmpty() || !fn->parent()->isClassNode())
1127 return;
1128
1129 auto *cn = static_cast<ClassNode *>(fn->parent());
1130 const FunctionNode *overrides = cn->findOverriddenFunction(fn);
1131 if (overrides && !overrides->isPrivate() && !overrides->parent()->isPrivate()) {
1132 if (overrides->hasDoc()) {
1133 Text text;
1134 text << Atom::ParaLeft << "Reimplements: ";
1135 QString fullName =
1136 overrides->parent()->name()
1137 + "::" + overrides->signature(Node::SignaturePlain);
1138 appendFullName(text, overrides->parent(), fullName, overrides);
1139 text << "." << Atom::ParaRight;
1140 generateText(text, fn, marker);
1141 } else {
1142 fn->doc().location().warning(
1143 QStringLiteral("Illegal \\reimp; no documented virtual function for %1")
1144 .arg(overrides->plainSignature()));
1145 }
1146 return;
1147 }
1148 const PropertyNode *sameName = cn->findOverriddenProperty(fn);
1149 if (sameName && sameName->hasDoc()) {
1150 Text text;
1151 text << Atom::ParaLeft << "Reimplements an access function for property: ";
1152 QString fullName = sameName->parent()->name() + "::" + sameName->name();
1153 appendFullName(text, sameName->parent(), fullName, sameName);
1154 text << "." << Atom::ParaRight;
1155 generateText(text, fn, marker);
1156 }
1157}
1158
1159QString Generator::formatSince(const Node *node)
1160{
1161 QStringList since = node->since().split(QLatin1Char(' '));
1162
1163 // If there is only one argument, assume it is the product version number.
1164 if (since.size() == 1) {
1165 const QString productName = Config::instance().get(CONFIG_PRODUCTNAME).asString();
1166 return productName.isEmpty() ? node->since() : productName + " " + since[0];
1167 }
1168
1169 // Otherwise, use the original <project> <version> string.
1170 return node->since();
1171}
1172
1173/*!
1174 \internal
1175 Returns a string representing status information of a \a node.
1176
1177 If a status description is returned, it is one of:
1178 \list
1179 \li Custom status set explicitly in node's documentation using
1180 \c {\meta {status} {<description>}},
1181 \li 'Deprecated [since <version>]' (\\deprecated [<version>]),
1182 \li 'Until <version>',
1183 \li 'Preliminary' (\\preliminary), or
1184 \li The description adopted from associated module's state:
1185 \c {\modulestate {<description>}}.
1186 \endlist
1187
1188 Otherwise, returns \c std::nullopt.
1189*/
1190std::optional<QString> formatStatus(const Node *node, QDocDatabase *qdb)
1191{
1192 QString status;
1193
1194 if (const auto metaMap = node->doc().metaTagMap(); metaMap) {
1195 status = metaMap->value("status");
1196 if (!status.isEmpty())
1197 return {status};
1198 }
1199 const auto &since = node->deprecatedSince();
1200 if (node->status() == Node::Deprecated) {
1201 status = u"Deprecated"_s;
1202 if (!since.isEmpty())
1203 status += " since %1"_L1.arg(since);
1204 } else if (!since.isEmpty()) {
1205 status = "Until %1"_L1.arg(since);
1206 } else if (node->status() == Node::Preliminary) {
1207 status = u"Preliminary"_s;
1208 } else if (const auto collection = qdb->getModuleNode(node); collection) {
1209 status = collection->state();
1210 }
1211
1212 return status.isEmpty() ? std::nullopt : std::optional(status);
1213}
1214
1215void Generator::generateSince(const Node *node, CodeMarker *marker)
1216{
1217 if (!node->since().isEmpty()) {
1218 Text text;
1219 text << Atom::ParaLeft << "This " << typeString(node) << " was introduced in "
1220 << formatSince(node) << "." << Atom::ParaRight;
1221 generateText(text, node, marker);
1222 }
1223}
1224
1225void Generator::generateNoexceptNote(const Node* node, CodeMarker* marker) {
1226 std::vector<const Node*> nodes;
1227 if (node->isSharedCommentNode()) {
1228 auto shared_node = static_cast<const SharedCommentNode*>(node);
1229 nodes.reserve(shared_node->collective().size());
1230 nodes.insert(nodes.begin(), shared_node->collective().begin(), shared_node->collective().end());
1231 } else nodes.push_back(node);
1232
1233 std::size_t counter{1};
1234 for (const Node* node : nodes) {
1235 if (node->isFunction(Genus::CPP)) {
1236 if (const auto &exception_info = static_cast<const FunctionNode*>(node)->getNoexcept(); exception_info && !(*exception_info).isEmpty()) {
1237 Text text;
1238 text << Atom::NoteLeft
1239 << (nodes.size() > 1 ? QString::fromStdString(" ("s + std::to_string(counter) + ")"s) : QString::fromStdString("This ") + typeString(node))
1240 << " is noexcept when "
1241 << Atom(Atom::C, marker->markedUpCode(*exception_info, nullptr, Location()))
1242 << " is " << Atom(Atom::C, "true") << "."
1243 << Atom::NoteRight;
1244 generateText(text, node, marker);
1245 }
1246 }
1247
1248 ++counter;
1249 }
1250}
1251
1252void Generator::generateStatus(const Node *node, CodeMarker *marker)
1253{
1254 Text text;
1255
1256 switch (node->status()) {
1257 case Node::Active:
1258 // Output the module 'state' description if set.
1259 if (node->isModule() || node->isQmlModule()) {
1260 const QString &state = static_cast<const CollectionNode*>(node)->state();
1261 if (!state.isEmpty()) {
1262 text << Atom::ParaLeft << "This " << typeString(node) << " is in "
1263 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_ITALIC) << state
1264 << Atom(Atom::FormattingRight, ATOM_FORMATTING_ITALIC) << " state."
1265 << Atom::ParaRight;
1266 break;
1267 }
1268 }
1269 if (const auto &version = node->deprecatedSince(); !version.isEmpty()) {
1270 text << Atom::ParaLeft << "This " << typeString(node)
1271 << " is scheduled for deprecation in version "
1272 << version << "." << Atom::ParaRight;
1273 }
1274 break;
1275 case Node::Preliminary:
1276 text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << "This "
1277 << typeString(node) << " is under development and is subject to change."
1278 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << Atom::ParaRight;
1279 break;
1280 case Node::Deprecated:
1281 text << Atom::ParaLeft;
1282 if (node->isAggregate())
1284 text << "This " << typeString(node) << " is deprecated";
1285 if (const QString &version = node->deprecatedSince(); !version.isEmpty()) {
1286 text << " since ";
1287 if (node->isQmlNode() && !node->logicalModuleName().isEmpty())
1288 text << node->logicalModuleName() << " ";
1289 text << version;
1290 }
1291
1292 text << ". We strongly advise against using it in new code.";
1293 if (node->isAggregate())
1295 text << Atom::ParaRight;
1296 break;
1297 case Node::Internal:
1298 default:
1299 break;
1300 }
1301 generateText(text, node, marker);
1302}
1303
1304/*!
1305 Generates an addendum note of type \a type for \a node, using \a marker
1306 as the code marker.
1307*/
1308void Generator::generateAddendum(const Node *node, Addendum type, CodeMarker *marker,
1309 AdmonitionPrefix prefix)
1310{
1311 Q_ASSERT(node && !node->name().isEmpty());
1312 Text text;
1313 text << Atom(Atom::DivLeft,
1314 "class=\"admonition %1\""_L1.arg(prefix == AdmonitionPrefix::Note ? u"note"_s : u"auto"_s));
1315 text << Atom::ParaLeft;
1316
1317 switch (prefix) {
1319 break;
1322 << "Note: " << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1323 break;
1324 }
1325 }
1326
1327 switch (type) {
1328 case Invokable:
1329 text << "This function can be invoked via the meta-object system and from QML. See "
1330 << Atom(Atom::AutoLink, "Q_INVOKABLE")
1333 break;
1334 case PrivateSignal:
1335 text << "This is a private signal. It can be used in signal connections "
1336 "but cannot be emitted by the user.";
1337 break;
1338 case QmlSignalHandler:
1339 {
1340 QString handler(node->name());
1341 qsizetype prefixLocation = handler.lastIndexOf('.', -2) + 1;
1342 handler[prefixLocation] = handler[prefixLocation].toTitleCase();
1343 handler.insert(prefixLocation, QLatin1String("on"));
1344 text << "The corresponding handler is "
1347 break;
1348 }
1350 {
1351 if (!node->isFunction())
1352 return;
1353 const auto *fn = static_cast<const FunctionNode *>(node);
1354 auto nodes = fn->associatedProperties();
1355 if (nodes.isEmpty())
1356 return;
1357 std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan);
1358
1359 // Group properties by their role for more concise output
1360 QMap<PropertyNode::FunctionRole, QList<const PropertyNode *>> roleGroups;
1361 for (const auto *n : std::as_const(nodes)) {
1362 const auto *pn = static_cast<const PropertyNode *>(n);
1363 if (pn->isInAPI()) {
1364 PropertyNode::FunctionRole role = pn->role(fn);
1365 roleGroups[role].append(pn);
1366 }
1367 }
1368
1369 if (roleGroups.isEmpty())
1370 return;
1371
1372 // Generate text for each role group in an explicit order
1373 static constexpr PropertyNode::FunctionRole roleOrder[] = {
1379 };
1380 for (auto role : roleOrder) {
1381 const auto it = roleGroups.constFind(role);
1382 if (it == roleGroups.cend())
1383 continue;
1384
1385 const auto &properties = it.value();
1386
1387 QString msg;
1388 switch (role) {
1389 case PropertyNode::FunctionRole::Getter:
1390 msg = u"Getter function"_s;
1391 break;
1392 case PropertyNode::FunctionRole::Setter:
1393 msg = u"Setter function"_s;
1394 break;
1395 case PropertyNode::FunctionRole::Resetter:
1396 msg = u"Resetter function"_s;
1397 break;
1398 case PropertyNode::FunctionRole::Notifier:
1399 msg = u"Notifier signal"_s;
1400 break;
1401 case PropertyNode::FunctionRole::Bindable:
1402 msg = u"Bindable function"_s;
1403 break;
1404 default:
1405 continue;
1406 }
1407
1408 if (properties.size() == 1) {
1409 const auto *pn = properties.first();
1410 text << msg << u" for property "_s << Atom(Atom::Link, pn->name())
1411 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << pn->name()
1412 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << u". "_s;
1413 } else {
1414 text << msg << u" for properties "_s;
1415 for (qsizetype i = 0; i < properties.size(); ++i) {
1416 const auto *pn = properties.at(i);
1417 text << Atom(Atom::Link, pn->name())
1418 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << pn->name()
1420 << Utilities::separator(i, properties.size());
1421 }
1422 text << u" "_s;
1423 }
1424 }
1425 break;
1426 }
1427 case BindableProperty:
1428 {
1429 text << "This property supports "
1430 << Atom(Atom::Link, "QProperty")
1431 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << "QProperty"
1433 text << " bindings.";
1434 break;
1435 }
1436 case OverloadNote:
1437 {
1438 const auto *func = static_cast<const FunctionNode *>(node);
1439
1440 if (func->isSignal() || func->isSlot()) {
1441 QString functionType = func->isSignal() ? "signal" : "slot";
1442 const QString &configKey = func->isSignal() ? "overloadedsignalstarget" : "overloadedslotstarget";
1443 const QString &defaultTarget = func->isSignal() ? "connecting-overloaded-signals" : "connecting-overloaded-slots";
1444 const QString &linkTarget = Config::instance().get(configKey).asString(defaultTarget);
1445
1446 text << "This " << functionType << " is overloaded. ";
1447
1448 QString snippet = generateOverloadSnippet(func);
1449 if (!snippet.isEmpty()) {
1450 text << "To connect to this " << functionType << ":\n\n"
1451 << Atom(Atom::Code, snippet) << "\n";
1452 }
1453
1454 text << "For more examples and approaches, see "
1455 << Atom(Atom::Link, linkTarget)
1457 << "connecting to overloaded " << functionType << "s"
1459 } else {
1460 const auto &args = node->doc().overloadList();
1461 if (args.first().first.isEmpty()) {
1462 text << "This is an overloaded function.";
1463 } else {
1464 QString target = args.first().first;
1465 // If the target is not fully qualified and we have a parent class context,
1466 // attempt to qualify it to improve link resolution
1467 if (!target.contains("::")) {
1468 const auto *parent = node->parent();
1469 if (parent && (parent->isClassNode() || parent->isNamespace())) {
1470 target = parent->name() + "::" + target;
1471 }
1472 }
1473 text << "This function overloads " << Atom(Atom::AutoLink, target) << ".";
1474 }
1475 }
1476 break;
1477 }
1478 default:
1479 return;
1480 }
1481
1482 text << Atom::ParaRight
1483 << Atom::DivRight;
1484 generateText(text, node, marker);
1485}
1486
1487/*!
1488 Generate the documentation for \a relative. i.e. \a relative
1489 is the node that represents the entity where a qdoc comment
1490 was found, and \a text represents the qdoc comment.
1491 */
1492bool Generator::generateText(const Text &text, const Node *relative, CodeMarker *marker)
1493{
1494 bool result = false;
1495 if (text.firstAtom() != nullptr) {
1496 int numAtoms = 0;
1498 generateAtomList(text.firstAtom(), relative, marker, true, numAtoms);
1499 result = true;
1500 }
1501 return result;
1502}
1503
1504/*
1505 The node is an aggregate, typically a class node, which has
1506 a threadsafeness level. This function checks all the children
1507 of the node to see if they are exceptions to the node's
1508 threadsafeness. If there are any exceptions, the exceptions
1509 are added to the appropriate set (reentrant, threadsafe, and
1510 nonreentrant, and true is returned. If there are no exceptions,
1511 the three node lists remain empty and false is returned.
1512 */
1513bool Generator::hasExceptions(const Node *node, NodeList &reentrant, NodeList &threadsafe,
1514 NodeList &nonreentrant)
1515{
1516 bool result = false;
1518 const NodeList &children = static_cast<const Aggregate *>(node)->childNodes();
1519 for (auto child : children) {
1520 if (!child->isDeprecated()) {
1521 switch (child->threadSafeness()) {
1522 case Node::Reentrant:
1523 reentrant.append(child);
1524 if (ts == Node::ThreadSafe)
1525 result = true;
1526 break;
1527 case Node::ThreadSafe:
1528 threadsafe.append(child);
1529 if (ts == Node::Reentrant)
1530 result = true;
1531 break;
1532 case Node::NonReentrant:
1533 nonreentrant.append(child);
1534 result = true;
1535 break;
1536 default:
1537 break;
1538 }
1539 }
1540 }
1541 return result;
1542}
1543
1544/*!
1545 Returns \c true if a trademark symbol should be appended to the
1546 output as determined by \a atom. Trademarks are tracked via the
1547 use of the \\tm formatting command.
1548
1549 Returns true if:
1550
1551 \list
1552 \li \a atom is of type Atom::FormattingRight containing
1553 ATOM_FORMATTING_TRADEMARK, and
1554 \li The trademarked string is the first appearance on the
1555 current sub-page.
1556 \endlist
1557*/
1559{
1561 return false;
1562 if (atom->string() != ATOM_FORMATTING_TRADEMARK)
1563 return false;
1564
1565 if (atom->count() > 1) {
1566 if (s_trademarks.contains(atom->string(1)))
1567 return false;
1568 s_trademarks << atom->string(1);
1569 }
1570
1571 return true;
1572}
1573
1574static void startNote(Text &text)
1575{
1577 << "Note:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " ";
1578}
1579
1580/*!
1581 Generates text that explains how threadsafe and/or reentrant
1582 \a node is.
1583 */
1585{
1586 Text text, rlink, tlink;
1587 NodeList reentrant;
1588 NodeList threadsafe;
1589 NodeList nonreentrant;
1591 bool exceptions = false;
1592
1593 rlink << Atom(Atom::Link, "reentrant") << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1594 << "reentrant" << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1595
1596 tlink << Atom(Atom::Link, "thread-safe") << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1597 << "thread-safe" << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1598
1599 switch (ts) {
1601 break;
1602 case Node::NonReentrant:
1603 text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1604 << "Warning:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " This "
1605 << typeString(node) << " is not " << rlink << "." << Atom::ParaRight;
1606 break;
1607 case Node::Reentrant:
1608 case Node::ThreadSafe:
1609 startNote(text);
1610 if (node->isAggregate()) {
1611 exceptions = hasExceptions(node, reentrant, threadsafe, nonreentrant);
1612 text << "All functions in this " << typeString(node) << " are ";
1613 if (ts == Node::ThreadSafe)
1614 text << tlink;
1615 else
1616 text << rlink;
1617
1618 if (!exceptions || (ts == Node::Reentrant && !threadsafe.isEmpty()))
1619 text << ".";
1620 else
1621 text << " with the following exceptions:";
1622 } else {
1623 text << "This " << typeString(node) << " is ";
1624 if (ts == Node::ThreadSafe)
1625 text << tlink;
1626 else
1627 text << rlink;
1628 text << ".";
1629 }
1630 text << Atom::ParaRight;
1631 break;
1632 default:
1633 break;
1634 }
1635 generateText(text, node, marker);
1636
1637 if (exceptions) {
1638 text.clear();
1639 if (ts == Node::Reentrant) {
1640 if (!nonreentrant.isEmpty()) {
1641 startNote(text);
1642 text << "These functions are not " << rlink << ":" << Atom::ParaRight;
1643 signatureList(nonreentrant, node, marker);
1644 }
1645 if (!threadsafe.isEmpty()) {
1646 text.clear();
1647 startNote(text);
1648 text << "These functions are also " << tlink << ":" << Atom::ParaRight;
1649 generateText(text, node, marker);
1650 signatureList(threadsafe, node, marker);
1651 }
1652 } else { // thread-safe
1653 if (!reentrant.isEmpty()) {
1654 startNote(text);
1655 text << "These functions are only " << rlink << ":" << Atom::ParaRight;
1656 signatureList(reentrant, node, marker);
1657 }
1658 if (!nonreentrant.isEmpty()) {
1659 text.clear();
1660 startNote(text);
1661 text << "These functions are not " << rlink << ":" << Atom::ParaRight;
1662 signatureList(nonreentrant, node, marker);
1663 }
1664 }
1665 }
1666}
1667
1668/*!
1669 \internal
1670
1671 Generates text that describes the comparison category of \a node.
1672 The CodeMarker \a marker is passed along to generateText().
1673 */
1675{
1676 auto category{node->comparisonCategory()};
1677 if (category == ComparisonCategory::None)
1678 return false;
1679
1680 Text text;
1681 text << Atom::ParaLeft << "This %1 is "_L1.arg(typeString(node))
1682 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_ITALIC)
1683 << QString::fromStdString(comparisonCategoryAsString(category))
1684 << ((category == ComparisonCategory::Equality) ? "-"_L1 : "ly "_L1)
1685 << Atom(Atom::String, "comparable"_L1)
1686 << Atom(Atom::FormattingRight, ATOM_FORMATTING_ITALIC)
1687 << "."_L1 << Atom::ParaRight;
1688 generateText(text, node, marker);
1689 return true;
1690}
1691
1692/*!
1693 Generates a list of types that compare to \a node with the comparison
1694 category that applies for the relationship, followed by (an optional)
1695 descriptive text.
1696
1697 Returns \c true if text was generated, \c false otherwise.
1698 */
1700{
1701 Q_ASSERT(node);
1702 if (!node->doc().comparesWithMap())
1703 return false;
1704
1705 Text relationshipText;
1706 for (auto [key, description] : node->doc().comparesWithMap()->asKeyValueRange()) {
1707 const QString &category = QString::fromStdString(comparisonCategoryAsString(key));
1708
1709 relationshipText << Atom::ParaLeft << "This %1 is "_L1.arg(typeString(node))
1710 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << category
1711 << ((key == ComparisonCategory::Equality) ? "-"_L1 : "ly "_L1)
1712 << "comparable"_L1
1713 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1714 << " with "_L1;
1715
1716 const QStringList types{description.firstAtom()->string().split(';'_L1)};
1717 for (const auto &name : types)
1718 relationshipText << Atom(Atom::AutoLink, name)
1719 << Utilities::separator(types.indexOf(name), types.size());
1720
1721 relationshipText << Atom(Atom::ParaRight) << description;
1722 }
1723
1724 generateText(relationshipText, node, nullptr);
1725 return !relationshipText.isEmpty();
1726}
1727
1728/*!
1729 Traverses the database recursively to generate all the documentation.
1730 */
1732{
1733 s_currentGenerator = this;
1735}
1736
1737Generator *Generator::generatorForFormat(const QString &format)
1738{
1739 for (const auto &generator : std::as_const(s_generators)) {
1740 if (generator->format() == format)
1741 return generator;
1742 }
1743 return nullptr;
1744}
1745
1746QString Generator::indent(int level, const QString &markedCode)
1747{
1748 if (level == 0)
1749 return markedCode;
1750
1751 QString t;
1752 int column = 0;
1753
1754 int i = 0;
1755 while (i < markedCode.size()) {
1756 if (markedCode.at(i) == QLatin1Char('\n')) {
1757 column = 0;
1758 } else {
1759 if (column == 0) {
1760 for (int j = 0; j < level; j++)
1761 t += QLatin1Char(' ');
1762 }
1763 column++;
1764 }
1765 t += markedCode.at(i++);
1766 }
1767 return t;
1768}
1769
1771{
1772 Config &config = Config::instance();
1773 s_outputFormats = config.getOutputFormats();
1774 s_redirectDocumentationToDevNull = config.get(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL).asBool();
1775
1776 for (auto &g : s_generators) {
1777 if (s_outputFormats.contains(g->format())) {
1778 s_currentGenerator = g;
1779 g->initializeGenerator();
1780 }
1781 }
1782
1783 const auto &configFormatting = config.subVars(CONFIG_FORMATTING);
1784 for (const auto &n : configFormatting) {
1785 QString formattingDotName = CONFIG_FORMATTING + Config::dot + n;
1786 const auto &formattingDotNames = config.subVars(formattingDotName);
1787 for (const auto &f : formattingDotNames) {
1788 const auto &configVar = config.get(formattingDotName + Config::dot + f);
1789 QString def{configVar.asString()};
1790 if (!def.isEmpty()) {
1791 int numParams = Config::numParams(def);
1792 int numOccs = def.count("\1");
1793 if (numParams != 1) {
1794 configVar.location().warning(QStringLiteral("Formatting '%1' must "
1795 "have exactly one "
1796 "parameter (found %2)")
1797 .arg(n, numParams));
1798 } else if (numOccs > 1) {
1799 configVar.location().fatal(QStringLiteral("Formatting '%1' must "
1800 "contain exactly one "
1801 "occurrence of '\\1' "
1802 "(found %2)")
1803 .arg(n, numOccs));
1804 } else {
1805 int paramPos = def.indexOf("\1");
1806 s_fmtLeftMaps[f].insert(n, def.left(paramPos));
1807 s_fmtRightMaps[f].insert(n, def.mid(paramPos + 1));
1808 }
1809 }
1810 }
1811 }
1812
1813 s_project = config.get(CONFIG_PROJECT).asString();
1814 s_outDir = config.getOutputDir();
1815 s_outSubdir = s_outDir.mid(s_outDir.lastIndexOf('/') + 1);
1816
1817 s_outputPrefixes.clear();
1818 QStringList items{config.get(CONFIG_OUTPUTPREFIXES).asStringList()};
1819 if (!items.isEmpty()) {
1820 for (const auto &prefix : items)
1821 s_outputPrefixes[prefix] =
1822 config.get(CONFIG_OUTPUTPREFIXES + Config::dot + prefix).asString();
1823 }
1824 if (!items.contains(u"QML"_s))
1825 s_outputPrefixes[u"QML"_s] = u"qml-"_s;
1826
1827 s_outputSuffixes.clear();
1828 for (const auto &suffix : config.get(CONFIG_OUTPUTSUFFIXES).asStringList())
1829 s_outputSuffixes[suffix] = config.get(CONFIG_OUTPUTSUFFIXES
1830 + Config::dot + suffix).asString();
1831
1832 s_noLinkErrors = config.get(CONFIG_NOLINKERRORS).asBool();
1833 s_autolinkErrors = config.get(CONFIG_AUTOLINKERRORS).asBool();
1834}
1835
1836/*!
1837 Creates template-specific subdirs (e.g. /styles and /scripts for HTML)
1838 and copies the files to them.
1839 */
1840void Generator::copyTemplateFiles(const QString &configVar, const QString &subDir)
1841{
1842 // TODO: [resolving-files-unlinked-to-doc]
1843 // This is another case of resolving files, albeit it doesn't use Doc::resolveFile.
1844 // While it may be left out of a first iteration of the file
1845 // resolution logic, it should later be integrated into it.
1846 // This should come naturally when the output directory logic is
1847 // extracted and copying a file should require a validated
1848 // intermediate format.
1849 // Do note that what is done here is a bit different from the
1850 // resolve file routine that is done for other user-given paths.
1851 // Thas is, the paths will always be absolute and not relative as
1852 // they are resolved from the configuration.
1853 // Ideally, this could be solved in the configuration already,
1854 // together with the other configuration resolution processes that
1855 // do not abide by the same constraints that, for example, snippet
1856 // resolution uses.
1857 Config &config = Config::instance();
1858 QStringList files = config.getCanonicalPathList(configVar, Config::Validate);
1859 const auto &loc = config.get(configVar).location();
1860 if (!files.isEmpty()) {
1861 QDir dirInfo;
1862 // TODO: [uncentralized-output-directory-structure]
1863 // As with other places in the generation pass, the details of
1864 // where something is saved in the output directory are spread
1865 // to whichever part of the generation does the saving.
1866 // It is hence complex to build a model of how an output
1867 // directory looks like, as the knowledge has no specific
1868 // entry point or chain-path that can be followed in full.
1869 // Each of those operations should be centralized in a system
1870 // that uniquely knows what the format of the output-directory
1871 // is and how to perform operations on it.
1872 // Later, move this operation to that centralized system.
1873 QString templateDir = s_outDir + QLatin1Char('/') + subDir;
1874 if (!dirInfo.exists(templateDir) && !dirInfo.mkdir(templateDir)) {
1875 // TODO: [uncentralized-admonition]
1876 loc.fatal(QStringLiteral("Cannot create %1 directory '%2'").arg(subDir, templateDir));
1877 } else {
1878 for (const auto &file : files) {
1879 if (!file.isEmpty())
1880 Config::copyFile(loc, file, file, templateDir);
1881 }
1882 }
1883 }
1884}
1885
1886/*!
1887 Reads format-specific variables from config, sets output
1888 (sub)directories, creates them on the filesystem and copies the
1889 template-specific files.
1890 */
1892{
1893 Config &config = Config::instance();
1894 s_outFileNames.clear();
1895 s_useOutputSubdirs = true;
1896 if (config.get(format() + Config::dot + "nosubdirs").asBool())
1898
1899 if (s_outputFormats.isEmpty())
1900 return;
1901
1902 s_outDir = config.getOutputDir(format());
1903 if (s_outDir.isEmpty()) {
1904 Location().fatal(QStringLiteral("No output directory specified in "
1905 "configuration file or on the command line"));
1906 } else {
1907 s_outSubdir = s_outDir.mid(s_outDir.lastIndexOf('/') + 1);
1908 }
1909
1910 QDir outputDir(s_outDir);
1911 if (outputDir.exists()) {
1913 if (!outputDir.isEmpty())
1914 Location().error(QStringLiteral("Output directory '%1' exists but is not empty")
1915 .arg(s_outDir));
1916 }
1917 } else if (!outputDir.mkpath(QStringLiteral("."))) {
1918 Location().fatal(QStringLiteral("Cannot create output directory '%1'").arg(s_outDir));
1919 }
1920
1921 // Output directory exists, which is enough for prepare phase.
1922 if (config.preparing())
1923 return;
1924
1925 const QLatin1String imagesDir("images");
1926 if (!outputDir.exists(imagesDir) && !outputDir.mkdir(imagesDir))
1927 Location().fatal(QStringLiteral("Cannot create images directory '%1'").arg(outputDir.filePath(imagesDir)));
1928
1929 copyTemplateFiles(format() + Config::dot + CONFIG_STYLESHEETS, "style");
1930 copyTemplateFiles(format() + Config::dot + CONFIG_SCRIPTS, "scripts");
1931 copyTemplateFiles(format() + Config::dot + CONFIG_EXTRAIMAGES, "images");
1932
1933 // Use a format-specific .quotinginformation if defined, otherwise a global value
1934 if (config.subVars(format()).contains(CONFIG_QUOTINGINFORMATION))
1935 m_quoting = config.get(format() + Config::dot + CONFIG_QUOTINGINFORMATION).asBool();
1936 else
1938}
1939
1940/*!
1941 No-op base implementation. Subclasses may override to perform
1942 generator-specific initialization.
1943 */
1945{
1946 // Default implementation does nothing
1947}
1948
1949bool Generator::matchAhead(const Atom *atom, Atom::AtomType expectedAtomType)
1950{
1951 return atom->next() && atom->next()->type() == expectedAtomType;
1952}
1953
1954/*!
1955 Used for writing to the current output stream. Returns a
1956 reference to the current output stream, which is then used
1957 with the \c {<<} operator for writing.
1958 */
1959QTextStream &Generator::out()
1960{
1961 return *outStreamStack.top();
1962}
1963
1965{
1966 return QFileInfo(static_cast<QFile *>(out().device())->fileName()).fileName();
1967}
1968
1969QString Generator::outputPrefix(const Node *node)
1970{
1971 // Omit prefix for module pages
1972 if (node->isPageNode() && !node->isCollectionNode()) {
1973 switch (node->genus()) {
1974 case Genus::QML:
1975 return s_outputPrefixes[u"QML"_s];
1976 case Genus::CPP:
1977 return s_outputPrefixes[u"CPP"_s];
1978 default:
1979 break;
1980 }
1981 }
1982 return QString();
1983}
1984
1985QString Generator::outputSuffix(const Node *node)
1986{
1987 if (node->isPageNode()) {
1988 switch (node->genus()) {
1989 case Genus::QML:
1990 return s_outputSuffixes[u"QML"_s];
1991 case Genus::CPP:
1992 return s_outputSuffixes[u"CPP"_s];
1993 default:
1994 break;
1995 }
1996 }
1997
1998 return QString();
1999}
2000
2001bool Generator::parseArg(const QString &src, const QString &tag, int *pos, int n,
2002 QStringView *contents, QStringView *par1)
2003{
2004#define SKIP_CHAR(c)
2005 if (i >= n || src[i] != c)
2006 return false;
2007 ++i;
2008
2009#define SKIP_SPACE
2010 while (i < n && src[i] == ' ')
2011 ++i;
2012
2013 qsizetype i = *pos;
2014 qsizetype j {};
2015
2016 // assume "<@" has been parsed outside
2017 // SKIP_CHAR('<');
2018 // SKIP_CHAR('@');
2019
2020 if (tag != QStringView(src).mid(i, tag.size())) {
2021 return false;
2022 }
2023
2024 // skip tag
2025 i += tag.size();
2026
2027 // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
2028 if (par1) {
2029 SKIP_SPACE;
2030 // read parameter name
2031 j = i;
2032 while (i < n && src[i].isLetter())
2033 ++i;
2034 if (src[i] == '=') {
2035 SKIP_CHAR('=');
2036 SKIP_CHAR('"');
2037 // skip parameter name
2038 j = i;
2039 while (i < n && src[i] != '"')
2040 ++i;
2041 *par1 = QStringView(src).mid(j, i - j);
2042 SKIP_CHAR('"');
2043 SKIP_SPACE;
2044 }
2045 }
2046 SKIP_SPACE;
2047 SKIP_CHAR('>');
2048
2049 // find contents up to closing "</@tag>
2050 j = i;
2051 for (; true; ++i) {
2052 if (i + 4 + tag.size() > n)
2053 return false;
2054 if (src[i] != '<')
2055 continue;
2056 if (src[i + 1] != '/')
2057 continue;
2058 if (src[i + 2] != '@')
2059 continue;
2060 if (tag != QStringView(src).mid(i + 3, tag.size()))
2061 continue;
2062 if (src[i + 3 + tag.size()] != '>')
2063 continue;
2064 break;
2065 }
2066
2067 *contents = QStringView(src).mid(j, i - j);
2068
2069 i += tag.size() + 4;
2070
2071 *pos = i;
2072 return true;
2073#undef SKIP_CHAR
2074#undef SKIP_SPACE
2075}
2076
2077QString Generator::plainCode(const QString &markedCode)
2078{
2079 QString t = markedCode;
2080 t.replace(tag, QString());
2081 t.replace(quot, QLatin1String("\""));
2082 t.replace(gt, QLatin1String(">"));
2083 t.replace(lt, QLatin1String("<"));
2084 t.replace(amp, QLatin1String("&"));
2085 return t;
2086}
2087
2088int Generator::skipAtoms(const Atom *atom, Atom::AtomType type) const
2089{
2090 int skipAhead = 0;
2091 atom = atom->next();
2092 while (atom && atom->type() != type) {
2093 skipAhead++;
2094 atom = atom->next();
2095 }
2096 return skipAhead;
2097}
2098
2099/*!
2100 Resets the variables used during text output.
2101 */
2103{
2104 m_inLink = false;
2105 m_inContents = false;
2106 m_inSectionHeading = false;
2107 m_inTableHeader = false;
2108 m_numTableRows = 0;
2110 m_link.clear();
2111 m_sectionNumber.clear();
2112}
2113
2114void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
2115{
2116 if (node->isFunction() && !node->isMacro()) {
2117 const auto fn = static_cast<const FunctionNode *>(node);
2118 if (fn->overloadNumber() == 0) {
2119 QString alternateName;
2120 const FunctionNode *alternateFunc = nullptr;
2121
2122 if (fn->name().startsWith("set") && fn->name().size() >= 4) {
2123 alternateName = fn->name()[3].toLower();
2124 alternateName += fn->name().mid(4);
2125 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2126
2127 if (!alternateFunc) {
2128 alternateName = "is" + fn->name().mid(3);
2129 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2130 if (!alternateFunc) {
2131 alternateName = "has" + fn->name().mid(3);
2132 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2133 }
2134 }
2135 } else if (!fn->name().isEmpty()) {
2136 alternateName = "set";
2137 alternateName += fn->name()[0].toUpper();
2138 alternateName += fn->name().mid(1);
2139 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2140 }
2141
2142 if (alternateFunc && alternateFunc->access() != Access::Private) {
2143 int i;
2144 for (i = 0; i < alsoList.size(); ++i) {
2145 if (alsoList.at(i).toString().contains(alternateName))
2146 break;
2147 }
2148
2149 if (i == alsoList.size()) {
2150 if (alternateFunc->isDeprecated() && !fn->isDeprecated())
2151 return;
2152 alternateName += "()";
2153
2154 Text also;
2155 also << Atom(Atom::Link, alternateName)
2156 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << alternateName
2158 alsoList.prepend(also);
2159 }
2160 }
2161 }
2162 }
2163}
2164
2166{
2167 const NativeEnum *nativeEnum{nullptr};
2168 if (auto *ne_if = dynamic_cast<const NativeEnumInterface *>(node))
2169 nativeEnum = ne_if->nativeEnum();
2170 else
2171 return;
2172
2173 if (!nativeEnum->enumNode())
2174 return;
2175
2176 // Retrieve atoms from C++ enum \value list
2177 const auto body{nativeEnum->enumNode()->doc().body()};
2178 const auto *start{body.firstAtom()};
2179 Text text;
2180
2181 while ((start = start->find(Atom::ListLeft, ATOM_LIST_VALUE))) {
2182 const auto end = start->find(Atom::ListRight, ATOM_LIST_VALUE);
2183 // Skip subsequent ListLeft atoms, collating multiple lists into one
2184 text << body.subText(text.isEmpty() ? start : start->next(), end);
2185 start = end;
2186 }
2187 if (text.isEmpty())
2188 return;
2189
2190 text << Atom(Atom::ListRight, ATOM_LIST_VALUE);
2191 if (marker)
2192 generateText(text, node, marker);
2193 else
2194 generateText(text, node);
2195}
2196
2198{
2199 for (const auto &generator : std::as_const(s_generators)) {
2200 if (s_outputFormats.contains(generator->format()))
2201 generator->terminateGenerator();
2202 }
2203
2204 // REMARK: Generators currently, due to recent changes and the
2205 // transitive nature of the current codebase, receive some of
2206 // their dependencies in the constructor and some of them in their
2207 // initialize-terminate lifetime.
2208 // This means that generators need to be constructed and
2209 // destructed between usages such that if multiple usages are
2210 // required, the generators present in the list will have been
2211 // destroyed by then such that accessing them would be an error.
2212 // The current codebase calls initialize and the correspective
2213 // terminate with the same scope as the lifetime of the
2214 // generators.
2215 // Then, clearing the list ensures that, if another generator
2216 // execution is needed, the stale generators will not be removed
2217 // as to be replaced by newly constructed ones.
2218 // Do note that it is not clear that this will happen for any call
2219 // in Qt's documentation and this should work only because of the
2220 // form of the current codebase and the scoping of the
2221 // initialize-terminate calls. As such, this should be considered
2222 // a patchwork that may or may not be doing anything and that may
2223 // break due to changes in other parts of the codebase.
2224 //
2225 // This is still to be considered temporary as the whole
2226 // initialize-terminate idiom must be removed from the codebase.
2227 s_generators.clear();
2228
2229 s_fmtLeftMaps.clear();
2230 s_fmtRightMaps.clear();
2231 s_outDir.clear();
2232}
2233
2235
2236/*!
2237 Trims trailing whitespace off the \a string and returns
2238 the trimmed string.
2239 */
2240QString Generator::trimmedTrailing(const QString &string, const QString &prefix,
2241 const QString &suffix)
2242{
2243 QString trimmed = string;
2244 while (trimmed.size() > 0 && trimmed[trimmed.size() - 1].isSpace())
2245 trimmed.truncate(trimmed.size() - 1);
2246
2247 trimmed.append(suffix);
2248 trimmed.prepend(prefix);
2249 return trimmed;
2250}
2251
2252QString Generator::typeString(const Node *node)
2253{
2254 switch (node->nodeType()) {
2256 return "namespace";
2257 case NodeType::Class:
2258 return "class";
2259 case NodeType::Struct:
2260 return "struct";
2261 case NodeType::Union:
2262 return "union";
2263 case NodeType::QmlType:
2265 return "type";
2266 case NodeType::Page:
2267 return "documentation";
2268 case NodeType::Enum:
2269 return "enum";
2270 case NodeType::Typedef:
2272 return "typedef";
2273 case NodeType::Function: {
2274 const auto fn = static_cast<const FunctionNode *>(node);
2275 switch (fn->metaness()) {
2277 return "signal";
2279 return "signal handler";
2281 return "method";
2284 return "macro";
2285 default:
2286 break;
2287 }
2288 return "function";
2289 }
2290 case NodeType::Property:
2292 return "property";
2293 case NodeType::Module:
2295 return "module";
2297 const auto &collective = static_cast<const SharedCommentNode *>(node)->collective();
2298 return collective.first()->nodeTypeString();
2299 }
2300 default:
2301 return "documentation";
2302 }
2303}
2304
2305void Generator::unknownAtom(const Atom *atom)
2306{
2307 Location::internalError(QStringLiteral("unknown atom type '%1' in %2 generator")
2308 .arg(atom->typeString(), format()));
2309}
2310
2311/*!
2312 * Generate the CMake requisite for the node \a cn, i.e. the the find_package and target_link_libraries
2313 * calls to use it.
2314 *
2315 * If only cmakepackage is set it will look like
2316 *
2317 * \badcode
2318 * find_package(Foo REQUIRED)
2319 * target_link_libraries(mytarget PRIVATE Foo:Foo)
2320 * \endcode
2321 *
2322 * If no cmakepackage is set Qt6 is assumed.
2323 *
2324 * If cmakecomponent is set it will look like
2325 *
2326 * \badcode
2327 * find_package(Qt6 REQUIRED COMPONENTS Bar)
2328 * target_link_libraries(mytarget PRIVATE Qt6::Bar)
2329 * \endcode
2330 *
2331 * If cmaketargetitem is set the item in target_link_libraries will be set accordingly
2332 *
2333 * \badcode
2334 * find_package(Qt6 REQUIRED COMPONENTS Bar)
2335 * target_link_libraries(mytarget PRIVATE My::Target)
2336 * \endcode
2337 *
2338 * Returns a pair consisting of the find package line and link libraries line.
2339 *
2340 * If no sensible requisite can be created (i.e. both cmakecomponent and cmakepackage are unset)
2341 * \c std::nullopt is returned.
2342 */
2343std::optional<std::pair<QString, QString>> Generator::cmakeRequisite(const CollectionNode *cn)
2344{
2345 if (!cn || (cn->cmakeComponent().isEmpty() && cn->cmakePackage().isEmpty())) {
2346 return {};
2347 }
2348
2349 const QString package =
2350 cn->cmakePackage().isEmpty() ? "Qt" + QString::number(QT_VERSION_MAJOR) : cn->cmakePackage();
2351
2352 QString findPackageText;
2353 if (cn->cmakeComponent().isEmpty()) {
2354 findPackageText = "find_package(" + package + " REQUIRED)";
2355 } else {
2356 findPackageText = "find_package(" + package + " REQUIRED COMPONENTS " + cn->cmakeComponent() + ")";
2357 }
2358
2359 QString targetText;
2360 if (cn->cmakeTargetItem().isEmpty()) {
2361 if (cn->cmakeComponent().isEmpty()) {
2362 targetText = package + "::" + package;
2363 } else {
2364 targetText = package + "::" + cn->cmakeComponent();
2365 }
2366 } else {
2367 targetText = cn->cmakeTargetItem();
2368 }
2369
2370 const QString targetLinkLibrariesText = "target_link_libraries(mytarget PRIVATE " + targetText + ")";
2371 const QStringList cmakeInfo { findPackageText, targetLinkLibrariesText };
2372
2373 return std::make_pair(findPackageText, targetLinkLibrariesText);
2374}
2375
2376/*!
2377 \brief Adds a formatted link to the specified \a text stream.
2378
2379 This function creates a sequence of Atom objects that together form a link
2380 and appends them to the \a text. The \a nodeRef parameter specifies the
2381 target of the link (typically obtained via stringForNode()), and \a linkText
2382 specifies the visible text for the link.
2383
2384 \sa Atom, stringForNode()
2385*/
2386void Generator::addNodeLink(Text &text, const QString &nodeRef, const QString &linkText) {
2387 text << Atom(Atom::LinkNode, nodeRef)
2389 << Atom(Atom::String, linkText)
2391}
2392
2393/*!
2394 \overload
2395
2396 This convenience overload automatically obtains the node reference string
2397 using stringForNode(). If \a linkText is empty, the node's name is used as
2398 the link text; otherwise, the specified \a linkText is used.
2399
2400 \sa stringForNode()
2401*/
2402void Generator::addNodeLink(Text &text, const INode *node, const QString &linkText) {
2403 addNodeLink(
2404 text,
2405 Utilities::stringForNode(node),
2406 linkText.isEmpty() ? node->name() : linkText
2407 );
2408}
2409
2410/*!
2411 Generates a contextual code snippet for connecting to an overloaded signal or slot.
2412 Returns an empty string if the function is not a signal or slot.
2413*/
2415{
2416 if (!func || (!func->isSignal() && !func->isSlot()))
2417 return QString();
2418
2419 QString className = func->parent()->name();
2420 QString functionName = func->name();
2421 QString parameters = func->parameters().generateTypeList();
2422
2423 QString objectName = generateObjectName(className);
2424
2425 QString snippet = QString(
2426 "// Connect using qOverload:\n"
2427 "connect(%1, qOverload<%2>(&%3::%4),\n"
2428 " receiver, &ReceiverClass::slot);\n\n"
2429 "// Or using a lambda:\n"
2430 "connect(%1, qOverload<%2>(&%3::%4),\n"
2431 " this, [](%5) { /* handle %4 */ });")
2432 .arg(objectName, parameters, className, functionName,
2433 func->parameters().generateTypeAndNameList());
2434
2435 return snippet;
2436}
2437
2438/*!
2439 Generates an appropriate object name for code snippets based on the class name.
2440 Converts class names like "QComboBox" to "comboBox".
2441*/
2442QString Generator::generateObjectName(const QString &className)
2443{
2444 QString name = className;
2445
2446 if (name.startsWith('Q') && name.length() > 1)
2447 name.remove(0, 1);
2448
2449 if (!name.isEmpty())
2450 name[0] = name[0].toLower();
2451
2452 return name;
2453}
2454
2455
2456QT_END_NAMESPACE
Access
Definition access.h:11
#define ATOM_FORMATTING_TELETYPE
Definition atom.h:210
#define ATOM_FORMATTING_BOLD
Definition atom.h:201
#define ATOM_FORMATTING_TRADEMARK
Definition atom.h:211
#define ATOM_LIST_VALUE
Definition atom.h:217
#define ATOM_FORMATTING_ITALIC
Definition atom.h:203
#define ATOM_FORMATTING_LINK
Definition atom.h:204
const NodeList & childNodes() const
Returns a const reference to the child list.
Definition aggregate.h:42
The Atom class is the fundamental unit for representing documents internally.
Definition atom.h:19
AtomType type() const
Return the type of this atom.
Definition atom.h:151
AtomType
\value AnnotatedList \value AutoLink \value BaseName \value BriefLeft \value BriefRight \value C \val...
Definition atom.h:21
@ DivRight
Definition atom.h:40
@ FormatElse
Definition atom.h:45
@ Code
Definition atom.h:31
@ String
Definition atom.h:93
@ ListLeft
Definition atom.h:63
@ ExampleFileLink
Definition atom.h:41
@ ListRight
Definition atom.h:69
@ ParaRight
Definition atom.h:76
@ FormattingLeft
Definition atom.h:48
@ FormattingRight
Definition atom.h:49
@ Link
Definition atom.h:61
@ FormatEndif
Definition atom.h:46
@ ExampleImageLink
Definition atom.h:42
@ AutoLink
Definition atom.h:23
@ LinkNode
Definition atom.h:62
@ ParaLeft
Definition atom.h:75
@ FormatIf
Definition atom.h:47
const Atom * next() const
Return the next atom in the atom list.
Definition atom.h:148
The ClassNode represents a C++ class.
Definition classnode.h:21
A class for holding the members of a collection of doc pages.
bool wasSeen() const override
Returns the seen flag data member of this node if it is a NamespaceNode or a CollectionNode.
const Location & location() const
Definition config.h:55
bool asBool() const
Returns this config variable as a boolean.
Definition config.cpp:273
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:85
@ Validate
Definition config.h:104
bool preparing() const
Definition config.h:187
bool generating() const
Definition config.h:188
const Location & location() const
Returns the starting location of a qdoc comment.
Definition doc.cpp:90
const Text & body() const
Definition doc.cpp:115
QStringMultiMap * metaTagMap() const
Definition doc.cpp:300
Encapsulate the logic that QDoc uses to find files whose path is provided by the user and that are re...
This node is used to represent any kind of function being documented.
signed short overloadNumber() const
Returns the overload number for this function.
bool isPrivateSignal() const
const Parameters & parameters() const
bool isMAssign() const
bool isVirtual() const
bool isCAssign() const
const QString & overridesThis() const
bool isInvokable() const
bool isDeprecated() const override
\reimp
bool hasOverloads() const
Returns true if this function has overloads.
bool returnsBool() const
bool isMarkedReimp() const override
Returns true if the FunctionNode is marked as a reimplemented function.
bool isDtor() const
bool isSignal() const
bool isQmlSignal() const
bool isOverload() const
bool isIgnored() const
In some cases, it is ok for a public function to be not documented.
bool isCCtor() const
bool isMCtor() const
bool isCtor() const
bool hasAssociatedProperties() const
bool isSlot() const
Metaness metaness() const
bool m_quoting
Definition generator.h:223
void appendSignature(Text &text, const Node *node)
Append the signature for the function named in node to text, so that is a link to the documentation f...
virtual void generateCollectionNode(CollectionNode *, CodeMarker *)
Definition generator.h:103
virtual void generateProxyPage(Aggregate *, CodeMarker *)
Definition generator.h:100
virtual void generateCppReferencePage(Aggregate *, CodeMarker *)
Definition generator.h:99
bool generateComparisonCategory(const Node *node, CodeMarker *marker=nullptr)
QMap< QString, QString > & formattingRightMap()
virtual QString typeString(const Node *node)
FileResolver & file_resolver
Definition generator.h:215
virtual bool generateText(const Text &text, const Node *relative)
Definition generator.h:107
virtual void initializeFormat()
Reads format-specific variables from config, sets output (sub)directories, creates them on the filesy...
virtual void generateDocumentation(Node *node)
Recursive writing of HTML files from the root node.
static void initialize()
const Atom * generateAtomList(const Atom *atom, const Node *relative, CodeMarker *marker, bool generate, int &numGeneratedAtoms)
void generateStatus(const Node *node, CodeMarker *marker)
virtual void generateAlsoList(const Node *node, CodeMarker *marker)
void appendFullName(Text &text, const Node *apparentNode, const Node *relative, const Node *actualNode=nullptr)
Definition generator.cpp:96
virtual void generateFileList(const ExampleNode *en, CodeMarker *marker, bool images)
This function is called when the documentation for an example is being formatted.
void generateThreadSafeness(const Node *node, CodeMarker *marker)
Generates text that explains how threadsafe and/or reentrant node is.
static void terminate()
Generator(FileResolver &file_resolver)
Constructs the generator base class.
Definition generator.cpp:80
QDocDatabase * m_qdb
Definition generator.h:217
QString linkForExampleFile(const QString &path, const QString &fileExt=QString())
Constructs an href link from an example file name, which is a path to the example file.
bool m_inContents
Definition generator.h:219
void beginSubPage(const Node *node, const QString &fileName)
Creates the file named fileName in the output directory.
bool generateComparisonList(const Node *node)
Generates a list of types that compare to node with the comparison category that applies for the rela...
static bool useOutputSubdirs()
Definition generator.h:82
void generateNoexceptNote(const Node *node, CodeMarker *marker)
void unknownAtom(const Atom *atom)
QString generateObjectName(const QString &className)
Generates an appropriate object name for code snippets based on the class name.
virtual bool generateText(const Text &text, const Node *relative, CodeMarker *marker)
Generate the documentation for relative.
int appendSortedQmlNames(Text &text, const Node *base, const QStringList &knownTypes, const QList< Node * > &subs)
void generateLinkToExample(const ExampleNode *en, CodeMarker *marker, const QString &exampleUrl)
Generates an external link to the project folder for example node.
virtual void terminateGenerator()
QString generateOverloadSnippet(const FunctionNode *func)
Generates a contextual code snippet for connecting to an overloaded signal or slot.
static bool matchAhead(const Atom *atom, Atom::AtomType expectedAtomType)
QString fullDocumentLocation(const Node *node)
Returns the full document location.
bool m_inLink
Definition generator.h:218
void addImageToCopy(const ExampleNode *en, const ResolvedFile &resolved_file)
virtual void generateDocs()
Traverses the database recursively to generate all the documentation.
bool m_inTableHeader
Definition generator.h:221
static bool appendTrademark(const Atom *atom)
Returns true if a trademark symbol should be appended to the output as determined by atom.
bool m_inSectionHeading
Definition generator.h:220
void generateEnumValuesForQmlReference(const Node *node, CodeMarker *marker)
virtual int skipAtoms(const Atom *atom, Atom::AtomType type) const
int m_numTableRows
Definition generator.h:224
bool m_threeColumnEnumValueTable
Definition generator.h:222
virtual void generateQmlTypePage(QmlTypeNode *, CodeMarker *)
Definition generator.h:101
void signatureList(const QList< Node * > &nodes, const Node *relative, CodeMarker *marker)
Generate a bullet list of function signatures.
void appendFullName(Text &text, const Node *apparentNode, const QString &fullName, const Node *actualNode)
QTextStream & out()
virtual void generateBody(const Node *node, CodeMarker *marker)
Generate the body of the documentation from the qdoc comment found with the entity represented by the...
QString outFileName()
virtual void generatePageNode(PageNode *, CodeMarker *)
Definition generator.h:102
virtual ~Generator()
Destroys the generator after removing it from the list of output generators.
Definition generator.cpp:91
void generateSince(const Node *node, CodeMarker *marker)
QMap< QString, QString > & formattingLeftMap()
int appendSortedNames(Text &text, const ClassNode *classe, const QList< RelatedClass > &classes)
void endSubPage()
Flush the text stream associated with the subpage, and then pop it off the text stream stack and dele...
virtual void generateAddendum(const Node *node, Addendum type, CodeMarker *marker)
Definition generator.h:136
QString indent(int level, const QString &markedCode)
QString fileName(const Node *node, const QString &extension=QString()) const
If the node has a URL, return the URL as the file name.
virtual void generateAddendum(const Node *node, Addendum type, CodeMarker *marker, AdmonitionPrefix prefix)
Generates an addendum note of type type for node, using marker as the code marker.
static void resetUseOutputSubdirs()
Definition generator.h:81
@ AssociatedProperties
Definition generator.h:46
@ PrivateSignal
Definition generator.h:44
@ QmlSignalHandler
Definition generator.h:45
@ BindableProperty
Definition generator.h:47
@ OverloadNote
Definition generator.h:48
bool parseArg(const QString &src, const QString &tag, int *pos, int n, QStringView *contents, QStringView *par1=nullptr)
virtual void generateGenericCollectionPage(CollectionNode *, CodeMarker *)
Definition generator.h:104
virtual QString fileBase(const Node *node) const
virtual void initializeGenerator()
No-op base implementation.
void initializeTextOutput()
Resets the variables used during text output.
void generateRequiredLinks(const Node *node, CodeMarker *marker)
Generates either a link to the project folder for example node, or a list of links files/images if 'u...
static Generator * currentGenerator()
Definition generator.h:71
Definition inode.h:20
static bool isIncluded(const InclusionPolicy &policy, const NodeContext &context)
static bool requiresDocumentation(const InclusionPolicy &policy, const NodeContext &context)
The Location class provides a way to mark a location in a file.
Definition location.h:20
Location()
Constructs an empty location.
Definition location.cpp:46
Interface implemented by Node subclasses that can refer to a C++ enum.
Definition nativeenum.h:28
virtual const NativeEnum * nativeEnum() const =0
Encapsulates information about native (C++) enum values.
Definition nativeenum.h:14
const EnumNode * enumNode() const
Definition nativeenum.h:19
QString styleString() const
OpenedList(ListStyle style)
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
This class describes one instance of using the Q_PROPERTY macro.
PropertyType propertyType() const
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
static QDocDatabase * qdocDB()
Creates the singleton.
NamespaceNode * primaryTreeRoot()
Returns a pointer to the root node of the primary tree.
const CollectionNode * getModuleNode(const Node *relative)
Returns the collection node representing the module that relative node belongs to,...
void mergeCollections(CollectionNode *c)
Finds all the collection nodes with the same name and type as c and merges their members into the mem...
Definition text.h:12
const Atom * firstAtom() const
Definition text.h:34
bool isEmpty() const
Definition text.h:31
void clear()
Definition text.cpp:269
#define SKIP_CHAR()
#define CONFIG_REDIRECTDOCUMENTATIONTODEVNULL
Definition config.h:406
#define CONFIG_AUTOLINKERRORS
Definition config.h:350
#define CONFIG_EXTRAIMAGES
Definition config.h:368
#define CONFIG_EXAMPLES
Definition config.h:364
#define CONFIG_URL
Definition config.h:425
#define CONFIG_OUTPUTSUFFIXES
Definition config.h:402
#define CONFIG_OUTPUTPREFIXES
Definition config.h:401
#define CONFIG_NOLINKERRORS
Definition config.h:398
#define CONFIG_PROJECT
Definition config.h:404
#define CONFIG_EXAMPLESINSTALLPATH
Definition config.h:365
#define CONFIG_PRODUCTNAME
Definition config.h:403
#define CONFIG_QUOTINGINFORMATION
Definition config.h:409
#define CONFIG_STYLESHEETS
Definition config.h:418
#define CONFIG_FORMATTING
Definition config.h:370
#define CONFIG_SCRIPTS
Definition config.h:411
QMultiMap< QString, QString > QStringMultiMap
Definition doc.h:28
NodeType
Definition genustypes.h:150
@ SharedComment
Definition genustypes.h:173
QList< Node * > NodeList
Definition node.h:44
@ Private
Definition access.h:11
static QLatin1String gt("&gt;")
#define SKIP_SPACE
static void startNote(Text &text)
static QLatin1String amp("&amp;")
static QLatin1String quot("&quot;")
static QLatin1String lt("&lt;")
static QRegularExpression tag("</?@[^>]*>")
std::optional< QString > formatStatus(const Node *node, QDocDatabase *qdb)
The Node class is the base class for all the nodes in QDoc's parse tree.
bool isGenericCollection() const
Returns true if the node type is Collection.
Definition node.h:139
bool isExternalPage() const
Returns true if the node type is ExternalPage.
Definition node.h:106
const Doc & doc() const
Returns a reference to the node's Doc data member.
Definition node.h:242
bool isQmlNode() const
Returns true if this node's Genus value is QML.
Definition node.h:126
virtual bool docMustBeGenerated() const
This function is called to perform a test to decide if the node must have documentation generated.
Definition node.h:202
virtual bool isWrapper() const
Returns true if the node is a class node or a QML type node that is marked as being a wrapper class o...
Definition node.cpp:906
bool isPrivate() const
Returns true if this node's access is Private.
Definition node.h:118
bool isNamespace() const
Returns true if the node type is Namespace.
Definition node.h:115
bool isQmlBasicType() const
Returns true if the node type is QmlBasicType.
Definition node.h:124
ComparisonCategory comparisonCategory() const
Definition node.h:191
bool hasFileNameBase() const
Returns true if the node's file name base has been set.
Definition node.h:174
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
Definition node.h:128
bool isSharedCommentNode() const
Returns true if the node type is SharedComment.
Definition node.h:131
bool isHeader() const
Returns true if the node type is HeaderFile.
Definition node.h:112
NodeType nodeType() const override
Returns this node's type.
Definition node.h:89
Genus genus() const override
Returns this node's Genus.
Definition node.h:92
virtual bool isPageNode() const
Returns true if this node represents something that generates a documentation page.
Definition node.h:155
virtual bool isMacro() const
returns true if either FunctionNode::isMacroWithParams() or FunctionNode::isMacroWithoutParams() retu...
Definition node.h:154
bool isEnumType() const
Returns true if the node type is Enum.
Definition node.h:100
virtual Status status() const
Returns the node's status value.
Definition node.h:249
virtual bool isTextPageNode() const
Returns true if the node is a PageNode but not an Aggregate.
Definition node.h:160
virtual bool isAttached() const
Returns true if the QML property or QML method node is marked as attached.
Definition node.h:149
Aggregate * parent() const
Returns the node's parent pointer.
Definition node.h:215
virtual bool isDeprecated() const
Returns true if this node's status is Deprecated.
Definition node.h:141
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:143
static bool nodeNameLessThan(const Node *first, const Node *second)
Returns true if the node n1 is less than node n2.
Definition node.cpp:59
const Location & location() const
If this node's definition location is empty, this function returns this node's declaration location.
Definition node.h:238
bool isProxyNode() const
Returns true if the node type is Proxy.
Definition node.h:120
Access access() const
Returns the node's Access setting, which can be Public, Protected, or Private.
Definition node.h:235
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
Definition node.h:107
ThreadSafeness threadSafeness() const
Returns the thread safeness value for whatever this node represents.
Definition node.cpp:779
virtual bool isMarkedReimp() const
Returns true if the FunctionNode is marked as a reimplemented function.
Definition node.h:157
bool isProperty() const
Returns true if the node type is Property.
Definition node.h:119
NodeContext createContext() const
Definition node.cpp:116
bool isModule() const
Returns true if the node type is Module.
Definition node.h:114
virtual bool isPropertyGroup() const
Returns true if the node is a SharedCommentNode for documenting multiple C++ properties or multiple Q...
Definition node.h:158
ThreadSafeness
An unsigned char that specifies the degree of thread-safeness of the element.
Definition node.h:65
@ ThreadSafe
Definition node.h:69
@ UnspecifiedSafeness
Definition node.h:66
@ Reentrant
Definition node.h:68
bool isSharingComment() const
This function returns true if the node is sharing a comment with other nodes.
Definition node.h:256
virtual CollectionNode * logicalModule() const
If this is a QmlTypeNode, a pointer to its QML module is returned, which is a pointer to a Collection...
Definition node.h:268
bool hasDoc() const
Returns true if this node is documented, or it represents a documented node read from the index ('had...
Definition node.cpp:859
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
Definition node.h:150
virtual bool isCollectionNode() const
Returns true if this is an instance of CollectionNode.
Definition node.h:151
@ Internal
Definition node.h:61
@ Active
Definition node.h:60
@ Deprecated
Definition node.h:58
@ Preliminary
Definition node.h:59
bool isQmlModule() const
Returns true if the node type is QmlModule.
Definition node.h:125
@ SignaturePlain
Definition node.h:73
bool isExample() const
Returns true if the node type is Example.
Definition node.h:105
bool isIndexNode() const
Returns true if this node was created from something in an index file.
Definition node.h:113
bool isQmlProperty() const
Returns true if the node type is QmlProperty.
Definition node.h:127
Represents a file that is reachable by QDoc based on its current configuration.