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