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