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