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 #include "qmlvisitor.h"
30 
31 #include "codechunk.h"
32 #include "codeparser.h"
33 #include "node.h"
34 #include "qdocdatabase.h"
35 #include "tokenizer.h"
36 
37 #include <QtCore/qdebug.h>
38 #include <QtCore/qfileinfo.h>
39 #include <QtCore/qglobal.h>
40 #include <QtCore/qstringlist.h>
41 
42 #ifndef QT_NO_DECLARATIVE
43 #    include <private/qqmljsast_p.h>
44 #    include <private/qqmljsastfwd_p.h>
45 #    include <private/qqmljsengine_p.h>
46 #endif
47 
48 QT_BEGIN_NAMESPACE
49 
50 #ifndef QT_NO_DECLARATIVE
51 /*!
52   The constructor stores all the parameters in local data members.
53  */
QmlDocVisitor(const QString & filePath,const QString & code,QQmlJS::Engine * engine,const QSet<QString> & commands,const QSet<QString> & topics)54 QmlDocVisitor::QmlDocVisitor(const QString &filePath, const QString &code, QQmlJS::Engine *engine,
55                              const QSet<QString> &commands, const QSet<QString> &topics)
56     : nestingLevel(0)
57 {
58     lastEndOffset = 0;
59     this->filePath_ = filePath;
60     this->name = QFileInfo(filePath).baseName();
61     document = code;
62     this->engine = engine;
63     this->commands_ = commands;
64     this->topics_ = topics;
65     current = QDocDatabase::qdocDB()->primaryTreeRoot();
66 }
67 
68 /*!
69   The destructor does nothing.
70  */
~QmlDocVisitor()71 QmlDocVisitor::~QmlDocVisitor()
72 {
73     // nothing.
74 }
75 
76 /*!
77   Returns the location of the nearest comment above the \a offset.
78  */
precedingComment(quint32 offset) const79 QQmlJS::SourceLocation QmlDocVisitor::precedingComment(quint32 offset) const
80 {
81     const auto comments = engine->comments();
82     for (auto it = comments.rbegin(); it != comments.rend(); ++it) {
83         QQmlJS::SourceLocation loc = *it;
84 
85         if (loc.begin() <= lastEndOffset) {
86             // Return if we reach the end of the preceding structure.
87             break;
88         } else if (usedComments.contains(loc.begin())) {
89             // Return if we encounter a previously used comment.
90             break;
91         } else if (loc.begin() > lastEndOffset && loc.end() < offset) {
92             // Only examine multiline comments in order to avoid snippet markers.
93             if (document.at(loc.offset - 1) == QLatin1Char('*')) {
94                 QString comment = document.mid(loc.offset, loc.length);
95                 if (comment.startsWith(QLatin1Char('!')) || comment.startsWith(QLatin1Char('*'))) {
96                     return loc;
97                 }
98             }
99         }
100     }
101 
102     return QQmlJS::SourceLocation();
103 }
104 
105 class QmlSignatureParser
106 {
107 public:
108     QmlSignatureParser(FunctionNode *func, const QString &signature, const Location &loc);
readToken()109     void readToken() { tok_ = tokenizer_->getToken(); }
lexeme()110     QString lexeme() { return tokenizer_->lexeme(); }
previousLexeme()111     QString previousLexeme() { return tokenizer_->previousLexeme(); }
112 
113     bool match(int target);
114     bool matchTypeAndName(CodeChunk *type, QString *var);
115     bool matchParameter();
116     bool matchFunctionDecl();
117 
118 private:
119     QString signature_;
120     QStringList names_;
121     QString funcName_;
122     Tokenizer *tokenizer_;
123     int tok_;
124     FunctionNode *func_;
125     const Location &location_;
126 };
127 
128 /*!
129   Finds the nearest unused qdoc comment above the QML entity
130   represented by the \a node and processes the qdoc commands
131   in that comment. The processed documentation is stored in
132   the \a node.
133 
134   If a qdoc comment is found for \a location, true is returned.
135   If a comment is not found there, false is returned.
136  */
applyDocumentation(QQmlJS::SourceLocation location,Node * node)137 bool QmlDocVisitor::applyDocumentation(QQmlJS::SourceLocation location, Node *node)
138 {
139     QQmlJS::SourceLocation loc = precedingComment(location.begin());
140 
141     if (loc.isValid()) {
142         QString source = document.mid(loc.offset, loc.length);
143         Location start(filePath_);
144         start.setLineNo(loc.startLine);
145         start.setColumnNo(loc.startColumn);
146         Location finish(filePath_);
147         finish.setLineNo(loc.startLine);
148         finish.setColumnNo(loc.startColumn);
149 
150         Doc doc(start, finish, source.mid(1), commands_, topics_);
151         const TopicList &topicsUsed = doc.topicsUsed();
152         NodeList nodes;
153         Node *nodePassedIn = node;
154         Aggregate *parent = nodePassedIn->parent();
155         node->setDoc(doc);
156         nodes.append(node);
157         if (topicsUsed.size() > 0) {
158             for (int i = 0; i < topicsUsed.size(); ++i) {
159                 QString topic = topicsUsed.at(i).topic;
160                 if (!topic.startsWith(QLatin1String("qml"))
161                     && !topic.startsWith(QLatin1String("js")))
162                     continue; // maybe a qdoc warning here? mws 18/07/18
163                 QString args = topicsUsed.at(i).args;
164                 if (topic == COMMAND_JSTYPE) {
165                     node->changeType(Node::QmlType, Node::JsType);
166                 } else if (topic.endsWith(QLatin1String("property"))) {
167                     QmlPropArgs qpa;
168                     if (splitQmlPropertyArg(doc, args, qpa)) {
169                         if (qpa.name_ == nodePassedIn->name()) {
170                             if (nodePassedIn->isAlias())
171                                 nodePassedIn->setDataType(qpa.type_);
172                         } else {
173                             bool isAttached = topic.contains(QLatin1String("attached"));
174                             QmlPropertyNode *n = parent->hasQmlProperty(qpa.name_, isAttached);
175                             if (n == nullptr)
176                                 n = new QmlPropertyNode(parent, qpa.name_, qpa.type_, isAttached);
177                             n->setLocation(doc.location());
178                             n->setDoc(doc);
179                             n->markReadOnly(nodePassedIn->isReadOnly());
180                             if (nodePassedIn->isDefault())
181                                 n->markDefault();
182                             if (isAttached)
183                                 n->markReadOnly(0);
184                             if ((topic == COMMAND_JSPROPERTY)
185                                 || (topic == COMMAND_JSATTACHEDPROPERTY))
186                                 n->changeType(Node::QmlProperty, Node::JsProperty);
187                             nodes.append(n);
188                         }
189                     } else
190                         qDebug() << "  FAILED TO PARSE QML OR JS PROPERTY:" << topic << args;
191                 } else if (topic.endsWith(QLatin1String("method")) || topic == COMMAND_QMLSIGNAL) {
192                     if (node->isFunction()) {
193                         FunctionNode *fn = static_cast<FunctionNode *>(node);
194                         QmlSignatureParser qsp(fn, args, doc.location());
195                         if (topic == COMMAND_JSMETHOD || topic == COMMAND_JSATTACHEDMETHOD)
196                             fn->changeMetaness(FunctionNode::QmlMethod, FunctionNode::JsMethod);
197                     }
198                 }
199             }
200         }
201         for (int i = 0; i < nodes.size(); ++i)
202             applyMetacommands(loc, nodes.at(i), doc);
203         usedComments.insert(loc.offset);
204         if (doc.isEmpty()) {
205             return false;
206         }
207         return true;
208     }
209     Location codeLoc(filePath_);
210     codeLoc.setLineNo(location.startLine);
211     node->setLocation(codeLoc);
212     return false;
213 }
214 
QmlSignatureParser(FunctionNode * func,const QString & signature,const Location & loc)215 QmlSignatureParser::QmlSignatureParser(FunctionNode *func, const QString &signature,
216                                        const Location &loc)
217     : signature_(signature), func_(func), location_(loc)
218 {
219     QByteArray latin1 = signature.toLatin1();
220     Tokenizer stringTokenizer(location_, latin1);
221     stringTokenizer.setParsingFnOrMacro(true);
222     tokenizer_ = &stringTokenizer;
223     readToken();
224     matchFunctionDecl();
225 }
226 
227 /*!
228   If the current token matches \a target, read the next
229   token and return true. Otherwise, don't read the next
230   token, and return false.
231  */
match(int target)232 bool QmlSignatureParser::match(int target)
233 {
234     if (tok_ == target) {
235         readToken();
236         return true;
237     }
238     return false;
239 }
240 
241 /*!
242   Parse a QML data type into \a type and an optional
243   variable name into \a var.
244  */
matchTypeAndName(CodeChunk * type,QString * var)245 bool QmlSignatureParser::matchTypeAndName(CodeChunk *type, QString *var)
246 {
247     /*
248       This code is really hard to follow... sorry. The loop is there to match
249       Alpha::Beta::Gamma::...::Omega.
250      */
251     for (;;) {
252         bool virgin = true;
253 
254         if (tok_ != Tok_Ident) {
255             while (match(Tok_signed) || match(Tok_unsigned) || match(Tok_short) || match(Tok_long)
256                    || match(Tok_int64)) {
257                 type->append(previousLexeme());
258                 virgin = false;
259             }
260         }
261 
262         if (virgin) {
263             if (match(Tok_Ident)) {
264                 type->append(previousLexeme());
265             } else if (match(Tok_void) || match(Tok_int) || match(Tok_char) || match(Tok_double)
266                        || match(Tok_Ellipsis))
267                 type->append(previousLexeme());
268             else
269                 return false;
270         } else if (match(Tok_int) || match(Tok_char) || match(Tok_double)) {
271             type->append(previousLexeme());
272         }
273 
274         if (match(Tok_Gulbrandsen))
275             type->append(previousLexeme());
276         else
277             break;
278     }
279 
280     while (match(Tok_Ampersand) || match(Tok_Aster) || match(Tok_const) || match(Tok_Caret))
281         type->append(previousLexeme());
282 
283     /*
284       The usual case: Look for an optional identifier, then for
285       some array brackets.
286      */
287     type->appendHotspot();
288 
289     if ((var != nullptr) && match(Tok_Ident))
290         *var = previousLexeme();
291 
292     if (tok_ == Tok_LeftBracket) {
293         int bracketDepth0 = tokenizer_->bracketDepth();
294         while ((tokenizer_->bracketDepth() >= bracketDepth0 && tok_ != Tok_Eoi)
295                || tok_ == Tok_RightBracket) {
296             type->append(lexeme());
297             readToken();
298         }
299     }
300     return true;
301 }
302 
matchParameter()303 bool QmlSignatureParser::matchParameter()
304 {
305     QString name;
306     CodeChunk type;
307     CodeChunk defaultValue;
308 
309     bool result = matchTypeAndName(&type, &name);
310     if (name.isEmpty()) {
311         name = type.toString();
312         type.clear();
313     }
314 
315     if (!result)
316         return false;
317     if (match(Tok_Equal)) {
318         int parenDepth0 = tokenizer_->parenDepth();
319         while (tokenizer_->parenDepth() >= parenDepth0
320                && (tok_ != Tok_Comma || tokenizer_->parenDepth() > parenDepth0)
321                && tok_ != Tok_Eoi) {
322             defaultValue.append(lexeme());
323             readToken();
324         }
325     }
326     func_->parameters().append(type.toString(), name, defaultValue.toString());
327     return true;
328 }
329 
matchFunctionDecl()330 bool QmlSignatureParser::matchFunctionDecl()
331 {
332     CodeChunk returnType;
333 
334     int firstBlank = signature_.indexOf(QChar(' '));
335     int leftParen = signature_.indexOf(QChar('('));
336     if ((firstBlank > 0) && (leftParen - firstBlank) > 1) {
337         if (!matchTypeAndName(&returnType, nullptr))
338             return false;
339     }
340 
341     while (match(Tok_Ident)) {
342         names_.append(previousLexeme());
343         if (!match(Tok_Gulbrandsen)) {
344             funcName_ = previousLexeme();
345             names_.pop_back();
346             break;
347         }
348     }
349 
350     if (tok_ != Tok_LeftParen)
351         return false;
352     /*
353       Parsing the parameters should be moved into class Parameters,
354       but it can wait. mws 14/12/2018
355      */
356     readToken();
357 
358     func_->setLocation(location_);
359     func_->setReturnType(returnType.toString());
360 
361     if (tok_ != Tok_RightParen) {
362         func_->parameters().clear();
363         do {
364             if (!matchParameter())
365                 return false;
366         } while (match(Tok_Comma));
367     }
368     if (!match(Tok_RightParen))
369         return false;
370     return true;
371 }
372 
373 /*!
374   A QML property argument has the form...
375 
376   <type> <component>::<name>
377   <type> <QML-module>::<component>::<name>
378 
379   This function splits the argument into one of those
380   two forms. The three part form is the old form, which
381   was used before the creation of QtQuick 2 and Qt
382   Components. A <QML-module> is the QML equivalent of a
383   C++ namespace. So this function splits \a arg on "::"
384   and stores the parts in the \e {type}, \e {module},
385   \e {component}, and \a {name}, fields of \a qpa. If it
386   is successful, it returns \c true. If not enough parts
387   are found, a qdoc warning is emitted and false is
388   returned.
389  */
splitQmlPropertyArg(const Doc & doc,const QString & arg,QmlPropArgs & qpa)390 bool QmlDocVisitor::splitQmlPropertyArg(const Doc &doc, const QString &arg, QmlPropArgs &qpa)
391 {
392     qpa.clear();
393     QStringList blankSplit = arg.split(QLatin1Char(' '));
394     if (blankSplit.size() > 1) {
395         qpa.type_ = blankSplit[0];
396         QStringList colonSplit(blankSplit[1].split("::"));
397         if (colonSplit.size() == 3) {
398             qpa.module_ = colonSplit[0];
399             qpa.component_ = colonSplit[1];
400             qpa.name_ = colonSplit[2];
401             return true;
402         } else if (colonSplit.size() == 2) {
403             qpa.component_ = colonSplit[0];
404             qpa.name_ = colonSplit[1];
405             return true;
406         } else if (colonSplit.size() == 1) {
407             qpa.name_ = colonSplit[0];
408             return true;
409         }
410         QString msg = "Unrecognizable QML module/component qualifier for " + arg;
411         doc.location().warning(tr(msg.toLatin1().data()));
412     } else {
413         QString msg = "Missing property type for " + arg;
414         doc.location().warning(tr(msg.toLatin1().data()));
415     }
416     return false;
417 }
418 
419 /*!
420   Applies the metacommands found in the comment.
421  */
applyMetacommands(QQmlJS::SourceLocation,Node * node,Doc & doc)422 void QmlDocVisitor::applyMetacommands(QQmlJS::SourceLocation, Node *node, Doc &doc)
423 {
424     QDocDatabase *qdb = QDocDatabase::qdocDB();
425     QSet<QString> metacommands = doc.metaCommandsUsed();
426     if (metacommands.count() > 0) {
427         metacommands.subtract(topics_);
428         for (const auto &command : qAsConst(metacommands)) {
429             const ArgList args = doc.metaCommandArgs(command);
430             if ((command == COMMAND_QMLABSTRACT) || (command == COMMAND_ABSTRACT)) {
431                 if (node->isQmlType() || node->isJsType()) {
432                     node->setAbstract(true);
433                 }
434             } else if (command == COMMAND_DEPRECATED) {
435                 node->setStatus(Node::Obsolete);
436             } else if ((command == COMMAND_INQMLMODULE) || (command == COMMAND_INJSMODULE)) {
437                 qdb->addToQmlModule(args[0].first, node);
438             } else if (command == COMMAND_QMLINHERITS) {
439                 if (node->name() == args[0].first)
440                     doc.location().warning(tr("%1 tries to inherit itself").arg(args[0].first));
441                 else if (node->isQmlType() || node->isJsType()) {
442                     QmlTypeNode *qmlType = static_cast<QmlTypeNode *>(node);
443                     qmlType->setQmlBaseName(args[0].first);
444                 }
445             } else if (command == COMMAND_QMLDEFAULT) {
446                 node->markDefault();
447             } else if (command == COMMAND_QMLREADONLY) {
448                 node->markReadOnly(1);
449             } else if ((command == COMMAND_INGROUP) && !args.isEmpty()) {
450                 for (const auto &argument : args)
451                     QDocDatabase::qdocDB()->addToGroup(argument.first, node);
452             } else if (command == COMMAND_INTERNAL) {
453                 node->setStatus(Node::Internal);
454             } else if (command == COMMAND_OBSOLETE) {
455                 node->setStatus(Node::Obsolete);
456             } else if (command == COMMAND_PRELIMINARY) {
457                 node->setStatus(Node::Preliminary);
458             } else if (command == COMMAND_SINCE) {
459                 QString arg = args[0].first; //.join(' ');
460                 node->setSince(arg);
461             } else if (command == COMMAND_WRAPPER) {
462                 node->setWrapper();
463             } else {
464                 doc.location().warning(tr("The \\%1 command is ignored in QML files").arg(command));
465             }
466         }
467     }
468 }
469 
470 /*!
471   Reconstruct the qualified \a id using dot notation
472   and return the fully qualified string.
473  */
getFullyQualifiedId(QQmlJS::AST::UiQualifiedId * id)474 QString QmlDocVisitor::getFullyQualifiedId(QQmlJS::AST::UiQualifiedId *id)
475 {
476     QString result;
477     if (id) {
478         result = id->name.toString();
479         id = id->next;
480         while (id != nullptr) {
481             result += QChar('.') + id->name.toString();
482             id = id->next;
483         }
484     }
485     return result;
486 }
487 
488 /*!
489   Begin the visit of the object \a definition, recording it in the
490   qdoc database. Increment the object nesting level, which is used
491   to test whether we are at the public API level. The public level
492   is level 1.
493 
494   Note that this visit() function creates the qdoc object node as a
495   QmlType. If it is actually a JsType, this fact is discovered when
496   the qdoc comment is applied to the node. The node's typet is then
497   changed to JsType.
498  */
visit(QQmlJS::AST::UiObjectDefinition * definition)499 bool QmlDocVisitor::visit(QQmlJS::AST::UiObjectDefinition *definition)
500 {
501     QString type = getFullyQualifiedId(definition->qualifiedTypeNameId);
502     nestingLevel++;
503 
504     if (current->isNamespace()) {
505         QmlTypeNode *component = nullptr;
506         Node *candidate = current->findChildNode(name, Node::QML);
507         if (candidate != nullptr)
508             component = static_cast<QmlTypeNode *>(candidate);
509         else
510             component = new QmlTypeNode(current, name);
511         component->setTitle(name);
512         component->setImportList(importList);
513         importList.clear();
514         if (applyDocumentation(definition->firstSourceLocation(), component))
515             component->setQmlBaseName(type);
516         current = component;
517     }
518 
519     return true;
520 }
521 
522 /*!
523   End the visit of the object \a definition. In particular,
524   decrement the object nesting level, which is used to test
525   whether we are at the public API level. The public API
526   level is level 1. It won't decrement below 0.
527  */
endVisit(QQmlJS::AST::UiObjectDefinition * definition)528 void QmlDocVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *definition)
529 {
530     if (nestingLevel > 0) {
531         --nestingLevel;
532     }
533     lastEndOffset = definition->lastSourceLocation().end();
534 }
535 
visit(QQmlJS::AST::UiImport * import)536 bool QmlDocVisitor::visit(QQmlJS::AST::UiImport *import)
537 {
538     QString name = document.mid(import->fileNameToken.offset, import->fileNameToken.length);
539     if (name[0] == '\"')
540         name = name.mid(1, name.length() - 2);
541     QString version;
542     if (import->version) {
543         const auto start = import->version->firstSourceLocation().begin();
544         const auto end = import->version->lastSourceLocation().end();
545         version = document.mid(start, end - start);
546     }
547     QString importId = document.mid(import->importIdToken.offset, import->importIdToken.length);
548     QString importUri = getFullyQualifiedId(import->importUri);
549     importList.append(ImportRec(name, version, importId, importUri));
550 
551     return true;
552 }
553 
endVisit(QQmlJS::AST::UiImport * definition)554 void QmlDocVisitor::endVisit(QQmlJS::AST::UiImport *definition)
555 {
556     lastEndOffset = definition->lastSourceLocation().end();
557 }
558 
visit(QQmlJS::AST::UiObjectBinding *)559 bool QmlDocVisitor::visit(QQmlJS::AST::UiObjectBinding *)
560 {
561     ++nestingLevel;
562     return true;
563 }
564 
endVisit(QQmlJS::AST::UiObjectBinding *)565 void QmlDocVisitor::endVisit(QQmlJS::AST::UiObjectBinding *)
566 {
567     --nestingLevel;
568 }
569 
visit(QQmlJS::AST::UiArrayBinding *)570 bool QmlDocVisitor::visit(QQmlJS::AST::UiArrayBinding *)
571 {
572     return true;
573 }
574 
endVisit(QQmlJS::AST::UiArrayBinding *)575 void QmlDocVisitor::endVisit(QQmlJS::AST::UiArrayBinding *) {}
576 
577 template<typename T>
578 QString qualifiedIdToString(T node);
579 
580 template<>
qualifiedIdToString(QStringRef node)581 QString qualifiedIdToString(QStringRef node)
582 {
583     return node.toString();
584 }
585 
586 template<>
qualifiedIdToString(QQmlJS::AST::UiQualifiedId * node)587 QString qualifiedIdToString(QQmlJS::AST::UiQualifiedId *node)
588 {
589     QString s;
590 
591     for (QQmlJS::AST::UiQualifiedId *it = node; it; it = it->next) {
592         s.append(it->name);
593 
594         if (it->next)
595             s.append(QLatin1Char('.'));
596     }
597 
598     return s;
599 }
600 
601 /*!
602     Visits the public \a member declaration, which can be a
603     signal or a property. It is a custom signal or property.
604     Only visit the \a member if the nestingLevel is 1.
605  */
visit(QQmlJS::AST::UiPublicMember * member)606 bool QmlDocVisitor::visit(QQmlJS::AST::UiPublicMember *member)
607 {
608     if (nestingLevel > 1) {
609         return true;
610     }
611     switch (member->type) {
612     case QQmlJS::AST::UiPublicMember::Signal: {
613         if (current->isQmlType() || current->isJsType()) {
614             QmlTypeNode *qmlType = static_cast<QmlTypeNode *>(current);
615             if (qmlType) {
616                 FunctionNode::Metaness metaness = FunctionNode::QmlSignal;
617                 if (qmlType->isJsType())
618                     metaness = FunctionNode::JsSignal;
619                 QString name = member->name.toString();
620                 FunctionNode *newSignal = new FunctionNode(metaness, current, name);
621                 Parameters &parameters = newSignal->parameters();
622                 for (QQmlJS::AST::UiParameterList *it = member->parameters; it; it = it->next) {
623                     const QString type = qualifiedIdToString(it->type);
624                     if (!type.isEmpty() && !it->name.isEmpty())
625                         parameters.append(type, QString(), it->name.toString());
626                 }
627                 applyDocumentation(member->firstSourceLocation(), newSignal);
628             }
629         }
630         break;
631     }
632     case QQmlJS::AST::UiPublicMember::Property: {
633         QString type = qualifiedIdToString(member->memberType);
634         QString name = member->name.toString();
635         if (current->isQmlType() || current->isJsType()) {
636             QmlTypeNode *qmlType = static_cast<QmlTypeNode *>(current);
637             if (qmlType) {
638                 QString name = member->name.toString();
639                 QmlPropertyNode *qmlPropNode = qmlType->hasQmlProperty(name);
640                 if (qmlPropNode == nullptr)
641                     qmlPropNode = new QmlPropertyNode(qmlType, name, type, false);
642                 qmlPropNode->markReadOnly(member->isReadonlyMember);
643                 if (member->isDefaultMember)
644                     qmlPropNode->markDefault();
645                 applyDocumentation(member->firstSourceLocation(), qmlPropNode);
646             }
647         }
648         break;
649     }
650     default:
651         return false;
652     }
653 
654     return true;
655 }
656 
657 /*!
658   End the visit of the \a member.
659  */
endVisit(QQmlJS::AST::UiPublicMember * member)660 void QmlDocVisitor::endVisit(QQmlJS::AST::UiPublicMember *member)
661 {
662     lastEndOffset = member->lastSourceLocation().end();
663 }
664 
visit(QQmlJS::AST::IdentifierPropertyName *)665 bool QmlDocVisitor::visit(QQmlJS::AST::IdentifierPropertyName *)
666 {
667     return true;
668 }
669 
670 /*!
671   Begin the visit of the function declaration \a fd, but only
672   if the nesting level is 1.
673  */
visit(QQmlJS::AST::FunctionDeclaration * fd)674 bool QmlDocVisitor::visit(QQmlJS::AST::FunctionDeclaration *fd)
675 {
676     if (nestingLevel <= 1) {
677         FunctionNode::Metaness metaness = FunctionNode::QmlMethod;
678         if (current->isJsType())
679             metaness = FunctionNode::JsMethod;
680         else if (!current->isQmlType())
681             return true;
682         QString name = fd->name.toString();
683         FunctionNode *method = new FunctionNode(metaness, current, name);
684         Parameters &parameters = method->parameters();
685         QQmlJS::AST::FormalParameterList *formals = fd->formals;
686         if (formals) {
687             QQmlJS::AST::FormalParameterList *fp = formals;
688             do {
689                 QString defaultValue;
690                 auto initializer = fp->element->initializer;
691                 if (initializer) {
692                     auto loc = initializer->firstSourceLocation();
693                     defaultValue = document.mid(loc.begin(), loc.length);
694                 }
695                 parameters.append(QString(), fp->element->bindingIdentifier.toString(),
696                         defaultValue);
697                 fp = fp->next;
698             } while (fp && fp != formals);
699         }
700         applyDocumentation(fd->firstSourceLocation(), method);
701     }
702     return true;
703 }
704 
705 /*!
706   End the visit of the function declaration, \a fd.
707  */
endVisit(QQmlJS::AST::FunctionDeclaration * fd)708 void QmlDocVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *fd)
709 {
710     lastEndOffset = fd->lastSourceLocation().end();
711 }
712 
713 /*!
714   Begin the visit of the signal handler declaration \a sb, but only
715   if the nesting level is 1.
716 
717   This visit is now deprecated. It has been decided to document
718   public signals. If a signal handler must be discussed in the
719   documentation, that discussion must take place in the comment
720   for the signal.
721  */
visit(QQmlJS::AST::UiScriptBinding *)722 bool QmlDocVisitor::visit(QQmlJS::AST::UiScriptBinding *)
723 {
724     return true;
725 }
726 
endVisit(QQmlJS::AST::UiScriptBinding * sb)727 void QmlDocVisitor::endVisit(QQmlJS::AST::UiScriptBinding *sb)
728 {
729     lastEndOffset = sb->lastSourceLocation().end();
730 }
731 
visit(QQmlJS::AST::UiQualifiedId *)732 bool QmlDocVisitor::visit(QQmlJS::AST::UiQualifiedId *)
733 {
734     return true;
735 }
736 
endVisit(QQmlJS::AST::UiQualifiedId *)737 void QmlDocVisitor::endVisit(QQmlJS::AST::UiQualifiedId *)
738 {
739     // nothing.
740 }
741 
throwRecursionDepthError()742 void QmlDocVisitor::throwRecursionDepthError()
743 {
744     hasRecursionDepthError = true;
745 }
746 
hasError() const747 bool QmlDocVisitor::hasError() const
748 {
749     return hasRecursionDepthError;
750 }
751 
752 #endif
753 
754 QT_END_NAMESPACE
755