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 "collectionnode.h"
12#include "config.h"
13#include "doc.h"
14#include "editdistance.h"
15#include "enumnode.h"
16#include "examplenode.h"
17#include "functionnode.h"
18#include "node.h"
19#include "openedlist.h"
20#include "propertynode.h"
21#include "qdocdatabase.h"
22#include "qmltypenode.h"
24#include "quoter.h"
26#include "tokenizer.h"
27#include "typedefnode.h"
28#include "utilities.h"
29
30#include <QtCore/qdebug.h>
31#include <QtCore/qdir.h>
32#include <QtCore/qregularexpression.h>
33
34#ifndef QT_BOOTSTRAPPED
35# include "QtCore/qurl.h"
36#endif
37
38#include <string>
39
40using namespace std::literals::string_literals;
41
42QT_BEGIN_NAMESPACE
43
44using namespace Qt::StringLiterals;
45
46Generator *Generator::s_currentGenerator;
47QMap<QString, QMap<QString, QString>> Generator::s_fmtLeftMaps;
48QMap<QString, QMap<QString, QString>> Generator::s_fmtRightMaps;
49QList<Generator *> Generator::s_generators;
50QString Generator::s_outDir;
51QString Generator::s_outSubdir;
52QStringList Generator::s_outFileNames;
53QSet<QString> Generator::s_trademarks;
54QSet<QString> Generator::s_outputFormats;
55QHash<QString, QString> Generator::s_outputPrefixes;
56QHash<QString, QString> Generator::s_outputSuffixes;
57QString Generator::s_project;
58bool Generator::s_noLinkErrors = false;
59bool Generator::s_autolinkErrors = false;
60bool Generator::s_redirectDocumentationToDevNull = false;
61bool Generator::s_useOutputSubdirs = true;
62QmlTypeNode *Generator::s_qmlTypeContext = nullptr;
63
64static QRegularExpression tag("</?@[^>]*>");
65static QLatin1String amp("&amp;");
66static QLatin1String gt("&gt;");
67static QLatin1String lt("&lt;");
68static QLatin1String quot("&quot;");
69
70/*!
71 Constructs the generator base class. Prepends the newly
72 constructed generator to the list of output generators.
73 Sets a pointer to the QDoc database singleton, which is
74 available to the generator subclasses.
75 */
77 : file_resolver{file_resolver}
78{
80 s_generators.prepend(this);
81}
82
83/*!
84 Destroys the generator after removing it from the list of
85 output generators.
86 */
88{
89 s_generators.removeAll(this);
90}
91
92void Generator::appendFullName(Text &text, const Node *apparentNode, const Node *relative,
93 const Node *actualNode)
94{
95 if (actualNode == nullptr)
96 actualNode = apparentNode;
97 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
99 << Atom(Atom::String, apparentNode->plainFullName(relative))
101}
102
103void Generator::appendFullName(Text &text, const Node *apparentNode, const QString &fullName,
104 const Node *actualNode)
105{
106 if (actualNode == nullptr)
107 actualNode = apparentNode;
108 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
111}
112
113/*!
114 Append the signature for the function named in \a node to
115 \a text, so that is a link to the documentation for that
116 function.
117 */
118void Generator::appendSignature(Text &text, const Node *node)
119{
120 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(node))
122 << Atom(Atom::String, node->signature(Node::SignaturePlain))
124}
125
126/*!
127 Generate a bullet list of function signatures. The function
128 nodes are in \a nodes. It uses the \a relative node and the
129 \a marker for the generation.
130 */
131void Generator::signatureList(const NodeList &nodes, const Node *relative, CodeMarker *marker)
132{
133 Text text;
134 int count = 0;
135 text << Atom(Atom::ListLeft, QString("bullet"));
136 for (const auto &node : nodes) {
137 text << Atom(Atom::ListItemNumber, QString::number(++count));
138 text << Atom(Atom::ListItemLeft, QString("bullet"));
139 appendSignature(text, node);
140 text << Atom(Atom::ListItemRight, QString("bullet"));
141 }
142 text << Atom(Atom::ListRight, QString("bullet"));
143 generateText(text, relative, marker);
144}
145
146int Generator::appendSortedNames(Text &text, const ClassNode *cn, const QList<RelatedClass> &rc)
147{
148 QMap<QString, Text> classMap;
149 for (const auto &relatedClass : rc) {
150 ClassNode *rcn = relatedClass.m_node;
151 if (rcn && rcn->isInAPI()) {
152 Text className;
153 appendFullName(className, rcn, cn);
154 classMap[className.toString().toLower()] = className;
155 }
156 }
157
158 int index = 0;
159 const QStringList classNames = classMap.keys();
160 for (const auto &className : classNames) {
161 text << classMap[className];
162 text << Utilities::comma(index++, classNames.size());
163 }
164 return index;
165}
166
167int Generator::appendSortedQmlNames(Text &text, const Node *base, const NodeList &subs)
168{
169 QMap<QString, Text> classMap;
170
171 for (const auto sub : subs) {
172 Text full_name;
173 appendFullName(full_name, sub, base);
174 classMap[full_name.toString().toLower()] = full_name;
175 }
176
177 int index = 0;
178 const auto &names = classMap.keys();
179 for (const auto &name : names)
180 text << classMap[name] << Utilities::comma(index++, names.size());
181 return index;
182}
183
184/*!
185 Creates the file named \a fileName in the output directory
186 and returns a QFile pointing to this file. In particular,
187 this method deals with errors when opening the file:
188 the returned QFile is always valid and can be written to.
189
190 \sa beginSubPage()
191 */
192QFile *Generator::openSubPageFile(const PageNode *node, const QString &fileName)
193{
194 // Skip generating a warning for license attribution pages, as their source
195 // is generated by qtattributionsscanner and may potentially include duplicates
196 if (s_outFileNames.contains(fileName) && !node->isAttribution())
197 node->location().warning("Already generated %1 for this project"_L1.arg(fileName));
198
199 QString path = outputDir() + QLatin1Char('/') + fileName;
200
201 auto outPath = s_redirectDocumentationToDevNull ? QStringLiteral("/dev/null") : path;
202 auto outFile = new QFile(outPath);
203
204 if (!s_redirectDocumentationToDevNull && outFile->exists()) {
205 const QString warningText {"Output file already exists, overwriting %1"_L1.arg(outFile->fileName())};
206 if (qEnvironmentVariableIsSet("QDOC_ALL_OVERWRITES_ARE_WARNINGS"))
207 node->location().warning(warningText);
208 else
209 qCDebug(lcQdoc) << qUtf8Printable(warningText);
210 }
211
212 if (!outFile->open(QFile::WriteOnly | QFile::Text)) {
213 node->location().fatal(
214 QStringLiteral("Cannot open output file '%1'").arg(outFile->fileName()));
215 }
216
217 qCDebug(lcQdoc, "Writing: %s", qPrintable(path));
218 s_outFileNames << fileName;
219 s_trademarks.clear();
220 return outFile;
221}
222
223/*!
224 Creates the file named \a fileName in the output directory.
225 Attaches a QTextStream to the created file, which is written
226 to all over the place using out().
227 */
228void Generator::beginSubPage(const Node *node, const QString &fileName)
229{
230 Q_ASSERT(node->isPageNode());
231 QFile *outFile = openSubPageFile(static_cast<const PageNode*>(node), fileName);
232 auto *out = new QTextStream(outFile);
233 outStreamStack.push(out);
234}
235
236/*!
237 Flush the text stream associated with the subpage, and
238 then pop it off the text stream stack and delete it.
239 This terminates output of the subpage.
240 */
242{
243 outStreamStack.top()->flush();
244 delete outStreamStack.top()->device();
245 delete outStreamStack.pop();
246}
247
248QString Generator::fileBase(const Node *node) const
249{
250 if (!node->isPageNode() && !node->isCollectionNode())
251 node = node->parent();
252
253 if (node->hasFileNameBase())
254 return node->fileNameBase();
255
256 QString base{node->name()};
257 if (base.endsWith(".html"))
258 base.truncate(base.size() - 5);
259
260 if (node->isCollectionNode()) {
261 if (node->isQmlModule())
262 base.append("-qmlmodule");
263 else if (node->isModule())
264 base.append("-module");
265 base.append(outputSuffix(node));
266 } else if (node->isTextPageNode()) {
267 if (node->isExample()) {
268 base.prepend("%1-"_L1.arg(s_project.toLower()));
269 base.append("-example");
270 }
271 } else if (node->isQmlType()) {
272 /*
273 To avoid file name conflicts in the html directory,
274 we prepend a prefix (by default, "qml-") and an optional suffix
275 to the file name. The suffix, if one exists, is appended to the
276 module name.
277
278 For historical reasons, skip the module name qualifier for QML value types
279 in order to avoid excess redirects in the online docs. TODO: re-assess
280 */
281 if (!node->logicalModuleName().isEmpty() && !node->isQmlBasicType()
282 && (!node->logicalModule()->isInternal() || m_showInternal))
283 base.prepend("%1%2-"_L1.arg(node->logicalModuleName(), outputSuffix(node)));
284
285 } else if (node->isProxyNode()) {
286 base.append("-%1-proxy"_L1.arg(node->tree()->physicalModuleName()));
287 } else {
288 base.clear();
289 const Node *p = node;
290 forever {
291 const Node *pp = p->parent();
292 base.prepend(p->name());
293 if (pp == nullptr || pp->name().isEmpty() || pp->isTextPageNode())
294 break;
295 base.prepend('-'_L1);
296 p = pp;
297 }
298 if (node->isNamespace() && !node->name().isEmpty()) {
299 const auto *ns = static_cast<const NamespaceNode *>(node);
300 if (!ns->isDocumentedHere()) {
301 base.append(QLatin1String("-sub-"));
302 base.append(ns->tree()->camelCaseModuleName());
303 }
304 }
305 base.append(outputSuffix(node));
306 }
307
308 base.prepend(outputPrefix(node));
309 QString canonicalName{ Utilities::asAsciiPrintable(base) };
310 Node *n = const_cast<Node *>(node);
311 n->setFileNameBase(canonicalName);
312 return canonicalName;
313}
314
315/*!
316 Constructs an href link from an example file name, which
317 is a \a path to the example file. If \a fileExt is empty
318 (default value), retrieve the file extension from
319 the generator.
320 */
321QString Generator::linkForExampleFile(const QString &path, const QString &fileExt)
322{
323 QString link{path};
324 link.prepend(s_project.toLower() + QLatin1Char('-'));
325
326 QString canonicalName{ Utilities::asAsciiPrintable(link) };
327 canonicalName.append(QLatin1Char('.'));
328 canonicalName.append(fileExt.isEmpty() ? fileExtension() : fileExt);
329 return canonicalName;
330}
331
332/*!
333 Helper function to construct a title for a file or image page
334 included in an example.
335*/
336QString Generator::exampleFileTitle(const ExampleNode *relative, const QString &fileName)
337{
338 QString suffix;
339 if (relative->files().contains(fileName))
340 suffix = QLatin1String(" Example File");
341 else if (relative->images().contains(fileName))
342 suffix = QLatin1String(" Image File");
343 else
344 return suffix;
345
346 return fileName.mid(fileName.lastIndexOf(QLatin1Char('/')) + 1) + suffix;
347}
348
349/*!
350 If the \a node has a URL, return the URL as the file name.
351 Otherwise, construct the file name from the fileBase() and
352 either the provided \a extension or fileExtension(), and
353 return the constructed name.
354 */
355QString Generator::fileName(const Node *node, const QString &extension) const
356{
357 if (!node->url().isEmpty())
358 return node->url();
359
360 QString name = fileBase(node) + QLatin1Char('.');
361 return name + (extension.isNull() ? fileExtension() : extension);
362}
363
364/*!
365 Clean the given \a ref to be used as an HTML anchor or an \c xml:id.
366 If \a xmlCompliant is set to \c true, a stricter process is used, as XML
367 is more rigorous in what it accepts. Otherwise, if \a xmlCompliant is set to
368 \c false, the basic HTML transformations are applied.
369
370 More specifically, only XML NCNames are allowed
371 (https://www.w3.org/TR/REC-xml-names/#NT-NCName).
372 */
373QString Generator::cleanRef(const QString &ref, bool xmlCompliant)
374{
375 // XML-compliance is ensured in two ways:
376 // - no digit (0-9) at the beginning of an ID (many IDs do not respect this property)
377 // - no colon (:) anywhere in the ID (occurs very rarely)
378
379 QString clean;
380
381 if (ref.isEmpty())
382 return clean;
383
384 clean.reserve(ref.size() + 20);
385 const QChar c = ref[0];
386 const uint u = c.unicode();
387
388 if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (!xmlCompliant && u >= '0' && u <= '9')) {
389 clean += c;
390 } else if (xmlCompliant && u >= '0' && u <= '9') {
391 clean += QLatin1Char('A') + c;
392 } else if (u == '~') {
393 clean += "dtor.";
394 } else if (u == '_') {
395 clean += "underscore.";
396 } else {
397 clean += QLatin1Char('A');
398 }
399
400 for (int i = 1; i < ref.size(); i++) {
401 const QChar c = ref[i];
402 const uint u = c.unicode();
403 if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9') || u == '-'
404 || u == '_' || (xmlCompliant && u == ':') || u == '.') {
405 clean += c;
406 } else if (c.isSpace()) {
407 clean += QLatin1Char('-');
408 } else if (u == '!') {
409 clean += "-not";
410 } else if (u == '&') {
411 clean += "-and";
412 } else if (u == '<') {
413 clean += "-lt";
414 } else if (u == '=') {
415 clean += "-eq";
416 } else if (u == '>') {
417 clean += "-gt";
418 } else if (u == '#') {
419 clean += QLatin1Char('#');
420 } else {
421 clean += QLatin1Char('-');
422 clean += QString::number(static_cast<int>(u), 16);
423 }
424 }
425 return clean;
426}
427
429{
430 return s_fmtLeftMaps[format()];
431}
432
434{
435 return s_fmtRightMaps[format()];
436}
437
438/*!
439 Returns the full document location.
440 */
442{
443 if (node == nullptr)
444 return QString();
445 if (!node->url().isEmpty())
446 return node->url();
447
448 QString parentName;
449 QString anchorRef;
450
451 if (node->isNamespace()) {
452 /*
453 The root namespace has no name - check for this before creating
454 an attribute containing the location of any documentation.
455 */
456 if (!fileBase(node).isEmpty())
457 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
458 else
459 return QString();
460 } else if (node->isQmlType()) {
461 return fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
462 } else if (node->isTextPageNode() || node->isCollectionNode()) {
463 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
464 } else if (fileBase(node).isEmpty())
465 return QString();
466
467 Node *parentNode = nullptr;
468
469 if ((parentNode = node->parent())) {
470 // use the parent's name unless the parent is the root namespace
471 if (!node->parent()->isNamespace() || !node->parent()->name().isEmpty())
472 parentName = fullDocumentLocation(node->parent());
473 }
474
475 switch (node->nodeType()) {
476 case Node::Class:
477 case Node::Struct:
478 case Node::Union:
479 case Node::Namespace:
480 case Node::Proxy:
481 parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
482 break;
483 case Node::Function: {
484 const auto *fn = static_cast<const FunctionNode *>(node);
485 switch (fn->metaness()) {
487 anchorRef = QLatin1Char('#') + node->name() + "-signal";
488 break;
490 anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
491 break;
493 anchorRef = QLatin1Char('#') + node->name() + "-method";
494 break;
495 default:
496 if (fn->isDtor())
497 anchorRef = "#dtor." + fn->name().mid(1);
498 else if (const auto *p = fn->primaryAssociatedProperty(); p && fn->doc().isEmpty())
499 return fullDocumentLocation(p);
500 else if (fn->overloadNumber() > 0)
501 anchorRef = QLatin1Char('#') + cleanRef(fn->name()) + QLatin1Char('-')
502 + QString::number(fn->overloadNumber());
503 else
504 anchorRef = QLatin1Char('#') + cleanRef(fn->name());
505 break;
506 }
507 break;
508 }
509 /*
510 Use node->name() instead of fileBase(node) as
511 the latter returns the name in lower-case. For
512 HTML anchors, we need to preserve the case.
513 */
514 case Node::Enum:
515 anchorRef = QLatin1Char('#') + node->name() + "-enum";
516 break;
517 case Node::Typedef: {
518 const auto *tdef = static_cast<const TypedefNode *>(node);
519 if (tdef->associatedEnum())
520 return fullDocumentLocation(tdef->associatedEnum());
521 } Q_FALLTHROUGH();
522 case Node::TypeAlias:
523 anchorRef = QLatin1Char('#') + node->name() + "-typedef";
524 break;
525 case Node::Property:
526 anchorRef = QLatin1Char('#') + node->name() + "-prop";
527 break;
528 case Node::SharedComment: {
529 if (!node->isPropertyGroup())
530 break;
531 } Q_FALLTHROUGH();
533 if (node->isAttached())
534 anchorRef = QLatin1Char('#') + node->name() + "-attached-prop";
535 else
536 anchorRef = QLatin1Char('#') + node->name() + "-prop";
537 break;
538 case Node::Variable:
539 anchorRef = QLatin1Char('#') + node->name() + "-var";
540 break;
541 case Node::QmlType:
542 case Node::Page:
543 case Node::Group:
544 case Node::HeaderFile:
545 case Node::Module:
546 case Node::QmlModule: {
547 parentName = fileBase(node);
548 parentName.replace(QLatin1Char('/'), QLatin1Char('-'))
549 .replace(QLatin1Char('.'), QLatin1Char('-'));
550 parentName += QLatin1Char('.') + currentGenerator()->fileExtension();
551 } break;
552 default:
553 break;
554 }
555
556 if (!node->isClassNode() && !node->isNamespace()) {
557 if (node->isDeprecated())
558 parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
559 "-obsolete." + currentGenerator()->fileExtension());
560 }
561
562 return parentName.toLower() + anchorRef;
563}
564
565void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
566{
567 QList<Text> alsoList = node->doc().alsoList();
568 supplementAlsoList(node, alsoList);
569
570 if (!alsoList.isEmpty()) {
571 Text text;
572 text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << "See also "
574
575 for (int i = 0; i < alsoList.size(); ++i)
576 text << alsoList.at(i) << Utilities::separator(i, alsoList.size());
577
578 text << Atom::ParaRight;
579 generateText(text, node, marker);
580 }
581}
582
583const Atom *Generator::generateAtomList(const Atom *atom, const Node *relative, CodeMarker *marker,
584 bool generate, int &numAtoms)
585{
586 while (atom != nullptr) {
587 if (atom->type() == Atom::FormatIf) {
588 int numAtoms0 = numAtoms;
589 bool rightFormat = canHandleFormat(atom->string());
590 atom = generateAtomList(atom->next(), relative, marker, generate && rightFormat,
591 numAtoms);
592 if (atom == nullptr)
593 return nullptr;
594
595 if (atom->type() == Atom::FormatElse) {
596 ++numAtoms;
597 atom = generateAtomList(atom->next(), relative, marker, generate && !rightFormat,
598 numAtoms);
599 if (atom == nullptr)
600 return nullptr;
601 }
602
603 if (atom->type() == Atom::FormatEndif) {
604 if (generate && numAtoms0 == numAtoms) {
605 relative->location().warning(QStringLiteral("Output format %1 not handled %2")
606 .arg(format(), outFileName()));
607 Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
608 generateAtomList(&unhandledFormatAtom, relative, marker, generate, numAtoms);
609 }
610 atom = atom->next();
611 }
612 } else if (atom->type() == Atom::FormatElse || atom->type() == Atom::FormatEndif) {
613 return atom;
614 } else {
615 int n = 1;
616 if (generate) {
617 n += generateAtom(atom, relative, marker);
618 numAtoms += n;
619 }
620 while (n-- > 0)
621 atom = atom->next();
622 }
623 }
624 return nullptr;
625}
626
627/*!
628 Generate the body of the documentation from the qdoc comment
629 found with the entity represented by the \a node.
630 */
631void Generator::generateBody(const Node *node, CodeMarker *marker)
632{
633 const FunctionNode *fn = node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
634 if (!node->hasDoc()) {
635 /*
636 Test for special function, like a destructor or copy constructor,
637 that has no documentation.
638 */
639 if (fn) {
640 if (fn->isDtor()) {
641 Text text;
642 text << "Destroys the instance of ";
643 text << fn->parent()->name() << ".";
644 if (fn->isVirtual())
645 text << " The destructor is virtual.";
646 out() << "<p>";
647 generateText(text, node, marker);
648 out() << "</p>";
649 } else if (fn->isCtor()) {
650 Text text;
651 text << "Default constructs an instance of ";
652 text << fn->parent()->name() << ".";
653 out() << "<p>";
654 generateText(text, node, marker);
655 out() << "</p>";
656 } else if (fn->isCCtor()) {
657 Text text;
658 text << "Copy constructor.";
659 out() << "<p>";
660 generateText(text, node, marker);
661 out() << "</p>";
662 } else if (fn->isMCtor()) {
663 Text text;
664 text << "Move-copy constructor.";
665 out() << "<p>";
666 generateText(text, node, marker);
667 out() << "</p>";
668 } else if (fn->isCAssign()) {
669 Text text;
670 text << "Copy-assignment operator.";
671 out() << "<p>";
672 generateText(text, node, marker);
673 out() << "</p>";
674 } else if (fn->isMAssign()) {
675 Text text;
676 text << "Move-assignment operator.";
677 out() << "<p>";
678 generateText(text, node, marker);
679 out() << "</p>";
680 } else if (!node->isWrapper() && !node->isMarkedReimp()) {
681 if (!fn->isIgnored()) // undocumented functions added by Q_OBJECT
682 node->location().warning(QStringLiteral("No documentation for '%1'")
683 .arg(node->plainSignature()));
684 }
685 } else if (!node->isWrapper() && !node->isMarkedReimp()) {
686 // Don't require documentation of things defined in Q_GADGET
687 if (node->name() != QLatin1String("QtGadgetHelper"))
688 node->location().warning(
689 QStringLiteral("No documentation for '%1'").arg(node->plainSignature()));
690 }
691 } else if (!node->isSharingComment()) {
692 // Reimplements clause and type alias info precede body text
693 if (fn && !fn->overridesThis().isEmpty())
694 generateReimplementsClause(fn, marker);
695 else if (node->isProperty()) {
698 }
699
700 if (!generateText(node->doc().body(), node, marker)) {
701 if (node->isMarkedReimp())
702 return;
703 }
704
705 if (fn) {
706 if (fn->isQmlSignal())
710 if (fn->isInvokable())
714 }
715
716 // Generate warnings
717 if (node->isEnumType()) {
718 const auto *enume = static_cast<const EnumNode *>(node);
719
720 QSet<QString> definedItems;
721 const QList<EnumItem> &items = enume->items();
722 for (const auto &item : items)
723 definedItems.insert(item.name());
724
725 const auto &documentedItemList = enume->doc().enumItemNames();
726 QSet<QString> documentedItems(documentedItemList.cbegin(), documentedItemList.cend());
727 const QSet<QString> allItems = definedItems + documentedItems;
728 if (allItems.size() > definedItems.size()
729 || allItems.size() > documentedItems.size()) {
730 for (const auto &it : allItems) {
731 if (!definedItems.contains(it)) {
732 QString details;
733 QString best = nearestName(it, definedItems);
734 if (!best.isEmpty() && !documentedItems.contains(best))
735 details = QStringLiteral("Maybe you meant '%1'?").arg(best);
736
737 node->doc().location().warning(
738 QStringLiteral("No such enum item '%1' in %2")
739 .arg(it, node->plainFullName()),
740 details);
741 } else if (!documentedItems.contains(it)) {
742 node->doc().location().warning(
743 QStringLiteral("Undocumented enum item '%1' in %2")
744 .arg(it, node->plainFullName()));
745 }
746 }
747 }
748 } else if (fn) {
749 const QSet<QString> declaredNames = fn->parameters().getNames();
750 const QSet<QString> documentedNames = fn->doc().parameterNames();
751 if (declaredNames != documentedNames) {
752 for (const auto &name : declaredNames) {
753 if (!documentedNames.contains(name)) {
754 if (fn->isActive() || fn->isPreliminary()) {
755 // Require no parameter documentation for overrides and overloads,
756 // and only require it for non-overloaded constructors.
757 if (!fn->isMarkedReimp() && !fn->isOverload() &&
758 !(fn->isSomeCtor() && fn->hasOverloads())) {
759 fn->doc().location().warning(
760 QStringLiteral("Undocumented parameter '%1' in %2")
761 .arg(name, node->plainFullName()));
762 }
763 }
764 }
765 }
766 for (const auto &name : documentedNames) {
767 if (!declaredNames.contains(name)) {
768 QString best = nearestName(name, declaredNames);
769 QString details;
770 if (!best.isEmpty())
771 details = QStringLiteral("Maybe you meant '%1'?").arg(best);
772 fn->doc().location().warning(QStringLiteral("No such parameter '%1' in %2")
773 .arg(name, fn->plainFullName()),
774 details);
775 }
776 }
777 }
778 /*
779 This return value check should be implemented
780 for all functions with a return type.
781 mws 13/12/2018
782 */
784 && !fn->isOverload()) {
785 if (!fn->doc().body().contains("return"))
786 node->doc().location().warning(
787 QStringLiteral("Undocumented return value "
788 "(hint: use 'return' or 'returns' in the text"));
789 }
790 }
791 }
793 generateRequiredLinks(node, marker);
794}
795
796/*!
797 Generates either a link to the project folder for example \a node, or a list
798 of links files/images if 'url.examples config' variable is not defined.
799
800 Does nothing for non-example nodes.
801*/
803{
804 if (!node->isExample())
805 return;
806
807 const auto *en = static_cast<const ExampleNode *>(node);
808 QString exampleUrl{Config::instance().get(CONFIG_URL + Config::dot + CONFIG_EXAMPLES).asString()};
809
810 if (exampleUrl.isEmpty()) {
811 if (!en->noAutoList()) {
812 generateFileList(en, marker, false); // files
813 generateFileList(en, marker, true); // images
814 }
815 } else {
816 generateLinkToExample(en, marker, exampleUrl);
817 }
818}
819
820/*!
821 Generates an external link to the project folder for example \a node.
822 The path to the example replaces a placeholder '\1' character if
823 one is found in the \a baseUrl string. If no such placeholder is found,
824 the path is appended to \a baseUrl, after a '/' character if \a baseUrl did
825 not already end in one.
826*/
827void Generator::generateLinkToExample(const ExampleNode *en, CodeMarker *marker,
828 const QString &baseUrl)
829{
830 QString exampleUrl(baseUrl);
831 QString link;
832#ifndef QT_BOOTSTRAPPED
833 link = QUrl(exampleUrl).host();
834#endif
835 if (!link.isEmpty())
836 link.prepend(" @ ");
837 link.prepend("Example project");
838
839 const QLatin1Char separator('/');
840 const QLatin1Char placeholder('\1');
841 if (!exampleUrl.contains(placeholder)) {
842 if (!exampleUrl.endsWith(separator))
843 exampleUrl += separator;
844 exampleUrl += placeholder;
845 }
846
847 // Construct a path to the example; <install path>/<example name>
848 QString pathRoot;
849 QStringMultiMap *metaTagMap = en->doc().metaTagMap();
850 if (metaTagMap)
851 pathRoot = metaTagMap->value(QLatin1String("installpath"));
852 if (pathRoot.isEmpty())
853 pathRoot = Config::instance().get(CONFIG_EXAMPLESINSTALLPATH).asString();
854 QStringList path = QStringList() << pathRoot << en->name();
855 path.removeAll(QString());
856
857 Text text;
858 text << Atom::ParaLeft
859 << Atom(Atom::Link, exampleUrl.replace(placeholder, path.join(separator)))
862
863 generateText(text, nullptr, marker);
864}
865
866void Generator::addImageToCopy(const ExampleNode *en, const ResolvedFile& resolved_file)
867{
868 QDir dirInfo;
869 // TODO: [uncentralized-output-directory-structure]
870 const QString prefix("/images/used-in-examples");
871
872 // TODO: Generators probably should not need to keep track of which files were generated.
873 // Understand if we really need this information and where it should
874 // belong, considering that it should be part of whichever system
875 // would actually store the file itself.
876 s_outFileNames << prefix.mid(1) + "/" + resolved_file.get_query();
877
878
879 // TODO: [uncentralized-output-directory-structure]
880 QString imgOutDir = s_outDir + prefix + "/" + QFileInfo{resolved_file.get_query()}.path();
881 if (!dirInfo.mkpath(imgOutDir))
882 en->location().fatal(QStringLiteral("Cannot create output directory '%1'").arg(imgOutDir));
883 Config::copyFile(en->location(), resolved_file.get_path(), QFileInfo{resolved_file.get_query()}.fileName(), imgOutDir);
884}
885
886// TODO: [multi-purpose-function-with-flag][generate-file-list]
887// Avoid the use of a boolean flag to dispatch to the correct
888// implementation trough branching.
889// We always have to process both images and files, such that we
890// should consider to remove the branching altogheter, performing both
891// operations in a single call.
892// Otherwise, if this turns out to be infeasible, complex or
893// possibly-confusing, consider extracting the processing code outside
894// the function and provide two higer-level dispathing functions for
895// files and images.
896
897/*!
898 This function is called when the documentation for an example is
899 being formatted. It outputs a list of files for the example, which
900 can be the example's source files or the list of images used by the
901 example. The images are copied into a subtree of
902 \c{...doc/html/images/used-in-examples/...}
903*/
904void Generator::generateFileList(const ExampleNode *en, CodeMarker *marker, bool images)
905{
906 Text text;
908 QString tag;
909 QStringList paths;
911
912 if (images) {
913 paths = en->images();
914 tag = "Images:";
915 atomType = Atom::ExampleImageLink;
916 } else { // files
917 paths = en->files();
918 tag = "Files:";
919 }
920 std::sort(paths.begin(), paths.end(), Generator::comparePaths);
921
922 text << Atom::ParaLeft << tag << Atom::ParaRight;
923 text << Atom(Atom::ListLeft, openedList.styleString());
924
925 for (const auto &path : std::as_const(paths)) {
926 auto maybe_resolved_file{file_resolver.resolve(path)};
927 if (!maybe_resolved_file) {
928 // TODO: [uncentralized-admonition][failed-resolve-file]
929 QString details = std::transform_reduce(
930 file_resolver.get_search_directories().cbegin(),
931 file_resolver.get_search_directories().cend(),
932 u"Searched directories:"_s,
933 std::plus(),
934 [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
935 );
936
937 en->location().warning(u"(Generator)Cannot find file to quote from: %1"_s.arg(path), details);
938
939 continue;
940 }
941
942 auto file{*maybe_resolved_file};
943 if (images) addImageToCopy(en, file);
944 else generateExampleFilePage(en, file, marker);
945
946 openedList.next();
947 text << Atom(Atom::ListItemNumber, openedList.numberString())
948 << Atom(Atom::ListItemLeft, openedList.styleString()) << Atom::ParaLeft
949 << Atom(atomType, file.get_query()) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << file.get_query()
950 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight
951 << Atom(Atom::ListItemRight, openedList.styleString());
952 }
953 text << Atom(Atom::ListRight, openedList.styleString());
954 if (!paths.isEmpty())
955 generateText(text, en, marker);
956}
957
958/*!
959 Recursive writing of HTML files from the root \a node.
960 */
962{
963 if (!node->url().isNull())
964 return;
965 if (node->isIndexNode())
966 return;
967 if (node->isInternal() && !m_showInternal)
968 return;
969 if (node->isExternalPage())
970 return;
971
972 /*
973 Obtain a code marker for the source file.
974 */
975 CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
976
977 if (node->parent() != nullptr) {
978 if (node->isCollectionNode()) {
979 /*
980 A collection node collects: groups, C++ modules, or QML
981 modules. Testing for a CollectionNode must be done
982 before testing for a TextPageNode because a
983 CollectionNode is a PageNode at this point.
984
985 Don't output an HTML page for the collection node unless
986 the \group, \module, or \qmlmodule command was actually
987 seen by qdoc in the qdoc comment for the node.
988
989 A key prerequisite in this case is the call to
990 mergeCollections(cn). We must determine whether this
991 group, module or QML module has members in other
992 modules. We know at this point that cn's members list
993 contains only members in the current module. Therefore,
994 before outputting the page for cn, we must search for
995 members of cn in the other modules and add them to the
996 members list.
997 */
998 auto *cn = static_cast<CollectionNode *>(node);
999 if (cn->wasSeen()) {
1001 beginSubPage(node, fileName(node));
1004 } else if (cn->isGenericCollection()) {
1005 // Currently used only for the module's related orphans page
1006 // but can be generalized for other kinds of collections if
1007 // other use cases pop up.
1008 QString name = cn->name().toLower();
1009 name.replace(QChar(' '), QString("-"));
1010 QString filename =
1011 cn->tree()->physicalModuleName() + "-" + name + "." + fileExtension();
1012 beginSubPage(node, filename);
1015 }
1016 } else if (node->isTextPageNode()) {
1017 beginSubPage(node, fileName(node));
1018 generatePageNode(static_cast<PageNode *>(node), marker);
1020 } else if (node->isAggregate()) {
1021 if ((node->isClassNode() || node->isHeader() || node->isNamespace())
1022 && node->docMustBeGenerated()) {
1023 beginSubPage(node, fileName(node));
1024 generateCppReferencePage(static_cast<Aggregate *>(node), marker);
1026 } else if (node->isQmlType()) {
1027 beginSubPage(node, fileName(node));
1028 auto *qcn = static_cast<QmlTypeNode *>(node);
1029 generateQmlTypePage(qcn, marker);
1031 } else if (node->isProxyNode()) {
1032 beginSubPage(node, fileName(node));
1033 generateProxyPage(static_cast<Aggregate *>(node), marker);
1035 }
1036 }
1037 }
1038
1039 if (node->isAggregate()) {
1040 auto *aggregate = static_cast<Aggregate *>(node);
1041 const NodeList &children = aggregate->childNodes();
1042 for (auto *child : children) {
1043 if (child->isPageNode() && !child->isPrivate()) {
1044 generateDocumentation(child);
1045 } else if (!node->parent() && child->isInAPI() && !child->isRelatedNonmember()) {
1046 // Warn if there are documented non-page-generating nodes in the root namespace
1047 child->location().warning(u"No documentation generated for %1 '%2' in global scope."_s
1048 .arg(typeString(child), child->name()),
1049 u"Maybe you forgot to use the '\\relates' command?"_s);
1050 child->setStatus(Node::DontDocument);
1051 } else if (child->isQmlModule() && !child->wasSeen()) {
1052 // An undocumented QML module that was constructed as a placeholder
1053 auto *qmlModule = static_cast<CollectionNode *>(child);
1054 for (const auto *member : qmlModule->members()) {
1055 member->location().warning(
1056 u"Undocumented QML module '%1' referred by type '%2' or its members"_s
1057 .arg(qmlModule->name(), member->name()),
1058 u"Maybe you forgot to document '\\qmlmodule %1'?"_s
1059 .arg(qmlModule->name()));
1060 }
1061 } else if (child->isQmlType() && !child->hasDoc()) {
1062 // A placeholder QML type with incorrect module identifier
1063 auto *qmlType = static_cast<QmlTypeNode *>(child);
1064 if (auto qmid = qmlType->logicalModuleName(); !qmid.isEmpty())
1065 qmlType->location().warning(u"No such type '%1' in QML module '%2'"_s
1066 .arg(qmlType->name(), qmid));
1067 }
1068 }
1069 }
1070}
1071
1072void Generator::generateReimplementsClause(const FunctionNode *fn, CodeMarker *marker)
1073{
1074 if (fn->overridesThis().isEmpty() || !fn->parent()->isClassNode())
1075 return;
1076
1077 auto *cn = static_cast<ClassNode *>(fn->parent());
1078 const FunctionNode *overrides = cn->findOverriddenFunction(fn);
1079 if (overrides && !overrides->isPrivate() && !overrides->parent()->isPrivate()) {
1080 if (overrides->hasDoc()) {
1081 Text text;
1082 text << Atom::ParaLeft << "Reimplements: ";
1083 QString fullName =
1084 overrides->parent()->name()
1085 + "::" + overrides->signature(Node::SignaturePlain);
1086 appendFullName(text, overrides->parent(), fullName, overrides);
1087 text << "." << Atom::ParaRight;
1088 generateText(text, fn, marker);
1089 } else {
1090 fn->doc().location().warning(
1091 QStringLiteral("Illegal \\reimp; no documented virtual function for %1")
1092 .arg(overrides->plainSignature()));
1093 }
1094 return;
1095 }
1096 const PropertyNode *sameName = cn->findOverriddenProperty(fn);
1097 if (sameName && sameName->hasDoc()) {
1098 Text text;
1099 text << Atom::ParaLeft << "Reimplements an access function for property: ";
1100 QString fullName = sameName->parent()->name() + "::" + sameName->name();
1101 appendFullName(text, sameName->parent(), fullName, sameName);
1102 text << "." << Atom::ParaRight;
1103 generateText(text, fn, marker);
1104 }
1105}
1106
1107QString Generator::formatSince(const Node *node)
1108{
1109 QStringList since = node->since().split(QLatin1Char(' '));
1110
1111 // If there is only one argument, assume it is the product version number.
1112 if (since.size() == 1) {
1113 const QString productName = Config::instance().get(CONFIG_PRODUCTNAME).asString();
1114 return productName.isEmpty() ? node->since() : productName + " " + since[0];
1115 }
1116
1117 // Otherwise, use the original <project> <version> string.
1118 return node->since();
1119}
1120
1121/*!
1122 \internal
1123 Returns a string representing status information of a \a node.
1124
1125 If a status description is returned, it is one of:
1126 \list
1127 \li Custom status set explicitly in node's documentation using
1128 \c {\meta {status} {<description>}},
1129 \li 'Deprecated [since <version>]' (\\deprecated [<version>]),
1130 \li 'Until <version>',
1131 \li 'Preliminary' (\\preliminary), or
1132 \li The description adopted from associated module's state:
1133 \c {\modulestate {<description>}}.
1134 \endlist
1135
1136 Otherwise, returns \c std::nullopt.
1137*/
1139{
1140 QString status;
1141
1142 if (const auto metaMap = node->doc().metaTagMap(); metaMap) {
1143 status = metaMap->value("status");
1144 if (!status.isEmpty())
1145 return {status};
1146 }
1147 const auto since = node->deprecatedSince();
1148 if (node->status() == Node::Deprecated) {
1149 status = u"Deprecated"_s;
1150 if (!since.isEmpty())
1151 status += " since %1"_L1.arg(since);
1152 } else if (!since.isEmpty()) {
1153 status = "Until %1"_L1.arg(since);
1154 } else if (node->status() == Node::Preliminary) {
1155 status = u"Preliminary"_s;
1156 } else if (const auto collection = qdb->getModuleNode(node); collection) {
1157 status = collection->state();
1158 }
1159
1160 return status.isEmpty() ? std::nullopt : std::optional(status);
1161}
1162
1163void Generator::generateSince(const Node *node, CodeMarker *marker)
1164{
1165 if (!node->since().isEmpty()) {
1166 Text text;
1167 text << Atom::ParaLeft << "This " << typeString(node) << " was introduced in "
1168 << formatSince(node) << "." << Atom::ParaRight;
1169 generateText(text, node, marker);
1170 }
1171}
1172
1173void Generator::generateNoexceptNote(const Node* node, CodeMarker* marker) {
1174 std::vector<const Node*> nodes;
1175 if (node->isSharedCommentNode()) {
1176 auto shared_node = static_cast<const SharedCommentNode*>(node);
1177 nodes.reserve(shared_node->collective().size());
1178 nodes.insert(nodes.begin(), shared_node->collective().begin(), shared_node->collective().end());
1179 } else nodes.push_back(node);
1180
1181 std::size_t counter{1};
1182 for (const Node* node : nodes) {
1183 if (node->isFunction(Node::CPP)) {
1184 if (auto exception_info = static_cast<const FunctionNode*>(node)->getNoexcept(); exception_info && !(*exception_info).isEmpty()) {
1185 Text text;
1186 text << Atom::NoteLeft
1187 << (nodes.size() > 1 ? QString::fromStdString(" ("s + std::to_string(counter) + ")"s) : QString::fromStdString("This ") + typeString(node))
1188 << " does not throw any exception when " << "\"" << *exception_info << "\"" << " is true."
1189 << Atom::NoteRight;
1190 generateText(text, node, marker);
1191 }
1192 }
1193
1194 ++counter;
1195 }
1196}
1197
1198void Generator::generateStatus(const Node *node, CodeMarker *marker)
1199{
1200 Text text;
1201
1202 switch (node->status()) {
1203 case Node::Active:
1204 // Output the module 'state' description if set.
1205 if (node->isModule() || node->isQmlModule()) {
1206 const QString &state = static_cast<const CollectionNode*>(node)->state();
1207 if (!state.isEmpty()) {
1208 text << Atom::ParaLeft << "This " << typeString(node) << " is in "
1209 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_ITALIC) << state
1210 << Atom(Atom::FormattingRight, ATOM_FORMATTING_ITALIC) << " state."
1211 << Atom::ParaRight;
1212 break;
1213 }
1214 }
1215 if (const auto version = node->deprecatedSince(); !version.isEmpty()) {
1216 text << Atom::ParaLeft << "This " << typeString(node)
1217 << " is scheduled for deprecation in version "
1218 << version << "." << Atom::ParaRight;
1219 }
1220 break;
1221 case Node::Preliminary:
1222 text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << "This "
1223 << typeString(node) << " is under development and is subject to change."
1224 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << Atom::ParaRight;
1225 break;
1226 case Node::Deprecated:
1227 text << Atom::ParaLeft;
1228 if (node->isAggregate())
1230 text << "This " << typeString(node) << " is deprecated";
1231 if (const QString &version = node->deprecatedSince(); !version.isEmpty()) {
1232 text << " since ";
1233 if (node->isQmlNode() && !node->logicalModuleName().isEmpty())
1234 text << node->logicalModuleName() << " ";
1235 text << version;
1236 }
1237
1238 text << ". We strongly advise against using it in new code.";
1239 if (node->isAggregate())
1241 text << Atom::ParaRight;
1242 break;
1243 case Node::Internal:
1244 default:
1245 break;
1246 }
1247 generateText(text, node, marker);
1248}
1249
1250/*!
1251 Generates an addendum note of type \a type for \a node, using \a marker
1252 as the code marker.
1253*/
1254void Generator::generateAddendum(const Node *node, Addendum type, CodeMarker *marker,
1255 bool generateNote)
1256{
1257 Q_ASSERT(node && !node->name().isEmpty());
1258 Text text;
1259 text << Atom(Atom::DivLeft,
1260 "class=\"admonition %1\""_L1.arg(generateNote ? u"note"_s : u"auto"_s));
1261 text << Atom::ParaLeft;
1262
1263 if (generateNote) {
1265 << "Note: " << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1266 }
1267
1268 switch (type) {
1269 case Invokable:
1270 text << "This function can be invoked via the meta-object system and from QML. See "
1271 << Atom(Atom::AutoLink, "Q_INVOKABLE")
1274 break;
1275 case PrivateSignal:
1276 text << "This is a private signal. It can be used in signal connections "
1277 "but cannot be emitted by the user.";
1278 break;
1279 case QmlSignalHandler:
1280 {
1281 QString handler(node->name());
1282 qsizetype prefixLocation = handler.lastIndexOf('.', -2) + 1;
1283 handler[prefixLocation] = handler[prefixLocation].toTitleCase();
1284 handler.insert(prefixLocation, QLatin1String("on"));
1285 text << "The corresponding handler is "
1288 break;
1289 }
1291 {
1292 if (!node->isFunction())
1293 return;
1294 const auto *fn = static_cast<const FunctionNode *>(node);
1295 auto nodes = fn->associatedProperties();
1296 if (nodes.isEmpty())
1297 return;
1298 std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan);
1299 for (const auto *n : std::as_const(nodes)) {
1300 QString msg;
1301 const auto *pn = static_cast<const PropertyNode *>(n);
1302 switch (pn->role(fn)) {
1303 case PropertyNode::FunctionRole::Getter:
1304 msg = QStringLiteral("Getter function");
1305 break;
1306 case PropertyNode::FunctionRole::Setter:
1307 msg = QStringLiteral("Setter function");
1308 break;
1309 case PropertyNode::FunctionRole::Resetter:
1310 msg = QStringLiteral("Resetter function");
1311 break;
1312 case PropertyNode::FunctionRole::Notifier:
1313 msg = QStringLiteral("Notifier signal");
1314 break;
1315 default:
1316 continue;
1317 }
1318 text << msg << " for property " << Atom(Atom::Link, pn->name())
1319 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << pn->name()
1320 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << ". ";
1321 }
1322 break;
1323 }
1324 case BindableProperty:
1325 {
1326 text << "This property supports "
1327 << Atom(Atom::Link, "QProperty")
1328 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << "QProperty"
1330 text << " bindings.";
1331 break;
1332 }
1333 default:
1334 return;
1335 }
1336
1337 text << Atom::ParaRight
1338 << Atom::DivRight;
1339 generateText(text, node, marker);
1340}
1341
1342/*!
1343 Generate the documentation for \a relative. i.e. \a relative
1344 is the node that represents the entity where a qdoc comment
1345 was found, and \a text represents the qdoc comment.
1346 */
1347bool Generator::generateText(const Text &text, const Node *relative, CodeMarker *marker)
1348{
1349 bool result = false;
1350 if (text.firstAtom() != nullptr) {
1351 int numAtoms = 0;
1353 generateAtomList(text.firstAtom(), relative, marker, true, numAtoms);
1354 result = true;
1355 }
1356 return result;
1357}
1358
1359/*
1360 The node is an aggregate, typically a class node, which has
1361 a threadsafeness level. This function checks all the children
1362 of the node to see if they are exceptions to the node's
1363 threadsafeness. If there are any exceptions, the exceptions
1364 are added to the appropriate set (reentrant, threadsafe, and
1365 nonreentrant, and true is returned. If there are no exceptions,
1366 the three node lists remain empty and false is returned.
1367 */
1368bool Generator::hasExceptions(const Node *node, NodeList &reentrant, NodeList &threadsafe,
1369 NodeList &nonreentrant)
1370{
1371 bool result = false;
1373 const NodeList &children = static_cast<const Aggregate *>(node)->childNodes();
1374 for (auto child : children) {
1375 if (!child->isDeprecated()) {
1376 switch (child->threadSafeness()) {
1377 case Node::Reentrant:
1378 reentrant.append(child);
1379 if (ts == Node::ThreadSafe)
1380 result = true;
1381 break;
1382 case Node::ThreadSafe:
1383 threadsafe.append(child);
1384 if (ts == Node::Reentrant)
1385 result = true;
1386 break;
1387 case Node::NonReentrant:
1388 nonreentrant.append(child);
1389 result = true;
1390 break;
1391 default:
1392 break;
1393 }
1394 }
1395 }
1396 return result;
1397}
1398
1399/*!
1400 Returns \c true if a trademark symbol should be appended to the
1401 output as determined by \a atom. Trademarks are tracked via the
1402 use of the \\tm formatting command.
1403
1404 Returns true if:
1405
1406 \list
1407 \li \a atom is of type Atom::FormattingRight containing
1408 ATOM_FORMATTING_TRADEMARK, and
1409 \li The trademarked string is the first appearance on the
1410 current sub-page.
1411 \endlist
1412*/
1414{
1416 return false;
1417 if (atom->string() != ATOM_FORMATTING_TRADEMARK)
1418 return false;
1419
1420 if (atom->count() > 1) {
1421 if (s_trademarks.contains(atom->string(1)))
1422 return false;
1423 s_trademarks << atom->string(1);
1424 }
1425
1426 return true;
1427}
1428
1429static void startNote(Text &text)
1430{
1432 << "Note:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " ";
1433}
1434
1435/*!
1436 Generates text that explains how threadsafe and/or reentrant
1437 \a node is.
1438 */
1440{
1441 Text text, rlink, tlink;
1442 NodeList reentrant;
1443 NodeList threadsafe;
1444 NodeList nonreentrant;
1446 bool exceptions = false;
1447
1448 rlink << Atom(Atom::Link, "reentrant") << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1449 << "reentrant" << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1450
1451 tlink << Atom(Atom::Link, "thread-safe") << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1452 << "thread-safe" << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1453
1454 switch (ts) {
1456 break;
1457 case Node::NonReentrant:
1458 text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1459 << "Warning:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " This "
1460 << typeString(node) << " is not " << rlink << "." << Atom::ParaRight;
1461 break;
1462 case Node::Reentrant:
1463 case Node::ThreadSafe:
1464 startNote(text);
1465 if (node->isAggregate()) {
1466 exceptions = hasExceptions(node, reentrant, threadsafe, nonreentrant);
1467 text << "All functions in this " << typeString(node) << " are ";
1468 if (ts == Node::ThreadSafe)
1469 text << tlink;
1470 else
1471 text << rlink;
1472
1473 if (!exceptions || (ts == Node::Reentrant && !threadsafe.isEmpty()))
1474 text << ".";
1475 else
1476 text << " with the following exceptions:";
1477 } else {
1478 text << "This " << typeString(node) << " is ";
1479 if (ts == Node::ThreadSafe)
1480 text << tlink;
1481 else
1482 text << rlink;
1483 text << ".";
1484 }
1485 text << Atom::ParaRight;
1486 break;
1487 default:
1488 break;
1489 }
1490 generateText(text, node, marker);
1491
1492 if (exceptions) {
1493 text.clear();
1494 if (ts == Node::Reentrant) {
1495 if (!nonreentrant.isEmpty()) {
1496 startNote(text);
1497 text << "These functions are not " << rlink << ":" << Atom::ParaRight;
1498 signatureList(nonreentrant, node, marker);
1499 }
1500 if (!threadsafe.isEmpty()) {
1501 text.clear();
1502 startNote(text);
1503 text << "These functions are also " << tlink << ":" << Atom::ParaRight;
1504 generateText(text, node, marker);
1505 signatureList(threadsafe, node, marker);
1506 }
1507 } else { // thread-safe
1508 if (!reentrant.isEmpty()) {
1509 startNote(text);
1510 text << "These functions are only " << rlink << ":" << Atom::ParaRight;
1511 signatureList(reentrant, node, marker);
1512 }
1513 if (!nonreentrant.isEmpty()) {
1514 text.clear();
1515 startNote(text);
1516 text << "These functions are not " << rlink << ":" << Atom::ParaRight;
1517 signatureList(nonreentrant, node, marker);
1518 }
1519 }
1520 }
1521}
1522
1523/*!
1524 \internal
1525
1526 Generates text that describes the comparison category of \a node.
1527 The CodeMarker \a marker is passed along to generateText().
1528 */
1530{
1531 auto category{node->comparisonCategory()};
1532 if (category == ComparisonCategory::None)
1533 return false;
1534
1535 Text text;
1536 text << Atom::ParaLeft << "This %1 is "_L1.arg(typeString(node))
1537 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_ITALIC)
1538 << QString::fromStdString(comparisonCategoryAsString(category))
1539 << ((category == ComparisonCategory::Equality) ? "-"_L1 : "ly "_L1)
1540 << Atom(Atom::String, "comparable"_L1)
1541 << Atom(Atom::FormattingRight, ATOM_FORMATTING_ITALIC)
1542 << "."_L1 << Atom::ParaRight;
1543 generateText(text, node, marker);
1544 return true;
1545}
1546
1547/*!
1548 Generates a list of types that compare to \a node with the comparison
1549 category that applies for the relationship, followed by (an optional)
1550 descriptive text.
1551
1552 Returns \c true if text was generated, \c false otherwise.
1553 */
1555{
1556 Q_ASSERT(node);
1557 if (!node->doc().comparesWithMap())
1558 return false;
1559
1560 Text relationshipText;
1561 for (auto [key, description] : node->doc().comparesWithMap()->asKeyValueRange()) {
1562 const QString &category = QString::fromStdString(comparisonCategoryAsString(key));
1563
1564 relationshipText << Atom::ParaLeft << "This %1 is "_L1.arg(typeString(node))
1565 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << category
1566 << ((key == ComparisonCategory::Equality) ? "-"_L1 : "ly "_L1)
1567 << "comparable"_L1
1568 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1569 << " with "_L1;
1570
1571 const QStringList types{description.firstAtom()->string().split(';'_L1)};
1572 for (const auto &name : types)
1573 relationshipText << Atom(Atom::AutoLink, name)
1574 << Utilities::separator(types.indexOf(name), types.size());
1575
1576 relationshipText << Atom(Atom::ParaRight) << description;
1577 }
1578
1579 generateText(relationshipText, node, nullptr);
1580 return !relationshipText.isEmpty();
1581}
1582
1583/*!
1584 Returns the string containing an example code of the input node,
1585 if it is an overloaded signal. Otherwise, returns an empty string.
1586 */
1587QString Generator::getOverloadedSignalCode(const Node *node)
1588{
1589 if (!node->isFunction())
1590 return QString();
1591 const auto func = static_cast<const FunctionNode *>(node);
1592 if (!func->isSignal() || !func->hasOverloads())
1593 return QString();
1594
1595 // Compute a friendly name for the object of that instance.
1596 // e.g: "QAbstractSocket" -> "abstractSocket"
1597 QString objectName = node->parent()->name();
1598 if (objectName.size() >= 2) {
1599 if (objectName[0] == 'Q')
1600 objectName = objectName.mid(1);
1601 objectName[0] = objectName[0].toLower();
1602 }
1603
1604 // We have an overloaded signal, show an example. Note, for const
1605 // overloaded signals, one should use Q{Const,NonConst}Overload, but
1606 // it is very unlikely that we will ever have public API overloading
1607 // signals by const.
1608 QString code = "connect(" + objectName + ", QOverload<";
1609 code += func->parameters().generateTypeList();
1610 code += ">::of(&" + func->parent()->name() + "::" + func->name() + "),\n [=](";
1611 code += func->parameters().generateTypeAndNameList();
1612 code += "){ /* ... */ });";
1613
1614 return code;
1615}
1616
1617/*!
1618 If the node is an overloaded signal, add a node with an example on how to connect to it
1619 */
1621{
1622 QString code = getOverloadedSignalCode(node);
1623 if (code.isEmpty())
1624 return;
1625
1626 Text text;
1628 << "Note:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " Signal "
1629 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_ITALIC) << node->name()
1631 << " is overloaded in this class. "
1632 "To connect to this signal by using the function pointer syntax, Qt "
1633 "provides a convenient helper for obtaining the function pointer as "
1634 "shown in this example:"
1635 << Atom(Atom::Code, marker->markedUpCode(code, node, node->location()));
1636
1637 generateText(text, node, marker);
1638}
1639
1640/*!
1641 Traverses the database recursively to generate all the documentation.
1642 */
1644{
1645 s_currentGenerator = this;
1647}
1648
1649Generator *Generator::generatorForFormat(const QString &format)
1650{
1651 for (const auto &generator : std::as_const(s_generators)) {
1652 if (generator->format() == format)
1653 return generator;
1654 }
1655 return nullptr;
1656}
1657
1658QString Generator::indent(int level, const QString &markedCode)
1659{
1660 if (level == 0)
1661 return markedCode;
1662
1663 QString t;
1664 int column = 0;
1665
1666 int i = 0;
1667 while (i < markedCode.size()) {
1668 if (markedCode.at(i) == QLatin1Char('\n')) {
1669 column = 0;
1670 } else {
1671 if (column == 0) {
1672 for (int j = 0; j < level; j++)
1673 t += QLatin1Char(' ');
1674 }
1675 column++;
1676 }
1677 t += markedCode.at(i++);
1678 }
1679 return t;
1680}
1681
1683{
1684 Config &config = Config::instance();
1685 s_outputFormats = config.getOutputFormats();
1686 s_redirectDocumentationToDevNull = config.get(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL).asBool();
1687
1688 for (auto &g : s_generators) {
1689 if (s_outputFormats.contains(g->format())) {
1690 s_currentGenerator = g;
1691 g->initializeGenerator();
1692 }
1693 }
1694
1695 const auto &configFormatting = config.subVars(CONFIG_FORMATTING);
1696 for (const auto &n : configFormatting) {
1697 QString formattingDotName = CONFIG_FORMATTING + Config::dot + n;
1698 const auto &formattingDotNames = config.subVars(formattingDotName);
1699 for (const auto &f : formattingDotNames) {
1700 const auto &configVar = config.get(formattingDotName + Config::dot + f);
1701 QString def{configVar.asString()};
1702 if (!def.isEmpty()) {
1703 int numParams = Config::numParams(def);
1704 int numOccs = def.count("\1");
1705 if (numParams != 1) {
1706 configVar.location().warning(QStringLiteral("Formatting '%1' must "
1707 "have exactly one "
1708 "parameter (found %2)")
1709 .arg(n, numParams));
1710 } else if (numOccs > 1) {
1711 configVar.location().fatal(QStringLiteral("Formatting '%1' must "
1712 "contain exactly one "
1713 "occurrence of '\\1' "
1714 "(found %2)")
1715 .arg(n, numOccs));
1716 } else {
1717 int paramPos = def.indexOf("\1");
1718 s_fmtLeftMaps[f].insert(n, def.left(paramPos));
1719 s_fmtRightMaps[f].insert(n, def.mid(paramPos + 1));
1720 }
1721 }
1722 }
1723 }
1724
1725 s_project = config.get(CONFIG_PROJECT).asString();
1726 s_outDir = config.getOutputDir();
1727 s_outSubdir = s_outDir.mid(s_outDir.lastIndexOf('/') + 1);
1728
1729 s_outputPrefixes.clear();
1730 QStringList items{config.get(CONFIG_OUTPUTPREFIXES).asStringList()};
1731 if (!items.isEmpty()) {
1732 for (const auto &prefix : items)
1733 s_outputPrefixes[prefix] =
1734 config.get(CONFIG_OUTPUTPREFIXES + Config::dot + prefix).asString();
1735 }
1736 if (!items.contains(u"QML"_s))
1737 s_outputPrefixes[u"QML"_s] = u"qml-"_s;
1738
1739 s_outputSuffixes.clear();
1740 for (const auto &suffix : config.get(CONFIG_OUTPUTSUFFIXES).asStringList())
1741 s_outputSuffixes[suffix] = config.get(CONFIG_OUTPUTSUFFIXES
1742 + Config::dot + suffix).asString();
1743
1744 s_noLinkErrors = config.get(CONFIG_NOLINKERRORS).asBool();
1745 s_autolinkErrors = config.get(CONFIG_AUTOLINKERRORS).asBool();
1746}
1747
1748/*!
1749 Creates template-specific subdirs (e.g. /styles and /scripts for HTML)
1750 and copies the files to them.
1751 */
1752void Generator::copyTemplateFiles(const QString &configVar, const QString &subDir)
1753{
1754 // TODO: [resolving-files-unlinked-to-doc]
1755 // This is another case of resolving files, albeit it doesn't use Doc::resolveFile.
1756 // While it may be left out of a first iteration of the file
1757 // resolution logic, it should later be integrated into it.
1758 // This should come naturally when the output directory logic is
1759 // extracted and copying a file should require a validated
1760 // intermediate format.
1761 // Do note that what is done here is a bit different from the
1762 // resolve file routine that is done for other user-given paths.
1763 // Thas is, the paths will always be absolute and not relative as
1764 // they are resolved from the configuration.
1765 // Ideally, this could be solved in the configuration already,
1766 // together with the other configuration resolution processes that
1767 // do not abide by the same constraints that, for example, snippet
1768 // resolution uses.
1769 Config &config = Config::instance();
1770 QStringList files = config.getCanonicalPathList(configVar, Config::Validate);
1771 const auto &loc = config.get(configVar).location();
1772 if (!files.isEmpty()) {
1773 QDir dirInfo;
1774 // TODO: [uncentralized-output-directory-structure]
1775 // As with other places in the generation pass, the details of
1776 // where something is saved in the output directory are spread
1777 // to whichever part of the generation does the saving.
1778 // It is hence complex to build a model of how an output
1779 // directory looks like, as the knowledge has no specific
1780 // entry point or chain-path that can be followed in full.
1781 // Each of those operations should be centralized in a system
1782 // that uniquely knows what the format of the output-directory
1783 // is and how to perform operations on it.
1784 // Later, move this operation to that centralized system.
1785 QString templateDir = s_outDir + QLatin1Char('/') + subDir;
1786 if (!dirInfo.exists(templateDir) && !dirInfo.mkdir(templateDir)) {
1787 // TODO: [uncentralized-admonition]
1788 loc.fatal(QStringLiteral("Cannot create %1 directory '%2'").arg(subDir, templateDir));
1789 } else {
1790 for (const auto &file : files) {
1791 if (!file.isEmpty())
1792 Config::copyFile(loc, file, file, templateDir);
1793 }
1794 }
1795 }
1796}
1797
1798/*!
1799 Reads format-specific variables from config, sets output
1800 (sub)directories, creates them on the filesystem and copies the
1801 template-specific files.
1802 */
1804{
1805 Config &config = Config::instance();
1806 s_outFileNames.clear();
1807 s_useOutputSubdirs = true;
1808 if (config.get(format() + Config::dot + "nosubdirs").asBool())
1810
1811 if (s_outputFormats.isEmpty())
1812 return;
1813
1814 s_outDir = config.getOutputDir(format());
1815 if (s_outDir.isEmpty()) {
1816 Location().fatal(QStringLiteral("No output directory specified in "
1817 "configuration file or on the command line"));
1818 } else {
1819 s_outSubdir = s_outDir.mid(s_outDir.lastIndexOf('/') + 1);
1820 }
1821
1822 QDir outputDir(s_outDir);
1823 if (outputDir.exists()) {
1825 if (!outputDir.isEmpty())
1826 Location().error(QStringLiteral("Output directory '%1' exists but is not empty")
1827 .arg(s_outDir));
1828 }
1829 } else if (!outputDir.mkpath(QStringLiteral("."))) {
1830 Location().fatal(QStringLiteral("Cannot create output directory '%1'").arg(s_outDir));
1831 }
1832
1833 // Output directory exists, which is enough for prepare phase.
1834 if (config.preparing())
1835 return;
1836
1837 const QLatin1String imagesDir("images");
1838 if (!outputDir.exists(imagesDir) && !outputDir.mkdir(imagesDir))
1839 Location().fatal(QStringLiteral("Cannot create images directory '%1'").arg(outputDir.filePath(imagesDir)));
1840
1841 copyTemplateFiles(format() + Config::dot + CONFIG_STYLESHEETS, "style");
1842 copyTemplateFiles(format() + Config::dot + CONFIG_SCRIPTS, "scripts");
1843 copyTemplateFiles(format() + Config::dot + CONFIG_EXTRAIMAGES, "images");
1844
1845 // Use a format-specific .quotinginformation if defined, otherwise a global value
1846 if (config.subVars(format()).contains(CONFIG_QUOTINGINFORMATION))
1847 m_quoting = config.get(format() + Config::dot + CONFIG_QUOTINGINFORMATION).asBool();
1848 else
1849 m_quoting = config.get(CONFIG_QUOTINGINFORMATION).asBool();
1850}
1851
1852/*!
1853 Updates the generator's m_showInternal from the Config.
1854 */
1856{
1857 m_showInternal = Config::instance().showInternal();
1858}
1859
1860bool Generator::matchAhead(const Atom *atom, Atom::AtomType expectedAtomType)
1861{
1862 return atom->next() && atom->next()->type() == expectedAtomType;
1863}
1864
1865/*!
1866 Used for writing to the current output stream. Returns a
1867 reference to the current output stream, which is then used
1868 with the \c {<<} operator for writing.
1869 */
1871{
1872 return *outStreamStack.top();
1873}
1874
1876{
1877 return QFileInfo(static_cast<QFile *>(out().device())->fileName()).fileName();
1878}
1879
1880QString Generator::outputPrefix(const Node *node)
1881{
1882 // Omit prefix for module pages
1883 if (node->isPageNode() && !node->isCollectionNode()) {
1884 switch (node->genus()) {
1885 case Node::QML:
1886 return s_outputPrefixes[u"QML"_s];
1887 case Node::CPP:
1888 return s_outputPrefixes[u"CPP"_s];
1889 default:
1890 break;
1891 }
1892 }
1893 return QString();
1894}
1895
1896QString Generator::outputSuffix(const Node *node)
1897{
1898 if (node->isPageNode()) {
1899 switch (node->genus()) {
1900 case Node::QML:
1901 return s_outputSuffixes[u"QML"_s];
1902 case Node::CPP:
1903 return s_outputSuffixes[u"CPP"_s];
1904 default:
1905 break;
1906 }
1907 }
1908
1909 return QString();
1910}
1911
1912bool Generator::parseArg(const QString &src, const QString &tag, int *pos, int n,
1913 QStringView *contents, QStringView *par1)
1914{
1915#define SKIP_CHAR(c)
1916 if (i >= n || src[i] != c)
1917 return false;
1918 ++i;
1919
1920#define SKIP_SPACE
1921 while (i < n && src[i] == ' ')
1922 ++i;
1923
1924 qsizetype i = *pos;
1925 qsizetype j {};
1926
1927 // assume "<@" has been parsed outside
1928 // SKIP_CHAR('<');
1929 // SKIP_CHAR('@');
1930
1931 if (tag != QStringView(src).mid(i, tag.size())) {
1932 return false;
1933 }
1934
1935 // skip tag
1936 i += tag.size();
1937
1938 // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
1939 if (par1) {
1940 SKIP_SPACE;
1941 // read parameter name
1942 j = i;
1943 while (i < n && src[i].isLetter())
1944 ++i;
1945 if (src[i] == '=') {
1946 SKIP_CHAR('=');
1947 SKIP_CHAR('"');
1948 // skip parameter name
1949 j = i;
1950 while (i < n && src[i] != '"')
1951 ++i;
1952 *par1 = QStringView(src).mid(j, i - j);
1953 SKIP_CHAR('"');
1954 SKIP_SPACE;
1955 }
1956 }
1957 SKIP_SPACE;
1958 SKIP_CHAR('>');
1959
1960 // find contents up to closing "</@tag>
1961 j = i;
1962 for (; true; ++i) {
1963 if (i + 4 + tag.size() > n)
1964 return false;
1965 if (src[i] != '<')
1966 continue;
1967 if (src[i + 1] != '/')
1968 continue;
1969 if (src[i + 2] != '@')
1970 continue;
1971 if (tag != QStringView(src).mid(i + 3, tag.size()))
1972 continue;
1973 if (src[i + 3 + tag.size()] != '>')
1974 continue;
1975 break;
1976 }
1977
1978 *contents = QStringView(src).mid(j, i - j);
1979
1980 i += tag.size() + 4;
1981
1982 *pos = i;
1983 return true;
1984#undef SKIP_CHAR
1985#undef SKIP_SPACE
1986}
1987
1988QString Generator::plainCode(const QString &markedCode)
1989{
1990 QString t = markedCode;
1991 t.replace(tag, QString());
1992 t.replace(quot, QLatin1String("\""));
1993 t.replace(gt, QLatin1String(">"));
1994 t.replace(lt, QLatin1String("<"));
1995 t.replace(amp, QLatin1String("&"));
1996 return t;
1997}
1998
1999int Generator::skipAtoms(const Atom *atom, Atom::AtomType type) const
2000{
2001 int skipAhead = 0;
2002 atom = atom->next();
2003 while (atom && atom->type() != type) {
2004 skipAhead++;
2005 atom = atom->next();
2006 }
2007 return skipAhead;
2008}
2009
2010/*!
2011 Resets the variables used during text output.
2012 */
2014{
2015 m_inLink = false;
2016 m_inContents = false;
2017 m_inSectionHeading = false;
2018 m_inTableHeader = false;
2019 m_numTableRows = 0;
2021 m_link.clear();
2022 m_sectionNumber.clear();
2023}
2024
2025void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
2026{
2027 if (node->isFunction() && !node->isMacro()) {
2028 const auto fn = static_cast<const FunctionNode *>(node);
2029 if (fn->overloadNumber() == 0) {
2030 QString alternateName;
2031 const FunctionNode *alternateFunc = nullptr;
2032
2033 if (fn->name().startsWith("set") && fn->name().size() >= 4) {
2034 alternateName = fn->name()[3].toLower();
2035 alternateName += fn->name().mid(4);
2036 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2037
2038 if (!alternateFunc) {
2039 alternateName = "is" + fn->name().mid(3);
2040 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2041 if (!alternateFunc) {
2042 alternateName = "has" + fn->name().mid(3);
2043 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2044 }
2045 }
2046 } else if (!fn->name().isEmpty()) {
2047 alternateName = "set";
2048 alternateName += fn->name()[0].toUpper();
2049 alternateName += fn->name().mid(1);
2050 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2051 }
2052
2053 if (alternateFunc && alternateFunc->access() != Access::Private) {
2054 int i;
2055 for (i = 0; i < alsoList.size(); ++i) {
2056 if (alsoList.at(i).toString().contains(alternateName))
2057 break;
2058 }
2059
2060 if (i == alsoList.size()) {
2061 if (alternateFunc->isDeprecated() && !fn->isDeprecated())
2062 return;
2063 alternateName += "()";
2064
2065 Text also;
2066 also << Atom(Atom::Link, alternateName)
2067 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << alternateName
2069 alsoList.prepend(also);
2070 }
2071 }
2072 }
2073 }
2074}
2075
2077{
2078 if (!node->isQmlProperty())
2079 return;
2080
2081 const auto *qpn = static_cast<const QmlPropertyNode*>(node);
2082
2083 if (!qpn->enumNode())
2084 return;
2085
2086 // Retrieve atoms from C++ enum \value list
2087 const auto body{qpn->enumNode()->doc().body()};
2088 const auto *start{body.firstAtom()};
2089 Text text;
2090
2091 while ((start = start->find(Atom::ListLeft, ATOM_LIST_VALUE))) {
2092 const auto end = start->find(Atom::ListRight, ATOM_LIST_VALUE);
2093 // Skip subsequent ListLeft atoms, collating multiple lists into one
2094 text << body.subText(text.isEmpty() ? start : start->next(), end);
2095 start = end;
2096 }
2097 if (text.isEmpty())
2098 return;
2099
2100 text << Atom(Atom::ListRight, ATOM_LIST_VALUE);
2101 if (marker)
2102 generateText(text, qpn, marker);
2103 else
2104 generateText(text, qpn);
2105}
2106
2108{
2109 for (const auto &generator : std::as_const(s_generators)) {
2110 if (s_outputFormats.contains(generator->format()))
2111 generator->terminateGenerator();
2112 }
2113
2114 // REMARK: Generators currently, due to recent changes and the
2115 // transitive nature of the current codebase, receive some of
2116 // their dependencies in the constructor and some of them in their
2117 // initialize-terminate lifetime.
2118 // This means that generators need to be constructed and
2119 // destructed between usages such that if multiple usages are
2120 // required, the generators present in the list will have been
2121 // destroyed by then such that accessing them would be an error.
2122 // The current codebase calls initialize and the correspective
2123 // terminate with the same scope as the lifetime of the
2124 // generators.
2125 // Then, clearing the list ensures that, if another generator
2126 // execution is needed, the stale generators will not be removed
2127 // as to be replaced by newly constructed ones.
2128 // Do note that it is not clear that this will happen for any call
2129 // in Qt's documentation and this should work only because of the
2130 // form of the current codebase and the scoping of the
2131 // initialize-terminate calls. As such, this should be considered
2132 // a patchwork that may or may not be doing anything and that may
2133 // break due to changes in other parts of the codebase.
2134 //
2135 // This is still to be considered temporary as the whole
2136 // initialize-terminate idiom must be removed from the codebase.
2137 s_generators.clear();
2138
2139 s_fmtLeftMaps.clear();
2140 s_fmtRightMaps.clear();
2141 s_outDir.clear();
2142}
2143
2145
2146/*!
2147 Trims trailing whitespace off the \a string and returns
2148 the trimmed string.
2149 */
2150QString Generator::trimmedTrailing(const QString &string, const QString &prefix,
2151 const QString &suffix)
2152{
2153 QString trimmed = string;
2154 while (trimmed.size() > 0 && trimmed[trimmed.size() - 1].isSpace())
2155 trimmed.truncate(trimmed.size() - 1);
2156
2157 trimmed.append(suffix);
2158 trimmed.prepend(prefix);
2159 return trimmed;
2160}
2161
2163{
2164 switch (node->nodeType()) {
2165 case Node::Namespace:
2166 return "namespace";
2167 case Node::Class:
2168 return "class";
2169 case Node::Struct:
2170 return "struct";
2171 case Node::Union:
2172 return "union";
2173 case Node::QmlType:
2174 case Node::QmlValueType:
2175 return "type";
2176 case Node::Page:
2177 return "documentation";
2178 case Node::Enum:
2179 return "enum";
2180 case Node::Typedef:
2181 case Node::TypeAlias:
2182 return "typedef";
2183 case Node::Function: {
2184 const auto fn = static_cast<const FunctionNode *>(node);
2185 switch (fn->metaness()) {
2187 return "signal";
2189 return "signal handler";
2191 return "method";
2194 return "macro";
2195 default:
2196 break;
2197 }
2198 return "function";
2199 }
2200 case Node::Property:
2201 case Node::QmlProperty:
2202 return "property";
2203 case Node::Module:
2204 case Node::QmlModule:
2205 return "module";
2206 case Node::SharedComment: {
2207 const auto &collective = static_cast<const SharedCommentNode *>(node)->collective();
2208 return collective.first()->nodeTypeString();
2209 }
2210 default:
2211 return "documentation";
2212 }
2213}
2214
2215void Generator::unknownAtom(const Atom *atom)
2216{
2217 Location::internalError(QStringLiteral("unknown atom type '%1' in %2 generator")
2218 .arg(atom->typeString(), format()));
2219}
2220
2221/*!
2222 * Generate the CMake requisite for the node \a cn, i.e. the the find_package and target_link_libraries
2223 * calls to use it.
2224 *
2225 * If only cmakepackage is set it will look like
2226 *
2227 * \badcode
2228 * find_package(Foo REQUIRED)
2229 * target_link_libraries(mytarget PRIVATE Foo:Foo)
2230 * \endcode
2231 *
2232 * If no cmakepackage is set Qt6 is assumed.
2233 *
2234 * If cmakecomponent is set it will look like
2235 *
2236 * \badcode
2237 * find_package(Qt6 REQUIRED COMPONENTS Bar)
2238 * target_link_libraries(mytarget PRIVATE Qt6::Bar)
2239 * \endcode
2240 *
2241 * If cmaketargetitem is set the item in target_link_libraries will be set accordingly
2242 *
2243 * \badcode
2244 * find_package(Qt6 REQUIRED COMPONENTS Bar)
2245 * target_link_libraries(mytarget PRIVATE My::Target)
2246 * \endcode
2247 *
2248 * Returns a pair consisting of the find package line and link libraries line.
2249 *
2250 * If no sensible requisite can be created (i.e. both cmakecomponent and cmakepackage are unset)
2251 * \c std::nullopt is returned.
2252 */
2253std::optional<std::pair<QString, QString>> Generator::cmakeRequisite(const CollectionNode *cn)
2254{
2255 if (!cn || (cn->cmakeComponent().isEmpty() && cn->cmakePackage().isEmpty())) {
2256 return {};
2257 }
2258
2259 const QString package =
2260 cn->cmakePackage().isEmpty() ? "Qt" + QString::number(QT_VERSION_MAJOR) : cn->cmakePackage();
2261
2262 QString findPackageText;
2263 if (cn->cmakeComponent().isEmpty()) {
2264 findPackageText = "find_package(" + package + " REQUIRED)";
2265 } else {
2266 findPackageText = "find_package(" + package + " REQUIRED COMPONENTS " + cn->cmakeComponent() + ")";
2267 }
2268
2269 QString targetText;
2270 if (cn->cmakeTargetItem().isEmpty()) {
2271 if (cn->cmakeComponent().isEmpty()) {
2272 targetText = package + "::" + package;
2273 } else {
2274 targetText = package + "::" + cn->cmakeComponent();
2275 }
2276 } else {
2277 targetText = cn->cmakeTargetItem();
2278 }
2279
2280 const QString targetLinkLibrariesText = "target_link_libraries(mytarget PRIVATE " + targetText + ")";
2281 const QStringList cmakeInfo { findPackageText, targetLinkLibrariesText };
2282
2283 return std::make_pair(findPackageText, targetLinkLibrariesText);
2284}
2285
2286QT_END_NAMESPACE
#define ATOM_FORMATTING_TELETYPE
Definition atom.h:207
#define ATOM_FORMATTING_BOLD
Definition atom.h:199
#define ATOM_FORMATTING_TRADEMARK
Definition atom.h:208
#define ATOM_LIST_VALUE
Definition atom.h:214
#define ATOM_FORMATTING_ITALIC
Definition atom.h:201
#define ATOM_FORMATTING_LINK
Definition atom.h:202
const NodeList & childNodes() const
Returns a const reference to the child list.
Definition aggregate.h:41
The Atom class is the fundamental unit for representing documents internally.
Definition atom.h:18
AtomType type() const
Return the type of this atom.
Definition atom.h:149
AtomType
\value AnnotatedList \value AutoLink \value BaseName \value BriefLeft \value BriefRight \value C \val...
Definition atom.h:20
@ DivRight
Definition atom.h:39
@ FormatElse
Definition atom.h:44
@ Code
Definition atom.h:30
@ String
Definition atom.h:92
@ ListLeft
Definition atom.h:62
@ ExampleFileLink
Definition atom.h:40
@ ListRight
Definition atom.h:68
@ ParaRight
Definition atom.h:75
@ FormattingLeft
Definition atom.h:47
@ FormattingRight
Definition atom.h:48
@ Link
Definition atom.h:60
@ FormatEndif
Definition atom.h:45
@ ExampleImageLink
Definition atom.h:41
@ AutoLink
Definition atom.h:22
@ LinkNode
Definition atom.h:61
@ ParaLeft
Definition atom.h:74
@ FormatIf
Definition atom.h:46
const Atom * next() const
Return the next atom in the atom list.
Definition atom.h:146
The ClassNode represents a C++ class.
Definition classnode.h:21
A class for holding the members of a collection of doc pages.
bool wasSeen() const override
Returns the seen flag data member of this node if it is a NamespaceNode or a CollectionNode.
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:84
bool preparing() const
Definition config.h:181
bool generating() const
Definition config.h:182
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:292
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.
signed short overloadNumber() const
Returns the overload number for this function.
bool isPrivateSignal() const
const Parameters & parameters() const
bool isMAssign() const
bool isVirtual() const
bool isCAssign() 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
Metaness metaness() const
bool m_quoting
Definition generator.h:203
void signatureList(const NodeList &nodes, const Node *relative, CodeMarker *marker)
Generate a bullet list of function signatures.
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:89
virtual void generateProxyPage(Aggregate *, CodeMarker *)
Definition generator.h:86
virtual void generateCppReferencePage(Aggregate *, CodeMarker *)
Definition generator.h:85
bool generateComparisonCategory(const Node *node, CodeMarker *marker=nullptr)
QMap< QString, QString > & formattingRightMap()
virtual void generateAddendum(const Node *node, Addendum type, CodeMarker *marker, bool generateNote)
Generates an addendum note of type type for node, using marker as the code marker.
virtual QString typeString(const Node *node)
static bool hasExceptions(const Node *node, NodeList &reentrant, NodeList &threadsafe, NodeList &nonreentrant)
void generateEnumValuesForQmlProperty(const Node *node, CodeMarker *marker)
FileResolver & file_resolver
Definition generator.h:194
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)
void appendFullName(Text &text, const Node *apparentNode, const Node *relative, const Node *actualNode=nullptr)
Definition generator.cpp:92
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.
Definition generator.cpp:76
QDocDatabase * m_qdb
Definition generator.h:196
bool m_inContents
Definition generator.h:198
bool generateComparisonList(const Node *node)
Generates a list of types that compare to node with the comparison category that applies for the rela...
static bool useOutputSubdirs()
Definition generator.h:68
void generateNoexceptNote(const Node *node, CodeMarker *marker)
void unknownAtom(const Atom *atom)
virtual bool generateText(const Text &text, const Node *relative, CodeMarker *marker)
Generate the documentation for relative.
virtual void terminateGenerator()
static bool matchAhead(const Atom *atom, Atom::AtomType expectedAtomType)
QString fullDocumentLocation(const Node *node)
Returns the full document location.
bool m_inLink
Definition generator.h:197
void addImageToCopy(const ExampleNode *en, const ResolvedFile &resolved_file)
virtual void generateDocs()
Traverses the database recursively to generate all the documentation.
int appendSortedQmlNames(Text &text, const Node *base, const NodeList &subs)
bool m_inTableHeader
Definition generator.h:200
void generateOverloadedSignal(const Node *node, CodeMarker *marker)
If the node is an overloaded signal, add a node with an example on how to connect to it.
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:199
virtual int skipAtoms(const Atom *atom, Atom::AtomType type) const
int m_numTableRows
Definition generator.h:204
bool m_threeColumnEnumValueTable
Definition generator.h:201
virtual void generateQmlTypePage(QmlTypeNode *, CodeMarker *)
Definition generator.h:87
QTextStream & out()
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:88
virtual ~Generator()
Destroys the generator after removing it from the list of output generators.
Definition generator.cpp:87
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:122
static void resetUseOutputSubdirs()
Definition generator.h:67
@ AssociatedProperties
Definition generator.h:39
@ PrivateSignal
Definition generator.h:37
@ QmlSignalHandler
Definition generator.h:38
@ BindableProperty
Definition generator.h:40
bool m_showInternal
Definition generator.h:202
virtual void generateGenericCollectionPage(CollectionNode *, CodeMarker *)
Definition generator.h:90
virtual QString fileBase(const Node *node) const
virtual void initializeGenerator()
Updates the generator's m_showInternal from the Config.
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...
static Generator * currentGenerator()
Definition generator.h:57
The Location class provides a way to mark a location in a file.
Definition location.h:15
Location()
Constructs an empty location.
Definition location.cpp:40
bool isGenericCollection() const
Returns true if the node type is Collection.
Definition node.h:166
bool isExternalPage() const
Returns true if the node type is ExternalPage.
Definition node.h:134
const Doc & doc() const
Returns a reference to the node's Doc data member.
Definition node.h:271
bool isQmlNode() const
Returns true if this node's Genus value is QML.
Definition node.h:153
virtual bool docMustBeGenerated() const
This function is called to perform a test to decide if the node must have documentation generated.
Definition node.h:228
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:953
bool isPrivate() const
Returns true if this node's access is Private.
Definition node.h:146
@ Variable
Definition node.h:69
@ Module
Definition node.h:71
@ Struct
Definition node.h:58
@ QmlModule
Definition node.h:73
@ Typedef
Definition node.h:66
@ QmlValueType
Definition node.h:75
@ Function
Definition node.h:65
@ TypeAlias
Definition node.h:67
@ Union
Definition node.h:59
@ Page
Definition node.h:61
@ Group
Definition node.h:70
@ Enum
Definition node.h:62
@ HeaderFile
Definition node.h:60
@ QmlProperty
Definition node.h:74
@ QmlType
Definition node.h:72
@ SharedComment
Definition node.h:76
@ Namespace
Definition node.h:56
@ Property
Definition node.h:68
@ Class
Definition node.h:57
bool isNamespace() const
Returns true if the node type is Namespace.
Definition node.h:143
ComparisonCategory comparisonCategory() const
Definition node.h:217
bool hasFileNameBase() const
Returns true if the node's file name base has been set.
Definition node.h:200
bool isFunction(Genus g=DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
Definition node.h:135
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
Definition node.h:155
bool isSharedCommentNode() const
Returns true if the node type is SharedComment.
Definition node.h:158
virtual bool isInternal() const
Returns true if the node's status is Internal, or if its parent is a class with Internal status.
Definition node.cpp:849
bool isHeader() const
Returns true if the node type is HeaderFile.
Definition node.h:140
virtual bool isPageNode() const
Returns true if this node represents something that generates a documentation page.
Definition node.h:182
virtual bool isMacro() const
returns true if either FunctionNode::isMacroWithParams() or FunctionNode::isMacroWithoutParams() retu...
Definition node.h:181
bool isEnumType() const
Returns true if the node type is Enum.
Definition node.h:132
virtual bool isTextPageNode() const
Returns true if the node is a PageNode but not an Aggregate.
Definition node.h:187
virtual bool isAttached() const
Returns true if the QML property or QML method node is marked as attached.
Definition node.h:176
Aggregate * parent() const
Returns the node's parent pointer.
Definition node.h:244
virtual bool isDeprecated() const
Returns true if this node's status is Deprecated.
Definition node.h:168
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:170
NodeType nodeType() const
Returns this node's type.
Definition node.h:121
static bool nodeNameLessThan(const Node *first, const Node *second)
Returns true if the node n1 is less than node n2.
Definition node.cpp:57
const Location & location() const
If this node's definition location is empty, this function returns this node's declaration location.
Definition node.h:267
bool isProxyNode() const
Returns true if the node type is Proxy.
Definition node.h:148
Access access() const
Returns the node's Access setting, which can be Public, Protected, or Private.
Definition node.h:264
ThreadSafeness threadSafeness() const
Returns the thread safeness value for whatever this node represents.
Definition node.cpp:826
virtual bool isMarkedReimp() const
Returns true if the FunctionNode is marked as a reimplemented function.
Definition node.h:184
bool isProperty() const
Returns true if the node type is Property.
Definition node.h:147
bool isModule() const
Returns true if the node type is Module.
Definition node.h:142
virtual bool isPropertyGroup() const
Returns true if the node is a SharedCommentNode for documenting multiple C++ properties or multiple Q...
Definition node.h:185
Genus genus() const
Returns this node's Genus.
Definition node.h:124
ThreadSafeness
An unsigned char that specifies the degree of thread-safeness of the element.
Definition node.h:97
@ ThreadSafe
Definition node.h:101
@ UnspecifiedSafeness
Definition node.h:98
@ Reentrant
Definition node.h:100
bool isSharingComment() const
This function returns true if the node is sharing a comment with other nodes.
Definition node.h:285
bool hasDoc() const
Returns true if this node is documented, or it represents a documented node read from the index ('had...
Definition node.cpp:906
LinkType
An unsigned char value that probably should be moved out of the Node base class.
Definition node.h:112
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
Definition node.h:177
virtual bool isCollectionNode() const
Returns true if this is an instance of CollectionNode.
Definition node.h:178
@ Internal
Definition node.h:93
@ Active
Definition node.h:92
@ Deprecated
Definition node.h:90
@ Preliminary
Definition node.h:91
bool isQmlModule() const
Returns true if the node type is QmlModule.
Definition node.h:152
@ SignaturePlain
Definition node.h:105
bool isExample() const
Returns true if the node type is Example.
Definition node.h:133
bool isIndexNode() const
Returns true if this node was created from something in an index file.
Definition node.h:141
Status status() const
Returns the node's status value.
Definition node.h:278
bool isQmlProperty() const
Returns true if the node type is QmlProperty.
Definition node.h:154
OpenedList(ListStyle style)
A PageNode is a Node that generates a documentation page.
Definition pagenode.h:18
bool noAutoList() const
Returns the value of the no auto-list flag.
Definition pagenode.h:41
This class describes one instance of using the Q_PROPERTY macro.
PropertyType propertyType() const
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,...
void mergeCollections(CollectionNode *c)
Finds all the collection nodes with the same name and type as c and merges their members into the mem...
const EnumNode * enumNode() const
Returns the node representing the C++ enumeration associated with this property, or \nullptr.
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:374
#define CONFIG_AUTOLINKERRORS
Definition config.h:323
#define CONFIG_EXTRAIMAGES
Definition config.h:340
#define CONFIG_EXAMPLES
Definition config.h:336
#define CONFIG_URL
Definition config.h:391
#define CONFIG_OUTPUTSUFFIXES
Definition config.h:371
#define CONFIG_OUTPUTPREFIXES
Definition config.h:370
#define CONFIG_NOLINKERRORS
Definition config.h:367
#define CONFIG_PROJECT
Definition config.h:373
#define CONFIG_EXAMPLESINSTALLPATH
Definition config.h:337
#define CONFIG_PRODUCTNAME
Definition config.h:372
#define CONFIG_QUOTINGINFORMATION
Definition config.h:376
#define CONFIG_STYLESHEETS
Definition config.h:384
#define CONFIG_FORMATTING
Definition config.h:342
#define CONFIG_SCRIPTS
Definition config.h:377
QMultiMap< QString, QString > QStringMultiMap
Definition doc.h:28
QList< Node * > NodeList
Definition node.h:41
static QLatin1String gt("&gt;")
#define SKIP_SPACE
static void startNote(Text &text)
static QLatin1String amp("&amp;")
static QLatin1String quot("&quot;")
static QLatin1String lt("&lt;")
static QRegularExpression tag("</?@[^>]*>")
std::optional< QString > formatStatus(const Node *node, QDocDatabase *qdb)
Represents a file that is reachable by QDoc based on its current configuration.