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