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"
24#include "openedlist.h"
27#include "propertynode.h"
28#include "qdocdatabase.h"
29#include "qmltypenode.h"
31#include "quoter.h"
33#include "tokenizer.h"
34#include "typedefnode.h"
35#include "utilities.h"
36#include "textutils.h"
37
38#include <QtCore/qdebug.h>
39#include <QtCore/qdir.h>
40#include <QtCore/qregularexpression.h>
41
42#ifndef QT_BOOTSTRAPPED
43# include "QtCore/qurl.h"
44#endif
45
46#include <string>
47#include <utility>
48
49using namespace std::literals::string_literals;
50
51QT_BEGIN_NAMESPACE
52
53using namespace Qt::StringLiterals;
54
55Generator *Generator::s_currentGenerator;
56QMap<QString, QMap<QString, QString>> Generator::s_fmtLeftMaps;
57QMap<QString, QMap<QString, QString>> Generator::s_fmtRightMaps;
58QList<Generator *> Generator::s_generators;
59QString Generator::s_outDir;
60QString Generator::s_imagesOutDir;
61QString Generator::s_outSubdir;
62QStringList Generator::s_outFileNames;
63QStringList Generator::s_exampleImageFileNames;
64QSet<QString> Generator::s_trademarks;
65QSet<QString> Generator::s_outputFormats;
66QHash<QString, QString> Generator::s_outputPrefixes;
67QHash<QString, QString> Generator::s_outputSuffixes;
68QString Generator::s_project;
69bool Generator::s_noLinkErrors = false;
70bool Generator::s_autolinkErrors = false;
72bool Generator::s_useOutputSubdirs = true;
73QmlTypeNode *Generator::s_qmlTypeContext = nullptr;
74
75static QRegularExpression tag("</?@[^>]*>");
76static QLatin1String amp("&amp;");
77static QLatin1String gt("&gt;");
78static QLatin1String lt("&lt;");
79static QLatin1String quot("&quot;");
80
81/*!
82 Returns the set of template parameter names inherited from the parent
83 scope chain of \a node. This includes template parameters from enclosing
84 class templates, which are visible but not required to be documented
85 in nested classes or member functions.
86*/
88{
89 QSet<QString> names;
90 for (const Node *p = node->parent(); p; p = p->parent()) {
92 names.unite(p->templateDecl()->parameterNames());
93 }
94 return names;
95}
96
97/*!
98 \enum ValidationContext
99 Selects warning message wording based on documentation context.
100
101 \value FunctionDoc Warns "No such parameter" (function docs may reference
102 both function parameters and template parameters).
103 \value TemplateDoc Warns "No such template parameter" (template class/alias
104 docs reference only template parameters).
107
108/*!
109 Warns about documented parameter names in \a node that don't exist in
110 \a allowedNames. Uses \a context to select appropriate wording.
111*/
113 const QSet<QString> &documentedNames,
114 const QSet<QString> &allowedNames,
115 ValidationContext context)
116{
117 for (const auto &name : documentedNames) {
118 if (!allowedNames.contains(name) && CodeParser::isWorthWarningAbout(node->doc())) {
119 const auto message = (context == ValidationContext::TemplateDoc)
120 ? "No such template parameter '%1' in %2"_L1
121 : "No such parameter '%1' in %2"_L1;
122 node->doc().location().warning(message.arg(name, node->plainFullName()),
123 suggestName(name, allowedNames));
124 }
125 }
126}
127
128/*!
129 Constructs the generator base class. Prepends the newly
130 constructed generator to the list of output generators.
131 Sets a pointer to the QDoc database singleton, which is
132 available to the generator subclasses.
133 */
135 : file_resolver{file_resolver}
136{
138 s_generators.prepend(this);
139}
140
141/*!
142 Destroys the generator after removing it from the list of
143 output generators.
144 */
146{
147 s_generators.removeAll(this);
148}
149
150void Generator::appendFullName(Text &text, const Node *apparentNode, const Node *relative,
151 const Node *actualNode)
152{
153 if (actualNode == nullptr)
154 actualNode = apparentNode;
155
156 addNodeLink(text, actualNode, apparentNode->plainFullName(relative));
157}
158
159void Generator::appendFullName(Text &text, const Node *apparentNode, const QString &fullName,
160 const Node *actualNode)
161{
162 if (actualNode == nullptr)
163 actualNode = apparentNode;
164
165 addNodeLink(text, actualNode, fullName);
166}
167
168/*!
169 Append the signature for the function named in \a node to
170 \a text, so that is a link to the documentation for that
171 function.
172 */
173void Generator::appendSignature(Text &text, const Node *node)
174{
175 addNodeLink(text, node, node->signature(Node::SignaturePlain));
176}
177
178/*!
179 Generate a bullet list of function signatures. The function
180 nodes are in \a nodes. It uses the \a relative node and the
181 \a marker for the generation.
182 */
183void Generator::signatureList(const NodeList &nodes, const Node *relative, CodeMarker *marker)
184{
185 Text text;
186 int count = 0;
187 text << Atom(Atom::ListLeft, QString("bullet"));
188 for (const auto &node : nodes) {
189 text << Atom(Atom::ListItemNumber, QString::number(++count));
190 text << Atom(Atom::ListItemLeft, QString("bullet"));
191 appendSignature(text, node);
192 text << Atom(Atom::ListItemRight, QString("bullet"));
193 }
194 text << Atom(Atom::ListRight, QString("bullet"));
195 generateText(text, relative, marker);
196}
197
198int Generator::appendSortedNames(Text &text, const ClassNode *cn, const QList<RelatedClass> &rc)
199{
200 QMap<QString, Text> classMap;
201 for (const auto &relatedClass : rc) {
202 ClassNode *rcn = relatedClass.m_node;
203 if (rcn && rcn->isInAPI()) {
204 Text className;
205 appendFullName(className, rcn, cn);
206 classMap[className.toString().toLower()] = className;
207 }
208 }
209
210 int index = 0;
211 const QStringList classNames = classMap.keys();
212 for (const auto &className : classNames) {
213 text << classMap[className];
214 text << TextUtils::comma(index++, classNames.size());
215 }
216 return index;
217}
218
219int Generator::appendSortedQmlNames(Text &text, const Node *base, const QStringList &knownTypes,
220 const NodeList &subs)
221{
222 QMap<QString, Text> classMap;
223
224 QStringList typeNames(knownTypes);
225 for (const auto sub : subs)
226 typeNames << sub->name();
227
228 for (const auto sub : subs) {
229 Text full_name;
230 appendFullName(full_name, sub, base);
231 // Disambiguate with '(<QML module name>)' if there are clashing type names
232 if (typeNames.count(sub->name()) > 1)
233 full_name << Atom(Atom::String, " (%1)"_L1.arg(sub->logicalModuleName()));
234 classMap[full_name.toString().toLower()] = full_name;
235 }
236
237 int index = 0;
238 const auto &names = classMap.keys();
239 for (const auto &name : names)
240 text << classMap[name] << TextUtils::comma(index++, names.size());
241 return index;
242}
243
244/*!
245 Creates the file named \a fileName in the output directory
246 and returns a QFile pointing to this file. In particular,
247 this method deals with errors when opening the file:
248 the returned QFile is always valid and can be written to.
249
250 \sa beginSubPage()
251 */
252QFile *Generator::openSubPageFile(const PageNode *node, const QString &fileName)
253{
254 // Skip generating a warning for license attribution pages, as their source
255 // is generated by qtattributionsscanner and may potentially include duplicates.
256 // NOTE: Depending on the value of the `QtParts` field in qt_attribution.json files,
257 // qtattributionsscanner may not use the \attribution QDoc command for the page
258 // (by design). Therefore, check also filename.
259 if (s_outFileNames.contains(fileName) && !node->isAttribution() && !fileName.contains("-attribution-"_L1))
260 node->location().warning("Already generated %1 for this project"_L1.arg(fileName));
261
262 QString path = outputDir() + QLatin1Char('/') + fileName;
263
264 const auto &outPath = s_redirectDocumentationToDevNull ? QStringLiteral("/dev/null") : path;
265 auto outFile = new QFile(outPath);
266
267 if (!s_redirectDocumentationToDevNull && outFile->exists()) {
268 const QString warningText {"Output file already exists, overwriting %1"_L1.arg(outFile->fileName())};
269 if (qEnvironmentVariableIsSet("QDOC_ALL_OVERWRITES_ARE_WARNINGS"))
270 node->location().warning(warningText);
271 else
272 qCDebug(lcQdoc) << qUtf8Printable(warningText);
273 }
274
275 if (!outFile->open(QFile::WriteOnly | QFile::Text)) {
276 node->location().fatal(
277 QStringLiteral("Cannot open output file '%1'").arg(outFile->fileName()));
278 }
279
280 qCDebug(lcQdoc, "Writing: %s", qPrintable(path));
281 s_outFileNames << fileName;
282 s_trademarks.clear();
283 return outFile;
284}
285
286/*!
287 Creates the file named \a fileName in the output directory.
288 Attaches a QTextStream to the created file, which is written
289 to all over the place using out().
290 */
291void Generator::beginSubPage(const PageNode *node, const QString &fileName)
292{
293 QFile *outFile = openSubPageFile(static_cast<const PageNode*>(node), fileName);
294 auto *out = new QTextStream(outFile);
295 outStreamStack.push(out);
296}
297
298/*!
299 Flush the text stream associated with the subpage, and
300 then pop it off the text stream stack and delete it.
301 This terminates output of the subpage.
302 */
304{
305 outStreamStack.top()->flush();
306 delete outStreamStack.top()->device();
307 delete outStreamStack.pop();
308}
309
310QString Generator::fileBase(const Node *node) const
311{
312 if (!node->isPageNode() && !node->isCollectionNode())
313 node = node->parent();
314
315 if (node->hasFileNameBase())
316 return node->fileNameBase();
317
318 QString result = Utilities::computeFileBase(
319 node, s_project,
320 [](const Node *n) { return outputPrefix(n); },
321 [](const Node *n) { return outputSuffix(n); });
322
323 const_cast<Node *>(node)->setFileNameBase(result);
324 return result;
325}
326
327/*!
328 Constructs an href link from an example file name, which
329 is a \a path to the example file. If \a fileExt is empty
330 (default value), retrieve the file extension from
331 the generator.
332 */
333QString Generator::linkForExampleFile(const QString &path, const QString &fileExt) const
334{
335 return Utilities::linkForExampleFile(path, s_project, fileExt.isEmpty() ? fileExtension() : fileExt);
336}
337
338/*!
339 Helper function to construct a title for a file or image page
340 included in an example.
341*/
342QString Generator::exampleFileTitle(const ExampleNode *relative, const QString &fileName)
343{
344 return Utilities::exampleFileTitle(relative->files(), relative->images(), fileName);
345}
346
347/*!
348 If the \a node has a URL, return the URL as the file name.
349 Otherwise, construct the file name from the fileBase() and
350 either the provided \a extension or fileExtension(), and
351 return the constructed name.
352 */
353QString Generator::fileName(const Node *node, const QString &extension) const
354{
355 if (!node->url().isEmpty())
356 return node->url();
357
358 // Special case for simple page nodes (\page commands) with explicit
359 // non-.html extensions. Use the normalized fileBase() but preserve
360 // user specified extension
361 if (node->isTextPageNode() && !node->isCollectionNode() && extension.isNull()) {
362 QFileInfo originalName(node->name());
363 QString suffix = originalName.suffix();
364 if (!suffix.isEmpty() && suffix != "html") {
365 // User specified a non-.html extension - use normalized base + original extension
366 QString name = fileBase(node);
367 return name + QLatin1Char('.') + suffix;
368 }
369 }
370
371 QString name = fileBase(node) + QLatin1Char('.');
372 return name + (extension.isNull() ? fileExtension() : extension);
373}
374
375/*!
376 Clean the given \a ref to be used as an HTML anchor or an \c xml:id.
377 If \a xmlCompliant is set to \c true, a stricter process is used, as XML
378 is more rigorous in what it accepts. Otherwise, if \a xmlCompliant is set to
379 \c false, the basic HTML transformations are applied.
380
381 More specifically, only XML NCNames are allowed
382 (https://www.w3.org/TR/REC-xml-names/#NT-NCName).
383 */
384QString Generator::cleanRef(const QString &ref, bool xmlCompliant)
385{
386 // XML-compliance is ensured in two ways:
387 // - no digit (0-9) at the beginning of an ID (many IDs do not respect this property)
388 // - no colon (:) anywhere in the ID (occurs very rarely)
389
390 QString clean;
391
392 if (ref.isEmpty())
393 return clean;
394
395 clean.reserve(ref.size() + 20);
396 const QChar c = ref[0];
397 const uint u = c.unicode();
398
399 if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (!xmlCompliant && u >= '0' && u <= '9')) {
400 clean += c;
401 } else if (xmlCompliant && u >= '0' && u <= '9') {
402 clean += QLatin1Char('A') + c;
403 } else if (u == '~') {
404 clean += "dtor.";
405 } else if (u == '_') {
406 clean += "underscore.";
407 } else {
408 clean += QLatin1Char('A');
409 }
410
411 for (int i = 1; i < ref.size(); i++) {
412 const QChar c = ref[i];
413 const uint u = c.unicode();
414 if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9') || u == '-'
415 || u == '_' || (xmlCompliant && u == ':') || u == '.') {
416 clean += c;
417 } else if (c.isSpace()) {
418 clean += QLatin1Char('-');
419 } else if (u == '!') {
420 clean += "-not";
421 } else if (u == '&') {
422 clean += "-and";
423 } else if (u == '<') {
424 clean += "-lt";
425 } else if (u == '=') {
426 clean += "-eq";
427 } else if (u == '>') {
428 clean += "-gt";
429 } else if (u == '#') {
430 clean += QLatin1Char('#');
431 } else {
432 clean += QLatin1Char('-');
433 clean += QString::number(static_cast<int>(u), 16);
434 }
435 }
436 return clean;
437}
438
440{
441 return s_fmtLeftMaps[format()];
442}
443
445{
446 return s_fmtRightMaps[format()];
447}
448
449/*!
450 Returns the full document location.
451 */
452QString Generator::fullDocumentLocation(const Node *node) const
453{
454 if (node == nullptr)
455 return QString();
456 if (!node->url().isEmpty())
457 return node->url();
458
459 QString parentName;
460 QString anchorRef;
461
462 if (node->isNamespace()) {
463 /*
464 The root namespace has no name - check for this before creating
465 an attribute containing the location of any documentation.
466 */
467 if (!fileBase(node).isEmpty())
468 parentName = fileBase(node) + QLatin1Char('.') + fileExtension();
469 else
470 return QString();
471 } else if (node->isQmlType()) {
472 return fileBase(node) + QLatin1Char('.') + fileExtension();
473 } else if (node->isTextPageNode() || node->isCollectionNode()) {
474 parentName = fileBase(node) + QLatin1Char('.') + fileExtension();
475 } else if (fileBase(node).isEmpty())
476 return QString();
477
478 Node *parentNode = nullptr;
479
480 if ((parentNode = node->parent())) {
481 // use the parent's name unless the parent is the root namespace
482 if (!node->parent()->isNamespace() || !node->parent()->name().isEmpty())
483 parentName = fullDocumentLocation(node->parent());
484 }
485
486 switch (node->nodeType()) {
487 case NodeType::Class:
488 case NodeType::Struct:
489 case NodeType::Union:
490 case NodeType::Namespace:
491 case NodeType::Proxy:
492 parentName = fileBase(node) + QLatin1Char('.') + fileExtension();
493 break;
494 case NodeType::Function: {
495 const auto *fn = static_cast<const FunctionNode *>(node);
496 switch (fn->metaness()) {
498 anchorRef = QLatin1Char('#') + node->name() + "-signal";
499 break;
501 anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
502 break;
504 anchorRef = QLatin1Char('#') + node->name() + "-method";
505 break;
506 default:
507 if (fn->isDtor())
508 anchorRef = "#dtor." + fn->name().mid(1);
509 else if (const auto *p = fn->primaryAssociatedProperty(); p && fn->doc().isEmpty())
510 return fullDocumentLocation(p);
511 else if (fn->overloadNumber() > 0)
512 anchorRef = QLatin1Char('#') + cleanRef(fn->name()) + QLatin1Char('-')
513 + QString::number(fn->overloadNumber());
514 else
515 anchorRef = QLatin1Char('#') + cleanRef(fn->name());
516 break;
517 }
518 break;
519 }
520 /*
521 Use node->name() instead of fileBase(node) as
522 the latter returns the name in lower-case. For
523 HTML anchors, we need to preserve the case.
524 */
525 case NodeType::Enum:
527 anchorRef = QLatin1Char('#') + node->name() + "-enum";
528 break;
529 case NodeType::Typedef: {
530 const auto *tdef = static_cast<const TypedefNode *>(node);
531 if (tdef->associatedEnum())
532 return fullDocumentLocation(tdef->associatedEnum());
533 } Q_FALLTHROUGH();
535 anchorRef = QLatin1Char('#') + node->name() + "-typedef";
536 break;
538 anchorRef = QLatin1Char('#') + node->name() + "-prop";
539 break;
541 if (!node->isPropertyGroup())
542 break;
543 } Q_FALLTHROUGH();
545 if (node->isAttached())
546 anchorRef = QLatin1Char('#') + node->name() + "-attached-prop";
547 else
548 anchorRef = QLatin1Char('#') + node->name() + "-prop";
549 break;
551 anchorRef = QLatin1Char('#') + node->name() + "-var";
552 break;
554 case NodeType::Page:
555 case NodeType::Group:
557 case NodeType::Module:
558 case NodeType::QmlModule: {
559 parentName = fileBase(node);
560 parentName.replace(QLatin1Char('/'), QLatin1Char('-'))
561 .replace(QLatin1Char('.'), QLatin1Char('-'));
562 parentName += QLatin1Char('.') + fileExtension();
563 } break;
564 default:
565 break;
566 }
567
568 if (!node->isPageNode() && node->isDeprecated())
569 parentName.replace(u'.' + fileExtension(), "-obsolete."_L1 + fileExtension());
570
571 return parentName.toLower() + anchorRef;
572}
573
574/*!
575 Generates text for a "see also" list for the given \a node and \a marker
576 if a list has been defined.
577
578 Check for links to the node containing the \sa command, looking for empty
579 ref fields to ensure that a link is referring to the node itself and not
580 a different section of a larger document.
581*/
582void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
583{
584 QList<Text> alsoList = node->doc().alsoList();
585 supplementAlsoList(node, alsoList);
586
587 if (!alsoList.isEmpty()) {
588 Text text;
589 text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << "See also "
591
592 QSet<QString> used;
593 QList<Text> items;
594 for (const auto &also : std::as_const(alsoList)) {
595 // Every item starts with a link atom.
596 const Atom *atom = also.firstAtom();
597 QString link = atom->string();
598 if (!used.contains(link)) {
599 items.append(also);
600 used.insert(link);
601
602 QString ref;
603 if (m_qdb->findNodeForAtom(atom, node, ref) == node && ref.isEmpty())
604 node->doc().location().warning("Redundant link to self in \\sa command for %1"_L1.arg(node->name()));
605 }
606 }
607
608 int i = 0;
609 for (const auto &also : std::as_const(items))
610 text << also << TextUtils::separator(i++, items.size());
611
612 text << Atom::ParaRight;
613 generateText(text, node, marker);
614 }
615}
616
617const Atom *Generator::generateAtomList(const Atom *atom, const Node *relative, CodeMarker *marker,
618 bool generate, int &numAtoms)
619{
620 while (atom != nullptr) {
621 if (atom->type() == Atom::FormatIf) {
622 int numAtoms0 = numAtoms;
623 bool rightFormat = canHandleFormat(atom->string());
624 atom = generateAtomList(atom->next(), relative, marker, generate && rightFormat,
625 numAtoms);
626 if (atom == nullptr)
627 return nullptr;
628
629 if (atom->type() == Atom::FormatElse) {
630 ++numAtoms;
631 atom = generateAtomList(atom->next(), relative, marker, generate && !rightFormat,
632 numAtoms);
633 if (atom == nullptr)
634 return nullptr;
635 }
636
637 if (atom->type() == Atom::FormatEndif) {
638 if (generate && numAtoms0 == numAtoms) {
639 relative->location().warning(QStringLiteral("Output format %1 not handled %2")
640 .arg(format(), outFileName()));
641 Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
642 generateAtomList(&unhandledFormatAtom, relative, marker, generate, numAtoms);
643 }
644 atom = atom->next();
645 }
646 } else if (atom->type() == Atom::FormatElse || atom->type() == Atom::FormatEndif) {
647 return atom;
648 } else {
649 int n = 1;
650 if (generate) {
651 n += generateAtom(atom, relative, marker);
652 numAtoms += n;
653 }
654 while (n-- > 0)
655 atom = atom->next();
656 }
657 }
658 return nullptr;
659}
660
661
662/*!
663 Generate the body of the documentation from the qdoc comment
664 found with the entity represented by the \a node.
665 */
666void Generator::generateBody(const Node *node, CodeMarker *marker)
667{
668 const FunctionNode *fn = node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
669 if (!node->hasDoc()) {
670 /*
671 Test for special function, like a destructor or copy constructor,
672 that has no documentation.
673 */
674 if (fn) {
675 if (fn->isDtor()) {
676 Text text;
677 text << "Destroys the instance of ";
678 text << fn->parent()->name() << ".";
679 if (fn->isVirtual())
680 text << " The destructor is virtual.";
681 out() << "<p>";
682 generateText(text, node, marker);
683 out() << "</p>";
684 } else if (fn->isCtor()) {
685 Text text;
686 text << "Default-constructs an instance of "
687 << fn->parent()->name() << ".";
688 out() << "<p>";
689 generateText(text, node, marker);
690 out() << "</p>";
691 } else if (fn->isCCtor()) {
692 Text text;
693 text << "Copy-constructs an instance of "
694 << fn->parent()->name() << ".";
695 out() << "<p>";
696 generateText(text, node, marker);
697 out() << "</p>";
698 } else if (fn->isMCtor()) {
699 Text text;
700 text << "Move-constructs an instance of "
701 << fn->parent()->name() << ".";
702 out() << "<p>";
703 generateText(text, node, marker);
704 out() << "</p>";
705 } else if (fn->isCAssign()) {
706 Text text;
707 text << "Copy-assigns "
710 << " to this " << fn->parent()->name() << " instance.";
711 out() << "<p>";
712 generateText(text, node, marker);
713 out() << "</p>";
714 } else if (fn->isMAssign()) {
715 Text text;
716 text << "Move-assigns "
719 << " to this " << fn->parent()->name() << " instance.";
720 out() << "<p>";
721 generateText(text, node, marker);
722 out() << "</p>";
723 } else if (!node->isWrapper() && !node->isMarkedReimp()) {
724 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
725 const NodeContext context = node->createContext();
726 if (!fn->isIgnored() && InclusionFilter::requiresDocumentation(policy, context)) // undocumented functions added by Q_OBJECT
727 node->location().warning(
728 QStringLiteral("No documentation for %1 '%2'")
729 .arg(fn->kindString(), 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_exampleImageFileNames << 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(NodeText::nodeTypeDescription(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 return;
1270
1271 const auto *shared = node->isSharedCommentNode()
1272 ? static_cast<const SharedCommentNode *>(node) : nullptr;
1273 const bool plural = shared && shared->collective().size() > 1;
1274 const Node *described = plural ? shared->collective().first() : node;
1275 const auto number = plural ? NodeText::GrammaticalNumber::Plural
1277
1278 Text text;
1279 text << Atom::ParaLeft
1280 << (plural ? "These "_L1 : "This "_L1)
1281 << NodeText::nodeTypeDescription(described, number)
1282 << (plural ? " were "_L1 : " was "_L1)
1283 << "introduced in " << formatSince(node) << "." << Atom::ParaRight;
1284
1285 generateText(text, node, marker);
1286}
1287
1288void Generator::generateNoexceptNote(const Node *node, CodeMarker *marker)
1289{
1290 std::vector<const Node *> nodes;
1291 if (node->isSharedCommentNode()) {
1292 const auto &collective = static_cast<const SharedCommentNode *>(node)->collective();
1293 nodes.assign(collective.begin(), collective.end());
1294 } else {
1295 nodes.push_back(node);
1296 }
1297
1298 std::size_t position = 0;
1299 for (const Node *member : nodes) {
1300 ++position;
1301 if (!member->isFunction(Genus::CPP))
1302 continue;
1303
1304 const auto &noexceptExpr = static_cast<const FunctionNode *>(member)->getNoexcept();
1305 if (!noexceptExpr || noexceptExpr->isEmpty())
1306 continue;
1307
1308 QString subject;
1309 if (nodes.size() > 1)
1310 subject = " ("_L1 + QString::number(position) + ")"_L1;
1311 else
1312 subject = "This "_L1 + NodeText::nodeTypeDescription(member);
1313
1314 Text text;
1315 text << Atom::NoteLeft << subject
1316 << " is noexcept when "
1317 << Atom(Atom::C, marker->markedUpCode(*noexceptExpr, nullptr, Location()))
1318 << " is " << Atom(Atom::C, "true") << "." << Atom::NoteRight;
1319 generateText(text, member, marker);
1320 }
1321}
1322
1323void Generator::generateStatus(const Node *node, CodeMarker *marker)
1324{
1325 Text text;
1326
1327 // A property group is one documented entity; other shared comments
1328 // with several members describe each of them.
1329 const auto *shared = node->isSharedCommentNode()
1330 ? static_cast<const SharedCommentNode *>(node) : nullptr;
1331 const bool plural = shared && !shared->isPropertyGroup() && shared->collective().size() > 1;
1332 const QString subject = plural
1333 ? "These "_L1 + NodeText::nodeTypeDescription(shared->collective().first(), NodeText::GrammaticalNumber::Plural) + " are"_L1
1334 : "This "_L1 + NodeText::nodeTypeDescription(node) + " is"_L1;
1335
1336 switch (node->status()) {
1337 case Status::Active:
1338 // Output the module 'state' description if set.
1339 if (node->isModule() || node->isQmlModule()) {
1340 const QString &state = static_cast<const CollectionNode*>(node)->state();
1341 if (!state.isEmpty()) {
1342 text << Atom::ParaLeft << "This " << NodeText::nodeTypeDescription(node) << " is in "
1344 << Atom(Atom::FormattingRight, ATOM_FORMATTING_ITALIC) << " state."
1345 << Atom::ParaRight;
1346 break;
1347 }
1348 }
1349 if (const auto &version = node->deprecatedSince(); !version.isEmpty()) {
1350 text << Atom::ParaLeft << subject
1351 << " scheduled for deprecation in version "
1352 << version << "." << Atom::ParaRight;
1353 }
1354 break;
1355 case Status::Preliminary: {
1356 auto description = Config::instance()
1357 .get(CONFIG_PRELIMINARY + Config::dot + CONFIG_DESCRIPTION)
1358 .asString();
1359 description.replace('\1'_L1, NodeText::nodeTypeDescription(node));
1361 << description
1363 } break;
1364 case Status::Deprecated:
1365 text << Atom::ParaLeft;
1366 if (node->isAggregate())
1368 text << subject << " deprecated";
1369 if (const QString &version = node->deprecatedSince(); !version.isEmpty()) {
1370 text << " since ";
1371 if (node->isQmlNode() && !node->logicalModuleName().isEmpty())
1372 text << node->logicalModuleName() << " ";
1373 text << version;
1374 }
1375
1376 text << ". We strongly advise against using " << (plural ? "them" : "it")
1377 << " in new code.";
1378 if (node->isAggregate())
1380 text << Atom::ParaRight;
1381 break;
1382 case Status::Internal:
1384 if (node->isPageNode())
1386 << "Part of developer documentation for internal use."
1388 break;
1389 default:
1390 break;
1391 }
1392 generateText(text, node, marker);
1393}
1394
1395/*!
1396 Generates an addendum note of type \a type for \a node, using \a marker
1397 as the code marker.
1398*/
1399void Generator::generateAddendum(const Node *node, Addendum type, CodeMarker *marker,
1400 AdmonitionPrefix prefix)
1401{
1402 Q_ASSERT(node && !node->name().isEmpty());
1403 Text text;
1404 text << Atom(Atom::DivLeft,
1405 "class=\"admonition %1\""_L1.arg(prefix == AdmonitionPrefix::Note ? u"note"_s : u"auto"_s));
1406 text << Atom::ParaLeft;
1407
1408 switch (prefix) {
1410 break;
1413 << "Note: " << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1414 break;
1415 }
1416 }
1417
1418 switch (type) {
1419 case Invokable:
1420 text << "This function can be invoked via the meta-object system and from QML. See "
1421 << Atom(Atom::AutoLink, "Q_INVOKABLE")
1424 break;
1425 case PrivateSignal:
1426 text << "This is a private signal. It can be used in signal connections "
1427 "but cannot be emitted by the user.";
1428 break;
1429 case QmlSignalHandler:
1430 {
1431 QString handler(node->name());
1432 qsizetype prefixLocation = handler.lastIndexOf('.', -2) + 1;
1433 handler[prefixLocation] = handler[prefixLocation].toTitleCase();
1434 handler.insert(prefixLocation, QLatin1String("on"));
1435 text << "The corresponding handler is "
1438 break;
1439 }
1441 {
1442 if (!node->isFunction())
1443 return;
1444 const auto *fn = static_cast<const FunctionNode *>(node);
1445 auto nodes = fn->associatedProperties();
1446 if (nodes.isEmpty())
1447 return;
1448 std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan);
1449
1450 // Group properties by their role for more concise output
1451 QMap<PropertyNode::FunctionRole, QList<const PropertyNode *>> roleGroups;
1452 for (const auto *n : std::as_const(nodes)) {
1453 const auto *pn = static_cast<const PropertyNode *>(n);
1454 if (pn->isInAPI()) {
1455 PropertyNode::FunctionRole role = pn->role(fn);
1456 roleGroups[role].append(pn);
1457 }
1458 }
1459
1460 if (roleGroups.isEmpty())
1461 return;
1462
1463 // Generate text for each role group in an explicit order
1464 static constexpr PropertyNode::FunctionRole roleOrder[] = {
1470 };
1471 for (auto role : roleOrder) {
1472 const auto it = roleGroups.constFind(role);
1473 if (it == roleGroups.cend())
1474 continue;
1475
1476 const auto &properties = it.value();
1477
1478 QString msg;
1479 switch (role) {
1480 case PropertyNode::FunctionRole::Getter:
1481 msg = u"Getter function"_s;
1482 break;
1483 case PropertyNode::FunctionRole::Setter:
1484 msg = u"Setter function"_s;
1485 break;
1486 case PropertyNode::FunctionRole::Resetter:
1487 msg = u"Resetter function"_s;
1488 break;
1489 case PropertyNode::FunctionRole::Notifier:
1490 msg = u"Notifier signal"_s;
1491 break;
1492 case PropertyNode::FunctionRole::Bindable:
1493 msg = u"Bindable function"_s;
1494 break;
1495 default:
1496 continue;
1497 }
1498
1499 if (properties.size() == 1) {
1500 const auto *pn = properties.first();
1501 text << msg << u" for property "_s << Atom(Atom::Link, pn->name())
1502 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << pn->name()
1503 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << u". "_s;
1504 } else {
1505 text << msg << u" for properties "_s;
1506 for (qsizetype i = 0; i < properties.size(); ++i) {
1507 const auto *pn = properties.at(i);
1508 text << Atom(Atom::Link, pn->name())
1509 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << pn->name()
1511 << TextUtils::separator(i, properties.size());
1512 }
1513 text << u" "_s;
1514 }
1515 }
1516 break;
1517 }
1518 case BindableProperty:
1519 {
1520 text << "This property supports "
1521 << Atom(Atom::Link, "QProperty")
1522 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << "QProperty"
1524 text << " bindings.";
1525 break;
1526 }
1527 case OverloadNote:
1528 {
1529 const auto *func = static_cast<const FunctionNode *>(node);
1530
1531 // Primary overloads should not display any overload note text
1532 if (func->isPrimaryOverload())
1533 return;
1534
1535 if (func->isSignal() || func->isSlot()) {
1536 QString functionType = func->isSignal() ? "signal" : "slot";
1537 const QString &configKey = func->isSignal() ? "overloadedsignalstarget" : "overloadedslotstarget";
1538 const QString &defaultTarget = func->isSignal() ? "connecting-overloaded-signals" : "connecting-overloaded-slots";
1539 const QString &linkTarget = Config::instance().get(configKey).asString(defaultTarget);
1540
1541 text << "This " << functionType << " is overloaded. ";
1542
1543 QString snippet = generateOverloadSnippet(func);
1544 if (!snippet.isEmpty()) {
1545 text << "To connect to this " << functionType << ":\n\n"
1546 << Atom(Atom::Code, snippet) << "\n";
1547 }
1548
1549 if (!linkTarget.isEmpty()) {
1550 text << "For more examples and approaches, see "
1551 << Atom(Atom::Link, linkTarget)
1553 << "connecting to overloaded " << functionType << "s"
1555 }
1556 } else {
1557 const auto &args = node->doc().overloadList();
1558 if (args.first().first.isEmpty()) {
1559 text << "This is an overloaded function.";
1560 } else {
1561 QString target = args.first().first;
1562 // If the target is not fully qualified and we have a parent class context,
1563 // attempt to qualify it to improve link resolution
1564 if (!target.contains("::")) {
1565 const auto *parent = node->parent();
1566 if (parent && (parent->isClassNode() || parent->isNamespace()))
1567 target = parent->name() + "::" + target;
1568 }
1569 text << "This function overloads " << Atom(Atom::AutoLink, target) << ".";
1570 }
1571 }
1572 break;
1573 }
1574 default:
1575 return;
1576 }
1577
1578 text << Atom::ParaRight
1579 << Atom::DivRight;
1580 generateText(text, node, marker);
1581}
1582
1583/*!
1584 Generate the documentation for \a relative. i.e. \a relative
1585 is the node that represents the entity where a qdoc comment
1586 was found, and \a text represents the qdoc comment.
1587 */
1588bool Generator::generateText(const Text &text, const Node *relative, CodeMarker *marker)
1589{
1590 bool result = false;
1591 if (text.firstAtom() != nullptr) {
1592 int numAtoms = 0;
1594 generateAtomList(text.firstAtom(), relative, marker, true, numAtoms);
1595 result = true;
1596 }
1597 return result;
1598}
1599
1600/*
1601 The node is an aggregate, typically a class node, which has
1602 a threadsafeness level. This function checks all the children
1603 of the node to see if they are exceptions to the node's
1604 threadsafeness. If there are any exceptions, the exceptions
1605 are added to the appropriate set (reentrant, threadsafe, and
1606 nonreentrant, and true is returned. If there are no exceptions,
1607 the three node lists remain empty and false is returned.
1608 */
1609bool Generator::hasExceptions(const Node *node, NodeList &reentrant, NodeList &threadsafe,
1610 NodeList &nonreentrant)
1611{
1612 bool result = false;
1614 const NodeList &children = static_cast<const Aggregate *>(node)->childNodes();
1615 for (auto child : children) {
1616 if (!child->isDeprecated()) {
1617 switch (child->threadSafeness()) {
1618 case Node::Reentrant:
1619 reentrant.append(child);
1620 if (ts == Node::ThreadSafe)
1621 result = true;
1622 break;
1623 case Node::ThreadSafe:
1624 threadsafe.append(child);
1625 if (ts == Node::Reentrant)
1626 result = true;
1627 break;
1628 case Node::NonReentrant:
1629 nonreentrant.append(child);
1630 result = true;
1631 break;
1632 default:
1633 break;
1634 }
1635 }
1636 }
1637 return result;
1638}
1639
1640/*!
1641 Returns \c true if a trademark symbol should be appended to the
1642 output as determined by \a atom. Trademarks are tracked via the
1643 use of the \\tm formatting command.
1644
1645 Returns true if:
1646
1647 \list
1648 \li \a atom is of type Atom::FormattingRight containing
1649 ATOM_FORMATTING_TRADEMARK, and
1650 \li The trademarked string is the first appearance on the
1651 current sub-page.
1652 \endlist
1653*/
1655{
1657 return false;
1658 if (atom->string() != ATOM_FORMATTING_TRADEMARK)
1659 return false;
1660
1661 if (atom->count() > 1) {
1662 if (s_trademarks.contains(atom->string(1)))
1663 return false;
1664 s_trademarks << atom->string(1);
1665 }
1666
1667 return true;
1668}
1669
1670static void startNote(Text &text)
1671{
1673 << "Note:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " ";
1674}
1675
1676/*!
1677 Generates text that explains how threadsafe and/or reentrant
1678 \a node is.
1679 */
1681{
1682 Text text, rlink, tlink;
1683 NodeList reentrant;
1684 NodeList threadsafe;
1685 NodeList nonreentrant;
1687 bool exceptions = false;
1688
1689 rlink << Atom(Atom::Link, "reentrant") << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1690 << "reentrant" << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1691
1692 tlink << Atom(Atom::Link, "thread-safe") << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1693 << "thread-safe" << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1694
1695 switch (ts) {
1697 break;
1698 case Node::NonReentrant:
1700 << "Warning:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " This "
1701 << NodeText::nodeTypeDescription(node) << " is not " << rlink << "." << Atom::ParaRight;
1702 break;
1703 case Node::Reentrant:
1704 case Node::ThreadSafe:
1705 startNote(text);
1706 if (node->isAggregate()) {
1707 exceptions = hasExceptions(node, reentrant, threadsafe, nonreentrant);
1708 text << "All functions in this " << NodeText::nodeTypeDescription(node) << " are ";
1709 if (ts == Node::ThreadSafe)
1710 text << tlink;
1711 else
1712 text << rlink;
1713
1714 if (!exceptions || (ts == Node::Reentrant && !threadsafe.isEmpty()))
1715 text << ".";
1716 else
1717 text << " with the following exceptions:";
1718 } else {
1719 text << "This " << NodeText::nodeTypeDescription(node) << " is ";
1720 if (ts == Node::ThreadSafe)
1721 text << tlink;
1722 else
1723 text << rlink;
1724 text << ".";
1725 }
1726 text << Atom::ParaRight;
1727 break;
1728 default:
1729 break;
1730 }
1731 generateText(text, node, marker);
1732
1733 if (exceptions) {
1734 text.clear();
1735 if (ts == Node::Reentrant) {
1736 if (!nonreentrant.isEmpty()) {
1737 startNote(text);
1738 text << "These functions are not " << rlink << ":" << Atom::ParaRight;
1739 signatureList(nonreentrant, node, marker);
1740 }
1741 if (!threadsafe.isEmpty()) {
1742 text.clear();
1743 startNote(text);
1744 text << "These functions are also " << tlink << ":" << Atom::ParaRight;
1745 generateText(text, node, marker);
1746 signatureList(threadsafe, node, marker);
1747 }
1748 } else { // thread-safe
1749 if (!reentrant.isEmpty()) {
1750 startNote(text);
1751 text << "These functions are only " << rlink << ":" << Atom::ParaRight;
1752 signatureList(reentrant, node, marker);
1753 }
1754 if (!nonreentrant.isEmpty()) {
1755 text.clear();
1756 startNote(text);
1757 text << "These functions are not " << rlink << ":" << Atom::ParaRight;
1758 signatureList(nonreentrant, node, marker);
1759 }
1760 }
1761 }
1762}
1763
1764/*!
1765 \internal
1766
1767 Generates text that describes the comparison category of \a node.
1768 The CodeMarker \a marker is passed along to generateText().
1769 */
1771{
1772 auto category{node->comparisonCategory()};
1773 if (category == ComparisonCategory::None)
1774 return false;
1775
1776 Text text;
1777 text << Atom::ParaLeft << "%1 is "_L1.arg(node->plainFullName())
1778 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_ITALIC)
1779 << QString::fromStdString(comparisonCategoryAsString(category))
1780 << ((category == ComparisonCategory::Equality) ? "-"_L1 : "ly "_L1)
1781 << Atom(Atom::String, "comparable"_L1)
1782 << Atom(Atom::FormattingRight, ATOM_FORMATTING_ITALIC)
1783 << "."_L1 << Atom::ParaRight;
1784 generateText(text, node, marker);
1785 return true;
1786}
1787
1788/*!
1789 Generates a table of comparison categories for \a node, combining both
1790 self-comparison (from \\compares) and comparisons with other types
1791 (from \\compareswith).
1792
1793 If the node has a comparison category set via \\compares, it appears
1794 as the first row in the table. Subsequent rows come from \\compareswith
1795 entries.
1796
1797 The Description column is only included if at least one \\compareswith
1798 entry has descriptive content.
1799
1800 Returns \c true if text was generated, \c false otherwise.
1801 */
1803{
1804 Q_ASSERT(node);
1805
1806 const auto selfCategory = node->comparisonCategory();
1807 const auto *map = node->doc().comparesWithMap();
1808
1809 const bool hasSelfComparison = (selfCategory != ComparisonCategory::None);
1810 const bool hasComparesWithEntries = (map && !map->isEmpty());
1811
1812 if (!hasSelfComparison && !hasComparesWithEntries)
1813 return false;
1814
1815 bool hasDescriptions = false;
1816 if (hasComparesWithEntries) {
1817 for (const auto &description : *map) {
1818 if (description.firstAtom()->next() != description.lastAtom()) {
1819 hasDescriptions = true;
1820 break;
1821 }
1822 }
1823 }
1824
1825 Text text;
1826
1827 text << Atom::ParaLeft
1828 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1829 << "%1 Comparisons"_L1.arg(node->plainFullName())
1830 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1831 << Atom::ParaRight;
1832
1833 text << Atom(Atom::TableLeft, "generic"_L1);
1834
1835 text << Atom::TableHeaderLeft
1836 << Atom::TableItemLeft << "Category"_L1 << Atom::TableItemRight
1837 << Atom::TableItemLeft << "Comparable Types"_L1 << Atom::TableItemRight;
1838 if (hasDescriptions)
1839 text << Atom::TableItemLeft << "Description"_L1 << Atom::TableItemRight;
1840 text << Atom::TableHeaderRight;
1841
1842 // First row: self-comparison from \compares
1843 if (hasSelfComparison) {
1844 const QString &category = QString::fromStdString(comparisonCategoryAsString(selfCategory));
1845
1846 text << Atom::TableRowLeft;
1847 text << Atom::TableItemLeft << category << Atom::TableItemRight;
1848 text << Atom::TableItemLeft
1849 << Atom(Atom::String, node->plainFullName()) << Atom::TableItemRight;
1850 if (hasDescriptions)
1852 text << Atom::TableRowRight;
1853 }
1854
1855 // Subsequent rows: \compareswith entries
1856 if (hasComparesWithEntries) {
1857 for (auto [key, description] : map->asKeyValueRange()) {
1858 const QString &category = QString::fromStdString(comparisonCategoryAsString(key));
1859
1860 text << Atom::TableRowLeft;
1861
1862 text << Atom::TableItemLeft << category << Atom::TableItemRight;
1863
1864 text << Atom::TableItemLeft;
1865 const QStringList types{description.firstAtom()->string().split(';'_L1)};
1866 for (const auto &name : types)
1867 text << Atom(Atom::AutoLink, name)
1868 << TextUtils::separator(types.indexOf(name), types.size());
1869 text << Atom::TableItemRight;
1870
1871 if (hasDescriptions) {
1872 text << Atom::TableItemLeft;
1873 if (description.firstAtom()->next() != description.lastAtom())
1874 text << Text::subText(description.firstAtom()->next(), description.lastAtom());
1875 text << Atom::TableItemRight;
1876 }
1877
1878 text << Atom::TableRowRight;
1879 }
1880 }
1881
1882 text << Atom::TableRight;
1883
1884 generateText(text, node, nullptr);
1885 return !text.isEmpty();
1886}
1887
1888/*!
1889 Traverses the database recursively to generate all the documentation.
1890 */
1892{
1893 s_currentGenerator = this;
1895}
1896
1897Generator *Generator::generatorForFormat(const QString &format)
1898{
1899 // First, check the OutputProducerRegistry for producers.
1900 // This supports both Generator-based producers (which register themselves)
1901 // and future non-Generator OutputProducer implementations.
1902 if (auto *producer = OutputProducerRegistry::instance().producerForFormat(format)) {
1903 // TODO: All registered producers are Generators, but this will
1904 // change as we migrate to OutputProducer-based implementations.
1905 if (auto *gen = dynamic_cast<Generator *>(producer))
1906 return gen;
1907 }
1908
1909 // Fallback: Check the legacy s_generators list for unregistered generators.
1910 // This should not normally be reached, but provides backward compatibility.
1911 for (const auto &generator : std::as_const(s_generators)) {
1912 if (generator->format() == format)
1913 return generator;
1914 }
1915 return nullptr;
1916}
1917
1918QString Generator::indent(int level, const QString &markedCode)
1919{
1920 if (level == 0)
1921 return markedCode;
1922
1923 QString t;
1924 int column = 0;
1925
1926 int i = 0;
1927 while (i < markedCode.size()) {
1928 if (markedCode.at(i) == QLatin1Char('\n')) {
1929 column = 0;
1930 } else {
1931 if (column == 0) {
1932 for (int j = 0; j < level; j++)
1933 t += QLatin1Char(' ');
1934 }
1935 column++;
1936 }
1937 t += markedCode.at(i++);
1938 }
1939 return t;
1940}
1941
1943{
1944 Config &config = Config::instance();
1945 s_outputFormats = config.getOutputFormats();
1947
1948 for (auto &g : s_generators) {
1949 if (s_outputFormats.contains(g->format())) {
1950 s_currentGenerator = g;
1951 OutputProducerRegistry::instance().registerProducer(g);
1952 g->initializeGenerator();
1953 }
1954 }
1955
1956 const auto &configFormatting = config.subVars(CONFIG_FORMATTING);
1957 for (const auto &n : configFormatting) {
1958 QString formattingDotName = CONFIG_FORMATTING + Config::dot + n;
1959 const auto &formattingDotNames = config.subVars(formattingDotName);
1960 for (const auto &f : formattingDotNames) {
1961 const auto &configVar = config.get(formattingDotName + Config::dot + f);
1962 QString def{configVar.asString()};
1963 if (!def.isEmpty()) {
1964 int numParams = Config::numParams(def);
1965 int numOccs = def.count("\1");
1966 if (numParams != 1) {
1967 configVar.location().warning(QStringLiteral("Formatting '%1' must "
1968 "have exactly one "
1969 "parameter (found %2)")
1970 .arg(n, numParams));
1971 } else if (numOccs > 1) {
1972 configVar.location().fatal(QStringLiteral("Formatting '%1' must "
1973 "contain exactly one "
1974 "occurrence of '\\1' "
1975 "(found %2)")
1976 .arg(n, numOccs));
1977 } else {
1978 int paramPos = def.indexOf("\1");
1979 s_fmtLeftMaps[f].insert(n, def.left(paramPos));
1980 s_fmtRightMaps[f].insert(n, def.mid(paramPos + 1));
1981 }
1982 }
1983 }
1984 }
1985
1986 s_project = config.get(CONFIG_PROJECT).asString();
1987 s_outDir = config.getOutputDir();
1988 s_outSubdir = s_outDir.mid(s_outDir.lastIndexOf('/') + 1);
1989
1990 s_outputPrefixes.clear();
1991 QStringList items{config.get(CONFIG_OUTPUTPREFIXES).asStringList()};
1992 if (!items.isEmpty()) {
1993 for (const auto &prefix : items)
1994 s_outputPrefixes[prefix] =
1995 config.get(CONFIG_OUTPUTPREFIXES + Config::dot + prefix).asString();
1996 }
1997 if (!items.contains(u"QML"_s))
1998 s_outputPrefixes[u"QML"_s] = u"qml-"_s;
1999
2000 s_outputSuffixes.clear();
2001 for (const auto &suffix : config.get(CONFIG_OUTPUTSUFFIXES).asStringList())
2002 s_outputSuffixes[suffix] = config.get(CONFIG_OUTPUTSUFFIXES
2003 + Config::dot + suffix).asString();
2004
2005 s_noLinkErrors = config.get(CONFIG_NOLINKERRORS).asBool();
2006 s_autolinkErrors = config.get(CONFIG_AUTOLINKERRORS).asBool();
2007}
2008
2009/*!
2010 Creates template-specific subdirs (e.g. /styles and /scripts for HTML)
2011 and copies the files to them.
2012 */
2013void Generator::copyTemplateFiles(const QString &configVar, const QString &subDir)
2014{
2015 // TODO: [resolving-files-unlinked-to-doc]
2016 // This is another case of resolving files, albeit it doesn't use Doc::resolveFile.
2017 // While it may be left out of a first iteration of the file
2018 // resolution logic, it should later be integrated into it.
2019 // This should come naturally when the output directory logic is
2020 // extracted and copying a file should require a validated
2021 // intermediate format.
2022 // Do note that what is done here is a bit different from the
2023 // resolve file routine that is done for other user-given paths.
2024 // Thas is, the paths will always be absolute and not relative as
2025 // they are resolved from the configuration.
2026 // Ideally, this could be solved in the configuration already,
2027 // together with the other configuration resolution processes that
2028 // do not abide by the same constraints that, for example, snippet
2029 // resolution uses.
2030 Config &config = Config::instance();
2031 QStringList files = config.getCanonicalPathList(configVar, Config::Validate);
2032 const auto &loc = config.get(configVar).location();
2033 if (!files.isEmpty()) {
2034 // TODO: [uncentralized-output-directory-structure]
2035 // OutputDirectory provides the centralized system for managing output
2036 // directory structure in Generator base class methods. However, the
2037 // format-specific generators (HtmlGenerator, DocBookGenerator,
2038 // WebXMLGenerator) still manually construct image paths using string
2039 // concatenation and direct Config::copyFile() calls. These should be
2040 // refactored to use OutputDirectory for consistency and security.
2041
2042 const OutputDirectory outDir =
2043 OutputDirectory::ensure(s_outDir, loc);
2044
2045 const OutputDirectory templateDir =
2046 outDir.ensureSubdir(subDir, loc);
2047
2048 for (const auto &file : files) {
2049 if (!file.isEmpty()) {
2050 const QFileInfo fi(file);
2051 Config::copyFile(loc, fi.absoluteFilePath(), fi.fileName(), templateDir.path());
2052 }
2053 }
2054 }
2055}
2056
2057/*!
2058 Reads format-specific variables from config, sets output
2059 (sub)directories, creates them on the filesystem and copies the
2060 template-specific files.
2061 */
2063{
2064 Config &config = Config::instance();
2065 s_outFileNames.clear();
2066 s_exampleImageFileNames.clear();
2067 s_useOutputSubdirs = true;
2068 if (config.get(format() + Config::dot + "nosubdirs").asBool())
2070
2071 if (s_outputFormats.isEmpty())
2072 return;
2074 return;
2075
2076 s_outDir = config.getOutputDir(format());
2077 if (s_outDir.isEmpty()) {
2078 Location().fatal(QStringLiteral("No output directory specified in "
2079 "configuration file or on the command line"));
2080 } else {
2081 s_outSubdir = s_outDir.mid(s_outDir.lastIndexOf('/') + 1);
2082 }
2083
2084 // Ensure output directory exists before proceeding
2085 const OutputDirectory outputDir =
2086 OutputDirectory::ensure(s_outDir, Location());
2087
2088 // Check if the directory is empty when required
2090 if (!outputDir.toQDir().isEmpty())
2091 Location().error("Output directory '%1' exists but is not empty"_L1.arg(s_outDir));
2092 }
2093
2094 // Output directory exists, which is enough for prepare phase.
2095 if (config.preparing())
2096 return;
2097
2098 auto imagesDir = config.get(CONFIG_IMAGESOUTPUTDIR).asString(u"images"_s);
2099 // Ensure images subdirectory exists
2100 [[maybe_unused]] const OutputDirectory imagesOutputDir =
2101 outputDir.ensureSubdir(imagesDir, Location());
2102 s_imagesOutDir = std::move(imagesDir);
2103
2104 copyTemplateFiles(format() + Config::dot + CONFIG_STYLESHEETS, "style");
2105 copyTemplateFiles(format() + Config::dot + CONFIG_SCRIPTS, "scripts");
2106 copyTemplateFiles(format() + Config::dot + CONFIG_EXTRAIMAGES, "images");
2107
2108 // Use a format-specific .quotinginformation if defined, otherwise a global value
2109 if (config.subVars(format()).contains(CONFIG_QUOTINGINFORMATION))
2110 m_quoting = config.get(format() + Config::dot + CONFIG_QUOTINGINFORMATION).asBool();
2111 else
2113}
2114
2115/*!
2116 No-op base implementation. Subclasses may override to perform
2117 generator-specific initialization.
2118 */
2120{
2121 // Default implementation does nothing
2122}
2123
2124bool Generator::matchAhead(const Atom *atom, Atom::AtomType expectedAtomType)
2125{
2126 return atom->next() && atom->next()->type() == expectedAtomType;
2127}
2128
2129/*!
2130 Used for writing to the current output stream. Returns a
2131 reference to the current output stream, which is then used
2132 with the \c {<<} operator for writing.
2133 */
2134QTextStream &Generator::out()
2135{
2136 return *outStreamStack.top();
2137}
2138
2140{
2141 return QFileInfo(static_cast<QFile *>(out().device())->fileName()).fileName();
2142}
2143
2144QString Generator::outputPrefix(const Node *node)
2145{
2146 // Omit prefix for module pages
2147 if (node->isPageNode() && !node->isCollectionNode()) {
2148 switch (node->genus()) {
2149 case Genus::QML:
2150 return s_outputPrefixes[u"QML"_s];
2151 case Genus::CPP:
2152 return s_outputPrefixes[u"CPP"_s];
2153 default:
2154 break;
2155 }
2156 }
2157 return QString();
2158}
2159
2160QString Generator::outputSuffix(const Node *node)
2161{
2162 if (node->isPageNode()) {
2163 switch (node->genus()) {
2164 case Genus::QML:
2165 return s_outputSuffixes[u"QML"_s];
2166 case Genus::CPP:
2167 return s_outputSuffixes[u"CPP"_s];
2168 default:
2169 break;
2170 }
2171 }
2172
2173 return QString();
2174}
2175
2176bool Generator::parseArg(const QString &src, const QString &tag, int *pos, int n,
2177 QStringView *contents, QStringView *par1)
2178{
2179#define SKIP_CHAR(c)
2180 if (i >= n || src[i] != c)
2181 return false;
2182 ++i;
2183
2184#define SKIP_SPACE
2185 while (i < n && src[i] == ' ')
2186 ++i;
2187
2188 qsizetype i = *pos;
2189 qsizetype j {};
2190
2191 // assume "<@" has been parsed outside
2192 // SKIP_CHAR('<');
2193 // SKIP_CHAR('@');
2194
2195 if (tag != QStringView(src).mid(i, tag.size())) {
2196 return false;
2197 }
2198
2199 // skip tag
2200 i += tag.size();
2201
2202 // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
2203 if (par1) {
2204 SKIP_SPACE;
2205 // read parameter name
2206 j = i;
2207 while (i < n && src[i].isLetter())
2208 ++i;
2209 if (src[i] == '=') {
2210 SKIP_CHAR('=');
2211 SKIP_CHAR('"');
2212 // skip parameter name
2213 j = i;
2214 while (i < n && src[i] != '"')
2215 ++i;
2216 *par1 = QStringView(src).mid(j, i - j);
2217 SKIP_CHAR('"');
2218 SKIP_SPACE;
2219 }
2220 }
2221 SKIP_SPACE;
2222 SKIP_CHAR('>');
2223
2224 // find contents up to closing "</@tag>
2225 j = i;
2226 for (; true; ++i) {
2227 if (i + 4 + tag.size() > n)
2228 return false;
2229 if (src[i] != '<')
2230 continue;
2231 if (src[i + 1] != '/')
2232 continue;
2233 if (src[i + 2] != '@')
2234 continue;
2235 if (tag != QStringView(src).mid(i + 3, tag.size()))
2236 continue;
2237 if (src[i + 3 + tag.size()] != '>')
2238 continue;
2239 break;
2240 }
2241
2242 *contents = QStringView(src).mid(j, i - j);
2243
2244 i += tag.size() + 4;
2245
2246 *pos = i;
2247 return true;
2248#undef SKIP_CHAR
2249#undef SKIP_SPACE
2250}
2251
2252QString Generator::plainCode(const QString &markedCode)
2253{
2254 QString t = markedCode;
2255 t.replace(tag, QString());
2256 t.replace(quot, QLatin1String("\""));
2257 t.replace(gt, QLatin1String(">"));
2258 t.replace(lt, QLatin1String("<"));
2259 t.replace(amp, QLatin1String("&"));
2260 return t;
2261}
2262
2263int Generator::skipAtoms(const Atom *atom, Atom::AtomType type) const
2264{
2265 int skipAhead = 0;
2266 atom = atom->next();
2267 while (atom && atom->type() != type) {
2268 skipAhead++;
2269 atom = atom->next();
2270 }
2271 return skipAhead;
2272}
2273
2274/*!
2275 Resets the variables used during text output.
2276 */
2278{
2279 m_inLink = false;
2280 m_inContents = false;
2281 m_inSectionHeading = false;
2282 m_inTableHeader = false;
2283 m_numTableRows = 0;
2285 m_link.clear();
2286 m_sectionNumber.clear();
2287}
2288
2289void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
2290{
2291 if (node->isFunction() && !node->isMacro()) {
2292 const auto fn = static_cast<const FunctionNode *>(node);
2293 if (fn->overloadNumber() == 0) {
2294 QString alternateName;
2295 const FunctionNode *alternateFunc = nullptr;
2296
2297 if (fn->name().startsWith("set") && fn->name().size() >= 4) {
2298 alternateName = fn->name()[3].toLower();
2299 alternateName += fn->name().mid(4);
2300 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2301
2302 if (!alternateFunc) {
2303 alternateName = "is" + fn->name().mid(3);
2304 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2305 if (!alternateFunc) {
2306 alternateName = "has" + fn->name().mid(3);
2307 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2308 }
2309 }
2310 } else if (!fn->name().isEmpty()) {
2311 alternateName = "set";
2312 alternateName += fn->name()[0].toUpper();
2313 alternateName += fn->name().mid(1);
2314 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2315 }
2316
2317 if (alternateFunc && alternateFunc->access() != Access::Private) {
2318 int i;
2319 for (i = 0; i < alsoList.size(); ++i) {
2320 if (alsoList.at(i).toString().contains(alternateName))
2321 break;
2322 }
2323
2324 if (i == alsoList.size()) {
2325 if (alternateFunc->isDeprecated() && !fn->isDeprecated())
2326 return;
2327 alternateName += "()";
2328
2329 Text also;
2330 also << Atom(Atom::Link, alternateName)
2331 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << alternateName
2333 alsoList.prepend(also);
2334 }
2335 }
2336 }
2337 }
2338}
2339
2341{
2342 const NativeEnum *nativeEnum{nullptr};
2343 if (auto *ne_if = dynamic_cast<const NativeEnumInterface *>(node))
2344 nativeEnum = ne_if->nativeEnum();
2345 else
2346 return;
2347
2348 if (!nativeEnum->enumNode())
2349 return;
2350
2351 // Retrieve atoms from C++ enum \value list
2352 const auto body{nativeEnum->enumNode()->doc().body()};
2353 const auto *start{body.firstAtom()};
2354 Text text;
2355
2356 while ((start = start->find(Atom::ListLeft, ATOM_LIST_VALUE))) {
2357 const auto end = start->find(Atom::ListRight, ATOM_LIST_VALUE);
2358 // Skip subsequent ListLeft atoms, collating multiple lists into one
2359 text << body.subText(text.isEmpty() ? start : start->next(), end);
2360 start = end;
2361 }
2362 if (text.isEmpty())
2363 return;
2364
2365 text << Atom(Atom::ListRight, ATOM_LIST_VALUE);
2366 if (marker)
2367 generateText(text, node, marker);
2368 else
2369 generateText(text, node);
2370}
2371
2373{
2374 for (const auto &generator : std::as_const(s_generators)) {
2375 if (s_outputFormats.contains(generator->format())) {
2376 OutputProducerRegistry::instance().unregisterProducer(generator);
2377 generator->terminateGenerator();
2378 }
2379 }
2380
2381 // REMARK: Generators currently, due to recent changes and the
2382 // transitive nature of the current codebase, receive some of
2383 // their dependencies in the constructor and some of them in their
2384 // initialize-terminate lifetime.
2385 // This means that generators need to be constructed and
2386 // destructed between usages such that if multiple usages are
2387 // required, the generators present in the list will have been
2388 // destroyed by then such that accessing them would be an error.
2389 // The current codebase calls initialize and the correspective
2390 // terminate with the same scope as the lifetime of the
2391 // generators.
2392 // Then, clearing the list ensures that, if another generator
2393 // execution is needed, the stale generators will not be removed
2394 // as to be replaced by newly constructed ones.
2395 // Do note that it is not clear that this will happen for any call
2396 // in Qt's documentation and this should work only because of the
2397 // form of the current codebase and the scoping of the
2398 // initialize-terminate calls. As such, this should be considered
2399 // a patchwork that may or may not be doing anything and that may
2400 // break due to changes in other parts of the codebase.
2401 //
2402 // This is still to be considered temporary as the whole
2403 // initialize-terminate idiom must be removed from the codebase.
2404 s_generators.clear();
2405
2406 s_fmtLeftMaps.clear();
2407 s_fmtRightMaps.clear();
2408 s_outDir.clear();
2409 s_imagesOutDir.clear();
2410}
2411
2413
2414/*!
2415 Trims trailing whitespace off the \a string and returns
2416 the trimmed string.
2417 */
2418QString Generator::trimmedTrailing(const QString &string, const QString &prefix,
2419 const QString &suffix)
2420{
2421 QString trimmed = string;
2422 while (trimmed.size() > 0 && trimmed[trimmed.size() - 1].isSpace())
2423 trimmed.truncate(trimmed.size() - 1);
2424
2425 trimmed.append(suffix);
2426 trimmed.prepend(prefix);
2427 return trimmed;
2428}
2429
2430void Generator::unknownAtom(const Atom *atom)
2431{
2432 Location::internalError(QStringLiteral("unknown atom type '%1' in %2 generator")
2433 .arg(atom->typeString(), format()));
2434}
2435
2436/*!
2437 * Generate the CMake requisite for the node \a cn, i.e. the the find_package and target_link_libraries
2438 * calls to use it.
2439 *
2440 * If only cmakepackage is set it will look like
2441 *
2442 * \badcode
2443 * find_package(Foo REQUIRED)
2444 * target_link_libraries(mytarget PRIVATE Foo:Foo)
2445 * \endcode
2446 *
2447 * If no cmakepackage is set Qt6 is assumed.
2448 *
2449 * If cmakecomponent is set it will look like
2450 *
2451 * \badcode
2452 * find_package(Qt6 REQUIRED COMPONENTS Bar)
2453 * target_link_libraries(mytarget PRIVATE Qt6::Bar)
2454 * \endcode
2455 *
2456 * If cmaketargetitem is set the item in target_link_libraries will be set accordingly
2457 *
2458 * \badcode
2459 * find_package(Qt6 REQUIRED COMPONENTS Bar)
2460 * target_link_libraries(mytarget PRIVATE My::Target)
2461 * \endcode
2462 *
2463 * Returns a pair consisting of the find package line and link libraries line.
2464 *
2465 * If no sensible requisite can be created (i.e. both cmakecomponent and cmakepackage are unset)
2466 * \c std::nullopt is returned.
2467 */
2468std::optional<std::pair<QString, QString>> Generator::cmakeRequisite(const CollectionNode *cn)
2469{
2470 if (!cn || (cn->cmakeComponent().isEmpty() && cn->cmakePackage().isEmpty())) {
2471 return {};
2472 }
2473
2474 const QString package =
2475 cn->cmakePackage().isEmpty() ? "Qt" + QString::number(QT_VERSION_MAJOR) : cn->cmakePackage();
2476
2477 QString findPackageText;
2478 if (cn->cmakeComponent().isEmpty()) {
2479 findPackageText = "find_package(" + package + " REQUIRED)";
2480 } else {
2481 findPackageText = "find_package(" + package + " REQUIRED COMPONENTS " + cn->cmakeComponent() + ")";
2482 }
2483
2484 QString targetText;
2485 if (cn->cmakeTargetItem().isEmpty()) {
2486 if (cn->cmakeComponent().isEmpty()) {
2487 targetText = package + "::" + package;
2488 } else {
2489 targetText = package + "::" + cn->cmakeComponent();
2490 }
2491 } else {
2492 targetText = cn->cmakeTargetItem();
2493 }
2494
2495 const QString targetLinkLibrariesText = "target_link_libraries(mytarget PRIVATE " + targetText + ")";
2496 const QStringList cmakeInfo { findPackageText, targetLinkLibrariesText };
2497
2498 return std::make_pair(findPackageText, targetLinkLibrariesText);
2499}
2500
2501/*!
2502 \brief Adds a formatted link to the specified \a text stream.
2503
2504 This function creates a sequence of Atom objects that together form a link
2505 and appends them to the \a text. The \a nodeRef parameter specifies the
2506 target of the link (typically obtained via stringForNode()), and \a linkText
2507 specifies the visible text for the link.
2508
2509 \sa Atom, stringForNode()
2510*/
2511void Generator::addNodeLink(Text &text, const QString &nodeRef, const QString &linkText) {
2512 text << Atom(Atom::LinkNode, nodeRef)
2514 << Atom(Atom::String, linkText)
2516}
2517
2518/*!
2519 \overload
2520
2521 This convenience overload automatically obtains the node reference string
2522 using stringForNode(). If \a linkText is empty, the node's name is used as
2523 the link text; otherwise, the specified \a linkText is used.
2524
2525 \sa stringForNode()
2526*/
2527void Generator::addNodeLink(Text &text, const INode *node, const QString &linkText) {
2528 addNodeLink(
2529 text,
2530 Utilities::stringForNode(node),
2531 linkText.isEmpty() ? node->name() : linkText
2532 );
2533}
2534
2535/*!
2536 Generates a contextual code snippet for connecting to an overloaded signal or
2537 slot. Returns an empty string if the function is not a signal or slot.
2538
2539 For signals, the snippet shows the signal in the second argument position of
2540 connect(). For slots, the snippet shows the slot in the fourth argument
2541 position (receiver side).
2542*/
2544{
2545 if (!func || (!func->isSignal() && !func->isSlot()))
2546 return QString();
2547
2548 QString className = func->parent()->name();
2549 QString functionName = func->name();
2550 QString typeList = func->parameters().generateTypeList();
2551 QString typeAndNameList = func->parameters().generateTypeAndNameList();
2552 QString nameList = func->parameters().generateNameList();
2553 QString objectName = generateObjectName(className);
2554
2555 QString snippet;
2556
2557 if (func->isSignal()) {
2558 snippet = QString(
2559 "// Connect using qOverload:\n"
2560 "connect(%1, qOverload<%2>(&%3::%4),\n"
2561 " receiver, &ReceiverClass::slot);\n\n"
2562 "// Or using a lambda:\n"
2563 "connect(%1, qOverload<%2>(&%3::%4),\n"
2564 " this, [](%5) { /* handle %4 */ });")
2565 .arg(objectName, typeList, className, functionName, typeAndNameList);
2566 } else {
2567 snippet = QString(
2568 "// Connect using qOverload:\n"
2569 "connect(sender, &SenderClass::signal,\n"
2570 " %1, qOverload<%2>(&%3::%4));\n\n"
2571 "// Or using a lambda as wrapper:\n"
2572 "connect(sender, &SenderClass::signal,\n"
2573 " %1, [receiver = %1](%5) { receiver->%4(%6); });")
2574 .arg(objectName, typeList, className, functionName, typeAndNameList, nameList);
2575 }
2576
2577 return snippet;
2578}
2579
2580/*!
2581 Generates an appropriate object name for code snippets based on the class name.
2582 Converts class names like "QComboBox" to "comboBox".
2583*/
2584QString Generator::generateObjectName(const QString &className)
2585{
2586 QString name = className;
2587
2588 if (name.startsWith('Q') && name.length() > 1)
2589 name.remove(0, 1);
2590
2591 if (!name.isEmpty())
2592 name[0] = name[0].toLower();
2593
2594 return name;
2595}
2596
2597QT_END_NAMESPACE
#define ATOM_FORMATTING_TELETYPE
Definition atom.h:204
#define ATOM_FORMATTING_BOLD
Definition atom.h:195
#define ATOM_FORMATTING_TRADEMARK
Definition atom.h:205
#define ATOM_LIST_VALUE
Definition atom.h:211
#define ATOM_FORMATTING_ITALIC
Definition atom.h:197
#define ATOM_FORMATTING_LINK
Definition atom.h:198
#define ATOM_FORMATTING_PARAMETER
Definition atom.h:200
The Atom class is the fundamental unit for representing documents internally.
Definition atom.h:19
AtomType type() const
Return the type of this atom.
Definition atom.h:144
AtomType
\value AnnotatedList \value AutoLink \value BaseName \value BriefLeft \value BriefRight \value C \val...
Definition atom.h:21
@ TableRight
Definition atom.h:97
@ DivRight
Definition atom.h:42
@ TableHeaderRight
Definition atom.h:99
@ FormatElse
Definition atom.h:47
@ TableRowRight
Definition atom.h:101
@ TableRowLeft
Definition atom.h:100
@ TableItemRight
Definition atom.h:103
@ Code
Definition atom.h:31
@ String
Definition atom.h:95
@ ListLeft
Definition atom.h:65
@ ExampleFileLink
Definition atom.h:43
@ ListRight
Definition atom.h:71
@ ParaRight
Definition atom.h:78
@ FormattingLeft
Definition atom.h:50
@ FormattingRight
Definition atom.h:51
@ Link
Definition atom.h:63
@ FormatEndif
Definition atom.h:48
@ ExampleImageLink
Definition atom.h:44
@ AutoLink
Definition atom.h:23
@ LinkNode
Definition atom.h:64
@ TableItemLeft
Definition atom.h:102
@ ParaLeft
Definition atom.h:77
@ FormatIf
Definition atom.h:49
const Atom * next() const
Return the next atom in the atom list.
Definition atom.h:141
The ClassNode represents a C++ class.
Definition classnode.h:23
A class for holding the members of a collection of doc pages.
const Location & location() const
Definition config.h:55
bool asBool() const
Returns this config variable as a boolean.
Definition config.cpp:284
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:95
@ Validate
Definition config.h:114
bool preparing() const
Definition config.h:199
bool generating() const
Definition config.h:200
const Location & location() const
Returns the starting location of a qdoc comment.
Definition doc.cpp:89
const Text & body() const
Definition doc.cpp:114
QStringMultiMap * metaTagMap() const
Definition doc.cpp:342
Encapsulate the logic that QDoc uses to find files whose path is provided by the user and that are re...
This node is used to represent any kind of function being documented.
bool isPrivateSignal() const
QString kindString() const
Returns a string representing the kind of function this Function node represents, which depends on th...
const Parameters & parameters() const
bool isMAssign() const
bool isVirtual() const
bool isCAssign() const
const QString & overridesThis() const
bool isInvokable() const
bool isDeprecated() const override
\reimp
bool hasOverloads() const
Returns true if this function has overloads.
bool returnsBool() const
bool isMarkedReimp() const override
Returns true if the FunctionNode is marked as a reimplemented function.
bool isDtor() const
bool isSignal() const
bool isQmlSignal() const
bool isOverload() const
bool isIgnored() const
In some cases, it is ok for a public function to be not documented.
bool isCCtor() const
bool isMCtor() const
bool isCtor() const
bool hasAssociatedProperties() const
bool isSlot() const
bool m_quoting
Definition generator.h:232
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:224
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:226
bool m_inContents
Definition generator.h:228
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:227
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:230
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:229
void generateEnumValuesForQmlReference(const Node *node, CodeMarker *marker)
virtual int skipAtoms(const Atom *atom, Atom::AtomType type) const
int m_numTableRows
Definition generator.h:233
bool m_threeColumnEnumValueTable
Definition generator.h:231
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:223
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:50
Interface implemented by Node subclasses that can refer to a C++ enum.
Definition nativeenum.h:28
virtual const NativeEnum * nativeEnum() const =0
Encapsulates information about native (C++) enum values.
Definition nativeenum.h:14
const EnumNode * enumNode() const
Definition nativeenum.h:19
QString styleString() const
OpenedList(ListStyle style)
Represents an output directory that has been verified to exist.
const QString & path() const noexcept
Singleton registry for discovering output producers by format.
OutputProducer * producerForFormat(const QString &format) const
Returns the producer registered for format, or nullptr if none.
static OutputProducerRegistry & instance()
Returns the singleton registry instance.
A PageNode is a Node that generates a documentation page.
Definition pagenode.h:19
bool isAttribution() const
Definition pagenode.h:51
This class describes one instance of using the Q_PROPERTY macro.
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
static QDocDatabase * qdocDB()
Creates the singleton.
NamespaceNode * primaryTreeRoot()
Returns a pointer to the root node of the primary tree.
const CollectionNode * getModuleNode(const Node *relative)
Returns the collection node representing the module that relative node belongs to,...
Status
Specifies the status of the QQmlIncubator.
Definition text.h:12
const Atom * firstAtom() const
Definition text.h:33
bool isEmpty() const
Definition text.h:30
void clear()
Definition text.cpp:250
#define SKIP_CHAR()
#define CONFIG_REDIRECTDOCUMENTATIONTODEVNULL
Definition config.h:440
#define CONFIG_AUTOLINKERRORS
Definition config.h:379
#define CONFIG_EXTRAIMAGES
Definition config.h:398
#define CONFIG_EXAMPLES
Definition config.h:394
#define CONFIG_URL
Definition config.h:460
#define CONFIG_OUTPUTSUFFIXES
Definition config.h:434
#define CONFIG_OUTPUTPREFIXES
Definition config.h:433
#define CONFIG_PRELIMINARY
Definition config.h:436
#define CONFIG_NOLINKERRORS
Definition config.h:430
#define CONFIG_DESCRIPTION
Definition config.h:389
#define CONFIG_PROJECT
Definition config.h:438
#define CONFIG_EXAMPLESINSTALLPATH
Definition config.h:395
#define CONFIG_PRODUCTNAME
Definition config.h:437
#define CONFIG_QUOTINGINFORMATION
Definition config.h:443
#define CONFIG_STYLESHEETS
Definition config.h:453
#define CONFIG_IMAGESOUTPUTDIR
Definition config.h:412
#define CONFIG_FORMATTING
Definition config.h:400
#define CONFIG_SCRIPTS
Definition config.h:445
QMultiMap< QString, QString > QStringMultiMap
Definition doc.h:29
NodeType
Definition genustypes.h:165
@ SharedComment
Definition genustypes.h:188
Metaness
Specifies the kind of function a FunctionNode represents.
Definition genustypes.h:242
@ QmlSignalHandler
Definition genustypes.h:256
Provides human-readable descriptions of QDoc nodes.
QString nodeTypeDescription(const Node *node, GrammaticalNumber number=GrammaticalNumber::Singular)
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:87
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:239
bool isQmlNode() const
Returns true if this node's Genus value is QML.
Definition node.h:121
virtual bool docMustBeGenerated() const
This function is called to perform a test to decide if the node must have documentation generated.
Definition node.h:198
virtual bool isWrapper() const
Returns true if the node is a class node or a QML type node that is marked as being a wrapper class o...
Definition node.cpp:989
bool isPrivate() const
Returns true if this node's access is Private.
Definition node.h:113
bool isActive() const
Returns true if this node's status is Active.
Definition node.h:89
bool isNamespace() const
Returns true if the node type is Namespace.
Definition node.h:110
ComparisonCategory comparisonCategory() const
Definition node.h:187
bool hasFileNameBase() const
Returns true if the node's file name base has been set.
Definition node.h:169
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
Definition node.h:123
bool isSharedCommentNode() const
Returns true if the node type is SharedComment.
Definition node.h:126
bool isHeader() const
Returns true if the node type is HeaderFile.
Definition node.h:106
NodeType nodeType() const override
Returns this node's type.
Definition node.h:82
Genus genus() const override
Returns this node's Genus.
Definition node.h:85
virtual bool isPageNode() const
Returns true if this node represents something that generates a documentation page.
Definition node.h:150
virtual bool isMacro() const
returns true if either FunctionNode::isMacroWithParams() or FunctionNode::isMacroWithoutParams() retu...
Definition node.h:149
bool isEnumType() const
Returns true if the node type is Enum.
Definition node.h:94
virtual Status status() const
Returns the node's status value.
Definition node.h:243
virtual bool isTextPageNode() const
Returns true if the node is a PageNode but not an Aggregate.
Definition node.h:155
virtual bool isAttached() const
Returns true if the QML property or QML method node is marked as attached.
Definition node.h:144
Aggregate * parent() const
Returns the node's parent pointer, that is, the aggregate under which the node is documented.
Definition node.h:211
virtual bool isDeprecated() const
Returns true if this node's status is Deprecated.
Definition node.h:136
virtual bool isAggregate() const
Returns true if this node is an aggregate, which means it inherits Aggregate and can therefore have c...
Definition node.h:138
static bool nodeNameLessThan(const Node *first, const Node *second)
Returns true if the node n1 is less than node n2.
Definition node.cpp:111
const Location & location() const
If this node's definition location is empty, this function returns this node's declaration location.
Definition node.h:235
bool isProxyNode() const
Returns true if the node type is Proxy.
Definition node.h:115
const std::optional< RelaxedTemplateDeclaration > & templateDecl() const
Definition node.h:247
Access access() const
Returns the node's Access setting, which can be Public, Protected, or Private.
Definition node.h:232
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
Definition node.h:101
ThreadSafeness threadSafeness() const
Returns the thread safeness value for whatever this node represents.
Definition node.cpp:845
virtual bool isMarkedReimp() const
Returns true if the FunctionNode is marked as a reimplemented function.
Definition node.h:152
bool isProperty() const
Returns true if the node type is Property.
Definition node.h:114
NodeContext createContext() const
Definition node.cpp:175
bool isModule() const
Returns true if the node type is Module.
Definition node.h:108
virtual bool isPropertyGroup() const
Returns true if the node is a SharedCommentNode for documenting multiple C++ properties or multiple Q...
Definition node.h:153
ThreadSafeness
An unsigned char that specifies the degree of thread-safeness of the element.
Definition node.h:58
@ ThreadSafe
Definition node.h:62
@ NonReentrant
Definition node.h:60
@ 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:250
bool hasDoc() const
Returns true if this node is documented, or it represents a documented node read from the index ('had...
Definition node.cpp:942
bool isPreliminary() const
Returns true if this node's status is Preliminary.
Definition node.h:112
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
Definition node.h:145
virtual bool isCollectionNode() const
Returns true if this is an instance of CollectionNode.
Definition node.h:146
bool isQmlModule() const
Returns true if the node type is QmlModule.
Definition node.h:120
@ SignaturePlain
Definition node.h:66
bool isExample() const
Returns true if the node type is Example.
Definition node.h:99
bool isIndexNode() const
Returns true if this node was created from something in an index file.
Definition node.h:107
bool isQmlProperty() const
Returns true if the node type is QmlProperty.
Definition node.h:122
Represents a file that is reachable by QDoc based on its current configuration.