1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 /*
30   generator.cpp
31 */
32 #include "generator.h"
33 
34 #include "codemarker.h"
35 #include "config.h"
36 #include "doc.h"
37 #include "editdistance.h"
38 #include "loggingcategory.h"
39 #include "node.h"
40 #include "openedlist.h"
41 #include "qdocdatabase.h"
42 #include "quoter.h"
43 #include "separator.h"
44 #include "tokenizer.h"
45 
46 #include <QtCore/qdebug.h>
47 #include <QtCore/qdir.h>
48 
49 #ifndef QT_BOOTSTRAPPED
50 #    include "QtCore/qurl.h"
51 #endif
52 
53 QT_BEGIN_NAMESPACE
54 
55 Generator *Generator::currentGenerator_;
56 QStringList Generator::exampleDirs;
57 QStringList Generator::exampleImgExts;
58 QMap<QString, QMap<QString, QString>> Generator::fmtLeftMaps;
59 QMap<QString, QMap<QString, QString>> Generator::fmtRightMaps;
60 QVector<Generator *> Generator::generators;
61 QStringList Generator::imageDirs;
62 QStringList Generator::imageFiles;
63 QMap<QString, QStringList> Generator::imgFileExts;
64 QString Generator::outDir_;
65 QString Generator::outSubdir_;
66 QStringList Generator::outFileNames_;
67 QSet<QString> Generator::outputFormats;
68 QHash<QString, QString> Generator::outputPrefixes;
69 QHash<QString, QString> Generator::outputSuffixes;
70 QString Generator::project_;
71 QStringList Generator::scriptDirs;
72 QStringList Generator::scriptFiles;
73 QStringList Generator::styleDirs;
74 QStringList Generator::styleFiles;
75 bool Generator::noLinkErrors_ = false;
76 bool Generator::autolinkErrors_ = false;
77 bool Generator::redirectDocumentationToDevNull_ = false;
78 bool Generator::qdocSingleExec_ = false;
79 bool Generator::useOutputSubdirs_ = true;
80 QmlTypeNode *Generator::qmlTypeContext_ = nullptr;
81 
82 static QRegExp tag("</?@[^>]*>");
83 static QLatin1String amp("&amp;");
84 static QLatin1String gt("&gt;");
85 static QLatin1String lt("&lt;");
86 static QLatin1String quot("&quot;");
87 
88 /*!
89   Constructs the generator base class. Prepends the newly
90   constructed generator to the list of output generators.
91   Sets a pointer to the QDoc database singleton, which is
92   available to the generator subclasses.
93  */
Generator()94 Generator::Generator()
95     : inLink_(false),
96       inContents_(false),
97       inSectionHeading_(false),
98       inTableHeader_(false),
99       threeColumnEnumValueTable_(true),
100       showInternal_(false),
101       singleExec_(false),
102       numTableRows_(0)
103 {
104     qdb_ = QDocDatabase::qdocDB();
105     generators.prepend(this);
106 }
107 
108 /*!
109   Destroys the generator after removing it from the list of
110   output generators.
111  */
~Generator()112 Generator::~Generator()
113 {
114     generators.removeAll(this);
115 }
116 
appendFullName(Text & text,const Node * apparentNode,const Node * relative,const Node * actualNode)117 void Generator::appendFullName(Text &text, const Node *apparentNode, const Node *relative,
118                                const Node *actualNode)
119 {
120     if (actualNode == nullptr)
121         actualNode = apparentNode;
122     text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
123          << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
124          << Atom(Atom::String, apparentNode->plainFullName(relative))
125          << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
126 }
127 
appendFullName(Text & text,const Node * apparentNode,const QString & fullName,const Node * actualNode)128 void Generator::appendFullName(Text &text, const Node *apparentNode, const QString &fullName,
129                                const Node *actualNode)
130 {
131     if (actualNode == nullptr)
132         actualNode = apparentNode;
133     text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
134          << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, fullName)
135          << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
136 }
137 
appendFullNames(Text & text,const NodeList & nodes,const Node * relative)138 void Generator::appendFullNames(Text &text, const NodeList &nodes, const Node *relative)
139 {
140     int index = 0;
141     for (const auto &node : nodes) {
142         appendFullName(text, node, relative);
143         text << comma(index++, nodes.count());
144     }
145 }
146 
147 /*!
148   Append the signature for the function named in \a node to
149   \a text, so that is is a link to the documentation for that
150   function.
151  */
appendSignature(Text & text,const Node * node)152 void Generator::appendSignature(Text &text, const Node *node)
153 {
154     text << Atom(Atom::LinkNode, CodeMarker::stringForNode(node))
155          << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
156          << Atom(Atom::String, node->signature(false, true))
157          << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
158 }
159 
160 /*!
161   Generate a bullet list of function signatures. The function
162   nodes are in \a nodes. It uses the \a relative node and the
163   \a marker for the generation.
164  */
signatureList(const NodeList & nodes,const Node * relative,CodeMarker * marker)165 void Generator::signatureList(const NodeList &nodes, const Node *relative, CodeMarker *marker)
166 {
167     Text text;
168     int count = 0;
169     text << Atom(Atom::ListLeft, QString("bullet"));
170     for (const auto &node : nodes) {
171         text << Atom(Atom::ListItemNumber, QString::number(++count));
172         text << Atom(Atom::ListItemLeft, QString("bullet"));
173         appendSignature(text, node);
174         text << Atom(Atom::ListItemRight, QString("bullet"));
175     }
176     text << Atom(Atom::ListRight, QString("bullet"));
177     generateText(text, relative, marker);
178 }
179 
appendSortedNames(Text & text,const ClassNode * cn,const QVector<RelatedClass> & rc)180 int Generator::appendSortedNames(Text &text, const ClassNode *cn, const QVector<RelatedClass> &rc)
181 {
182     QMap<QString, Text> classMap;
183     for (const auto &relatedClass : rc) {
184         ClassNode *rcn = relatedClass.node_;
185         if (rcn && rcn->isInAPI()) {
186             Text className;
187             appendFullName(className, rcn, cn);
188             classMap[className.toString().toLower()] = className;
189         }
190     }
191 
192     int index = 0;
193     const QStringList classNames = classMap.keys();
194     for (const auto &className : classNames) {
195         text << classMap[className];
196         text << comma(index++, classNames.count());
197     }
198     return index;
199 }
200 
appendSortedQmlNames(Text & text,const Node * base,const NodeList & subs)201 int Generator::appendSortedQmlNames(Text &text, const Node *base, const NodeList &subs)
202 {
203     QMap<QString, Text> classMap;
204 
205     for (const auto sub : subs) {
206         Text text;
207         if (!base->isQtQuickNode() || !sub->isQtQuickNode()
208             || (base->logicalModuleName() == sub->logicalModuleName())) {
209             appendFullName(text, sub, base);
210             classMap[text.toString().toLower()] = text;
211         }
212     }
213 
214     int index = 0;
215     const QStringList names = classMap.keys();
216     for (const auto &name : names) {
217         text << classMap[name];
218         text << comma(index++, names.count());
219     }
220     return index;
221 }
222 
223 /*!
224   For debugging qdoc.
225  */
writeOutFileNames()226 void Generator::writeOutFileNames()
227 {
228     QFile files("outputlist.txt");
229     if (!files.open(QFile::WriteOnly))
230         return;
231     QTextStream filesout(&files);
232     const auto names = outFileNames_;
233     for (const auto &file : names) {
234         filesout << file << "\n";
235     }
236 }
237 
238 /*!
239   Creates the file named \a fileName in the output directory
240   and returns a QFile pointing to this file. In particular,
241   this method deals with errors when opening the file:
242   the returned QFile is always valid and can be written to.
243 
244   \sa beginFilePage()
245  */
openSubPageFile(const Node * node,const QString & fileName)246 QFile *Generator::openSubPageFile(const Node *node, const QString &fileName)
247 {
248     QString path = outputDir() + QLatin1Char('/');
249     if (Generator::useOutputSubdirs() && !node->outputSubdirectory().isEmpty()
250         && !outputDir().endsWith(node->outputSubdirectory())) {
251         path += node->outputSubdirectory() + QLatin1Char('/');
252     }
253     path += fileName;
254 
255     auto outPath = redirectDocumentationToDevNull_ ? QStringLiteral("/dev/null") : path;
256     auto outFile = new QFile(outPath);
257     if (!redirectDocumentationToDevNull_ && outFile->exists()) {
258         node->location().error(
259                 tr("Output file already exists; overwriting %1").arg(outFile->fileName()));
260     }
261     if (!outFile->open(QFile::WriteOnly)) {
262         node->location().fatal(tr("Cannot open output file '%1'").arg(outFile->fileName()));
263     }
264     qCDebug(lcQdoc, "Writing: %s", qPrintable(path));
265     outFileNames_ << fileName;
266     return outFile;
267 }
268 
269 /*!
270   Creates the file named \a fileName in the output directory.
271   Attaches a QTextStream to the created file, which is written
272   to all over the place using out(). This function does not
273   store the \a fileName in the \a node as the output file name.
274 
275   \sa beginSubPage()
276  */
beginFilePage(const Node * node,const QString & fileName)277 void Generator::beginFilePage(const Node *node, const QString &fileName)
278 {
279     QFile *outFile = openSubPageFile(node, fileName);
280     QTextStream *out = new QTextStream(outFile);
281 #ifndef QT_NO_TEXTCODEC
282     if (outputCodec)
283         out->setCodec(outputCodec);
284 #endif
285     outStreamStack.push(out);
286 }
287 
288 /*!
289  Creates the file named \a fileName in the output directory.
290  Attaches a QTextStream to the created file, which is written
291  to all over the place using out(). This function calls another
292  function, \c beginFilePage(), which is really just most of what
293  this function used to contain. We needed a different version
294  that doesn't store the \a fileName in the \a node as the output
295  file name.
296 
297  \sa beginFilePage()
298 */
beginSubPage(const Node * node,const QString & fileName)299 void Generator::beginSubPage(const Node *node, const QString &fileName)
300 {
301     beginFilePage(node, fileName);
302     const_cast<Node *>(node)->setOutputFileName(fileName);
303 }
304 
305 /*!
306   Flush the text stream associated with the subpage, and
307   then pop it off the text stream stack and delete it.
308   This terminates output of the subpage.
309  */
endSubPage()310 void Generator::endSubPage()
311 {
312     outStreamStack.top()->flush();
313     delete outStreamStack.top()->device();
314     delete outStreamStack.pop();
315 }
316 
317 /*
318   the code below is effectively equivalent to:
319   input.replace(QRegExp("[^A-Za-z0-9]+"), " ");
320   input = input.trimmed();
321   input.replace(QLatin1Char(' '), QLatin1Char('-'));
322   input = input.toLower();
323   as this function accounted for ~8% of total running time
324   we optimize a bit...
325 */
transmogrify(QString & input,QString & output)326 static void transmogrify(QString &input, QString &output)
327 {
328     // +5 prevents realloc in fileName() below
329     output.reserve(input.size() + 5);
330     bool begun = false;
331     for (int i = 0; i != input.size(); ++i) {
332         QChar c = input.at(i);
333         uint u = c.unicode();
334         if (u >= 'A' && u <= 'Z')
335             u += 'a' - 'A';
336         if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) {
337             output += QLatin1Char(u);
338             begun = true;
339         } else if (begun) {
340             output += QLatin1Char('-');
341             begun = false;
342         }
343     }
344     while (output.endsWith(QLatin1Char('-')))
345         output.chop(1);
346 }
347 
fileBase(const Node * node) const348 QString Generator::fileBase(const Node *node) const
349 {
350     if (!node->isPageNode() && !node->isCollectionNode())
351         node = node->parent();
352 
353     if (node->hasFileNameBase())
354         return node->fileNameBase();
355 
356     QString base;
357     if (node->isCollectionNode()) {
358         base = node->name() + outputSuffix(node);
359         if (base.endsWith(".html"))
360             base.truncate(base.length() - 5);
361 
362         if (node->isQmlModule())
363             base.append("-qmlmodule");
364         else if (node->isJsModule())
365             base.append("-jsmodule");
366         else if (node->isModule())
367             base.append("-module");
368         // Why not add "-group" for group pages?
369     } else if (node->isTextPageNode()) {
370         base = node->name();
371         if (base.endsWith(".html"))
372             base.truncate(base.length() - 5);
373 
374         if (node->isExample()) {
375             QString modPrefix(node->physicalModuleName());
376             if (modPrefix.isEmpty()) {
377                 modPrefix = project_;
378             }
379             base.prepend(modPrefix.toLower() + QLatin1Char('-'));
380         }
381         if (node->isExample()) {
382             base.append(QLatin1String("-example"));
383         }
384     } else if (node->isQmlType() || node->isQmlBasicType() || node->isJsType()
385                || node->isJsBasicType()) {
386         base = node->name();
387         /*
388           To avoid file name conflicts in the html directory,
389           we prepend a prefix (by default, "qml-") and an optional suffix
390           to the file name. The suffix, if one exists, is appended to the
391           module name.
392         */
393         if (!node->logicalModuleName().isEmpty()
394             && (!node->logicalModule()->isInternal() || showInternal_))
395             base.prepend(node->logicalModuleName() + outputSuffix(node) + QLatin1Char('-'));
396 
397         base.prepend(outputPrefix(node));
398     } else if (node->isProxyNode()) {
399         base = node->name();
400         base.append("-proxy");
401     } else {
402         const Node *p = node;
403         forever {
404             const Node *pp = p->parent();
405             base.prepend(p->name());
406             if (pp == nullptr || pp->name().isEmpty() || pp->isTextPageNode())
407                 break;
408             base.prepend(QLatin1Char('-'));
409             p = pp;
410         }
411         if (node->isNamespace() && !node->name().isEmpty()) {
412             const NamespaceNode *ns = static_cast<const NamespaceNode *>(node);
413             if (!ns->isDocumentedHere()) {
414                 base.append(QLatin1String("-sub-"));
415                 base.append(ns->tree()->camelCaseModuleName());
416             }
417         }
418     }
419 
420     QString res;
421     transmogrify(base, res);
422     Node *n = const_cast<Node *>(node);
423     n->setFileNameBase(res);
424     return res;
425 }
426 
427 /*!
428   Constructs an href link from an example file name, which
429   is a path to the example file. If \a fileExtension is
430   empty (default value), retrieve the file extension from
431   the generator.
432  */
linkForExampleFile(const QString & path,const Node * parent,const QString & fileExt)433 QString Generator::linkForExampleFile(const QString &path, const Node *parent,
434                                       const QString &fileExt)
435 {
436     QString link = path;
437     QString modPrefix(parent->physicalModuleName());
438     if (modPrefix.isEmpty())
439         modPrefix = project_;
440     link.prepend(modPrefix.toLower() + QLatin1Char('-'));
441 
442     QString res;
443     transmogrify(link, res);
444     res.append(QLatin1Char('.'));
445     res.append(fileExt);
446     if (fileExt.isEmpty())
447         res.append(fileExtension());
448     return res;
449 }
450 
451 /*!
452     Helper function to construct a title for a file or image page
453     included in an example.
454 */
exampleFileTitle(const ExampleNode * relative,const QString & fileName)455 QString Generator::exampleFileTitle(const ExampleNode *relative, const QString &fileName)
456 {
457     QString suffix;
458     if (relative->files().contains(fileName))
459         suffix = QLatin1String(" Example File");
460     else if (relative->images().contains(fileName))
461         suffix = QLatin1String(" Image File");
462     else
463         return suffix;
464 
465     return fileName.mid(fileName.lastIndexOf(QLatin1Char('/')) + 1) + suffix;
466 }
467 
468 /*!
469   If the \a node has a URL, return the URL as the file name.
470   Otherwise, construct the file name from the fileBase() and
471   either the provided \a extension or fileExtension(), and
472   return the constructed name.
473  */
fileName(const Node * node,const QString & extension) const474 QString Generator::fileName(const Node *node, const QString &extension) const
475 {
476     if (!node->url().isEmpty())
477         return node->url();
478 
479     QString name = fileBase(node) + QLatin1Char('.');
480     return extension.isNull() ? name + fileExtension() : name + extension;
481 }
482 
cleanRef(const QString & ref)483 QString Generator::cleanRef(const QString &ref)
484 {
485     QString clean;
486 
487     if (ref.isEmpty())
488         return clean;
489 
490     clean.reserve(ref.size() + 20);
491     const QChar c = ref[0];
492     const uint u = c.unicode();
493 
494     if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9')) {
495         clean += c;
496     } else if (u == '~') {
497         clean += "dtor.";
498     } else if (u == '_') {
499         clean += "underscore.";
500     } else {
501         clean += QLatin1Char('A');
502     }
503 
504     for (int i = 1; i < ref.length(); i++) {
505         const QChar c = ref[i];
506         const uint u = c.unicode();
507         if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9') || u == '-'
508             || u == '_' || u == ':' || u == '.') {
509             clean += c;
510         } else if (c.isSpace()) {
511             clean += QLatin1Char('-');
512         } else if (u == '!') {
513             clean += "-not";
514         } else if (u == '&') {
515             clean += "-and";
516         } else if (u == '<') {
517             clean += "-lt";
518         } else if (u == '=') {
519             clean += "-eq";
520         } else if (u == '>') {
521             clean += "-gt";
522         } else if (u == '#') {
523             clean += QLatin1Char('#');
524         } else {
525             clean += QLatin1Char('-');
526             clean += QString::number(static_cast<int>(u), 16);
527         }
528     }
529     return clean;
530 }
531 
formattingLeftMap()532 QMap<QString, QString> &Generator::formattingLeftMap()
533 {
534     return fmtLeftMaps[format()];
535 }
536 
formattingRightMap()537 QMap<QString, QString> &Generator::formattingRightMap()
538 {
539     return fmtRightMaps[format()];
540 }
541 
542 /*!
543   Returns the full document location.
544  */
fullDocumentLocation(const Node * node,bool useSubdir)545 QString Generator::fullDocumentLocation(const Node *node, bool useSubdir)
546 {
547     if (node == nullptr)
548         return QString();
549     if (!node->url().isEmpty())
550         return node->url();
551 
552     QString parentName;
553     QString anchorRef;
554     QString fdl;
555 
556     /*
557       If the useSubdir parameter is set, then the output is
558       being sent to subdirectories of the output directory.
559       Prepend the subdirectory name + '/' to the result.
560      */
561     if (useSubdir) {
562         fdl = node->outputSubdirectory();
563         if (!fdl.isEmpty())
564             fdl.append(QLatin1Char('/'));
565     }
566     if (node->isNamespace()) {
567         /*
568           The root namespace has no name - check for this before creating
569           an attribute containing the location of any documentation.
570         */
571         if (!fileBase(node).isEmpty())
572             parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
573         else
574             return QString();
575     } else if (node->isQmlType() || node->isQmlBasicType() || node->isJsType()
576                || node->isJsBasicType()) {
577         QString fb = fileBase(node);
578         if (fb.startsWith(outputPrefix(node)))
579             return fb + QLatin1Char('.') + currentGenerator()->fileExtension();
580         else {
581             QString mq;
582             if (!node->logicalModuleName().isEmpty()) {
583                 mq = node->logicalModuleName().replace(QChar('.'), QChar('-'));
584                 mq = mq.toLower() + QLatin1Char('-');
585             }
586             return fdl + outputPrefix(node) + mq + fileBase(node) + QLatin1Char('.')
587                     + currentGenerator()->fileExtension();
588         }
589     } else if (node->isTextPageNode() || node->isCollectionNode()) {
590         parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
591     } else if (fileBase(node).isEmpty())
592         return QString();
593 
594     Node *parentNode = nullptr;
595 
596     if ((parentNode = node->parent())) {
597         // use the parent's name unless the parent is the root namespace
598         if (!node->parent()->isNamespace() || !node->parent()->name().isEmpty())
599             parentName = fullDocumentLocation(node->parent());
600     }
601 
602     switch (node->nodeType()) {
603     case Node::Class:
604     case Node::Struct:
605     case Node::Union:
606     case Node::Namespace:
607     case Node::Proxy:
608         parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
609         break;
610     case Node::Function: {
611         const FunctionNode *fn = static_cast<const FunctionNode *>(node);
612         switch (fn->metaness()) {
613         case FunctionNode::JsSignal:
614         case FunctionNode::QmlSignal:
615             anchorRef = QLatin1Char('#') + node->name() + "-signal";
616             break;
617         case FunctionNode::JsSignalHandler:
618         case FunctionNode::QmlSignalHandler:
619             anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
620             break;
621         case FunctionNode::JsMethod:
622         case FunctionNode::QmlMethod:
623             anchorRef = QLatin1Char('#') + node->name() + "-method";
624             break;
625         default:
626             if (fn->isDtor())
627                 anchorRef = "#dtor." + fn->name().mid(1);
628             else if (fn->hasOneAssociatedProperty() && fn->doc().isEmpty())
629                 return fullDocumentLocation(fn->firstAssociatedProperty());
630             else if (fn->overloadNumber() > 0)
631                 anchorRef = QLatin1Char('#') + cleanRef(fn->name()) + QLatin1Char('-')
632                         + QString::number(fn->overloadNumber());
633             else
634                 anchorRef = QLatin1Char('#') + cleanRef(fn->name());
635             break;
636         }
637         break;
638     }
639     /*
640       Use node->name() instead of fileBase(node) as
641       the latter returns the name in lower-case. For
642       HTML anchors, we need to preserve the case.
643     */
644     case Node::Enum:
645         anchorRef = QLatin1Char('#') + node->name() + "-enum";
646         break;
647     case Node::TypeAlias:
648         anchorRef = QLatin1Char('#') + node->name() + "-alias";
649         break;
650     case Node::Typedef: {
651         const TypedefNode *tdef = static_cast<const TypedefNode *>(node);
652         if (tdef->associatedEnum()) {
653             return fullDocumentLocation(tdef->associatedEnum());
654         }
655         anchorRef = QLatin1Char('#') + node->name() + "-typedef";
656         break;
657     }
658     case Node::Property:
659         anchorRef = QLatin1Char('#') + node->name() + "-prop";
660         break;
661     case Node::JsProperty:
662     case Node::QmlProperty:
663         if (node->isAttached())
664             anchorRef = QLatin1Char('#') + node->name() + "-attached-prop";
665         else
666             anchorRef = QLatin1Char('#') + node->name() + "-prop";
667         break;
668     case Node::Variable:
669         anchorRef = QLatin1Char('#') + node->name() + "-var";
670         break;
671     case Node::JsType:
672     case Node::QmlType:
673     case Node::Page:
674     case Node::Group:
675     case Node::HeaderFile:
676     case Node::Module:
677     case Node::JsModule:
678     case Node::QmlModule: {
679         parentName = fileBase(node);
680         parentName.replace(QLatin1Char('/'), QLatin1Char('-'))
681                 .replace(QLatin1Char('.'), QLatin1Char('-'));
682         parentName += QLatin1Char('.') + currentGenerator()->fileExtension();
683     } break;
684     default:
685         break;
686     }
687 
688     if (!node->isClassNode() && !node->isNamespace()) {
689         if (node->isObsolete())
690             parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
691                                "-obsolete." + currentGenerator()->fileExtension());
692     }
693 
694     return fdl + parentName.toLower() + anchorRef;
695 }
696 
generateAlsoList(const Node * node,CodeMarker * marker)697 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
698 {
699     QVector<Text> alsoList = node->doc().alsoList();
700     supplementAlsoList(node, alsoList);
701 
702     if (!alsoList.isEmpty()) {
703         Text text;
704         text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << "See also "
705              << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
706 
707         for (int i = 0; i < alsoList.size(); ++i)
708             text << alsoList.at(i) << separator(i, alsoList.size());
709 
710         text << Atom::ParaRight;
711         generateText(text, node, marker);
712     }
713 }
714 
generateAtomList(const Atom * atom,const Node * relative,CodeMarker * marker,bool generate,int & numAtoms)715 const Atom *Generator::generateAtomList(const Atom *atom, const Node *relative, CodeMarker *marker,
716                                         bool generate, int &numAtoms)
717 {
718     while (atom != nullptr) {
719         if (atom->type() == Atom::FormatIf) {
720             int numAtoms0 = numAtoms;
721             bool rightFormat = canHandleFormat(atom->string());
722             atom = generateAtomList(atom->next(), relative, marker, generate && rightFormat,
723                                     numAtoms);
724             if (atom == nullptr)
725                 return nullptr;
726 
727             if (atom->type() == Atom::FormatElse) {
728                 ++numAtoms;
729                 atom = generateAtomList(atom->next(), relative, marker, generate && !rightFormat,
730                                         numAtoms);
731                 if (atom == nullptr)
732                     return nullptr;
733             }
734 
735             if (atom->type() == Atom::FormatEndif) {
736                 if (generate && numAtoms0 == numAtoms) {
737                     relative->location().warning(
738                             tr("Output format %1 not handled %2").arg(format()).arg(outFileName()));
739                     Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
740                     generateAtomList(&unhandledFormatAtom, relative, marker, generate, numAtoms);
741                 }
742                 atom = atom->next();
743             }
744         } else if (atom->type() == Atom::FormatElse || atom->type() == Atom::FormatEndif) {
745             return atom;
746         } else {
747             int n = 1;
748             if (generate) {
749                 n += generateAtom(atom, relative, marker);
750                 numAtoms += n;
751             }
752             while (n-- > 0)
753                 atom = atom->next();
754         }
755     }
756     return nullptr;
757 }
758 
759 /*!
760   Generate the body of the documentation from the qdoc comment
761   found with the entity represented by the \a node.
762  */
generateBody(const Node * node,CodeMarker * marker)763 void Generator::generateBody(const Node *node, CodeMarker *marker)
764 {
765     const FunctionNode *fn = node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
766     if (!node->hasDoc() && !node->hasSharedDoc()) {
767         /*
768           Test for special function, like a destructor or copy constructor,
769           that has no documentation.
770         */
771         if (fn) {
772             if (fn->isDtor()) {
773                 Text text;
774                 text << "Destroys the instance of ";
775                 text << fn->parent()->name() << ".";
776                 if (fn->isVirtual())
777                     text << " The destructor is virtual.";
778                 out() << "<p>";
779                 generateText(text, node, marker);
780                 out() << "</p>";
781             } else if (fn->isCtor()) {
782                 Text text;
783                 text << "Default constructs an instance of ";
784                 text << fn->parent()->name() << ".";
785                 out() << "<p>";
786                 generateText(text, node, marker);
787                 out() << "</p>";
788             } else if (fn->isCCtor()) {
789                 Text text;
790                 text << "Copy constructor.";
791                 out() << "<p>";
792                 generateText(text, node, marker);
793                 out() << "</p>";
794             } else if (fn->isMCtor()) {
795                 Text text;
796                 text << "Move-copy constructor.";
797                 out() << "<p>";
798                 generateText(text, node, marker);
799                 out() << "</p>";
800             } else if (fn->isCAssign()) {
801                 Text text;
802                 text << "Copy-assignment operator.";
803                 out() << "<p>";
804                 generateText(text, node, marker);
805                 out() << "</p>";
806             } else if (fn->isMAssign()) {
807                 Text text;
808                 text << "Move-assignment operator.";
809                 out() << "<p>";
810                 generateText(text, node, marker);
811                 out() << "</p>";
812             } else if (!node->isWrapper() && !node->isMarkedReimp()) {
813                 if (!fn->isIgnored()) // undocumented functions added by Q_OBJECT
814                     node->location().warning(
815                             tr("No documentation for '%1'").arg(node->plainSignature()));
816             }
817         } else if (!node->isWrapper() && !node->isMarkedReimp()) {
818             // Don't require documentation of things defined in Q_GADGET
819             if (node->name() != QLatin1String("QtGadgetHelper"))
820                 node->location().warning(
821                         tr("No documentation for '%1'").arg(node->plainSignature()));
822         }
823     } else if (!node->isSharingComment()) {
824         // Reimplements clause and type alias info precede body text
825         if (fn && !fn->overridesThis().isEmpty())
826             generateReimplementsClause(fn, marker);
827         else if (node->isTypeAlias())
828             generateAddendum(node, TypeAlias, marker, false);
829 
830         if (!generateText(node->doc().body(), node, marker)) {
831             if (node->isMarkedReimp())
832                 return;
833         }
834 
835         if (fn) {
836             if (fn->isQmlSignal())
837                 generateAddendum(node, QmlSignalHandler, marker);
838             if (fn->isPrivateSignal())
839                 generateAddendum(node, PrivateSignal, marker);
840             if (fn->isInvokable())
841                 generateAddendum(node, Invokable, marker);
842             if (fn->hasAssociatedProperties())
843                 generateAddendum(node, AssociatedProperties, marker);
844         }
845 
846         // Generate warnings
847         if (node->isEnumType()) {
848             const EnumNode *enume = static_cast<const EnumNode *>(node);
849 
850             QSet<QString> definedItems;
851             const QVector<EnumItem> &items = enume->items();
852             for (const auto &item : items)
853                 definedItems.insert(item.name());
854 
855             const auto &documentedItemList = enume->doc().enumItemNames();
856             QSet<QString> documentedItems(documentedItemList.cbegin(), documentedItemList.cend());
857             const QSet<QString> allItems = definedItems + documentedItems;
858             if (allItems.count() > definedItems.count()
859                 || allItems.count() > documentedItems.count()) {
860                 for (const auto &it : allItems) {
861                     if (!definedItems.contains(it)) {
862                         QString details;
863                         QString best = nearestName(it, definedItems);
864                         if (!best.isEmpty() && !documentedItems.contains(best))
865                             details = tr("Maybe you meant '%1'?").arg(best);
866 
867                         node->doc().location().warning(tr("No such enum item '%1' in %2")
868                                                                .arg(it)
869                                                                .arg(node->plainFullName()),
870                                                        details);
871                     } else if (!documentedItems.contains(it)) {
872                         node->doc().location().warning(tr("Undocumented enum item '%1' in %2")
873                                                                .arg(it)
874                                                                .arg(node->plainFullName()));
875                     }
876                 }
877             }
878         } else if (fn) {
879             const QSet<QString> declaredNames = fn->parameters().getNames();
880             const QSet<QString> documentedNames = fn->doc().parameterNames();
881             if (declaredNames != documentedNames) {
882                 for (const auto &name : declaredNames) {
883                     if (!documentedNames.contains(name)) {
884                         if (fn->isActive() || fn->isPreliminary()) {
885                             if (!fn->isMarkedReimp() && !fn->isOverload()) {
886                                 fn->doc().location().warning(tr("Undocumented parameter '%1' in %2")
887                                                                      .arg(name)
888                                                                      .arg(node->plainFullName()));
889                             }
890                         }
891                     }
892                 }
893                 for (const auto &name : documentedNames) {
894                     if (!declaredNames.contains(name)) {
895                         QString best = nearestName(name, declaredNames);
896                         QString details;
897                         if (!best.isEmpty())
898                             details = tr("Maybe you meant '%1'?").arg(best);
899                         fn->doc().location().warning(tr("No such parameter '%1' in %2")
900                                                              .arg(name)
901                                                              .arg(fn->plainFullName()),
902                                                      details);
903                     }
904                 }
905             }
906             /*
907               This return value check should be implemented
908               for all functions with a return type.
909               mws 13/12/2018
910             */
911             if (!fn->isObsolete() && fn->returnsBool() && !fn->isMarkedReimp()
912                 && !fn->isOverload()) {
913                 if (!fn->doc().body().contains("return"))
914                     node->doc().location().warning(
915                             tr("Undocumented return value "
916                                "(hint: use 'return' or 'returns' in the text"));
917             }
918         }
919     }
920     generateRequiredLinks(node, marker);
921 }
922 
923 /*!
924   Generates either a link to the project folder for example \a node, or a list
925   of links files/images if 'url.examples config' variable is not defined.
926 
927   Does nothing for non-example nodes.
928 */
generateRequiredLinks(const Node * node,CodeMarker * marker)929 void Generator::generateRequiredLinks(const Node *node, CodeMarker *marker)
930 {
931     if (!node->isExample())
932         return;
933 
934     const ExampleNode *en = static_cast<const ExampleNode *>(node);
935     QString exampleUrl = Config::instance().getString(CONFIG_URL + Config::dot + CONFIG_EXAMPLES);
936 
937     if (exampleUrl.isEmpty()) {
938         if (!en->noAutoList()) {
939             generateFileList(en, marker, false); // files
940             generateFileList(en, marker, true); // images
941         }
942     } else {
943         generateLinkToExample(en, marker, exampleUrl);
944     }
945 }
946 
947 /*!
948   Generates an external link to the project folder for example \a node.
949   The path to the example replaces a placeholder '\1' character if
950   one is found in the \a baseUrl string. If no such placeholder is found,
951   the path is appended to \a baseUrl, after a '/' character if \a baseUrl did
952   not already end in one.
953 */
generateLinkToExample(const ExampleNode * en,CodeMarker * marker,const QString & baseUrl)954 void Generator::generateLinkToExample(const ExampleNode *en, CodeMarker *marker,
955                                       const QString &baseUrl)
956 {
957     QString exampleUrl(baseUrl);
958     QString link;
959 #ifndef QT_BOOTSTRAPPED
960     link = QUrl(exampleUrl).host();
961 #endif
962     if (!link.isEmpty())
963         link.prepend(" @ ");
964     link.prepend("Example project");
965 
966     const QLatin1Char separator('/');
967     const QLatin1Char placeholder('\1');
968     if (!exampleUrl.contains(placeholder)) {
969         if (!exampleUrl.endsWith(separator))
970             exampleUrl += separator;
971         exampleUrl += placeholder;
972     }
973 
974     // Construct a path to the example; <install path>/<example name>
975     QString pathRoot = en->doc().metaTagMap().value(QLatin1String("installpath"));
976     if (pathRoot.isEmpty())
977         pathRoot = Config::instance().getString(CONFIG_EXAMPLESINSTALLPATH);
978     QStringList path = QStringList() << pathRoot << en->name();
979     path.removeAll({});
980 
981     Text text;
982     text << Atom::ParaLeft
983          << Atom(Atom::Link, exampleUrl.replace(placeholder, path.join(separator)))
984          << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, link)
985          << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight;
986 
987     generateText(text, nullptr, marker);
988 }
989 
addImageToCopy(const ExampleNode * en,const QString & file)990 void Generator::addImageToCopy(const ExampleNode *en, const QString &file)
991 {
992     QDir dirInfo;
993     QString userFriendlyFilePath;
994     const QString prefix("/images/used-in-examples/");
995     QString srcPath = Config::findFile(en->location(), QStringList(), exampleDirs, file,
996                                        exampleImgExts, &userFriendlyFilePath);
997     outFileNames_ << prefix.mid(1) + userFriendlyFilePath;
998     userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
999     QString imgOutDir = outDir_ + prefix + userFriendlyFilePath;
1000     if (!dirInfo.mkpath(imgOutDir))
1001         en->location().fatal(tr("Cannot create output directory '%1'").arg(imgOutDir));
1002     Config::copyFile(en->location(), srcPath, file, imgOutDir);
1003 }
1004 
1005 /*!
1006   This function is called when the documentation for an example is
1007   being formatted. It outputs a list of files for the example, which
1008   can be the example's source files or the list of images used by the
1009   example. The images are copied into a subtree of
1010   \c{...doc/html/images/used-in-examples/...}
1011 */
generateFileList(const ExampleNode * en,CodeMarker * marker,bool images)1012 void Generator::generateFileList(const ExampleNode *en, CodeMarker *marker, bool images)
1013 {
1014     Text text;
1015     OpenedList openedList(OpenedList::Bullet);
1016     QString tag;
1017     QStringList paths;
1018     Atom::AtomType atomType = Atom::ExampleFileLink;
1019 
1020     if (images) {
1021         paths = en->images();
1022         tag = "Images:";
1023         atomType = Atom::ExampleImageLink;
1024     } else { // files
1025         paths = en->files();
1026         tag = "Files:";
1027     }
1028     std::sort(paths.begin(), paths.end(), Generator::comparePaths);
1029 
1030     text << Atom::ParaLeft << tag << Atom::ParaRight;
1031     text << Atom(Atom::ListLeft, openedList.styleString());
1032 
1033     QString path;
1034     for (const auto &file : qAsConst(paths)) {
1035         if (images) {
1036             if (!file.isEmpty())
1037                 addImageToCopy(en, file);
1038         } else {
1039             generateExampleFilePage(en, file, marker);
1040         }
1041 
1042         openedList.next();
1043         text << Atom(Atom::ListItemNumber, openedList.numberString())
1044              << Atom(Atom::ListItemLeft, openedList.styleString()) << Atom::ParaLeft
1045              << Atom(atomType, file) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << file
1046              << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight
1047              << Atom(Atom::ListItemRight, openedList.styleString());
1048         path = file;
1049     }
1050     text << Atom(Atom::ListRight, openedList.styleString());
1051     if (!paths.isEmpty())
1052         generateText(text, en, marker);
1053 }
1054 
generateInheritedBy(const ClassNode * classe,CodeMarker * marker)1055 void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker)
1056 {
1057     if (!classe->derivedClasses().isEmpty()) {
1058         Text text;
1059         text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1060              << "Inherited by: " << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1061 
1062         appendSortedNames(text, classe, classe->derivedClasses());
1063         text << Atom::ParaRight;
1064         generateText(text, classe, marker);
1065     }
1066 }
1067 
generateInherits(const ClassNode * classe,CodeMarker * marker)1068 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
1069 {
1070     if (!classe->baseClasses().isEmpty()) {
1071         Text text;
1072         text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1073              << "Inherits: " << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1074 
1075         int index = 0;
1076         const QVector<RelatedClass> &baseClasses = classe->baseClasses();
1077         for (const auto &cls : baseClasses) {
1078             if (cls.node_) {
1079                 appendFullName(text, cls.node_, classe);
1080 
1081                 if (cls.access_ == Node::Protected) {
1082                     text << " (protected)";
1083                 } else if (cls.access_ == Node::Private) {
1084                     text << " (private)";
1085                 }
1086                 text << separator(index++, classe->baseClasses().count());
1087             }
1088         }
1089         text << Atom::ParaRight;
1090         generateText(text, classe, marker);
1091     }
1092 }
1093 
1094 /*!
1095   Recursive writing of HTML files from the root \a node.
1096  */
generateDocumentation(Node * node)1097 void Generator::generateDocumentation(Node *node)
1098 {
1099     if (!node->url().isNull())
1100         return;
1101     if (node->isIndexNode())
1102         return;
1103     if (node->isInternal() && !showInternal_)
1104         return;
1105     if (node->isExternalPage())
1106         return;
1107 
1108     /*
1109       Obtain a code marker for the source file.
1110      */
1111     CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
1112 
1113     if (node->parent() != nullptr) {
1114         if (node->isCollectionNode()) {
1115             /*
1116               A collection node collects: groups, C++ modules,
1117               QML modules or JavaScript modules. Testing for a
1118               CollectionNode must be done before testing for a
1119               TextPageNode because a CollectionNode is a PageNode
1120               at this point.
1121 
1122               Don't output an HTML page for the collection
1123               node unless the \group, \module, \qmlmodule or
1124               \jsmodule command was actually seen by qdoc in
1125               the qdoc comment for the node.
1126 
1127               A key prerequisite in this case is the call to
1128               mergeCollections(cn). We must determine whether
1129               this group, module, QML module, or JavaScript
1130               module has members in other modules. We know at
1131               this point that cn's members list contains only
1132               members in the current module. Therefore, before
1133               outputting the page for cn, we must search for
1134               members of cn in the other modules and add them
1135               to the members list.
1136             */
1137             CollectionNode *cn = static_cast<CollectionNode *>(node);
1138             if (cn->wasSeen()) {
1139                 qdb_->mergeCollections(cn);
1140                 beginSubPage(node, fileName(node));
1141                 generateCollectionNode(cn, marker);
1142                 endSubPage();
1143             } else if (cn->isGenericCollection()) {
1144                 // Currently used only for the module's related orphans page
1145                 // but can be generalized for other kinds of collections if
1146                 // other use cases pop up.
1147                 QString name = cn->name().toLower();
1148                 name.replace(QChar(' '), QString("-"));
1149                 QString filename =
1150                         cn->tree()->physicalModuleName() + "-" + name + "." + fileExtension();
1151                 beginSubPage(node, filename);
1152                 generateGenericCollectionPage(cn, marker);
1153                 endSubPage();
1154             }
1155         } else if (node->isTextPageNode()) {
1156             beginSubPage(node, fileName(node));
1157             generatePageNode(static_cast<PageNode *>(node), marker);
1158             endSubPage();
1159         } else if (node->isAggregate()) {
1160             if ((node->isClassNode() || node->isHeader() || node->isNamespace())
1161                 && node->docMustBeGenerated()) {
1162                 beginSubPage(node, fileName(node));
1163                 generateCppReferencePage(static_cast<Aggregate *>(node), marker);
1164                 endSubPage();
1165             } else if (node->isQmlType() || node->isJsType()) {
1166                 beginSubPage(node, fileName(node));
1167                 QmlTypeNode *qcn = static_cast<QmlTypeNode *>(node);
1168                 generateQmlTypePage(qcn, marker);
1169                 endSubPage();
1170             } else if (node->isQmlBasicType() || node->isJsBasicType()) {
1171                 beginSubPage(node, fileName(node));
1172                 QmlBasicTypeNode *qbtn = static_cast<QmlBasicTypeNode *>(node);
1173                 generateQmlBasicTypePage(qbtn, marker);
1174                 endSubPage();
1175             } else if (node->isProxyNode()) {
1176                 beginSubPage(node, fileName(node));
1177                 generateProxyPage(static_cast<Aggregate *>(node), marker);
1178                 endSubPage();
1179             }
1180         }
1181     }
1182 
1183     if (node->isAggregate()) {
1184         Aggregate *aggregate = static_cast<Aggregate *>(node);
1185         const NodeList &children = aggregate->childNodes();
1186         for (auto *node : children) {
1187             if (node->isPageNode() && !node->isPrivate())
1188                 generateDocumentation(node);
1189         }
1190     }
1191 }
1192 
1193 /*!
1194   Generate a list of maintainers in the output
1195  */
generateMaintainerList(const Aggregate * node,CodeMarker * marker)1196 void Generator::generateMaintainerList(const Aggregate *node, CodeMarker *marker)
1197 {
1198     QStringList sl = getMetadataElements(node, "maintainer");
1199 
1200     if (!sl.isEmpty()) {
1201         Text text;
1202         text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1203              << "Maintained by: " << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1204 
1205         for (int i = 0; i < sl.size(); ++i)
1206             text << sl.at(i) << separator(i, sl.size());
1207 
1208         text << Atom::ParaRight;
1209         generateText(text, node, marker);
1210     }
1211 }
1212 
1213 /*!
1214   Output the "Inherit by" list for the QML element,
1215   if it is inherited by any other elements.
1216  */
generateQmlInheritedBy(const QmlTypeNode * qcn,CodeMarker * marker)1217 void Generator::generateQmlInheritedBy(const QmlTypeNode *qcn, CodeMarker *marker)
1218 {
1219     if (qcn) {
1220         NodeList subs;
1221         QmlTypeNode::subclasses(qcn, subs);
1222         if (!subs.isEmpty()) {
1223             Text text;
1224             text << Atom::ParaLeft << "Inherited by ";
1225             appendSortedQmlNames(text, qcn, subs);
1226             text << Atom::ParaRight;
1227             generateText(text, qcn, marker);
1228         }
1229     }
1230 }
1231 
1232 /*!
1233   Extract sections of markup text surrounded by \e qmltext
1234   and \e endqmltext and output them.
1235  */
generateQmlText(const Text & text,const Node * relative,CodeMarker * marker,const QString &)1236 bool Generator::generateQmlText(const Text &text, const Node *relative, CodeMarker *marker,
1237                                 const QString & /* qmlName */)
1238 {
1239     const Atom *atom = text.firstAtom();
1240     bool result = false;
1241 
1242     if (atom != nullptr) {
1243         initializeTextOutput();
1244         while (atom) {
1245             if (atom->type() != Atom::QmlText)
1246                 atom = atom->next();
1247             else {
1248                 atom = atom->next();
1249                 while (atom && (atom->type() != Atom::EndQmlText)) {
1250                     int n = 1 + generateAtom(atom, relative, marker);
1251                     while (n-- > 0)
1252                         atom = atom->next();
1253                 }
1254             }
1255         }
1256         result = true;
1257     }
1258     return result;
1259 }
1260 
generateReimplementsClause(const FunctionNode * fn,CodeMarker * marker)1261 void Generator::generateReimplementsClause(const FunctionNode *fn, CodeMarker *marker)
1262 {
1263     if (!fn->overridesThis().isEmpty()) {
1264         if (fn->parent()->isClassNode()) {
1265             ClassNode *cn = static_cast<ClassNode *>(fn->parent());
1266             const FunctionNode *overrides = cn->findOverriddenFunction(fn);
1267             if (overrides && !overrides->isPrivate() && !overrides->parent()->isPrivate()) {
1268                 if (overrides->hasDoc()) {
1269                     Text text;
1270                     text << Atom::ParaLeft << "Reimplements: ";
1271                     QString fullName =
1272                             overrides->parent()->name() + "::" + overrides->signature(false, true);
1273                     appendFullName(text, overrides->parent(), fullName, overrides);
1274                     text << "." << Atom::ParaRight;
1275                     generateText(text, fn, marker);
1276                 } else {
1277                     fn->doc().location().warning(
1278                             tr("Illegal \\reimp; no documented virtual function for %1")
1279                                     .arg(overrides->plainSignature()));
1280                 }
1281                 return;
1282             }
1283             const PropertyNode *sameName = cn->findOverriddenProperty(fn);
1284             if (sameName && sameName->hasDoc()) {
1285                 Text text;
1286                 text << Atom::ParaLeft << "Reimplements an access function for property: ";
1287                 QString fullName = sameName->parent()->name() + "::" + sameName->name();
1288                 appendFullName(text, sameName->parent(), fullName, sameName);
1289                 text << "." << Atom::ParaRight;
1290                 generateText(text, fn, marker);
1291             }
1292         }
1293     }
1294 }
1295 
formatSince(const Node * node)1296 QString Generator::formatSince(const Node *node)
1297 {
1298     QStringList since = node->since().split(QLatin1Char(' '));
1299 
1300     // If there is only one argument, assume it is the Qt version number.
1301     if (since.count() == 1)
1302         return "Qt " + since[0];
1303 
1304     // Otherwise, use the original <project> <version> string.
1305     return node->since();
1306 }
1307 
generateSince(const Node * node,CodeMarker * marker)1308 void Generator::generateSince(const Node *node, CodeMarker *marker)
1309 {
1310     if (!node->since().isEmpty()) {
1311         Text text;
1312         text << Atom::ParaLeft << "This " << typeString(node) << " was introduced ";
1313         if (node->isEnumType())
1314             text << "or modified ";
1315         text << "in " << formatSince(node) << "." << Atom::ParaRight;
1316         generateText(text, node, marker);
1317     }
1318 }
1319 
generateStatus(const Node * node,CodeMarker * marker)1320 void Generator::generateStatus(const Node *node, CodeMarker *marker)
1321 {
1322     Text text;
1323 
1324     switch (node->status()) {
1325     case Node::Active:
1326         // Do nothing.
1327         break;
1328     case Node::Preliminary:
1329         text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << "This "
1330              << typeString(node) << " is under development and is subject to change."
1331              << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << Atom::ParaRight;
1332         break;
1333     case Node::Deprecated:
1334         text << Atom::ParaLeft;
1335         if (node->isAggregate())
1336             text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1337         text << "This " << typeString(node) << " is deprecated.";
1338         if (node->isAggregate())
1339             text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1340         text << Atom::ParaRight;
1341         break;
1342     case Node::Obsolete:
1343         text << Atom::ParaLeft;
1344         if (node->isAggregate())
1345             text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1346         text << "This " << typeString(node) << " is obsolete.";
1347         if (node->isAggregate())
1348             text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1349         text << " It is provided to keep old source code working. "
1350              << "We strongly advise against "
1351              << "using it in new code." << Atom::ParaRight;
1352         break;
1353     case Node::Internal:
1354     default:
1355         break;
1356     }
1357     generateText(text, node, marker);
1358 }
1359 
1360 /*!
1361   Generates an addendum note of type \a type for \a node, using \a marker
1362   as the code marker.
1363 */
generateAddendum(const Node * node,Addendum type,CodeMarker * marker,bool generateNote)1364 void Generator::generateAddendum(const Node *node, Addendum type, CodeMarker *marker,
1365                                  bool generateNote)
1366 {
1367     Q_ASSERT(node && !node->name().isEmpty());
1368     Text text;
1369     text << Atom::ParaLeft;
1370 
1371     if (generateNote) {
1372         text  << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1373               << "Note: " << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1374     }
1375 
1376     switch (type) {
1377     case Invokable:
1378         text << "This function can be invoked via the meta-object system and from QML. See "
1379              << Atom(Atom::Link, "Q_INVOKABLE")
1380              << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << "Q_INVOKABLE"
1381              << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << ".";
1382         break;
1383     case PrivateSignal:
1384         text << "This is a private signal. It can be used in signal connections "
1385                 "but cannot be emitted by the user.";
1386         break;
1387     case QmlSignalHandler:
1388     {
1389         QString handler(node->name());
1390         handler[0] = handler[0].toTitleCase();
1391         handler.prepend(QLatin1String("on"));
1392         text << "The corresponding handler is "
1393              << Atom(Atom::FormattingLeft, ATOM_FORMATTING_TELETYPE) << handler
1394              << Atom(Atom::FormattingRight, ATOM_FORMATTING_TELETYPE) << ".";
1395         break;
1396     }
1397     case AssociatedProperties:
1398     {
1399         if (!node->isFunction())
1400             return;
1401         const FunctionNode *fn = static_cast<const FunctionNode *>(node);
1402         NodeList nodes = fn->associatedProperties();
1403         if (nodes.isEmpty())
1404             return;
1405         std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan);
1406         for (const auto *n : qAsConst(nodes)) {
1407             QString msg;
1408             const PropertyNode *pn = static_cast<const PropertyNode *>(n);
1409             switch (pn->role(fn)) {
1410             case PropertyNode::Getter:
1411                 msg = QStringLiteral("Getter function");
1412                 break;
1413             case PropertyNode::Setter:
1414                 msg = QStringLiteral("Setter function");
1415                 break;
1416             case PropertyNode::Resetter:
1417                 msg = QStringLiteral("Resetter function");
1418                 break;
1419             case PropertyNode::Notifier:
1420                 msg = QStringLiteral("Notifier signal");
1421                 break;
1422             default:
1423                 continue;
1424             }
1425             text << msg << " for property " << Atom(Atom::Link, pn->name())
1426              << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << pn->name()
1427              << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << ". ";
1428         }
1429         break;
1430     }
1431     case TypeAlias:
1432     {
1433         if (!node->isTypeAlias())
1434             return;
1435         const auto *ta = static_cast<const TypeAliasNode *>(node);
1436         text << "This is a type alias for ";
1437         if (ta->aliasedNode() && ta->aliasedNode()->isInAPI()) {
1438             text << Atom(Atom::LinkNode, CodeMarker::stringForNode(ta->aliasedNode()))
1439                  << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1440                  << Atom(Atom::String, ta->aliasedNode()->plainFullName(ta->parent()))
1441                  << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << ".";
1442         } else {
1443             text << Atom(Atom::String, ta->aliasedType()) << ".";
1444         }
1445         break;
1446     }
1447     default:
1448         return;
1449     }
1450 
1451     text << Atom::ParaRight;
1452     generateText(text, node, marker);
1453 }
1454 
1455 /*!
1456   Generate the documentation for \a relative. i.e. \a relative
1457   is the node that represents the entity where a qdoc comment
1458   was found, and \a text represents the qdoc comment.
1459  */
generateText(const Text & text,const Node * relative,CodeMarker * marker)1460 bool Generator::generateText(const Text &text, const Node *relative, CodeMarker *marker)
1461 {
1462     bool result = false;
1463     if (text.firstAtom() != nullptr) {
1464         int numAtoms = 0;
1465         initializeTextOutput();
1466         generateAtomList(text.firstAtom(), relative, marker, true, numAtoms);
1467         result = true;
1468     }
1469     return result;
1470 }
1471 
1472 /*
1473   The node is an aggregate, typically a class node, which has
1474   a threadsafeness level. This function checks all the children
1475   of the node to see if they are exceptions to the node's
1476   threadsafeness. If there are any exceptions, the exceptions
1477   are added to the appropriate set (reentrant, threadsafe, and
1478   nonreentrant, and true is returned. If there are no exceptions,
1479   the three node lists remain empty and false is returned.
1480  */
hasExceptions(const Node * node,NodeList & reentrant,NodeList & threadsafe,NodeList & nonreentrant)1481 bool Generator::hasExceptions(const Node *node, NodeList &reentrant, NodeList &threadsafe,
1482                               NodeList &nonreentrant)
1483 {
1484     bool result = false;
1485     Node::ThreadSafeness ts = node->threadSafeness();
1486     const NodeList &children = static_cast<const Aggregate *>(node)->childNodes();
1487     for (auto child : children) {
1488         if (!child->isObsolete()) {
1489             switch (child->threadSafeness()) {
1490             case Node::Reentrant:
1491                 reentrant.append(child);
1492                 if (ts == Node::ThreadSafe)
1493                     result = true;
1494                 break;
1495             case Node::ThreadSafe:
1496                 threadsafe.append(child);
1497                 if (ts == Node::Reentrant)
1498                     result = true;
1499                 break;
1500             case Node::NonReentrant:
1501                 nonreentrant.append(child);
1502                 result = true;
1503                 break;
1504             default:
1505                 break;
1506             }
1507         }
1508     }
1509     return result;
1510 }
1511 
startNote(Text & text)1512 static void startNote(Text &text)
1513 {
1514     text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1515          << "Note:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " ";
1516 }
1517 
1518 /*!
1519   Generates text that explains how threadsafe and/or reentrant
1520   \a node is.
1521  */
generateThreadSafeness(const Node * node,CodeMarker * marker)1522 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
1523 {
1524     Text text, rlink, tlink;
1525     NodeList reentrant;
1526     NodeList threadsafe;
1527     NodeList nonreentrant;
1528     Node::ThreadSafeness ts = node->threadSafeness();
1529     bool exceptions = false;
1530 
1531     rlink << Atom(Atom::Link, "reentrant") << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1532           << "reentrant" << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1533 
1534     tlink << Atom(Atom::Link, "thread-safe") << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1535           << "thread-safe" << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1536 
1537     switch (ts) {
1538     case Node::UnspecifiedSafeness:
1539         break;
1540     case Node::NonReentrant:
1541         text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1542              << "Warning:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " This "
1543              << typeString(node) << " is not " << rlink << "." << Atom::ParaRight;
1544         break;
1545     case Node::Reentrant:
1546     case Node::ThreadSafe:
1547         startNote(text);
1548         if (node->isAggregate()) {
1549             exceptions = hasExceptions(node, reentrant, threadsafe, nonreentrant);
1550             text << "All functions in this " << typeString(node) << " are ";
1551             if (ts == Node::ThreadSafe)
1552                 text << tlink;
1553             else
1554                 text << rlink;
1555 
1556             if (!exceptions || (ts == Node::Reentrant && !threadsafe.isEmpty()))
1557                 text << ".";
1558             else
1559                 text << " with the following exceptions:";
1560         } else {
1561             text << "This " << typeString(node) << " is ";
1562             if (ts == Node::ThreadSafe)
1563                 text << tlink;
1564             else
1565                 text << rlink;
1566             text << ".";
1567         }
1568         text << Atom::ParaRight;
1569         break;
1570     default:
1571         break;
1572     }
1573     generateText(text, node, marker);
1574 
1575     if (exceptions) {
1576         text.clear();
1577         if (ts == Node::Reentrant) {
1578             if (!nonreentrant.isEmpty()) {
1579                 startNote(text);
1580                 text << "These functions are not " << rlink << ":" << Atom::ParaRight;
1581                 signatureList(nonreentrant, node, marker);
1582             }
1583             if (!threadsafe.isEmpty()) {
1584                 text.clear();
1585                 startNote(text);
1586                 text << "These functions are also " << tlink << ":" << Atom::ParaRight;
1587                 generateText(text, node, marker);
1588                 signatureList(threadsafe, node, marker);
1589             }
1590         } else { // thread-safe
1591             if (!reentrant.isEmpty()) {
1592                 startNote(text);
1593                 text << "These functions are only " << rlink << ":" << Atom::ParaRight;
1594                 signatureList(reentrant, node, marker);
1595             }
1596             if (!nonreentrant.isEmpty()) {
1597                 text.clear();
1598                 startNote(text);
1599                 text << "These functions are not " << rlink << ":" << Atom::ParaRight;
1600                 signatureList(nonreentrant, node, marker);
1601             }
1602         }
1603     }
1604 }
1605 
1606 /*!
1607   Returns the string containing an example code of the input node,
1608   if it is an overloaded signal. Otherwise, returns an empty string.
1609  */
getOverloadedSignalCode(const Node * node)1610 QString Generator::getOverloadedSignalCode(const Node *node)
1611 {
1612     if (!node->isFunction())
1613         return QString();
1614     const auto func = static_cast<const FunctionNode *>(node);
1615     if (!func->isSignal() || !func->hasOverloads())
1616         return QString();
1617 
1618     // Compute a friendly name for the object of that instance.
1619     // e.g:  "QAbstractSocket" -> "abstractSocket"
1620     QString objectName = node->parent()->name();
1621     if (objectName.size() >= 2) {
1622         if (objectName[0] == 'Q')
1623             objectName = objectName.mid(1);
1624         objectName[0] = objectName[0].toLower();
1625     }
1626 
1627     // We have an overloaded signal, show an example. Note, for const
1628     // overloaded signals, one should use Q{Const,NonConst}Overload, but
1629     // it is very unlikely that we will ever have public API overloading
1630     // signals by const.
1631     QString code = "connect(" + objectName + ", QOverload<";
1632     code += func->parameters().generateTypeList();
1633     code += ">::of(&" + func->parent()->name() + "::" + func->name() + "),\n    [=](";
1634     code += func->parameters().generateTypeAndNameList();
1635     code += "){ /* ... */ });";
1636 
1637     return code;
1638 }
1639 
1640 /*!
1641     If the node is an overloaded signal, add a node with an example on how to connect to it
1642  */
generateOverloadedSignal(const Node * node,CodeMarker * marker)1643 void Generator::generateOverloadedSignal(const Node *node, CodeMarker *marker)
1644 {
1645     QString code = getOverloadedSignalCode(node);
1646     if (code.isEmpty())
1647         return;
1648 
1649     Text text;
1650     text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1651          << "Note:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " Signal "
1652          << Atom(Atom::FormattingLeft, ATOM_FORMATTING_ITALIC) << node->name()
1653          << Atom(Atom::FormattingRight, ATOM_FORMATTING_ITALIC)
1654          << " is overloaded in this class. "
1655             "To connect to this signal by using the function pointer syntax, Qt "
1656             "provides a convenient helper for obtaining the function pointer as "
1657             "shown in this example:"
1658          << Atom(Atom::Code, marker->markedUpCode(code, node, node->location()));
1659 
1660     generateText(text, node, marker);
1661 }
1662 
1663 /*!
1664   Traverses the database recursively to generate all the documentation.
1665  */
generateDocs()1666 void Generator::generateDocs()
1667 {
1668     currentGenerator_ = this;
1669     generateDocumentation(qdb_->primaryTreeRoot());
1670 }
1671 
generatorForFormat(const QString & format)1672 Generator *Generator::generatorForFormat(const QString &format)
1673 {
1674     for (const auto &generator : qAsConst(generators)) {
1675         if (generator->format() == format)
1676             return generator;
1677     }
1678     return nullptr;
1679 }
1680 
1681 /*!
1682   Looks up the tag \a tag in the map of metadata values for the
1683   current topic in \a inner. If a value for the tag is found,
1684   the value is returned.
1685 
1686   \note If \a tag is found in the metadata map, it is erased.
1687   i.e. Once you call this function for a particular \a tag,
1688   you consume \a tag.
1689  */
getMetadataElement(const Aggregate * inner,const QString & tag)1690 QString Generator::getMetadataElement(const Aggregate *inner, const QString &tag)
1691 {
1692     QString s;
1693     QStringMultiMap &metaTagMap = const_cast<QStringMultiMap &>(inner->doc().metaTagMap());
1694     for (auto it = metaTagMap.find(tag); it != metaTagMap.end();) {
1695         s = it.value();
1696         metaTagMap.erase(it);
1697     }
1698     return s;
1699 }
1700 
1701 /*!
1702   Looks up the tag \a t in the map of metadata values for the
1703   current topic in \a inner. If values for the tag are found,
1704   they are returned in a string list.
1705 
1706   \note If \a t is found in the metadata map, all the pairs
1707   having the key \a t are erased. i.e. Once you call this
1708   function for a particular \a t, you consume \a t.
1709  */
getMetadataElements(const Aggregate * inner,const QString & t)1710 QStringList Generator::getMetadataElements(const Aggregate *inner, const QString &t)
1711 {
1712     QStringList s;
1713     QStringMultiMap &metaTagMap = const_cast<QStringMultiMap &>(inner->doc().metaTagMap());
1714     s = metaTagMap.values(t);
1715     if (!s.isEmpty())
1716         metaTagMap.remove(t);
1717     return s;
1718 }
1719 
1720 /*!
1721   Returns a relative path name for an image.
1722  */
imageFileName(const Node * relative,const QString & fileBase)1723 QString Generator::imageFileName(const Node *relative, const QString &fileBase)
1724 {
1725     QString userFriendlyFilePath;
1726     QString filePath = Config::findFile(relative->doc().location(), imageFiles, imageDirs, fileBase,
1727                                         imgFileExts[format()], &userFriendlyFilePath);
1728 
1729     if (filePath.isEmpty())
1730         return QString();
1731 
1732     QString path = Config::copyFile(relative->doc().location(), filePath, userFriendlyFilePath,
1733                                     outputDir() + QLatin1String("/images"));
1734     int images_slash = path.lastIndexOf("images/");
1735     QString relImagePath;
1736     if (images_slash != -1)
1737         relImagePath = path.mid(images_slash);
1738     return relImagePath;
1739 }
1740 
indent(int level,const QString & markedCode)1741 QString Generator::indent(int level, const QString &markedCode)
1742 {
1743     if (level == 0)
1744         return markedCode;
1745 
1746     QString t;
1747     int column = 0;
1748 
1749     int i = 0;
1750     while (i < markedCode.length()) {
1751         if (markedCode.at(i) == QLatin1Char('\n')) {
1752             column = 0;
1753         } else {
1754             if (column == 0) {
1755                 for (int j = 0; j < level; j++)
1756                     t += QLatin1Char(' ');
1757             }
1758             column++;
1759         }
1760         t += markedCode.at(i++);
1761     }
1762     return t;
1763 }
1764 
initialize()1765 void Generator::initialize()
1766 {
1767     Config &config = Config::instance();
1768     outputFormats = config.getOutputFormats();
1769     redirectDocumentationToDevNull_ = config.getBool(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL);
1770 
1771     imageFiles = config.getCanonicalPathList(CONFIG_IMAGES);
1772     imageDirs = config.getCanonicalPathList(CONFIG_IMAGEDIRS);
1773     scriptFiles = config.getCanonicalPathList(CONFIG_SCRIPTS);
1774     scriptDirs = config.getCanonicalPathList(CONFIG_SCRIPTDIRS);
1775     styleFiles = config.getCanonicalPathList(CONFIG_STYLES);
1776     styleDirs = config.getCanonicalPathList(CONFIG_STYLEDIRS);
1777     exampleDirs = config.getCanonicalPathList(CONFIG_EXAMPLEDIRS);
1778     exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS);
1779 
1780     QString imagesDotFileExtensions = CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
1781     for (const auto &ext : config.subVars(imagesDotFileExtensions))
1782         imgFileExts[ext] = config.getStringList(imagesDotFileExtensions + Config::dot + ext);
1783 
1784     for (auto &g : generators) {
1785         if (outputFormats.contains(g->format())) {
1786             currentGenerator_ = g;
1787             g->initializeGenerator();
1788         }
1789     }
1790 
1791     for (const auto &n : config.subVars(CONFIG_FORMATTING)) {
1792         QString formattingDotName = CONFIG_FORMATTING + Config::dot + n;
1793         for (const auto &f : config.subVars(formattingDotName)) {
1794             QString def = config.getString(formattingDotName + Config::dot + f);
1795             if (!def.isEmpty()) {
1796                 int numParams = Config::numParams(def);
1797                 int numOccs = def.count("\1");
1798                 if (numParams != 1) {
1799                     config.lastLocation().warning(tr("Formatting '%1' must "
1800                                                      "have exactly one "
1801                                                      "parameter (found %2)")
1802                                                           .arg(n)
1803                                                           .arg(numParams));
1804                 } else if (numOccs > 1) {
1805                     config.lastLocation().fatal(tr("Formatting '%1' must "
1806                                                    "contain exactly one "
1807                                                    "occurrence of '\\1' "
1808                                                    "(found %2)")
1809                                                         .arg(n)
1810                                                         .arg(numOccs));
1811                 } else {
1812                     int paramPos = def.indexOf("\1");
1813                     fmtLeftMaps[f].insert(n, def.left(paramPos));
1814                     fmtRightMaps[f].insert(n, def.mid(paramPos + 1));
1815                 }
1816             }
1817         }
1818     }
1819 
1820     project_ = config.getString(CONFIG_PROJECT);
1821     outDir_ = config.getOutputDir();
1822     outSubdir_ = outDir_.mid(outDir_.lastIndexOf('/') + 1);
1823 
1824     outputPrefixes.clear();
1825     QStringList items = config.getStringList(CONFIG_OUTPUTPREFIXES);
1826     if (!items.isEmpty()) {
1827         for (const auto &prefix : items)
1828             outputPrefixes[prefix] = config.getString(CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
1829     } else {
1830         outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
1831         outputPrefixes[QLatin1String("JS")] = QLatin1String("js-");
1832     }
1833 
1834     outputSuffixes.clear();
1835     for (const auto &suffix : config.getStringList(CONFIG_OUTPUTSUFFIXES))
1836         outputSuffixes[suffix] = config.getString(CONFIG_OUTPUTSUFFIXES + Config::dot + suffix);
1837 
1838     noLinkErrors_ = config.getBool(CONFIG_NOLINKERRORS);
1839     autolinkErrors_ = config.getBool(CONFIG_AUTOLINKERRORS);
1840 }
1841 
1842 /*!
1843   Creates template-specific subdirs (e.g. /styles and /scripts for HTML)
1844   and copies the files to them.
1845   */
copyTemplateFiles(const QString & configVar,const QString & subDir)1846 void Generator::copyTemplateFiles(const QString &configVar, const QString &subDir)
1847 {
1848     Config &config = Config::instance();
1849     QStringList files = config.getCanonicalPathList(configVar, true);
1850     if (!files.isEmpty()) {
1851         QDir dirInfo;
1852         QString templateDir = outDir_ + QLatin1Char('/') + subDir;
1853         if (!dirInfo.exists(templateDir) && !dirInfo.mkdir(templateDir)) {
1854             config.lastLocation().fatal(
1855                     tr("Cannot create %1 directory '%2'").arg(subDir, templateDir));
1856         } else {
1857             for (const auto &file : files) {
1858                 if (!file.isEmpty())
1859                     Config::copyFile(config.lastLocation(), file, file, templateDir);
1860             }
1861         }
1862     }
1863 }
1864 
1865 /*!
1866     Reads format-specific variables from config, sets output
1867     (sub)directories, creates them on the filesystem and copies the
1868     template-specific files.
1869  */
initializeFormat()1870 void Generator::initializeFormat()
1871 {
1872     Config &config = Config::instance();
1873     outFileNames_.clear();
1874     useOutputSubdirs_ = true;
1875     if (config.getBool(format() + Config::dot + "nosubdirs"))
1876         resetUseOutputSubdirs();
1877 
1878     if (outputFormats.isEmpty())
1879         return;
1880 
1881     outDir_ = config.getOutputDir(format());
1882     if (outDir_.isEmpty()) {
1883         config.lastLocation().fatal(tr("No output directory specified in "
1884                                        "configuration file or on the command line"));
1885     } else {
1886         outSubdir_ = outDir_.mid(outDir_.lastIndexOf('/') + 1);
1887     }
1888 
1889     QDir dirInfo;
1890     if (dirInfo.exists(outDir_)) {
1891         if (!config.generating() && Generator::useOutputSubdirs()) {
1892             if (!Config::removeDirContents(outDir_))
1893                 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
1894         }
1895     } else if (!dirInfo.mkpath(outDir_)) {
1896         config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
1897     }
1898 
1899     // Output directory exists, which is enough for prepare phase.
1900     if (config.preparing())
1901         return;
1902 
1903     if (!dirInfo.exists(outDir_ + "/images") && !dirInfo.mkdir(outDir_ + "/images"))
1904         config.lastLocation().fatal(
1905                 tr("Cannot create images directory '%1'").arg(outDir_ + "/images"));
1906 
1907     copyTemplateFiles(format() + Config::dot + CONFIG_STYLESHEETS, "style");
1908     copyTemplateFiles(format() + Config::dot + CONFIG_SCRIPTS, "scripts");
1909     copyTemplateFiles(format() + Config::dot + CONFIG_EXTRAIMAGES, "images");
1910 
1911     // Use a format-specific .quotinginformation if defined, otherwise a global value
1912     if (config.subVars(format()).contains(CONFIG_QUOTINGINFORMATION))
1913         quoting_ = config.getBool(format() + Config::dot + CONFIG_QUOTINGINFORMATION);
1914     else
1915         quoting_ = config.getBool(CONFIG_QUOTINGINFORMATION);
1916 }
1917 
1918 /*!
1919   Appends each directory path in \a moreImageDirs to the
1920   list of image directories.
1921  */
augmentImageDirs(QSet<QString> & moreImageDirs)1922 void Generator::augmentImageDirs(QSet<QString> &moreImageDirs)
1923 {
1924     if (moreImageDirs.isEmpty())
1925         return;
1926     for (const auto &it : moreImageDirs)
1927         imageDirs.append(it);
1928 }
1929 
1930 /*!
1931   Sets the generator's pointer to the Config instance.
1932  */
initializeGenerator()1933 void Generator::initializeGenerator()
1934 {
1935     showInternal_ = Config::instance().getBool(CONFIG_SHOWINTERNAL);
1936     singleExec_ = Config::instance().getBool(CONFIG_SINGLEEXEC);
1937 }
1938 
matchAhead(const Atom * atom,Atom::AtomType expectedAtomType)1939 bool Generator::matchAhead(const Atom *atom, Atom::AtomType expectedAtomType)
1940 {
1941     return atom->next() && atom->next()->type() == expectedAtomType;
1942 }
1943 
1944 /*!
1945   Used for writing to the current output stream. Returns a
1946   reference to the current output stream, which is then used
1947   with the \c {<<} operator for writing.
1948  */
out()1949 QTextStream &Generator::out()
1950 {
1951     return *outStreamStack.top();
1952 }
1953 
outFileName()1954 QString Generator::outFileName()
1955 {
1956     return QFileInfo(static_cast<QFile *>(out().device())->fileName()).fileName();
1957 }
1958 
outputPrefix(const Node * node)1959 QString Generator::outputPrefix(const Node *node)
1960 {
1961     // Prefix is applied to QML and JS types
1962     if (node->isQmlType() || node->isQmlBasicType())
1963         return outputPrefixes[QLatin1String("QML")];
1964     if (node->isJsType() || node->isJsBasicType())
1965         return outputPrefixes[QLatin1String("JS")];
1966     return QString();
1967 }
1968 
outputSuffix(const Node * node)1969 QString Generator::outputSuffix(const Node *node)
1970 {
1971     // Suffix is applied to QML and JS types, as
1972     // well as module pages.
1973     if (node->isQmlModule() || node->isQmlType() || node->isQmlBasicType())
1974         return outputSuffixes[QLatin1String("QML")];
1975     if (node->isJsModule() || node->isJsType() || node->isJsBasicType())
1976         return outputSuffixes[QLatin1String("JS")];
1977     return QString();
1978 }
1979 
parseArg(const QString & src,const QString & tag,int * pos,int n,QStringRef * contents,QStringRef * par1,bool debug)1980 bool Generator::parseArg(const QString &src, const QString &tag, int *pos, int n,
1981                          QStringRef *contents, QStringRef *par1, bool debug)
1982 {
1983 #define SKIP_CHAR(c)                                                                               \
1984     if (debug)                                                                                     \
1985         qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i);               \
1986     if (i >= n || src[i] != c) {                                                                   \
1987         if (debug)                                                                                 \
1988             qDebug() << " char '" << c << "' not found";                                           \
1989         return false;                                                                              \
1990     }                                                                                              \
1991     ++i;
1992 
1993 #define SKIP_SPACE                                                                                 \
1994     while (i < n && src[i] == ' ')                                                                 \
1995         ++i;
1996 
1997     int i = *pos;
1998     int j = i;
1999 
2000     // assume "<@" has been parsed outside
2001     // SKIP_CHAR('<');
2002     // SKIP_CHAR('@');
2003 
2004     if (tag != QStringRef(&src, i, tag.length())) {
2005         return false;
2006     }
2007 
2008     if (debug)
2009         qDebug() << "haystack:" << src << "needle:" << tag << "i:" << i;
2010 
2011     // skip tag
2012     i += tag.length();
2013 
2014     // parse stuff like:  linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
2015     if (par1) {
2016         SKIP_SPACE;
2017         // read parameter name
2018         j = i;
2019         while (i < n && src[i].isLetter())
2020             ++i;
2021         if (src[i] == '=') {
2022             if (debug)
2023                 qDebug() << "read parameter" << QString(src.data() + j, i - j);
2024             SKIP_CHAR('=');
2025             SKIP_CHAR('"');
2026             // skip parameter name
2027             j = i;
2028             while (i < n && src[i] != '"')
2029                 ++i;
2030             *par1 = QStringRef(&src, j, i - j);
2031             SKIP_CHAR('"');
2032             SKIP_SPACE;
2033         } else {
2034             if (debug)
2035                 qDebug() << "no optional parameter found";
2036         }
2037     }
2038     SKIP_SPACE;
2039     SKIP_CHAR('>');
2040 
2041     // find contents up to closing "</@tag>
2042     j = i;
2043     for (; true; ++i) {
2044         if (i + 4 + tag.length() > n)
2045             return false;
2046         if (src[i] != '<')
2047             continue;
2048         if (src[i + 1] != '/')
2049             continue;
2050         if (src[i + 2] != '@')
2051             continue;
2052         if (tag != QStringRef(&src, i + 3, tag.length()))
2053             continue;
2054         if (src[i + 3 + tag.length()] != '>')
2055             continue;
2056         break;
2057     }
2058 
2059     *contents = QStringRef(&src, j, i - j);
2060 
2061     i += tag.length() + 4;
2062 
2063     *pos = i;
2064     if (debug)
2065         qDebug() << " tag " << tag << " found: pos now: " << i;
2066     return true;
2067 #undef SKIP_CHAR
2068 }
2069 
plainCode(const QString & markedCode)2070 QString Generator::plainCode(const QString &markedCode)
2071 {
2072     QString t = markedCode;
2073     t.replace(tag, QString());
2074     t.replace(quot, QLatin1String("\""));
2075     t.replace(gt, QLatin1String(">"));
2076     t.replace(lt, QLatin1String("<"));
2077     t.replace(amp, QLatin1String("&"));
2078     return t;
2079 }
2080 
setImageFileExtensions(const QStringList & extensions)2081 void Generator::setImageFileExtensions(const QStringList &extensions)
2082 {
2083     imgFileExts[format()] = extensions;
2084 }
2085 
singularPlural(Text & text,const NodeList & nodes)2086 void Generator::singularPlural(Text &text, const NodeList &nodes)
2087 {
2088     if (nodes.count() == 1)
2089         text << " is";
2090     else
2091         text << " are";
2092 }
2093 
skipAtoms(const Atom * atom,Atom::AtomType type) const2094 int Generator::skipAtoms(const Atom *atom, Atom::AtomType type) const
2095 {
2096     int skipAhead = 0;
2097     atom = atom->next();
2098     while (atom && atom->type() != type) {
2099         skipAhead++;
2100         atom = atom->next();
2101     }
2102     return skipAhead;
2103 }
2104 
2105 /*!
2106   Resets the variables used during text output.
2107  */
initializeTextOutput()2108 void Generator::initializeTextOutput()
2109 {
2110     inLink_ = false;
2111     inContents_ = false;
2112     inSectionHeading_ = false;
2113     inTableHeader_ = false;
2114     numTableRows_ = 0;
2115     threeColumnEnumValueTable_ = true;
2116     link_.clear();
2117     sectionNumber_.clear();
2118 }
2119 
supplementAlsoList(const Node * node,QVector<Text> & alsoList)2120 void Generator::supplementAlsoList(const Node *node, QVector<Text> &alsoList)
2121 {
2122     if (node->isFunction() && !node->isMacro()) {
2123         const auto fn = static_cast<const FunctionNode *>(node);
2124         if (fn->overloadNumber() == 0) {
2125             QString alternateName;
2126             const FunctionNode *alternateFunc = nullptr;
2127 
2128             if (fn->name().startsWith("set") && fn->name().size() >= 4) {
2129                 alternateName = fn->name()[3].toLower();
2130                 alternateName += fn->name().mid(4);
2131                 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2132 
2133                 if (!alternateFunc) {
2134                     alternateName = "is" + fn->name().mid(3);
2135                     alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2136                     if (!alternateFunc) {
2137                         alternateName = "has" + fn->name().mid(3);
2138                         alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2139                     }
2140                 }
2141             } else if (!fn->name().isEmpty()) {
2142                 alternateName = "set";
2143                 alternateName += fn->name()[0].toUpper();
2144                 alternateName += fn->name().mid(1);
2145                 alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
2146             }
2147 
2148             if (alternateFunc && alternateFunc->access() != Node::Private) {
2149                 int i;
2150                 for (i = 0; i < alsoList.size(); ++i) {
2151                     if (alsoList.at(i).toString().contains(alternateName))
2152                         break;
2153                 }
2154 
2155                 if (i == alsoList.size()) {
2156                     alternateName += "()";
2157 
2158                     Text also;
2159                     also << Atom(Atom::Link, alternateName)
2160                          << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << alternateName
2161                          << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
2162                     alsoList.prepend(also);
2163                 }
2164             }
2165         }
2166     }
2167 }
2168 
terminate()2169 void Generator::terminate()
2170 {
2171     for (const auto &generator : qAsConst(generators)) {
2172         if (outputFormats.contains(generator->format()))
2173             generator->terminateGenerator();
2174     }
2175 
2176     fmtLeftMaps.clear();
2177     fmtRightMaps.clear();
2178     imgFileExts.clear();
2179     imageFiles.clear();
2180     imageDirs.clear();
2181     outDir_.clear();
2182 }
2183 
terminateGenerator()2184 void Generator::terminateGenerator() {}
2185 
2186 /*!
2187   Trims trailing whitespace off the \a string and returns
2188   the trimmed string.
2189  */
trimmedTrailing(const QString & string,const QString & prefix,const QString & suffix)2190 QString Generator::trimmedTrailing(const QString &string, const QString &prefix,
2191                                    const QString &suffix)
2192 {
2193     QString trimmed = string;
2194     while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
2195         trimmed.truncate(trimmed.length() - 1);
2196 
2197     trimmed.append(suffix);
2198     trimmed.prepend(prefix);
2199     return trimmed;
2200 }
2201 
typeString(const Node * node)2202 QString Generator::typeString(const Node *node)
2203 {
2204     switch (node->nodeType()) {
2205     case Node::Namespace:
2206         return "namespace";
2207     case Node::Class:
2208         return "class";
2209     case Node::Struct:
2210         return "struct";
2211     case Node::Union:
2212         return "union";
2213     case Node::QmlType:
2214     case Node::QmlBasicType:
2215     case Node::JsBasicType:
2216         return "type";
2217     case Node::Page:
2218         return "documentation";
2219     case Node::Enum:
2220         return "enum";
2221     case Node::Typedef:
2222         return "typedef";
2223     case Node::TypeAlias:
2224         return "alias";
2225     case Node::Function: {
2226         const auto fn = static_cast<const FunctionNode *>(node);
2227         switch (fn->metaness()) {
2228         case FunctionNode::JsSignal:
2229         case FunctionNode::QmlSignal:
2230             return "signal";
2231         case FunctionNode::JsSignalHandler:
2232         case FunctionNode::QmlSignalHandler:
2233             return "signal handler";
2234         case FunctionNode::JsMethod:
2235         case FunctionNode::QmlMethod:
2236             return "method";
2237         default:
2238             break;
2239         }
2240         return "function";
2241     }
2242     case Node::Property:
2243     case Node::QmlProperty:
2244         return "property";
2245     case Node::Module:
2246     case Node::JsModule:
2247     case Node::QmlModule:
2248         return "module";
2249     case Node::SharedComment: {
2250         const auto &collective = static_cast<const SharedCommentNode *>(node)->collective();
2251         return collective.first()->nodeTypeString();
2252     }
2253     default:
2254         return "documentation";
2255     }
2256 }
2257 
unknownAtom(const Atom * atom)2258 void Generator::unknownAtom(const Atom *atom)
2259 {
2260     Location::internalError(
2261             tr("unknown atom type '%1' in %2 generator").arg(atom->typeString()).arg(format()));
2262 }
2263 
2264 QT_END_NAMESPACE
2265