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