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