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 ¶meters = 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 ¶meters = 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