30#include <QtCore/qdebug.h>
31#include <QtCore/qdir.h>
32#include <QtCore/qregularexpression.h>
34#ifndef QT_BOOTSTRAPPED
35# include "QtCore/qurl.h"
40using namespace std::literals::string_literals;
44using namespace Qt::StringLiterals;
47QMap<QString, QMap<QString, QString>> Generator::s_fmtLeftMaps;
48QMap<QString, QMap<QString, QString>> Generator::s_fmtRightMaps;
49QList<Generator *>
Generator::s_generators;
55QHash<QString, QString>
Generator::s_outputPrefixes;
56QHash<QString, QString>
Generator::s_outputSuffixes;
60bool Generator::s_redirectDocumentationToDevNull =
false;
64static QRegularExpression
tag(
"</?@[^>]*>");
71
72
73
74
75
80 s_generators.prepend(
this);
85
86
89 s_generators.removeAll(
this);
93 const Node *actualNode)
95 if (actualNode ==
nullptr)
96 actualNode = apparentNode;
104 const Node *actualNode)
106 if (actualNode ==
nullptr)
107 actualNode = apparentNode;
114
115
116
117
127
128
129
130
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"));
148 QMap<QString, Text> classMap;
149 for (
const auto &relatedClass : rc) {
150 ClassNode *rcn = relatedClass.m_node;
151 if (rcn && rcn->isInAPI()) {
153 appendFullName(className, rcn, cn);
154 classMap[className.toString().toLower()] = className;
159 const QStringList classNames = classMap.keys();
160 for (
const auto &className : classNames) {
161 text << classMap[className];
162 text << Utilities::comma(index++, classNames.size());
169 QMap<QString, Text> classMap;
171 for (
const auto sub : subs) {
173 appendFullName(full_name, sub, base);
174 classMap[full_name.toString().toLower()] = full_name;
178 const auto &names = classMap.keys();
179 for (
const auto &name : names)
180 text << classMap[name] << Utilities::comma(index++, names.size());
185
186
187
188
189
190
191
196 if (s_outFileNames.contains(fileName) && !node->isAttribution())
197 node->location().warning(
"Already generated %1 for this project"_L1.arg(fileName));
199 QString path = outputDir() + QLatin1Char(
'/') + fileName;
201 auto outPath = s_redirectDocumentationToDevNull ? QStringLiteral(
"/dev/null") : path;
202 auto outFile =
new QFile(outPath);
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"))
209 qCDebug(lcQdoc) << qUtf8Printable(warningText);
212 if (!outFile->open(QFile::WriteOnly | QFile::Text)) {
214 QStringLiteral(
"Cannot open output file '%1'").arg(outFile->fileName()));
217 qCDebug(lcQdoc,
"Writing: %s", qPrintable(path));
218 s_outFileNames << fileName;
219 s_trademarks.clear();
224
225
226
227
231 QFile *outFile = openSubPageFile(
static_cast<
const PageNode*>(node), fileName);
232 auto *out =
new QTextStream(outFile);
233 outStreamStack.push(out);
237
238
239
240
243 outStreamStack.top()->flush();
244 delete outStreamStack.top()->device();
245 delete outStreamStack.pop();
254 return node->fileNameBase();
256 QString base{node->name()};
257 if (base.endsWith(
".html"))
258 base.truncate(base.size() - 5);
262 base.append(
"-qmlmodule");
264 base.append(
"-module");
265 base.append(outputSuffix(node));
268 base.prepend(
"%1-"_L1.arg(s_project.toLower()));
269 base.append(
"-example");
273
274
275
276
277
278
279
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)));
286 base.append(
"-%1-proxy"_L1.arg(node->tree()->physicalModuleName()));
289 const Node *p = node;
291 const Node *pp = p->parent();
292 base.prepend(p->name());
293 if (pp ==
nullptr || pp->name().isEmpty() || pp->isTextPageNode())
295 base.prepend(
'-'_L1);
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());
305 base.append(outputSuffix(node));
308 base.prepend(outputPrefix(node));
309 QString canonicalName{ Utilities::asAsciiPrintable(base) };
311 n->setFileNameBase(canonicalName);
312 return canonicalName;
316
317
318
319
320
321QString
Generator::linkForExampleFile(
const QString &path,
const QString &fileExt)
324 link.prepend(s_project.toLower() + QLatin1Char(
'-'));
326 QString canonicalName{ Utilities::asAsciiPrintable(link) };
327 canonicalName.append(QLatin1Char(
'.'));
328 canonicalName.append(fileExt.isEmpty() ? fileExtension() : fileExt);
329 return canonicalName;
333
334
335
339 if (relative->files().contains(fileName))
340 suffix = QLatin1String(
" Example File");
341 else if (relative->images().contains(fileName))
342 suffix = QLatin1String(
" Image File");
346 return fileName.mid(fileName.lastIndexOf(QLatin1Char(
'/')) + 1) + suffix;
350
351
352
353
354
355QString
Generator::fileName(
const Node *node,
const QString &extension)
const
357 if (!node->url().isEmpty())
360 QString name = fileBase(node) + QLatin1Char(
'.');
361 return name + (extension.isNull() ? fileExtension() : extension);
365
366
367
368
369
370
371
372
373QString
Generator::cleanRef(
const QString &ref,
bool xmlCompliant)
384 clean.reserve(ref.size() + 20);
385 const QChar c = ref[0];
386 const uint u = c.unicode();
388 if ((u >=
'a' && u <=
'z') || (u >=
'A' && u <=
'Z') || (!xmlCompliant && u >=
'0' && u <=
'9')) {
390 }
else if (xmlCompliant && u >=
'0' && u <=
'9') {
391 clean += QLatin1Char(
'A') + c;
392 }
else if (u ==
'~') {
394 }
else if (u ==
'_') {
395 clean +=
"underscore.";
397 clean += QLatin1Char(
'A');
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 ==
'.') {
406 }
else if (c.isSpace()) {
407 clean += QLatin1Char(
'-');
408 }
else if (u ==
'!') {
410 }
else if (u ==
'&') {
412 }
else if (u ==
'<') {
414 }
else if (u ==
'=') {
416 }
else if (u ==
'>') {
418 }
else if (u ==
'#') {
419 clean += QLatin1Char(
'#');
421 clean += QLatin1Char(
'-');
422 clean += QString::number(
static_cast<
int>(u), 16);
430 return s_fmtLeftMaps[format()];
435 return s_fmtRightMaps[format()];
439
440
445 if (!node->url().isEmpty())
453
454
455
456 if (!fileBase(node).isEmpty())
457 parentName = fileBase(node) + QLatin1Char(
'.') + currentGenerator()->fileExtension();
461 return fileBase(node) + QLatin1Char(
'.') + currentGenerator()->fileExtension();
463 parentName = fileBase(node) + QLatin1Char(
'.') + currentGenerator()->fileExtension();
464 }
else if (fileBase(node).isEmpty())
467 Node *parentNode =
nullptr;
471 if (!node->parent()->isNamespace() || !node->parent()->name().isEmpty())
472 parentName = fullDocumentLocation(node->parent());
479 case Node::Namespace:
481 parentName = fileBase(node) + QLatin1Char(
'.') + currentGenerator()->fileExtension();
484 const auto *fn =
static_cast<
const FunctionNode *>(node);
487 anchorRef = QLatin1Char(
'#') + node->name() +
"-signal";
490 anchorRef = QLatin1Char(
'#') + node->name() +
"-signal-handler";
493 anchorRef = QLatin1Char(
'#') + node->name() +
"-method";
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());
504 anchorRef = QLatin1Char(
'#') + cleanRef(fn->name());
510
511
512
513
515 anchorRef = QLatin1Char(
'#') + node->name() +
"-enum";
518 const auto *tdef =
static_cast<
const TypedefNode *>(node);
519 if (tdef->associatedEnum())
520 return fullDocumentLocation(tdef->associatedEnum());
523 anchorRef = QLatin1Char(
'#') + node->name() +
"-typedef";
526 anchorRef = QLatin1Char(
'#') + node->name() +
"-prop";
534 anchorRef = QLatin1Char(
'#') + node->name() +
"-attached-prop";
536 anchorRef = QLatin1Char(
'#') + node->name() +
"-prop";
539 anchorRef = QLatin1Char(
'#') + node->name() +
"-var";
547 parentName = fileBase(node);
548 parentName.replace(QLatin1Char(
'/'), QLatin1Char(
'-'))
549 .replace(QLatin1Char(
'.'), QLatin1Char(
'-'));
562 return parentName.toLower() + anchorRef;
567 QList<Text> alsoList = node->doc().alsoList();
568 supplementAlsoList(node, alsoList);
570 if (!alsoList.isEmpty()) {
575 for (
int i = 0; i < alsoList.size(); ++i)
576 text << alsoList.at(i) << Utilities::separator(i, alsoList.size());
584 bool generate,
int &numAtoms)
586 while (atom !=
nullptr) {
588 int numAtoms0 = numAtoms;
589 bool rightFormat = canHandleFormat(atom->string());
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());
617 n += generateAtom(atom, relative, marker);
628
629
630
636
637
638
642 text <<
"Destroys the instance of ";
643 text << fn
->parent()->name() <<
".";
645 text <<
" The destructor is virtual.";
651 text <<
"Default constructs an instance of ";
652 text << fn
->parent()->name() <<
".";
658 text <<
"Copy constructor.";
664 text <<
"Move-copy constructor.";
670 text <<
"Copy-assignment operator.";
676 text <<
"Move-assignment operator.";
682 node
->location().warning(QStringLiteral(
"No documentation for '%1'")
683 .arg(node->plainSignature()));
687 if (node->name() != QLatin1String(
"QtGadgetHelper"))
689 QStringLiteral(
"No documentation for '%1'").arg(node->plainSignature()));
693 if (fn && !fn->overridesThis().isEmpty())
694 generateReimplementsClause(fn, marker);
718 const auto *enume =
static_cast<
const EnumNode *>(node);
720 QSet<QString> definedItems;
721 const QList<EnumItem> &items = enume->items();
722 for (
const auto &item : items)
723 definedItems.insert(item.name());
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)) {
733 QString best = nearestName(it, definedItems);
734 if (!best.isEmpty() && !documentedItems.contains(best))
735 details = QStringLiteral(
"Maybe you meant '%1'?").arg(best);
737 node->doc().location().warning(
738 QStringLiteral(
"No such enum item '%1' in %2")
739 .arg(it, node->plainFullName()),
741 }
else if (!documentedItems.contains(it)) {
742 node->doc().location().warning(
743 QStringLiteral(
"Undocumented enum item '%1' in %2")
744 .arg(it, node->plainFullName()));
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()) {
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()));
766 for (
const auto &name : documentedNames) {
767 if (!declaredNames.contains(name)) {
768 QString best = nearestName(name, declaredNames);
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()),
779
780
781
782
785 if (!fn
->doc().body().contains(
"return"))
787 QStringLiteral(
"Undocumented return value "
788 "(hint: use 'return' or 'returns' in the text"));
797
798
799
800
801
807 const auto *en =
static_cast<
const ExampleNode *>(node);
810 if (exampleUrl.isEmpty()) {
816 generateLinkToExample(en, marker, exampleUrl);
821
822
823
824
825
826
828 const QString &baseUrl)
830 QString exampleUrl(baseUrl);
832#ifndef QT_BOOTSTRAPPED
833 link = QUrl(exampleUrl).host();
837 link.prepend(
"Example project");
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;
851 pathRoot = metaTagMap->value(QLatin1String(
"installpath"));
852 if (pathRoot.isEmpty())
854 QStringList path = QStringList() << pathRoot << en->name();
855 path.removeAll(QString());
859 <<
Atom(
Atom::Link, exampleUrl.replace(placeholder, path.join(separator)))
870 const QString prefix(
"/images/used-in-examples");
876 s_outFileNames << prefix.mid(1) +
"/" + resolved_file.get_query();
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);
898
899
900
901
902
903
913 paths = en->images();
920 std::sort(paths.begin(), paths.end(), Generator::comparePaths);
925 for (
const auto &path : std::as_const(paths)) {
926 auto maybe_resolved_file{file_resolver.resolve(path)};
927 if (!maybe_resolved_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,
934 [](
const DirectoryPath &directory_path) -> QString {
return u' ' + directory_path.value(); }
937 en->location().warning(u"(Generator)Cannot find file to quote from: %1"_s.arg(path), details);
942 auto file{*maybe_resolved_file};
943 if (images) addImageToCopy(en, file);
944 else generateExampleFilePage(en, file, marker);
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()
951 << Atom(Atom::ListItemRight, openedList.styleString());
954 if (!paths.isEmpty())
955 generateText(text, en, marker);
959
960
963 if (!node->url().isNull())
973
974
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
1001 beginSubPage(node, fileName(node));
1008 QString name = cn->name().toLower();
1009 name.replace(QChar(
' '), QString(
"-"));
1011 cn->tree()->physicalModuleName() +
"-" + name +
"." + fileExtension();
1012 beginSubPage(node, filename);
1017 beginSubPage(node, fileName(node));
1023 beginSubPage(node, fileName(node));
1027 beginSubPage(node, fileName(node));
1032 beginSubPage(node, fileName(node));
1040 auto *aggregate =
static_cast<
Aggregate *>(node);
1042 for (
auto *child : children) {
1043 if (child->isPageNode() && !child->isPrivate()) {
1044 generateDocumentation(child);
1045 }
else if (!node->parent() && child->isInAPI() && !child->isRelatedNonmember()) {
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()) {
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()));
1061 }
else if (child->isQmlType() && !child->hasDoc()) {
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));
1074 if (fn->overridesThis().isEmpty() || !fn
->parent()->isClassNode())
1078 const FunctionNode *overrides = cn->findOverriddenFunction(fn);
1084 overrides->parent()->name()
1085 +
"::" + overrides->signature(Node::SignaturePlain);
1088 generateText(text, fn, marker);
1090 fn
->doc().location().warning(
1091 QStringLiteral(
"Illegal \\reimp; no documented virtual function for %1")
1092 .arg(overrides->plainSignature()));
1096 const PropertyNode *sameName = cn->findOverriddenProperty(fn);
1097 if (sameName && sameName
->hasDoc()) {
1099 text <<
Atom::ParaLeft <<
"Reimplements an access function for property: ";
1100 QString fullName = sameName->parent()->name() +
"::" + sameName->name();
1103 generateText(text, fn, marker);
1109 QStringList since = node->since().split(QLatin1Char(
' '));
1112 if (since.size() == 1) {
1114 return productName.isEmpty() ? node->since() : productName +
" " + since[0];
1118 return node->since();
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1143 status = metaMap->value(
"status");
1144 if (!status.isEmpty())
1147 const auto since = node->deprecatedSince();
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);
1155 status = u"Preliminary"_s;
1157 status = collection->state();
1160 return status.isEmpty() ?
std::nullopt :
std::optional(status);
1165 if (!node->since().isEmpty()) {
1167 text << Atom::ParaLeft <<
"This " << typeString(node) <<
" was introduced in "
1168 << formatSince(node) <<
"." << Atom::ParaRight;
1174 std::vector<
const Node*> nodes;
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);
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()) {
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."
1190 generateText(text, node, marker);
1206 const QString &state =
static_cast<
const CollectionNode*>(node)->state();
1207 if (!state.isEmpty()) {
1208 text << Atom::ParaLeft <<
"This " << typeString(node) <<
" is in "
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;
1221 case Node::Preliminary:
1223 << typeString(node) <<
" is under development and is subject to change."
1230 text <<
"This " << typeString(node) <<
" is deprecated";
1231 if (
const QString &version = node->deprecatedSince(); !version.isEmpty()) {
1233 if (node
->isQmlNode() && !node->logicalModuleName().isEmpty())
1234 text << node->logicalModuleName() <<
" ";
1238 text <<
". We strongly advise against using it in new code.";
1251
1252
1253
1257 Q_ASSERT(node && !node->name().isEmpty());
1259 text << Atom(Atom::DivLeft,
1260 "class=\"admonition %1\""_L1.arg(generateNote ? u"note"_s : u"auto"_s));
1270 text <<
"This function can be invoked via the meta-object system and from QML. See "
1276 text <<
"This is a private signal. It can be used in signal connections "
1277 "but cannot be emitted by the user.";
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 "
1294 const auto *fn =
static_cast<
const FunctionNode *>(node);
1295 auto nodes = fn->associatedProperties();
1296 if (nodes.isEmpty())
1299 for (
const auto *n : std::as_const(nodes)) {
1301 const auto *pn =
static_cast<
const PropertyNode *>(n);
1302 switch (pn->role(fn)) {
1303 case PropertyNode::FunctionRole::Getter:
1304 msg = QStringLiteral(
"Getter function");
1306 case PropertyNode::FunctionRole::Setter:
1307 msg = QStringLiteral(
"Setter function");
1309 case PropertyNode::FunctionRole::Resetter:
1310 msg = QStringLiteral(
"Resetter function");
1312 case PropertyNode::FunctionRole::Notifier:
1313 msg = QStringLiteral(
"Notifier signal");
1318 text << msg <<
" for property " << Atom(Atom::Link, pn->name())
1326 text <<
"This property supports "
1330 text <<
" bindings.";
1343
1344
1345
1346
1349 bool result =
false;
1360
1361
1362
1363
1364
1365
1366
1367
1371 bool result =
false;
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)
1382 case Node::ThreadSafe:
1383 threadsafe.append(child);
1384 if (ts == Node::Reentrant)
1387 case Node::NonReentrant:
1388 nonreentrant.append(child);
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1420 if (atom->count() > 1) {
1421 if (s_trademarks.contains(atom->string(1)))
1423 s_trademarks << atom->string(1);
1436
1437
1438
1441 Text text, rlink, tlink;
1446 bool exceptions =
false;
1457 case Node::NonReentrant:
1460 << typeString(node) <<
" is not " << rlink <<
"." << Atom::ParaRight;
1467 text <<
"All functions in this " << typeString(node) <<
" are ";
1476 text <<
" with the following exceptions:";
1478 text <<
"This " << typeString(node) <<
" is ";
1495 if (!nonreentrant.isEmpty()) {
1497 text <<
"These functions are not " << rlink <<
":" <<
Atom::ParaRight;
1500 if (!threadsafe.isEmpty()) {
1503 text <<
"These functions are also " << tlink <<
":" <<
Atom::ParaRight;
1508 if (!reentrant.isEmpty()) {
1510 text <<
"These functions are only " << rlink <<
":" <<
Atom::ParaRight;
1513 if (!nonreentrant.isEmpty()) {
1516 text <<
"These functions are not " << rlink <<
":" <<
Atom::ParaRight;
1524
1525
1526
1527
1528
1532 if (category == ComparisonCategory::None)
1536 text << Atom::ParaLeft <<
"This %1 is "_L1.arg(typeString(node))
1538 << QString::fromStdString(comparisonCategoryAsString(category))
1539 << ((category == ComparisonCategory::Equality) ?
"-"_L1 :
"ly "_L1)
1540 << Atom(Atom::String,
"comparable"_L1)
1542 <<
"."_L1 << Atom::ParaRight;
1548
1549
1550
1551
1552
1553
1557 if (!node
->doc().comparesWithMap())
1560 Text relationshipText;
1561 for (
auto [key, description] : node->doc().comparesWithMap()->asKeyValueRange()) {
1562 const QString &category = QString::fromStdString(comparisonCategoryAsString(key));
1564 relationshipText << Atom::ParaLeft <<
"This %1 is "_L1.arg(typeString(node))
1566 << ((key == ComparisonCategory::Equality) ?
"-"_L1 :
"ly "_L1)
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());
1576 relationshipText << Atom(Atom::ParaRight) << description;
1584
1585
1586
1591 const auto func =
static_cast<
const FunctionNode *>(node);
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();
1608 QString code =
"connect(" + objectName +
", QOverload<";
1610 code +=
">::of(&" + func
->parent()->name() +
"::" + func->name() +
"),\n [=](";
1612 code +=
"){ /* ... */ });";
1618
1619
1622 QString code = getOverloadedSignalCode(node);
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:"
1641
1642
1645 s_currentGenerator =
this;
1651 for (
const auto &generator : std::as_const(s_generators)) {
1652 if (generator->format() == format)
1658QString
Generator::indent(
int level,
const QString &markedCode)
1667 while (i < markedCode.size()) {
1668 if (markedCode.at(i) == QLatin1Char(
'\n')) {
1672 for (
int j = 0; j < level; j++)
1673 t += QLatin1Char(
' ');
1677 t += markedCode.at(i++);
1684 Config &config = Config::instance();
1685 s_outputFormats = config.getOutputFormats();
1688 for (
auto &g : s_generators) {
1689 if (s_outputFormats.contains(g->format())) {
1690 s_currentGenerator = g;
1691 g->initializeGenerator();
1696 for (
const auto &n : configFormatting) {
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 "
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' "
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));
1726 s_outDir = config.getOutputDir();
1727 s_outSubdir = s_outDir.mid(s_outDir.lastIndexOf(
'/') + 1);
1729 s_outputPrefixes.clear();
1731 if (!items.isEmpty()) {
1732 for (
const auto &prefix : items)
1733 s_outputPrefixes[prefix] =
1736 if (!items.contains(u"QML"_s))
1737 s_outputPrefixes[u"QML"_s] = u"qml-"_s;
1739 s_outputSuffixes.clear();
1742 + Config::dot + suffix).asString();
1749
1750
1751
1752void Generator::copyTemplateFiles(
const QString &configVar,
const QString &subDir)
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()) {
1785 QString templateDir = s_outDir + QLatin1Char(
'/') + subDir;
1786 if (!dirInfo.exists(templateDir) && !dirInfo.mkdir(templateDir)) {
1788 loc.fatal(QStringLiteral(
"Cannot create %1 directory '%2'").arg(subDir, templateDir));
1790 for (
const auto &file : files) {
1791 if (!file.isEmpty())
1792 Config::copyFile(loc, file, file, templateDir);
1799
1800
1801
1802
1805 Config &config = Config::instance();
1806 s_outFileNames.clear();
1807 s_useOutputSubdirs =
true;
1808 if (config.get(format() + Config::dot +
"nosubdirs").asBool())
1811 if (s_outputFormats.isEmpty())
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"));
1819 s_outSubdir = s_outDir.mid(s_outDir.lastIndexOf(
'/') + 1);
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")
1829 }
else if (!outputDir.mkpath(QStringLiteral(
"."))) {
1830 Location().fatal(QStringLiteral(
"Cannot create output directory '%1'").arg(s_outDir));
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)));
1842 copyTemplateFiles(format() + Config::dot +
CONFIG_SCRIPTS,
"scripts");
1853
1854
1857 m_showInternal = Config::instance().showInternal();
1866
1867
1868
1869
1872 return *outStreamStack.top();
1877 return QFileInfo(
static_cast<QFile *>(out().device())->fileName()).fileName();
1886 return s_outputPrefixes[u"QML"_s];
1888 return s_outputPrefixes[u"CPP"_s];
1901 return s_outputSuffixes[u"QML"_s];
1903 return s_outputSuffixes[u"CPP"_s];
1912bool Generator::parseArg(
const QString &src,
const QString &tag,
int *pos,
int n,
1913 QStringView *contents, QStringView *par1)
1916 if (i >= n || src[i] != c)
1921 while (i < n && src[i] == ' ')
1931 if (tag != QStringView(src).mid(i, tag.size())) {
1943 while (i < n && src[i].isLetter())
1945 if (src[i] ==
'=') {
1950 while (i < n && src[i] !=
'"')
1952 *par1 = QStringView(src).mid(j, i - j);
1963 if (i + 4 + tag.size() > n)
1967 if (src[i + 1] !=
'/')
1969 if (src[i + 2] !=
'@')
1971 if (tag != QStringView(src).mid(i + 3, tag.size()))
1973 if (src[i + 3 + tag.size()] !=
'>')
1978 *contents = QStringView(src).mid(j, i - j);
1980 i += tag.size() + 4;
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(
"&"));
2003 while (atom && atom
->type() != type) {
2011
2012
2022 m_sectionNumber.clear();
2028 const auto fn =
static_cast<
const FunctionNode *>(node);
2030 QString alternateName;
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());
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());
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());
2053 if (alternateFunc && alternateFunc
->access() != Access::Private) {
2055 for (i = 0; i < alsoList.size(); ++i) {
2056 if (alsoList.at(i).toString().contains(alternateName))
2060 if (i == alsoList.size()) {
2063 alternateName +=
"()";
2069 alsoList.prepend(also);
2088 const auto *start{body.firstAtom()};
2094 text << body.subText(text
.isEmpty() ? start : start->next(), end);
2102 generateText(text, qpn, marker);
2104 generateText(text, qpn);
2109 for (
const auto &generator : std::as_const(s_generators)) {
2110 if (s_outputFormats.contains(generator->format()))
2111 generator->terminateGenerator();
2137 s_generators.clear();
2139 s_fmtLeftMaps.clear();
2140 s_fmtRightMaps.clear();
2147
2148
2149
2150QString
Generator::trimmedTrailing(
const QString &string,
const QString &prefix,
2151 const QString &suffix)
2153 QString trimmed = string;
2154 while (trimmed.size() > 0 && trimmed[trimmed.size() - 1].isSpace())
2155 trimmed.truncate(trimmed.size() - 1);
2157 trimmed.append(suffix);
2158 trimmed.prepend(prefix);
2177 return "documentation";
2184 const auto fn =
static_cast<
const FunctionNode *>(node);
2189 return "signal handler";
2207 const auto &collective =
static_cast<
const SharedCommentNode *>(node)->collective();
2208 return collective.first()->nodeTypeString();
2211 return "documentation";
2217 Location::internalError(QStringLiteral(
"unknown atom type '%1' in %2 generator")
2218 .arg(atom->typeString(), format()));
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2255 if (!cn || (cn->cmakeComponent().isEmpty() && cn->cmakePackage().isEmpty())) {
2259 const QString package =
2260 cn->cmakePackage().isEmpty() ?
"Qt" + QString::number(QT_VERSION_MAJOR) : cn->cmakePackage();
2262 QString findPackageText;
2263 if (cn->cmakeComponent().isEmpty()) {
2264 findPackageText =
"find_package(" + package +
" REQUIRED)";
2266 findPackageText =
"find_package(" + package +
" REQUIRED COMPONENTS " + cn->cmakeComponent() +
")";
2270 if (cn->cmakeTargetItem().isEmpty()) {
2271 if (cn->cmakeComponent().isEmpty()) {
2272 targetText = package +
"::" + package;
2274 targetText = package +
"::" + cn->cmakeComponent();
2277 targetText = cn->cmakeTargetItem();
2280 const QString targetLinkLibrariesText =
"target_link_libraries(mytarget PRIVATE " + targetText +
")";
2281 const QStringList cmakeInfo { findPackageText, targetLinkLibrariesText };
2283 return std::make_pair(findPackageText, targetLinkLibrariesText);
#define ATOM_FORMATTING_TELETYPE
#define ATOM_FORMATTING_BOLD
#define ATOM_FORMATTING_TRADEMARK
#define ATOM_FORMATTING_ITALIC
#define ATOM_FORMATTING_LINK
const NodeList & childNodes() const
Returns a const reference to the child list.
The Atom class is the fundamental unit for representing documents internally.
AtomType type() const
Return the type of this atom.
AtomType
\value AnnotatedList \value AutoLink \value BaseName \value BriefLeft \value BriefRight \value C \val...
const Atom * next() const
Return the next atom in the atom list.
The ClassNode represents a C++ class.
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...
const Location & location() const
Returns the starting location of a qdoc comment.
const Text & body() const
QStringMultiMap * metaTagMap() const
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 isDeprecated() const override
\reimp
bool hasOverloads() const
Returns true if this function has overloads.
bool isMarkedReimp() const override
Returns true if the FunctionNode is marked as a reimplemented function.
bool isIgnored() const
In some cases, it is ok for a public function to be not documented.
bool hasAssociatedProperties() const
Metaness metaness() const
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 *)
virtual void generateProxyPage(Aggregate *, CodeMarker *)
virtual void generateCppReferencePage(Aggregate *, CodeMarker *)
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
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.
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)
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.
Generator(FileResolver &file_resolver)
Constructs the generator base class.
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()
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.
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)
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.
virtual int skipAtoms(const Atom *atom, Atom::AtomType type) const
bool m_threeColumnEnumValueTable
virtual void generateQmlTypePage(QmlTypeNode *, CodeMarker *)
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...
virtual void generatePageNode(PageNode *, CodeMarker *)
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)
static void resetUseOutputSubdirs()
virtual void generateGenericCollectionPage(CollectionNode *, CodeMarker *)
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()
The Location class provides a way to mark a location in a file.
Location()
Constructs an empty location.
bool isGenericCollection() const
Returns true if the node type is Collection.
bool isExternalPage() const
Returns true if the node type is ExternalPage.
const Doc & doc() const
Returns a reference to the node's Doc data member.
bool isQmlNode() const
Returns true if this node's Genus value is QML.
virtual bool docMustBeGenerated() const
This function is called to perform a test to decide if the node must have documentation generated.
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...
bool isPrivate() const
Returns true if this node's access is Private.
bool isNamespace() const
Returns true if the node type is Namespace.
ComparisonCategory comparisonCategory() const
bool hasFileNameBase() const
Returns true if the node's file name base has been set.
bool isFunction(Genus g=DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
bool isSharedCommentNode() const
Returns true if the node type is SharedComment.
virtual bool isInternal() const
Returns true if the node's status is Internal, or if its parent is a class with Internal status.
bool isHeader() const
Returns true if the node type is HeaderFile.
virtual bool isPageNode() const
Returns true if this node represents something that generates a documentation page.
virtual bool isMacro() const
returns true if either FunctionNode::isMacroWithParams() or FunctionNode::isMacroWithoutParams() retu...
bool isEnumType() const
Returns true if the node type is Enum.
virtual bool isTextPageNode() const
Returns true if the node is a PageNode but not an Aggregate.
virtual bool isAttached() const
Returns true if the QML property or QML method node is marked as attached.
Aggregate * parent() const
Returns the node's parent pointer.
virtual bool isDeprecated() const
Returns true if this node's status is Deprecated.
virtual bool isAggregate() const
Returns true if this node is an aggregate, which means it inherits Aggregate and can therefore have c...
NodeType nodeType() const
Returns this node's type.
static bool nodeNameLessThan(const Node *first, const Node *second)
Returns true if the node n1 is less than node n2.
const Location & location() const
If this node's definition location is empty, this function returns this node's declaration location.
bool isProxyNode() const
Returns true if the node type is Proxy.
Access access() const
Returns the node's Access setting, which can be Public, Protected, or Private.
ThreadSafeness threadSafeness() const
Returns the thread safeness value for whatever this node represents.
virtual bool isMarkedReimp() const
Returns true if the FunctionNode is marked as a reimplemented function.
bool isProperty() const
Returns true if the node type is Property.
bool isModule() const
Returns true if the node type is Module.
virtual bool isPropertyGroup() const
Returns true if the node is a SharedCommentNode for documenting multiple C++ properties or multiple Q...
Genus genus() const
Returns this node's Genus.
ThreadSafeness
An unsigned char that specifies the degree of thread-safeness of the element.
bool isSharingComment() const
This function returns true if the node is sharing a comment with other nodes.
bool hasDoc() const
Returns true if this node is documented, or it represents a documented node read from the index ('had...
LinkType
An unsigned char value that probably should be moved out of the Node base class.
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
virtual bool isCollectionNode() const
Returns true if this is an instance of CollectionNode.
bool isQmlModule() const
Returns true if the node type is QmlModule.
bool isExample() const
Returns true if the node type is Example.
bool isIndexNode() const
Returns true if this node was created from something in an index file.
Status status() const
Returns the node's status value.
bool isQmlProperty() const
Returns true if the node type is QmlProperty.
OpenedList(ListStyle style)
A PageNode is a Node that generates a documentation page.
bool noAutoList() const
Returns the value of the no auto-list flag.
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.
const Atom * firstAtom() const
#define CONFIG_REDIRECTDOCUMENTATIONTODEVNULL
#define CONFIG_AUTOLINKERRORS
#define CONFIG_EXTRAIMAGES
#define CONFIG_OUTPUTSUFFIXES
#define CONFIG_OUTPUTPREFIXES
#define CONFIG_NOLINKERRORS
#define CONFIG_EXAMPLESINSTALLPATH
#define CONFIG_PRODUCTNAME
#define CONFIG_QUOTINGINFORMATION
#define CONFIG_STYLESHEETS
#define CONFIG_FORMATTING
QMultiMap< QString, QString > QStringMultiMap
Represents a file that is reachable by QDoc based on its current configuration.