1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qmlvisitor.h"
5
6#include "aggregate.h"
7#include "codechunk.h"
8#include "codeparser.h"
9#include "functionnode.h"
10#include "genustypes.h"
11#include "nativeenum.h"
12#include "node.h"
13#include "qdocdatabase.h"
14#include "qmlpropertyarguments.h"
15#include "qmlpropertynode.h"
16#include "sharedcommentnode.h"
17#include "tokenizer.h"
18#include "utilities.h"
19
20#include <QtCore/qdebug.h>
21#include <QtCore/qfileinfo.h>
22#include <QtCore/qglobal.h>
23
24#include <private/qqmljsast_p.h>
25#include <private/qqmljsengine_p.h>
26
27QT_BEGIN_NAMESPACE
28
29using namespace Qt::StringLiterals;
30
31/*!
32 The constructor stores all the parameters in local data members.
33 */
34QmlDocVisitor::QmlDocVisitor(const QString &filePath, const QString &code, QQmlJS::Engine *engine,
35 const QSet<QString> &commands, const QSet<QString> &topics)
36 : m_nestingLevel(0)
37{
38 m_lastEndOffset = 0;
39 this->m_filePath = filePath;
40 this->m_name = QFileInfo(filePath).baseName();
41 m_document = code;
42 this->m_engine = engine;
43 this->m_commands = commands;
44 this->m_topics = topics;
45 m_current = QDocDatabase::qdocDB()->primaryTreeRoot();
46}
47
48/*!
49 Returns the location of the nearest comment above the \a offset.
50 */
51QQmlJS::SourceLocation QmlDocVisitor::precedingComment(quint32 offset) const
52{
53 const auto comments = m_engine->comments();
54 for (auto it = comments.rbegin(); it != comments.rend(); ++it) {
55 QQmlJS::SourceLocation loc = *it;
56
57 if (loc.begin() <= m_lastEndOffset) {
58 // Return if we reach the end of the preceding structure.
59 break;
60 } else if (m_usedComments.contains(value: loc.begin())) {
61 // Return if we encounter a previously used comment.
62 break;
63 } else if (loc.begin() > m_lastEndOffset && loc.end() < offset) {
64 // Only examine multiline comments in order to avoid snippet markers.
65 if (m_document.at(i: loc.offset - 1) == QLatin1Char('*')) {
66 QString comment = m_document.mid(position: loc.offset, n: loc.length);
67 if (comment.startsWith(c: QLatin1Char('!')) || comment.startsWith(c: QLatin1Char('*'))) {
68 return loc;
69 }
70 }
71 }
72 }
73
74 return QQmlJS::SourceLocation();
75}
76
77class QmlSignatureParser
78{
79public:
80 QmlSignatureParser(FunctionNode *func, const QString &signature, const Location &loc);
81 void readToken() { tok_ = tokenizer_->getToken(); }
82 QString lexeme() { return tokenizer_->lexeme(); }
83 QString previousLexeme() { return tokenizer_->previousLexeme(); }
84
85 bool match(int target);
86 bool matchTypeAndName(CodeChunk *type, QString *var);
87 bool matchParameter();
88 bool matchFunctionDecl();
89
90private:
91 QString signature_;
92 QStringList names_;
93 Tokenizer *tokenizer_;
94 int tok_;
95 FunctionNode *func_;
96 const Location &location_;
97};
98
99/*!
100 Finds the nearest unused qdoc comment above the QML entity
101 represented by a \a node and processes the qdoc commands
102 in that comment. The processed documentation is stored in
103 \a node.
104
105 If \a node is a \c nullptr and there is a valid comment block,
106 the QML module identifier (\inqmlmodule argument) is used
107 for searching an existing QML type node. If an existing node
108 is not found, constructs a new QmlTypeNode instance.
109
110 Returns a pointer to the QmlTypeNode instance if one was
111 found or constructed. Otherwise, returns a pointer to the \a
112 node that was passed as an argument.
113 */
114Node *QmlDocVisitor::applyDocumentation(QQmlJS::SourceLocation location, Node *node)
115{
116 QQmlJS::SourceLocation loc = precedingComment(offset: location.begin());
117 Location comment_loc(m_filePath);
118
119 // No preceding comment; construct a new QML type if
120 // needed.
121 if (!loc.isValid()) {
122 if (!node)
123 node = new QmlTypeNode(m_current, m_name, NodeType::QmlType);
124 comment_loc.setLineNo(location.startLine);
125 node->setLocation(comment_loc);
126 return node;
127 }
128
129 QString source = m_document.mid(position: loc.offset + 1, n: loc.length - 1);
130 comment_loc.setLineNo(loc.startLine);
131 comment_loc.setColumnNo(loc.startColumn);
132
133 Doc doc(comment_loc, comment_loc, source, m_commands, m_topics);
134 const TopicList &topicsUsed = doc.topicsUsed();
135 NodeList nodes;
136 if (!node) {
137 QString qmid;
138 if (auto args = doc.metaCommandArgs(COMMAND_INQMLMODULE); !args.isEmpty())
139 qmid = args.first().first;
140 node = QDocDatabase::qdocDB()->findQmlTypeInPrimaryTree(qmid, name: m_name);
141 if (!node) {
142 node = new QmlTypeNode(m_current, m_name, NodeType::QmlType);
143 node->setLocation(comment_loc);
144 }
145 }
146
147 auto *parent{node->parent()};
148 nodes << node;
149 if (!topicsUsed.empty()) {
150 for (int i = 0; i < topicsUsed.size(); ++i) {
151 QString topic = topicsUsed.at(i).m_topic;
152 QString args = topicsUsed.at(i).m_args;
153 if (topic.endsWith(s: QLatin1String("property"))) {
154 auto *qmlProperty = static_cast<QmlPropertyNode *>(node);
155 if (auto qpa = QmlPropertyArguments::parse(arg: args, loc: doc.location())) {
156 if (qpa->m_name == node->name()) {
157 // Allow overriding data type from the arguments
158 qmlProperty->setDataType(qpa->m_type);
159 } else {
160 bool isAttached = topic.contains(s: QLatin1String("attached"));
161 QmlPropertyNode *n = parent->hasQmlProperty(qpa->m_name, attached: isAttached);
162 if (n == nullptr)
163 n = new QmlPropertyNode(parent, qpa->m_name, qpa->m_type, isAttached);
164 n->setIsList(qpa->m_isList);
165 // Use the const-overload of QmlPropertyNode::isReadOnly() as there's
166 // no associated C++ property to resolve the read-only status from
167 n->markReadOnly(flag: const_cast<const QmlPropertyNode *>(qmlProperty)->isReadOnly()
168 && !isAttached);
169 if (qmlProperty->isDefault())
170 n->markDefault();
171 nodes << n;
172 }
173 } else
174 qCDebug(lcQdoc) << "Failed to parse QML property:" << topic << args;
175 } else if (topic == COMMAND_QMLSIGNAL || topic == COMMAND_QMLMETHOD ||
176 topic == COMMAND_QMLATTACHEDSIGNAL || topic == COMMAND_QMLATTACHEDMETHOD) {
177 if (node->isFunction()) {
178 QmlSignatureParser qsp(static_cast<FunctionNode *>(node), args, doc.location());
179 if (nodes.size() > 1) {
180 doc.location().warning(message: "\\%1 cannot be mixed with other topic commands"_L1.arg(args&: topic));
181 nodes = {node};
182 }
183 break;
184 }
185 } else if (topic == COMMAND_QMLTYPE || topic == COMMAND_QMLVALUETYPE ||
186 topic == COMMAND_QMLBASICTYPE) {
187 if (node->isQmlType()) {
188 if (nodes.size() > 1) {
189 doc.location().warning(message: "\\%1 cannot be mixed with other topic commands"_L1.arg(args&: topic));
190 nodes = {node};
191 }
192 break;
193 }
194 }
195 }
196 }
197
198 for (auto *n : nodes) {
199 applyMetacommands(location: loc, node: n, doc);
200 }
201
202 // Test if we're documenting a property group; We need three nodes at minimum:
203 // The property that acts as the `root` in the group, and two more to make a
204 // group. Each property must be prefixed with the root name + '.'.
205 bool isPropertyGroup{false};
206 if (nodes.size() > 2 && parent->isQmlType()) {
207 isPropertyGroup =
208 std::all_of(first: std::next(x: nodes.cbegin()), last: nodes.cend(), pred: [node](const Node *p) {
209 return p->name().startsWith(s: "%1."_L1.arg(args: node->name()));
210 });
211 }
212 // Construct a SharedCommentNode (SCN) representing a QML property group.
213 //
214 // Note that it's important to do this *after* constructing
215 // the topic nodes - which need to be written to index before the related
216 // SCN.
217 if (isPropertyGroup) {
218 // The first node in `nodes` is the group property;
219 // create SCN with the same name
220 QString group{nodes.takeFirst()->name()};
221 auto *scn = new SharedCommentNode(static_cast<QmlTypeNode*>(parent),
222 nodes.size(), group);
223 scn->setDoc(doc);
224 scn->setLocation(doc.startLocation());
225 applyMetacommands(location: loc, node: scn, doc);
226 for (const auto n : std::as_const(t&: nodes))
227 scn->append(node: n);
228 scn->sort();
229 } else {
230 for (auto *n : nodes) {
231 n->setDoc(doc);
232 n->setLocation(doc.location());
233 }
234 }
235
236 m_usedComments.insert(value: loc.offset);
237 return node;
238}
239
240QmlSignatureParser::QmlSignatureParser(FunctionNode *func, const QString &signature,
241 const Location &loc)
242 : signature_(signature), func_(func), location_(loc)
243{
244 QByteArray latin1 = signature.toLatin1();
245 Tokenizer stringTokenizer(location_, std::move(latin1));
246 stringTokenizer.setParsingFnOrMacro(true);
247 tokenizer_ = &stringTokenizer;
248 readToken();
249 matchFunctionDecl();
250}
251
252/*!
253 If the current token matches \a target, read the next
254 token and return true. Otherwise, don't read the next
255 token, and return false.
256 */
257bool QmlSignatureParser::match(int target)
258{
259 if (tok_ == target) {
260 readToken();
261 return true;
262 }
263 return false;
264}
265
266/*!
267 Parse a QML data type into \a type and an optional
268 variable name into \a var.
269 */
270bool QmlSignatureParser::matchTypeAndName(CodeChunk *type, QString *var)
271{
272 /*
273 This code is really hard to follow... sorry. The loop is there to match
274 Alpha::Beta::Gamma::...::Omega.
275 */
276 for (;;) {
277 bool virgin = true;
278
279 // If not an identifier, try to match a sequence of qualifiers.
280 if (tok_ != Tok_Ident) {
281 while (match(target: Tok_signed) || match(target: Tok_unsigned) || match(target: Tok_short) || match(target: Tok_long)
282 || match(target: Tok_int64)) {
283 // Append the matched qualifier token.
284 type->append(lexeme: previousLexeme());
285 virgin = false;
286 }
287 }
288
289 if (virgin) {
290 if (match(target: Tok_Ident)) {
291 type->append(lexeme: previousLexeme());
292 } else if (match(target: Tok_void) || match(target: Tok_int) || match(target: Tok_char) || match(target: Tok_double)
293 || match(target: Tok_Ellipsis))
294 type->append(lexeme: previousLexeme());
295 else
296 return false;
297 } else if (match(target: Tok_int) || match(target: Tok_char) || match(target: Tok_double)) {
298 type->append(lexeme: previousLexeme());
299 }
300
301 // Match and append a namespace separator or break.
302 if (match(target: Tok_Gulbrandsen))
303 type->append(lexeme: previousLexeme());
304 else
305 break;
306 }
307
308 // Matches a sequence of & * const ^ tokens.
309 while (match(target: Tok_Ampersand) || match(target: Tok_Aster) || match(target: Tok_const) || match(target: Tok_Caret))
310 type->append(lexeme: previousLexeme());
311
312 /*
313 The usual case: Look for an optional identifier, then for
314 some array brackets.
315 */
316 type->appendHotspot();
317
318 // Set the variable name if it is unset and an identifier is matched.
319 if ((var != nullptr) && match(target: Tok_Ident))
320 *var = previousLexeme();
321
322 // Skip pairs of braces and their contents.
323 if (tok_ == Tok_LeftBracket) {
324 int bracketDepth0 = tokenizer_->bracketDepth();
325 while ((tokenizer_->bracketDepth() >= bracketDepth0 && tok_ != Tok_Eoi)
326 || tok_ == Tok_RightBracket) {
327 type->append(lexeme: lexeme());
328 readToken();
329 }
330 }
331 return true;
332}
333
334bool QmlSignatureParser::matchParameter()
335{
336 QString name;
337 CodeChunk type;
338 CodeChunk defaultValue;
339
340 bool result = matchTypeAndName(type: &type, var: &name);
341 if (name.isEmpty()) {
342 name = type.toString();
343 type.clear();
344 }
345
346 if (!result)
347 return false;
348 if (match(target: Tok_Equal)) {
349 int parenDepth0 = tokenizer_->parenDepth();
350 while (tokenizer_->parenDepth() >= parenDepth0
351 && (tok_ != Tok_Comma || tokenizer_->parenDepth() > parenDepth0)
352 && tok_ != Tok_Eoi) {
353 defaultValue.append(lexeme: lexeme());
354 readToken();
355 }
356 }
357 func_->parameters().append(type: type.toString(), name, value: defaultValue.toString());
358 return true;
359}
360
361bool QmlSignatureParser::matchFunctionDecl()
362{
363 CodeChunk returnType;
364
365 qsizetype firstBlank = signature_.indexOf(ch: QChar(' '));
366 qsizetype leftParen = signature_.indexOf(ch: QChar('('));
367 if ((firstBlank > 0) && (leftParen - firstBlank) > 1) {
368 if (!matchTypeAndName(type: &returnType, var: nullptr))
369 return false;
370 }
371
372 while (match(target: Tok_Ident)) {
373 names_.append(t: previousLexeme());
374 if (!match(target: Tok_Gulbrandsen)) {
375 previousLexeme();
376 names_.pop_back();
377 break;
378 }
379 }
380
381 if (tok_ != Tok_LeftParen)
382 return false;
383 /*
384 Parsing the parameters should be moved into class Parameters,
385 but it can wait. mws 14/12/2018
386 */
387 readToken();
388
389 func_->setLocation(location_);
390 func_->setReturnType(returnType.toString());
391
392 if (tok_ != Tok_RightParen) {
393 func_->parameters().clear();
394 do {
395 if (!matchParameter())
396 return false;
397 } while (match(target: Tok_Comma));
398 }
399 if (!match(target: Tok_RightParen))
400 return false;
401 return true;
402}
403
404/*!
405 Applies the metacommands found in the comment.
406 */
407void QmlDocVisitor::applyMetacommands(QQmlJS::SourceLocation, Node *node, Doc &doc)
408{
409 QDocDatabase *qdb = QDocDatabase::qdocDB();
410 QSet<QString> metacommands = doc.metaCommandsUsed();
411 if (metacommands.size() > 0) {
412 metacommands.subtract(other: m_topics);
413 for (const auto &command : std::as_const(t&: metacommands)) {
414 const ArgList args = doc.metaCommandArgs(metaCommand: command);
415 if ((command == COMMAND_QMLABSTRACT) || (command == COMMAND_ABSTRACT)) {
416 if (node->isQmlType()) {
417 node->setAbstract(true);
418 }
419 } else if (command == COMMAND_DEPRECATED) {
420 node->setDeprecated(args[0].second);
421 } else if (command == COMMAND_INQMLMODULE) {
422 qdb->addToQmlModule(name: args[0].first, node);
423 } else if (command == COMMAND_QMLINHERITS) {
424 if (node->name() == args[0].first)
425 doc.location().warning(
426 QStringLiteral("%1 tries to inherit itself").arg(a: args[0].first));
427 else if (node->isQmlType()) {
428 auto *qmlType = static_cast<QmlTypeNode *>(node);
429 qmlType->setQmlBaseName(args[0].first);
430 }
431 } else if (command == COMMAND_DEFAULT) {
432 if (!node->isQmlProperty()) {
433 doc.location().warning(QStringLiteral("Ignored '\\%1', applies only to '\\%2'")
434 .arg(args: command, COMMAND_QMLPROPERTY));
435 } else if (args.isEmpty() || args[0].first.isEmpty()) {
436 doc.location().warning(QStringLiteral("Expected an argument for '\\%1' (maybe you meant '\\%2'?)")
437 .arg(args: command, COMMAND_QMLDEFAULT));
438 } else {
439 static_cast<QmlPropertyNode *>(node)->setDefaultValue(args[0].first);
440 }
441 } else if (command == COMMAND_QMLDEFAULT) {
442 node->markDefault();
443 } else if (command == COMMAND_QMLENUMERATORSFROM) {
444 if (!node->isQmlProperty()) {
445 doc.location().warning(message: "Ignored '\\%1', applies only to '\\%2'"_L1
446 .arg(args: command, COMMAND_QMLPROPERTY));
447 } else if (!static_cast<QmlPropertyNode*>(node)->nativeEnum()->resolve(path: args[0].first, registeredQmlName: args[0].second)) {
448 doc.location().warning(message: "Failed to find C++ enumeration '%2' passed to \\%1"_L1
449 .arg(args: command, args: args[0].first), details: "Use \\value commands instead"_L1);
450 }
451 } else if (command == COMMAND_QMLREADONLY) {
452 node->markReadOnly(1);
453 } else if (command == COMMAND_QMLREQUIRED) {
454 if (node->isQmlProperty())
455 static_cast<QmlPropertyNode *>(node)->setRequired();
456 } else if ((command == COMMAND_INGROUP) && !args.isEmpty()) {
457 for (const auto &argument : args)
458 QDocDatabase::qdocDB()->addToGroup(name: argument.first, node);
459 } else if (command == COMMAND_INTERNAL) {
460 node->setStatus(Node::Internal);
461 } else if (command == COMMAND_OBSOLETE) {
462 node->setStatus(Node::Deprecated);
463 } else if (command == COMMAND_PRELIMINARY) {
464 node->setStatus(Node::Preliminary);
465 } else if (command == COMMAND_SINCE) {
466 QString arg = args[0].first; //.join(' ');
467 node->setSince(arg);
468 } else if (command == COMMAND_WRAPPER) {
469 node->setWrapper();
470 } else {
471 doc.location().warning(
472 QStringLiteral("The \\%1 command is ignored in QML files").arg(a: command));
473 }
474 }
475 }
476}
477
478/*!
479 Reconstruct the qualified \a id using dot notation
480 and return the fully qualified string.
481 */
482QString QmlDocVisitor::getFullyQualifiedId(QQmlJS::AST::UiQualifiedId *id)
483{
484 QString result;
485 if (id) {
486 result = id->name.toString();
487 id = id->next;
488 while (id != nullptr) {
489 result += QChar('.') + id->name.toString();
490 id = id->next;
491 }
492 }
493 return result;
494}
495
496/*!
497 Begin the visit of the object \a definition, recording it in the
498 qdoc database. Increment the object nesting level, which is used
499 to test whether we are at the public API level. The public level
500 is level 1.
501
502 Defers the construction of a QmlTypeNode instance to
503 applyDocumentation(), by passing \c nullptr as the second
504 argument.
505 */
506bool QmlDocVisitor::visit(QQmlJS::AST::UiObjectDefinition *definition)
507{
508 QString type = getFullyQualifiedId(id: definition->qualifiedTypeNameId);
509 m_nestingLevel++;
510 if (m_current->isNamespace()) {
511 auto component = applyDocumentation(location: definition->firstSourceLocation(), node: nullptr);
512 Q_ASSERT(component);
513 auto *qmlTypeNode = static_cast<QmlTypeNode *>(component);
514 if (!component->doc().isEmpty())
515 qmlTypeNode->setQmlBaseName(type);
516 qmlTypeNode->setTitle(m_name);
517 qmlTypeNode->setImportList(m_importList);
518 m_importList.clear();
519 m_current = qmlTypeNode;
520 }
521
522 return true;
523}
524
525/*!
526 End the visit of the object \a definition. In particular,
527 decrement the object nesting level, which is used to test
528 whether we are at the public API level. The public API
529 level is level 1. It won't decrement below 0.
530 */
531void QmlDocVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *definition)
532{
533 if (m_nestingLevel > 0) {
534 --m_nestingLevel;
535 }
536 m_lastEndOffset = definition->lastSourceLocation().end();
537}
538
539bool QmlDocVisitor::visit(QQmlJS::AST::UiImport *import)
540{
541 QString name = m_document.mid(position: import->fileNameToken.offset, n: import->fileNameToken.length);
542 if (name[0] == '\"')
543 name = name.mid(position: 1, n: name.size() - 2);
544 QString version;
545 if (import->version) {
546 const auto start = import->version->firstSourceLocation().begin();
547 const auto end = import->version->lastSourceLocation().end();
548 version = m_document.mid(position: start, n: end - start);
549 }
550 QString importUri = getFullyQualifiedId(id: import->importUri);
551 m_importList.append(t: ImportRec(std::move(name), std::move(version), std::move(importUri), import->importId));
552
553 return true;
554}
555
556void QmlDocVisitor::endVisit(QQmlJS::AST::UiImport *definition)
557{
558 m_lastEndOffset = definition->lastSourceLocation().end();
559}
560
561bool QmlDocVisitor::visit(QQmlJS::AST::UiObjectBinding *)
562{
563 ++m_nestingLevel;
564 return true;
565}
566
567void QmlDocVisitor::endVisit(QQmlJS::AST::UiObjectBinding *)
568{
569 --m_nestingLevel;
570}
571
572bool QmlDocVisitor::visit(QQmlJS::AST::UiArrayBinding *)
573{
574 return true;
575}
576
577void QmlDocVisitor::endVisit(QQmlJS::AST::UiArrayBinding *) {}
578
579static QString qualifiedIdToString(QQmlJS::AST::UiQualifiedId *node)
580{
581 QString s;
582
583 for (QQmlJS::AST::UiQualifiedId *it = node; it; it = it->next) {
584 s.append(v: it->name);
585
586 if (it->next)
587 s.append(c: QLatin1Char('.'));
588 }
589
590 return s;
591}
592
593/*!
594 Visits the public \a member declaration, which can be a
595 signal or a property. It is a custom signal or property.
596 Only visit the \a member if the nestingLevel is 1.
597 */
598bool QmlDocVisitor::visit(QQmlJS::AST::UiPublicMember *member)
599{
600 if (m_nestingLevel > 1) {
601 return true;
602 }
603 switch (member->type) {
604 case QQmlJS::AST::UiPublicMember::Signal: {
605 if (m_current->isQmlType()) {
606 auto *qmlType = static_cast<QmlTypeNode *>(m_current);
607 if (qmlType) {
608 FunctionNode::Metaness metaness = FunctionNode::QmlSignal;
609 QString name = member->name.toString();
610 auto *newSignal = new FunctionNode(metaness, m_current, name);
611 Parameters &parameters = newSignal->parameters();
612 for (QQmlJS::AST::UiParameterList *it = member->parameters; it; it = it->next) {
613 const QString type = it->type ? it->type->toString() : QString();
614 if (!type.isEmpty() && !it->name.isEmpty())
615 parameters.append(type, name: it->name.toString());
616 }
617 applyDocumentation(location: member->firstSourceLocation(), node: newSignal);
618 }
619 }
620 break;
621 }
622 case QQmlJS::AST::UiPublicMember::Property: {
623 QString type = qualifiedIdToString(node: member->memberType);
624 if (m_current->isQmlType()) {
625 auto *qmlType = static_cast<QmlTypeNode *>(m_current);
626 if (qmlType) {
627 QString name = member->name.toString();
628 QmlPropertyNode *qmlPropNode = qmlType->hasQmlProperty(name);
629 if (qmlPropNode == nullptr)
630 qmlPropNode = new QmlPropertyNode(qmlType, std::move(name), std::move(type), false);
631 qmlPropNode->markReadOnly(flag: member->isReadonly());
632 if (member->isDefaultMember())
633 qmlPropNode->markDefault();
634 if (member->requiredToken().isValid())
635 qmlPropNode->setRequired();
636 qmlPropNode->setIsList(member->typeModifier == "list"_L1);
637 applyDocumentation(location: member->firstSourceLocation(), node: qmlPropNode);
638 }
639 }
640 break;
641 }
642 default:
643 return false;
644 }
645
646 return true;
647}
648
649/*!
650 End the visit of the \a member.
651 */
652void QmlDocVisitor::endVisit(QQmlJS::AST::UiPublicMember *member)
653{
654 m_lastEndOffset = member->lastSourceLocation().end();
655}
656
657bool QmlDocVisitor::visit(QQmlJS::AST::IdentifierPropertyName *)
658{
659 return true;
660}
661
662/*!
663 Begin the visit of the function declaration \a fd, but only
664 if the nesting level is 1.
665 */
666bool QmlDocVisitor::visit(QQmlJS::AST::FunctionDeclaration *fd)
667{
668 if (m_nestingLevel <= 1) {
669 FunctionNode::Metaness metaness = FunctionNode::QmlMethod;
670 if (!m_current->isQmlType())
671 return true;
672 QString name = fd->name.toString();
673 auto *method = new FunctionNode(metaness, m_current, name);
674 Parameters &parameters = method->parameters();
675 QQmlJS::AST::FormalParameterList *formals = fd->formals;
676 if (formals) {
677 QQmlJS::AST::FormalParameterList *fp = formals;
678 do {
679 QString defaultValue;
680 auto initializer = fp->element->initializer;
681 if (initializer) {
682 auto loc = initializer->firstSourceLocation();
683 defaultValue = m_document.mid(position: loc.begin(), n: loc.length);
684 }
685 parameters.append(type: QString(), name: fp->element->bindingIdentifier.toString(),
686 value: defaultValue);
687 fp = fp->next;
688 } while (fp && fp != formals);
689 }
690 applyDocumentation(location: fd->firstSourceLocation(), node: method);
691 }
692 return true;
693}
694
695/*!
696 End the visit of the function declaration, \a fd.
697 */
698void QmlDocVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *fd)
699{
700 m_lastEndOffset = fd->lastSourceLocation().end();
701}
702
703/*!
704 Begin the visit of the signal handler declaration \a sb, but only
705 if the nesting level is 1.
706
707 This visit is now deprecated. It has been decided to document
708 public signals. If a signal handler must be discussed in the
709 documentation, that discussion must take place in the comment
710 for the signal.
711 */
712bool QmlDocVisitor::visit(QQmlJS::AST::UiScriptBinding *)
713{
714 return true;
715}
716
717void QmlDocVisitor::endVisit(QQmlJS::AST::UiScriptBinding *sb)
718{
719 m_lastEndOffset = sb->lastSourceLocation().end();
720}
721
722bool QmlDocVisitor::visit(QQmlJS::AST::UiQualifiedId *)
723{
724 return true;
725}
726
727void QmlDocVisitor::endVisit(QQmlJS::AST::UiQualifiedId *)
728{
729 // nothing.
730}
731
732void QmlDocVisitor::throwRecursionDepthError()
733{
734 hasRecursionDepthError = true;
735}
736
737bool QmlDocVisitor::hasError() const
738{
739 return hasRecursionDepthError;
740}
741
742QT_END_NAMESPACE
743

source code of qttools/src/qdoc/qdoc/src/qdoc/qmlvisitor.cpp