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