1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the tools applications of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
47 #include "codemarker.h"
49 #include "ditaxmlgenerator.h"
51 #include "editdistance.h"
52 #include "generator.h"
54 #include "openedlist.h"
56 #include "separator.h"
57 #include "tokenizer.h"
62 QString Generator::baseDir_;
63 QStringList Generator::exampleDirs;
64 QStringList Generator::exampleImgExts;
65 QMap<QString, QMap<QString, QString> > Generator::fmtLeftMaps;
66 QMap<QString, QMap<QString, QString> > Generator::fmtRightMaps;
67 QList<Generator *> Generator::generators;
68 QStringList Generator::imageDirs;
69 QStringList Generator::imageFiles;
70 QMap<QString, QStringList> Generator::imgFileExts;
71 QString Generator::outDir_;
72 QSet<QString> Generator::outputFormats;
73 QHash<QString, QString> Generator::outputPrefixes;
74 QString Generator::project;
75 QStringList Generator::scriptDirs;
76 QStringList Generator::scriptFiles;
77 QString Generator::sinceTitles[] =
81 " New Member Functions",
82 " New Functions in Namespaces",
83 " New Global Functions",
90 " New QML Properties",
92 " New QML Signal Handlers",
96 QStringList Generator::styleDirs;
97 QStringList Generator::styleFiles;
100 Generator::Generator()
108 generators.prepend(this);
111 Generator::~Generator()
113 generators.removeAll(this);
116 void Generator::appendFullName(Text& text,
117 const Node *apparentNode,
118 const Node *relative,
120 const Node *actualNode)
123 actualNode = apparentNode;
124 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
125 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
126 << Atom(Atom::String, marker->plainFullName(apparentNode, relative))
127 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
130 void Generator::appendFullName(Text& text,
131 const Node *apparentNode,
132 const QString& fullName,
133 const Node *actualNode)
136 actualNode = apparentNode;
137 text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
138 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
139 << Atom(Atom::String, fullName)
140 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
143 void Generator::appendFullNames(Text& text,
144 const NodeList& nodes,
145 const Node* relative,
148 NodeList::ConstIterator n = nodes.begin();
150 while (n != nodes.end()) {
151 appendFullName(text,*n,relative,marker);
152 text << comma(index++,nodes.count());
157 void Generator::appendSortedNames(Text& text,
158 const ClassNode *classe,
159 const QList<RelatedClass> &classes,
162 QList<RelatedClass>::ConstIterator r;
163 QMap<QString,Text> classMap;
167 while (r != classes.end()) {
168 if ((*r).node->access() == Node::Public &&
169 (*r).node->status() != Node::Internal
170 && !(*r).node->doc().isEmpty()) {
172 appendFullName(className, (*r).node, classe, marker);
173 classMap[className.toString().toLower()] = className;
178 QStringList classNames = classMap.keys();
181 foreach (const QString &className, classNames) {
182 text << classMap[className];
183 text << separator(index++, classNames.count());
187 void Generator::appendSortedQmlNames(Text& text,
189 const NodeList& subs,
192 QMap<QString,Text> classMap;
195 for (int i = 0; i < subs.size(); ++i) {
197 if (!base->isQtQuickNode() || !subs[i]->isQtQuickNode() ||
198 (base->qmlModuleIdentifier() == subs[i]->qmlModuleIdentifier())) {
199 appendFullName(t, subs[i], base, marker);
200 classMap[t.toString().toLower()] = t;
204 QStringList names = classMap.keys();
207 foreach (const QString &name, names) {
208 text << classMap[name];
209 text << separator(index++, names.count());
214 Creates the file named \a fileName in the output directory.
215 Attaches a QTextStream to the created file, which is written
216 to all over the place using out().
218 void Generator::beginSubPage(const InnerNode* node, const QString& fileName)
220 QString path = outputDir() + QLatin1Char('/');
221 if (!node->outputSubdirectory().isEmpty())
222 path += node->outputSubdirectory() + QLatin1Char('/');
224 QFile* outFile = new QFile(path);
225 if (!outFile->open(QFile::WriteOnly))
226 node->location().fatal(tr("Cannot open output file '%1'").arg(outFile->fileName()));
227 QTextStream* out = new QTextStream(outFile);
230 out->setCodec(outputCodec);
231 outStreamStack.push(out);
232 const_cast<InnerNode*>(node)->setOutputFileName(fileName);
236 Flush the text stream associated with the subpage, and
237 then pop it off the text stream stack and delete it.
238 This terminates output of the subpage.
240 void Generator::endSubPage()
242 outStreamStack.top()->flush();
243 delete outStreamStack.top()->device();
244 delete outStreamStack.pop();
247 void Generator::endText(const Node * /* relative */,
248 CodeMarker * /* marker */)
252 QString Generator::fileBase(const Node *node) const
255 node = node->relates();
256 else if (!node->isInnerNode())
257 node = node->parent();
258 if (node->subType() == Node::QmlPropertyGroup) {
259 node = node->parent();
262 QString base = node->doc().baseName();
266 const Node *p = node;
269 const Node *pp = p->parent();
270 base.prepend(p->name());
271 if (!p->qmlModuleIdentifier().isEmpty())
272 base.prepend(p->qmlModuleIdentifier()+QChar('-'));
274 To avoid file name conflicts in the html directory,
275 we prepend a prefix (by default, "qml-") to the file name of QML
278 if ((p->subType() == Node::QmlClass) ||
279 (p->subType() == Node::QmlBasicType)) {
280 base.prepend(outputPrefix(QLatin1String("QML")));
282 if (!pp || pp->name().isEmpty() || pp->type() == Node::Fake)
284 base.prepend(QLatin1Char('-'));
287 if (node->type() == Node::Fake) {
288 if (node->subType() == Node::Collision) {
289 const NameCollisionNode* ncn = static_cast<const NameCollisionNode*>(node);
290 if (ncn->currentChild())
291 return fileBase(ncn->currentChild());
292 base.prepend("collision-");
294 //Was QDOC2_COMPAT, required for index.html
295 if (base.endsWith(".html"))
296 base.truncate(base.length() - 5);
298 if (node->subType() == Node::QmlModule) {
299 base.prepend("qmlmodule-");
301 if (node->subType() == Node::Module) {
302 base.append("-module");
306 // the code below is effectively equivalent to:
307 // base.replace(QRegExp("[^A-Za-z0-9]+"), " ");
308 // base = base.trimmed();
309 // base.replace(QLatin1Char(' '), QLatin1Char('-'));
310 // base = base.toLower();
311 // as this function accounted for ~8% of total running time
312 // we optimize a bit...
315 // +5 prevents realloc in fileName() below
316 res.reserve(base.size() + 5);
318 for (int i = 0; i != base.size(); ++i) {
319 QChar c = base.at(i);
320 uint u = c.unicode();
321 if (u >= 'A' && u <= 'Z')
323 if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) {
324 res += QLatin1Char(u);
328 res += QLatin1Char('-');
332 while (res.endsWith(QLatin1Char('-')))
338 If the \a node has a URL, return the URL as the file name.
339 Otherwise, construct the file name from the fileBase() and
340 the fileExtension(), and return the constructed name.
342 QString Generator::fileName(const Node* node) const
344 if (!node->url().isEmpty())
347 QString name = fileBase(node);
348 name += QLatin1Char('.');
349 name += fileExtension(node);
354 For generating the "New Classes... in x.y" section on the
355 What's New in Qt x.y" page.
357 void Generator::findAllSince(const InnerNode *node)
359 NodeList::const_iterator child = node->childNodes().constBegin();
361 // Traverse the tree, starting at the node supplied.
363 while (child != node->childNodes().constEnd()) {
365 QString sinceString = (*child)->since();
367 if (((*child)->access() != Node::Private) && !sinceString.isEmpty()) {
369 // Insert a new entry into each map for each new since string found.
370 NewSinceMaps::iterator nsmap = newSinceMaps.find(sinceString);
371 if (nsmap == newSinceMaps.end())
372 nsmap = newSinceMaps.insert(sinceString,NodeMultiMap());
374 NewClassMaps::iterator ncmap = newClassMaps.find(sinceString);
375 if (ncmap == newClassMaps.end())
376 ncmap = newClassMaps.insert(sinceString,NodeMap());
378 NewClassMaps::iterator nqcmap = newQmlClassMaps.find(sinceString);
379 if (nqcmap == newQmlClassMaps.end())
380 nqcmap = newQmlClassMaps.insert(sinceString,NodeMap());
382 if ((*child)->type() == Node::Function) {
383 // Insert functions into the general since map.
384 FunctionNode *func = static_cast<FunctionNode *>(*child);
385 if ((func->status() > Node::Obsolete) &&
386 (func->metaness() != FunctionNode::Ctor) &&
387 (func->metaness() != FunctionNode::Dtor)) {
388 nsmap.value().insert(func->name(),(*child));
391 else if ((*child)->url().isEmpty()) {
392 if ((*child)->type() == Node::Class && !(*child)->doc().isEmpty()) {
393 // Insert classes into the since and class maps.
394 QString className = (*child)->name();
395 if ((*child)->parent() &&
396 (*child)->parent()->type() == Node::Namespace &&
397 !(*child)->parent()->name().isEmpty())
398 className = (*child)->parent()->name()+"::"+className;
400 nsmap.value().insert(className,(*child));
401 ncmap.value().insert(className,(*child));
403 else if ((*child)->subType() == Node::QmlClass) {
404 // Insert QML elements into the since and element maps.
405 QString className = (*child)->name();
406 if ((*child)->parent() &&
407 (*child)->parent()->type() == Node::Namespace &&
408 !(*child)->parent()->name().isEmpty())
409 className = (*child)->parent()->name()+"::"+className;
411 nsmap.value().insert(className,(*child));
412 nqcmap.value().insert(className,(*child));
414 else if ((*child)->type() == Node::QmlProperty) {
415 // Insert QML properties into the since map.
416 QString propertyName = (*child)->name();
417 nsmap.value().insert(propertyName,(*child));
421 // Insert external documents into the general since map.
422 QString name = (*child)->name();
423 if ((*child)->parent() &&
424 (*child)->parent()->type() == Node::Namespace &&
425 !(*child)->parent()->name().isEmpty())
426 name = (*child)->parent()->name()+"::"+name;
428 nsmap.value().insert(name,(*child));
431 // Find child nodes with since commands.
432 if ((*child)->isInnerNode()) {
433 findAllSince(static_cast<InnerNode *>(*child));
440 QMap<QString, QString>& Generator::formattingLeftMap()
442 return fmtLeftMaps[format()];
445 QMap<QString, QString>& Generator::formattingRightMap()
447 return fmtRightMaps[format()];
450 QString Generator::fullName(const Node *node,
451 const Node *relative,
452 CodeMarker *marker) const
454 if (node->type() == Node::Fake) {
455 const FakeNode* fn = static_cast<const FakeNode *>(node);
457 // Removed for QTBUG-22870
458 // Unremoved by mws 30/03/12
459 if (!fn->qmlModuleIdentifier().isEmpty())
460 return fn->qmlModuleIdentifier() + "::" + fn->title();
464 else if (node->type() == Node::Class &&
465 !(static_cast<const ClassNode *>(node))->serviceName().isEmpty())
466 return (static_cast<const ClassNode *>(node))->serviceName();
468 return marker->plainFullName(node, relative);
471 void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
473 QList<Text> alsoList = node->doc().alsoList();
474 supplementAlsoList(node, alsoList);
476 if (!alsoList.isEmpty()) {
478 text << Atom::ParaLeft
479 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
481 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
483 for (int i = 0; i < alsoList.size(); ++i)
484 text << alsoList.at(i) << separator(i, alsoList.size());
486 text << Atom::ParaRight;
487 generateText(text, node, marker);
491 int Generator::generateAtom(const Atom * /* atom */,
492 const Node * /* relative */,
493 CodeMarker * /* marker */)
498 const Atom *Generator::generateAtomList(const Atom *atom,
499 const Node *relative,
505 if (atom->type() == Atom::FormatIf) {
506 int numAtoms0 = numAtoms;
507 bool rightFormat = canHandleFormat(atom->string());
508 atom = generateAtomList(atom->next(),
511 generate && rightFormat,
516 if (atom->type() == Atom::FormatElse) {
518 atom = generateAtomList(atom->next(),
521 generate && !rightFormat,
527 if (atom->type() == Atom::FormatEndif) {
528 if (generate && numAtoms0 == numAtoms) {
529 relative->location().warning(tr("Output format %1 not handled %2")
530 .arg(format()).arg(outFileName()));
531 Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
532 generateAtomList(&unhandledFormatAtom,
541 else if (atom->type() == Atom::FormatElse ||
542 atom->type() == Atom::FormatEndif) {
548 n += generateAtom(atom, relative, marker);
558 void Generator::generateBody(const Node *node, CodeMarker *marker)
562 if (node->type() == Node::Fake) {
563 const FakeNode *fake = static_cast<const FakeNode *>(node);
564 if (fake->subType() == Node::Example) {
565 generateExampleFiles(fake, marker);
567 else if ((fake->subType() == Node::File) || (fake->subType() == Node::Image)) {
571 if (node->doc().isEmpty()) {
572 if (!quiet && !node->isReimp()) { // ### might be unnecessary
573 node->location().warning(tr("No documentation for '%1'")
574 .arg(marker->plainFullName(node)));
578 if (node->type() == Node::Function) {
579 const FunctionNode *func = static_cast<const FunctionNode *>(node);
580 if (func->reimplementedFrom() != 0)
581 generateReimplementedFrom(func, marker);
584 if (!generateText(node->doc().body(), node, marker)) {
589 if (node->type() == Node::Enum) {
590 const EnumNode *enume = (const EnumNode *) node;
592 QSet<QString> definedItems;
593 QList<EnumItem>::ConstIterator it = enume->items().begin();
594 while (it != enume->items().end()) {
595 definedItems.insert((*it).name());
599 QSet<QString> documentedItems = enume->doc().enumItemNames().toSet();
600 QSet<QString> allItems = definedItems + documentedItems;
601 if (allItems.count() > definedItems.count() ||
602 allItems.count() > documentedItems.count()) {
603 QSet<QString>::ConstIterator a = allItems.begin();
604 while (a != allItems.end()) {
605 if (!definedItems.contains(*a)) {
607 QString best = nearestName(*a, definedItems);
608 if (!best.isEmpty() && !documentedItems.contains(best))
609 details = tr("Maybe you meant '%1'?").arg(best);
611 node->doc().location().warning(
612 tr("No such enum item '%1' in %2").arg(*a).arg(marker->plainFullName(node)),
615 qDebug() << "VOID:" << node->name() << definedItems;
617 else if (!documentedItems.contains(*a)) {
618 node->doc().location().warning(
619 tr("Undocumented enum item '%1' in %2").arg(*a).arg(marker->plainFullName(node)));
625 else if (node->type() == Node::Function) {
626 const FunctionNode *func = static_cast<const FunctionNode *>(node);
627 QSet<QString> definedParams;
628 QList<Parameter>::ConstIterator p = func->parameters().begin();
629 while (p != func->parameters().end()) {
630 if ((*p).name().isEmpty() && (*p).leftType() != QLatin1String("...")
631 && func->name() != QLatin1String("operator++")
632 && func->name() != QLatin1String("operator--")) {
633 node->doc().location().warning(tr("Missing parameter name"));
636 definedParams.insert((*p).name());
641 QSet<QString> documentedParams = func->doc().parameterNames();
642 QSet<QString> allParams = definedParams + documentedParams;
643 if (allParams.count() > definedParams.count()
644 || allParams.count() > documentedParams.count()) {
645 QSet<QString>::ConstIterator a = allParams.begin();
646 while (a != allParams.end()) {
647 if (!definedParams.contains(*a)) {
649 QString best = nearestName(*a, definedParams);
651 details = tr("Maybe you meant '%1'?").arg(best);
653 node->doc().location().warning(
654 tr("No such parameter '%1' in %2").arg(*a).arg(marker->plainFullName(node)),
657 else if (!(*a).isEmpty() && !documentedParams.contains(*a)) {
658 bool needWarning = (func->status() > Node::Obsolete);
659 if (func->overloadNumber() > 1) {
660 FunctionNode *primaryFunc =
661 func->parent()->findFunctionNode(func->name());
663 foreach (const Parameter ¶m,
664 primaryFunc->parameters()) {
665 if (param.name() == *a) {
672 if (needWarning && !func->isReimp())
673 node->doc().location().warning(
674 tr("Undocumented parameter '%1' in %2")
675 .arg(*a).arg(marker->plainFullName(node)));
681 Something like this return value check should
682 be implemented at some point.
684 if (func->status() > Node::Obsolete && func->returnType() == "bool"
685 && func->reimplementedFrom() == 0 && !func->isOverload()) {
686 QString body = func->doc().body().toString();
687 if (!body.contains("return", Qt::CaseInsensitive))
688 node->doc().location().warning(tr("Undocumented return value"));
693 if (node->type() == Node::Fake) {
694 const FakeNode *fake = static_cast<const FakeNode *>(node);
695 if (fake->subType() == Node::File) {
698 Doc::quoteFromFile(fake->doc().location(), quoter, fake->name());
699 QString code = quoter.quoteTo(fake->location(), "", "");
700 CodeMarker *codeMarker = CodeMarker::markerForFileName(fake->name());
701 text << Atom(codeMarker->atomType(), code);
702 generateText(text, fake, codeMarker);
707 void Generator::generateClassLikeNode(InnerNode* /* classe */, CodeMarker* /* marker */)
711 void Generator::generateExampleFiles(const FakeNode *fake, CodeMarker *marker)
713 if (fake->childNodes().isEmpty())
715 generateFileList(fake, marker, Node::File, QString("Files:"));
716 generateFileList(fake, marker, Node::Image, QString("Images:"));
719 void Generator::generateFakeNode(FakeNode* /* fake */, CodeMarker* /* marker */)
724 This function is called when the documentation for an
725 example is being formatted. It outputs the list of source
726 files comprising the example, and the list of images used
727 by the example. The images are copied into a subtree of
728 \c{...doc/html/images/used-in-examples/...}
730 void Generator::generateFileList(const FakeNode* fake,
732 Node::SubType subtype,
737 OpenedList openedList(OpenedList::Bullet);
739 text << Atom::ParaLeft << tag << Atom::ParaRight
740 << Atom(Atom::ListLeft, openedList.styleString());
742 foreach (const Node* child, fake->childNodes()) {
743 if (child->subType() == subtype) {
745 QString file = child->name();
746 if (subtype == Node::Image) {
747 if (!file.isEmpty()) {
749 QString userFriendlyFilePath;
750 QString srcPath = Config::findFile(fake->location(),
755 userFriendlyFilePath);
756 userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/'));
758 QString imgOutDir = outDir_ + "/images/used-in-examples/" + userFriendlyFilePath;
759 if (!dirInfo.mkpath(imgOutDir))
760 fake->location().fatal(tr("Cannot create output directory '%1'")
763 QString imgOutName = Config::copyFile(fake->location(),
772 text << Atom(Atom::ListItemNumber, openedList.numberString())
773 << Atom(Atom::ListItemLeft, openedList.styleString())
775 << Atom(Atom::Link, file)
776 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
778 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
780 << Atom(Atom::ListItemRight, openedList.styleString());
783 text << Atom(Atom::ListRight, openedList.styleString());
785 generateText(text, fake, marker);
788 void Generator::generateInheritedBy(const ClassNode *classe,
791 if (!classe->derivedClasses().isEmpty()) {
793 text << Atom::ParaLeft
794 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
796 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
798 appendSortedNames(text, classe, classe->derivedClasses(), marker);
799 text << Atom::ParaRight;
800 generateText(text, classe, marker);
804 void Generator::generateInherits(const ClassNode *classe, CodeMarker *marker)
806 QList<RelatedClass>::ConstIterator r;
809 if (!classe->baseClasses().isEmpty()) {
811 text << Atom::ParaLeft
812 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
814 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
816 r = classe->baseClasses().begin();
818 while (r != classe->baseClasses().end()) {
819 text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node))
820 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
821 << Atom(Atom::String, (*r).dataTypeWithTemplateArgs)
822 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
824 if ((*r).access == Node::Protected) {
825 text << " (protected)";
827 else if ((*r).access == Node::Private) {
828 text << " (private)";
830 text << separator(index++, classe->baseClasses().count());
833 text << Atom::ParaRight;
834 generateText(text, classe, marker);
839 Recursive writing of HTML files from the root \a node.
841 \note NameCollisionNodes are skipped here and processed
842 later. See HtmlGenerator::generateCollisionPages() for
845 void Generator::generateInnerNode(InnerNode* node)
847 if (!node->url().isNull())
850 if (node->type() == Node::Fake) {
851 FakeNode* fakeNode = static_cast<FakeNode*>(node);
852 if (fakeNode->subType() == Node::ExternalPage)
854 if (fakeNode->subType() == Node::Image)
856 if (fakeNode->subType() == Node::QmlPropertyGroup)
858 if (fakeNode->subType() == Node::Page) {
859 if (node->count() > 0)
860 qDebug("PAGE %s HAS CHILDREN", qPrintable(fakeNode->title()));
865 Obtain a code marker for the source file.
867 CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
869 if (node->parent() != 0) {
871 Skip name collision nodes here and process them
872 later in generateCollisionPages(). Each one is
873 appended to a list for later.
875 if ((node->type() == Node::Fake) && (node->subType() == Node::Collision)) {
876 NameCollisionNode* ncn = static_cast<NameCollisionNode*>(node);
877 collisionNodes.append(const_cast<NameCollisionNode*>(ncn));
880 beginSubPage(node, fileName(node));
881 if (node->type() == Node::Namespace || node->type() == Node::Class) {
882 generateClassLikeNode(node, marker);
884 else if (node->type() == Node::Fake) {
885 generateFakeNode(static_cast<FakeNode*>(node), marker);
891 NodeList::ConstIterator c = node->childNodes().begin();
892 while (c != node->childNodes().end()) {
893 if ((*c)->isInnerNode() && (*c)->access() != Node::Private) {
894 generateInnerNode((InnerNode*)*c);
901 Generate a list of maintainers in the output
903 void Generator::generateMaintainerList(const InnerNode* node, CodeMarker* marker)
905 QStringList sl = getMetadataElements(node,"maintainer");
909 text << Atom::ParaLeft
910 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
912 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD);
914 for (int i = 0; i < sl.size(); ++i)
915 text << sl.at(i) << separator(i, sl.size());
917 text << Atom::ParaRight;
918 generateText(text, node, marker);
924 Output the "Inherit by" list for the QML element,
925 if it is inherited by any other elements.
927 void Generator::generateQmlInheritedBy(const QmlClassNode* qcn,
932 QmlClassNode::subclasses(qcn->name(),subs);
933 if (!subs.isEmpty()) {
935 text << Atom::ParaLeft << "Inherited by ";
936 appendSortedQmlNames(text,qcn,subs,marker);
937 text << Atom::ParaRight;
938 generateText(text, qcn, marker);
947 void Generator::generateQmlInherits(const QmlClassNode* , CodeMarker* )
955 Extract sections of markup text surrounded by \e qmltext
956 and \e endqmltext and output them.
958 bool Generator::generateQmlText(const Text& text,
959 const Node *relative,
961 const QString& /* qmlName */ )
963 const Atom* atom = text.firstAtom();
967 startText(relative, marker);
969 if (atom->type() != Atom::QmlText)
973 while (atom && (atom->type() != Atom::EndQmlText)) {
974 int n = 1 + generateAtom(atom, relative, marker);
980 endText(relative, marker);
987 void Generator::generateReimplementedFrom(const FunctionNode *func,
990 if (func->reimplementedFrom() != 0) {
991 const FunctionNode *from = func->reimplementedFrom();
992 if (from->access() != Node::Private &&
993 from->parent()->access() != Node::Private) {
995 text << Atom::ParaLeft << "Reimplemented from ";
996 QString fullName = from->parent()->name() + "::" + from->name() + "()";
997 appendFullName(text, from->parent(), fullName, from);
998 text << "." << Atom::ParaRight;
999 generateText(text, func, marker);
1004 void Generator::generateSince(const Node *node, CodeMarker *marker)
1006 if (!node->since().isEmpty()) {
1008 text << Atom::ParaLeft
1010 << typeString(node);
1011 if (node->type() == Node::Enum)
1012 text << " was introduced or modified in ";
1014 text << " was introduced in ";
1016 QStringList since = node->since().split(QLatin1Char(' '));
1017 if (since.count() == 1) {
1018 // Handle legacy use of \since <version>.
1019 if (project.isEmpty())
1023 text << " " << since[0];
1025 // Reconstruct the <project> <version> string.
1026 text << " " << since.join(" ");
1029 text << "." << Atom::ParaRight;
1030 generateText(text, node, marker);
1034 void Generator::generateStatus(const Node *node, CodeMarker *marker)
1038 switch (node->status()) {
1039 case Node::Commendable:
1042 case Node::Preliminary:
1043 text << Atom::ParaLeft
1044 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1047 << " is under development and is subject to change."
1048 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1051 case Node::Deprecated:
1052 text << Atom::ParaLeft;
1053 if (node->isInnerNode())
1054 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1055 text << "This " << typeString(node) << " is deprecated.";
1056 if (node->isInnerNode())
1057 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1058 text << Atom::ParaRight;
1060 case Node::Obsolete:
1061 text << Atom::ParaLeft;
1062 if (node->isInnerNode())
1063 text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
1064 text << "This " << typeString(node) << " is obsolete.";
1065 if (node->isInnerNode())
1066 text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
1067 text << " It is provided to keep old source code working. "
1068 << "We strongly advise against "
1069 << "using it in new code." << Atom::ParaRight;
1072 // reimplemented in HtmlGenerator subclass
1073 if (node->isInnerNode()) {
1074 text << Atom::ParaLeft
1075 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
1078 << " is part of the Qt compatibility layer."
1079 << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
1080 << " It is provided to keep old source code working. "
1081 << "We strongly advise against using it in new code."
1085 case Node::Internal:
1089 generateText(text, node, marker);
1092 bool Generator::generateText(const Text& text,
1093 const Node *relative,
1096 bool result = false;
1097 if (text.firstAtom() != 0) {
1099 startText(relative, marker);
1100 generateAtomList(text.firstAtom(),
1105 endText(relative, marker);
1111 void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
1114 Node::ThreadSafeness threadSafeness = node->threadSafeness();
1117 rlink << Atom(Atom::Link,"reentrant")
1118 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1120 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1123 tlink << Atom(Atom::Link,"thread-safe")
1124 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1126 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1128 switch (threadSafeness) {
1129 case Node::UnspecifiedSafeness:
1131 case Node::NonReentrant:
1132 text << Atom::ParaLeft
1133 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1135 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1143 case Node::Reentrant:
1144 case Node::ThreadSafe:
1145 text << Atom::ParaLeft
1146 << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
1148 << Atom(Atom::FormattingRight,ATOM_FORMATTING_BOLD)
1151 if (node->isInnerNode()) {
1152 const InnerNode* innerNode = static_cast<const InnerNode*>(node);
1153 text << "All functions in this "
1156 if (threadSafeness == Node::ThreadSafe)
1161 bool exceptions = false;
1163 NodeList threadsafe;
1164 NodeList nonreentrant;
1165 NodeList::ConstIterator c = innerNode->childNodes().begin();
1166 while (c != innerNode->childNodes().end()) {
1168 if ((*c)->status() != Node::Obsolete){
1169 switch ((*c)->threadSafeness()) {
1170 case Node::Reentrant:
1171 reentrant.append(*c);
1172 if (threadSafeness == Node::ThreadSafe)
1175 case Node::ThreadSafe:
1176 threadsafe.append(*c);
1177 if (threadSafeness == Node::Reentrant)
1180 case Node::NonReentrant:
1181 nonreentrant.append(*c);
1192 else if (threadSafeness == Node::Reentrant) {
1193 if (nonreentrant.isEmpty()) {
1194 if (!threadsafe.isEmpty()) {
1196 appendFullNames(text,threadsafe,innerNode,marker);
1197 singularPlural(text,threadsafe);
1198 text << " also " << tlink << ".";
1204 text << ", except for ";
1205 appendFullNames(text,nonreentrant,innerNode,marker);
1207 singularPlural(text,nonreentrant);
1208 text << " nonreentrant.";
1209 if (!threadsafe.isEmpty()) {
1211 appendFullNames(text,threadsafe,innerNode,marker);
1212 singularPlural(text,threadsafe);
1213 text << " " << tlink << ".";
1217 else { // thread-safe
1218 if (!nonreentrant.isEmpty() || !reentrant.isEmpty()) {
1219 text << ", except for ";
1220 if (!reentrant.isEmpty()) {
1221 appendFullNames(text,reentrant,innerNode,marker);
1223 singularPlural(text,reentrant);
1224 text << " only " << rlink;
1225 if (!nonreentrant.isEmpty())
1228 if (!nonreentrant.isEmpty()) {
1229 appendFullNames(text,nonreentrant,innerNode,marker);
1231 singularPlural(text,nonreentrant);
1232 text << " nonreentrant.";
1239 text << "This " << typeString(node) << " is ";
1240 if (threadSafeness == Node::ThreadSafe)
1246 text << Atom::ParaRight;
1248 generateText(text,node,marker);
1252 This function is recursive.
1254 void Generator::generateTree(Tree *tree)
1257 generateInnerNode(tree->root());
1260 Generator *Generator::generatorForFormat(const QString& format)
1262 QList<Generator *>::ConstIterator g = generators.begin();
1263 while (g != generators.end()) {
1264 if ((*g)->format() == format)
1272 This function can be called if getLink() returns an empty
1273 string. It tests the \a atom string to see if it is a link
1274 of the form <element> :: <name>, where <element> is a QML
1275 element or component without a module qualifier. If so, it
1276 constructs a link to the <name> clause on the disambiguation
1277 page for <element> and returns that link string. It also
1278 adds the <name> as a target in the NameCollisionNode for
1279 <element>. These clauses are then constructed when the
1280 disambiguation page is actually generated.
1282 QString Generator::getCollisionLink(const Atom* atom)
1285 if (!atom->string().contains("::"))
1287 QStringList path = atom->string().split("::");
1288 NameCollisionNode* ncn = tree_->findCollisionNode(path[0]);
1291 if (atom->next() && atom->next()->next()) {
1292 if (atom->next()->type() == Atom::FormattingLeft &&
1293 atom->next()->next()->type() == Atom::String)
1294 label = atom->next()->next()->string();
1296 ncn->addLinkTarget(path[1],label);
1297 link = fileName(ncn);
1298 link += QLatin1Char('#');
1299 link += Doc::canonicalTitle(path[1]);
1306 Looks up the tag \a t in the map of metadata values for the
1307 current topic in \a inner. If a value for the tag is found,
1308 the value is returned.
1310 \note If \a t is found in the metadata map, it is erased.
1311 i.e. Once you call this function for a particular \a t,
1314 QString Generator::getMetadataElement(const InnerNode* inner, const QString& t)
1317 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1318 QStringMultiMap::iterator i = metaTagMap.find(t);
1319 if (i != metaTagMap.end()) {
1321 metaTagMap.erase(i);
1327 Looks up the tag \a t in the map of metadata values for the
1328 current topic in \a inner. If values for the tag are found,
1329 they are returned in a string list.
1331 \note If \a t is found in the metadata map, all the pairs
1332 having the key \a t are erased. i.e. Once you call this
1333 function for a particular \a t, you consume \a t.
1335 QStringList Generator::getMetadataElements(const InnerNode* inner, const QString& t)
1338 QStringMultiMap& metaTagMap = const_cast<QStringMultiMap&>(inner->doc().metaTagMap());
1339 s = metaTagMap.values(t);
1341 metaTagMap.remove(t);
1346 Returns a relative path name for an image.
1348 QString Generator::imageFileName(const Node *relative, const QString& fileBase)
1350 QString userFriendlyFilePath;
1351 QString filePath = Config::findFile(
1352 relative->doc().location(), imageFiles, imageDirs, fileBase,
1353 imgFileExts[format()], userFriendlyFilePath);
1355 if (filePath.isEmpty())
1358 QString path = Config::copyFile(relative->doc().location(),
1360 userFriendlyFilePath,
1361 outputDir() + QLatin1String("/images"));
1362 QString images = "images";
1364 images.append(QLatin1Char('/'));
1365 return images + path;
1368 QString Generator::indent(int level, const QString& markedCode)
1377 while (i < (int) markedCode.length()) {
1378 if (markedCode.at(i) == QLatin1Char('\n')) {
1383 for (int j = 0; j < level; j++)
1384 t += QLatin1Char(' ');
1388 t += markedCode.at(i++);
1393 void Generator::initialize(const Config &config)
1395 outputFormats = config.getOutputFormats();
1396 if (!outputFormats.isEmpty()) {
1397 outDir_ = config.getOutputDir();
1398 baseDir_ = config.getString(CONFIG_BASEDIR);
1399 if (!baseDir_.isEmpty())
1400 config.location().warning(tr("\"basedir\" specified in config file. "
1401 "All output will be in module directories of the output directory"));
1402 if (outDir_.isEmpty())
1403 config.lastLocation().fatal(tr("No output directory specified in configuration file or on the command line"));
1406 if (dirInfo.exists(outDir_)) {
1407 if (!Config::removeDirContents(outDir_))
1408 config.lastLocation().error(tr("Cannot empty output directory '%1'").arg(outDir_));
1411 if (!dirInfo.mkpath(outDir_))
1412 config.lastLocation().fatal(tr("Cannot create output directory '%1'").arg(outDir_));
1415 if (!dirInfo.mkdir(outDir_ + "/images"))
1416 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1417 .arg(outDir_ + "/images"));
1418 if (!dirInfo.mkdir(outDir_ + "/images/used-in-examples"))
1419 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1420 .arg(outDir_ + "/images/used-in-examples"));
1421 if (!dirInfo.mkdir(outDir_ + "/scripts"))
1422 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1423 .arg(outDir_ + "/scripts"));
1424 if (!dirInfo.mkdir(outDir_ + "/style"))
1425 config.lastLocation().fatal(tr("Cannot create output directory '%1'")
1426 .arg(outDir_ + "/style"));
1429 imageFiles = config.getCleanPathList(CONFIG_IMAGES);
1430 imageDirs = config.getCleanPathList(CONFIG_IMAGEDIRS);
1431 scriptFiles = config.getCleanPathList(CONFIG_SCRIPTS);
1432 scriptDirs = config.getCleanPathList(CONFIG_SCRIPTDIRS);
1433 styleFiles = config.getCleanPathList(CONFIG_STYLES);
1434 styleDirs = config.getCleanPathList(CONFIG_STYLEDIRS);
1435 exampleDirs = config.getCleanPathList(CONFIG_EXAMPLEDIRS);
1436 exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot +
1437 CONFIG_IMAGEEXTENSIONS);
1439 QString imagesDotFileExtensions =
1440 CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS;
1441 QSet<QString> formats = config.subVars(imagesDotFileExtensions);
1442 QSet<QString>::ConstIterator f = formats.begin();
1443 while (f != formats.end()) {
1444 imgFileExts[*f] = config.getStringList(imagesDotFileExtensions +
1449 QList<Generator *>::ConstIterator g = generators.begin();
1450 while (g != generators.end()) {
1451 if (outputFormats.contains((*g)->format())) {
1452 (*g)->initializeGenerator(config);
1453 QStringList extraImages =
1454 config.getCleanPathList(CONFIG_EXTRAIMAGES+Config::dot+(*g)->format());
1455 QStringList::ConstIterator e = extraImages.begin();
1456 while (e != extraImages.end()) {
1457 QString userFriendlyFilePath;
1458 QString filePath = Config::findFile(config.lastLocation(),
1462 imgFileExts[(*g)->format()],
1463 userFriendlyFilePath);
1464 if (!filePath.isEmpty())
1465 Config::copyFile(config.lastLocation(),
1467 userFriendlyFilePath,
1473 // Documentation template handling
1474 QString templateDir = config.getString(
1475 (*g)->format() + Config::dot + CONFIG_TEMPLATEDIR);
1477 if (!templateDir.isEmpty()) {
1479 QStringList searchDirs = QStringList() << templateDir;
1480 QStringList scripts =
1481 config.getCleanPathList((*g)->format()+Config::dot+CONFIG_SCRIPTS);
1482 e = scripts.begin();
1483 while (e != scripts.end()) {
1484 QString userFriendlyFilePath;
1485 QString filePath = Config::findFile(config.lastLocation(),
1490 userFriendlyFilePath);
1491 if (!filePath.isEmpty())
1492 Config::copyFile(config.lastLocation(),
1494 userFriendlyFilePath,
1500 QStringList styles =
1501 config.getCleanPathList((*g)->format()+Config::dot+CONFIG_STYLESHEETS);
1503 while (e != styles.end()) {
1504 QString userFriendlyFilePath;
1505 QString filePath = Config::findFile(config.lastLocation(),
1510 userFriendlyFilePath);
1511 if (!filePath.isEmpty())
1512 Config::copyFile(config.lastLocation(),
1514 userFriendlyFilePath,
1524 QRegExp secondParamAndAbove("[\2-\7]");
1525 QSet<QString> formattingNames = config.subVars(CONFIG_FORMATTING);
1526 QSet<QString>::ConstIterator n = formattingNames.begin();
1527 while (n != formattingNames.end()) {
1528 QString formattingDotName = CONFIG_FORMATTING + Config::dot + *n;
1530 QSet<QString> formats = config.subVars(formattingDotName);
1531 QSet<QString>::ConstIterator f = formats.begin();
1532 while (f != formats.end()) {
1533 QString def = config.getString(formattingDotName +
1535 if (!def.isEmpty()) {
1536 int numParams = Config::numParams(def);
1537 int numOccs = def.count("\1");
1539 if (numParams != 1) {
1540 config.lastLocation().warning(tr("Formatting '%1' must "
1542 "parameter (found %2)")
1543 .arg(*n).arg(numParams));
1545 else if (numOccs > 1) {
1546 config.lastLocation().fatal(tr("Formatting '%1' must "
1547 "contain exactly one "
1548 "occurrence of '\\1' "
1550 .arg(*n).arg(numOccs));
1553 int paramPos = def.indexOf("\1");
1554 fmtLeftMaps[*f].insert(*n, def.left(paramPos));
1555 fmtRightMaps[*f].insert(*n, def.mid(paramPos + 1));
1563 project = config.getString(CONFIG_PROJECT);
1565 QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
1566 if (!prefixes.isEmpty()) {
1567 foreach (QString prefix, prefixes)
1568 outputPrefixes[prefix] = config.getString(
1569 CONFIG_OUTPUTPREFIXES + Config::dot + prefix);
1571 outputPrefixes[QLatin1String("QML")] = QLatin1String("qml-");
1574 void Generator::initializeGenerator(const Config & /* config */)
1578 bool Generator::matchAhead(const Atom *atom, Atom::Type expectedAtomType)
1580 return atom->next() != 0 && atom->next()->type() == expectedAtomType;
1584 Used for writing to the current output stream. Returns a
1585 reference to the current output stream, which is then used
1586 with the \c {<<} operator for writing.
1588 QTextStream &Generator::out()
1590 return *outStreamStack.top();
1593 QString Generator::outFileName()
1595 return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName();
1598 QString Generator::outputPrefix(const QString &nodeType)
1600 return outputPrefixes[nodeType];
1603 bool Generator::parseArg(const QString& src,
1607 QStringRef* contents,
1611 #define SKIP_CHAR(c) \
1613 qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \
1614 if (i >= n || src[i] != c) { \
1616 qDebug() << " char '" << c << "' not found"; \
1622 #define SKIP_SPACE \
1623 while (i < n && src[i] == ' ') \
1629 // assume "<@" has been parsed outside
1633 if (tag != QStringRef(&src, i, tag.length())) {
1635 qDebug() << "tag " << tag << " not found at " << i;
1640 qDebug() << "haystack:" << src << "needle:" << tag << "i:" <<i;
1645 // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
1648 // read parameter name
1650 while (i < n && src[i].isLetter())
1652 if (src[i] == '=') {
1654 qDebug() << "read parameter" << QString(src.data() + j, i - j);
1657 // skip parameter name
1659 while (i < n && src[i] != '"')
1661 *par1 = QStringRef(&src, j, i - j);
1666 qDebug() << "no optional parameter found";
1672 // find contents up to closing "</@tag>
1675 if (i + 4 + tag.length() > n)
1679 if (src[i + 1] != '/')
1681 if (src[i + 2] != '@')
1683 if (tag != QStringRef(&src, i + 3, tag.length()))
1685 if (src[i + 3 + tag.length()] != '>')
1690 *contents = QStringRef(&src, j, i - j);
1692 i += tag.length() + 4;
1696 qDebug() << " tag " << tag << " found: pos now: " << i;
1701 QString Generator::plainCode(const QString& markedCode)
1703 QString t = markedCode;
1704 t.replace(tag, QString());
1705 t.replace(quot, QLatin1String("\""));
1706 t.replace(gt, QLatin1String(">"));
1707 t.replace(lt, QLatin1String("<"));
1708 t.replace(amp, QLatin1String("&"));
1712 void Generator::setImageFileExtensions(const QStringList& extensions)
1714 imgFileExts[format()] = extensions;
1717 void Generator::singularPlural(Text& text, const NodeList& nodes)
1719 if (nodes.count() == 1)
1725 int Generator::skipAtoms(const Atom *atom, Atom::Type type) const
1728 atom = atom->next();
1729 while (atom != 0 && atom->type() != type) {
1731 atom = atom->next();
1736 void Generator::startText(const Node * /* relative */,
1737 CodeMarker * /* marker */)
1741 void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
1743 if (node->type() == Node::Function) {
1744 const FunctionNode *func = static_cast<const FunctionNode *>(node);
1745 if (func->overloadNumber() == 1) {
1746 QString alternateName;
1747 const FunctionNode *alternateFunc = 0;
1749 if (func->name().startsWith("set") && func->name().size() >= 4) {
1750 alternateName = func->name()[3].toLower();
1751 alternateName += func->name().mid(4);
1752 alternateFunc = func->parent()->findFunctionNode(alternateName);
1754 if (!alternateFunc) {
1755 alternateName = "is" + func->name().mid(3);
1756 alternateFunc = func->parent()->findFunctionNode(alternateName);
1757 if (!alternateFunc) {
1758 alternateName = "has" + func->name().mid(3);
1759 alternateFunc = func->parent()->findFunctionNode(alternateName);
1763 else if (!func->name().isEmpty()) {
1764 alternateName = "set";
1765 alternateName += func->name()[0].toUpper();
1766 alternateName += func->name().mid(1);
1767 alternateFunc = func->parent()->findFunctionNode(alternateName);
1770 if (alternateFunc && alternateFunc->access() != Node::Private) {
1772 for (i = 0; i < alsoList.size(); ++i) {
1773 if (alsoList.at(i).toString().contains(alternateName))
1777 if (i == alsoList.size()) {
1778 alternateName += "()";
1781 also << Atom(Atom::Link, alternateName)
1782 << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1784 << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1785 alsoList.prepend(also);
1792 void Generator::terminate()
1794 QList<Generator *>::ConstIterator g = generators.begin();
1795 while (g != generators.end()) {
1796 if (outputFormats.contains((*g)->format()))
1797 (*g)->terminateGenerator();
1801 fmtLeftMaps.clear();
1802 fmtRightMaps.clear();
1803 imgFileExts.clear();
1807 QmlClassNode::terminate();
1808 ExampleNode::terminate();
1811 void Generator::terminateGenerator()
1816 Trims trailing whitespace off the \a string and returns
1819 QString Generator::trimmedTrailing(const QString& string)
1821 QString trimmed = string;
1822 while (trimmed.length() > 0 && trimmed[trimmed.length() - 1].isSpace())
1823 trimmed.truncate(trimmed.length() - 1);
1827 QString Generator::typeString(const Node *node)
1829 switch (node->type()) {
1830 case Node::Namespace:
1836 switch (node->subType()) {
1837 case Node::QmlClass:
1839 case Node::QmlPropertyGroup:
1840 return "property group";
1841 case Node::QmlBasicType:
1844 return "documentation";
1851 case Node::Function:
1853 case Node::Property:
1856 return "documentation";
1860 void Generator::unknownAtom(const Atom *atom)
1862 Location::internalError(tr("unknown atom type '%1' in %2 generator")
1863 .arg(atom->typeString()).arg(format()));