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("&");
84 static QLatin1String gt(">");
85 static QLatin1String lt("<");
86 static QLatin1String 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