20#include <QtCore/qdebug.h>
21#include <QtCore/qfileinfo.h>
22#include <QtCore/qglobal.h>
24#include <private/qqmljsast_p.h>
25#include <private/qqmljsengine_p.h>
29using namespace Qt::StringLiterals;
32
33
35 const QSet<QString> &commands,
const QSet<QString> &topics)
39 this->m_filePath = filePath;
40 this->m_name = QFileInfo(filePath).baseName();
42 this->m_engine = engine;
43 this->m_commands = commands;
44 this->m_topics = topics;
49
50
51QQmlJS::SourceLocation
QmlDocVisitor::precedingComment(quint32 offset)
const
53 const auto comments = m_engine->comments();
54 for (
auto it = comments.rbegin(); it != comments.rend(); ++it) {
55 QQmlJS::SourceLocation loc = *it;
57 if (loc.begin() <= m_lastEndOffset) {
60 }
else if (m_usedComments.contains(loc.begin())) {
63 }
else if (loc.begin() > m_lastEndOffset && loc.end() < offset) {
65 if (m_document.at(loc.offset - 1) == QLatin1Char(
'*')) {
66 QString comment = m_document.mid(loc.offset, loc.length);
67 if (comment.startsWith(QLatin1Char(
'!')) || comment.startsWith(QLatin1Char(
'*'))) {
74 return QQmlJS::SourceLocation();
100
101
102
103
104
105
106
107
108
109
110
111
112
113
116 QQmlJS::SourceLocation loc = precedingComment(location.begin());
121 if (!loc.isValid()) {
123 node =
new QmlTypeNode(m_current, m_name, NodeType::QmlType);
124 if (m_singletonPragmaFound)
132 QString source = m_document.mid(loc.offset + 1, loc.length - 1);
136 Doc doc(comment_loc, comment_loc, source, m_commands, m_topics);
142 qmid = args.first().first;
143 node = QDocDatabase::qdocDB()->findQmlTypeInPrimaryTree(qmid, m_name);
145 node =
new QmlTypeNode(m_current, m_name, NodeType::QmlType);
154 if (!topicsUsed.empty()) {
155 for (
int i = 0; i < topicsUsed.size(); ++i) {
156 QString topic = topicsUsed.at(i).m_topic;
157 QString args = topicsUsed.at(i).m_args;
158 if (topic.endsWith(QLatin1String(
"property"))) {
160 if (
auto qpa = QmlPropertyArguments::parse(args, doc.location())) {
161 if (qpa->m_name == node->name()) {
163 qmlProperty->setDataType(qpa->m_type);
165 bool isAttached = topic.contains(QLatin1String(
"attached"));
179 qCDebug(lcQdoc) <<
"Failed to parse QML property:" << topic << args;
184 if (nodes.size() > 1) {
185 doc.location().warning(
"\\%1 cannot be mixed with other topic commands"_L1.arg(topic));
193 if (nodes.size() > 1) {
194 doc.location().warning(
"\\%1 cannot be mixed with other topic commands"_L1.arg(topic));
203 for (
auto *n : nodes) {
204 applyMetacommands(loc, n, doc);
210 bool isPropertyGroup{
false};
213 std::all_of(
std::next(nodes.cbegin()), nodes.cend(), [node](
const Node *p) {
214 return p->name().startsWith(
"%1."_L1.arg(node->name()));
222 if (isPropertyGroup) {
225 QString group{nodes.takeFirst()->name()};
227 nodes.size(), group);
230 applyMetacommands(loc, scn, doc);
231 for (
const auto n : std::as_const(nodes))
235 for (
auto *n : nodes) {
237 n->setLocation(doc.location());
241 m_usedComments.insert(loc.offset);
249 QByteArray latin1 = signature.toLatin1();
250 Tokenizer stringTokenizer(location_,
std::move(latin1));
252 tokenizer_ = &stringTokenizer;
258
259
260
261
264 if (tok_ == target) {
272
273
274
280 type->append(previousLexeme());
283 bool hasModifiers =
false;
287 type->append(previousLexeme());
293 type->append(previousLexeme());
294 }
else if (!hasModifiers) {
297 type->append(previousLexeme());
306 type->append(previousLexeme());
312 while (match(Tok_Ampersand) || match(Tok_Aster) || match(Tok_const) || match(Tok_Caret))
313 type->append(previousLexeme());
316
317
318
319 type->appendHotspot();
322 if ((var !=
nullptr) && match(Tok_Ident))
323 *var = previousLexeme();
330 type->append(lexeme());
341 CodeChunk defaultValue;
344 if (name.isEmpty()) {
345 name = type.toString();
356 defaultValue.append(lexeme());
360 func_
->parameters().append(type.toString(), name, defaultValue.toString());
366 CodeChunk returnType;
368 qsizetype firstBlank = signature_.indexOf(QChar(
' '));
369 qsizetype leftParen = signature_.indexOf(QChar(
'('));
370 if ((firstBlank > 0) && (leftParen - firstBlank) > 1) {
376 names_.append(previousLexeme());
387
388
389
393 func_->setReturnType(returnType.toString());
408
409
413 QSet<QString> metacommands = doc.metaCommandsUsed();
414 if (metacommands.size() > 0) {
415 metacommands.subtract(m_topics);
416 for (
const auto &command : std::as_const(metacommands)) {
417 const ArgList args = doc.metaCommandArgs(command);
419 if (node->isQmlType()) {
420 node->setAbstract(
true);
423 node->setDeprecated(args[0].second);
425 qdb->addToQmlModule(args[0].first, node);
427 if (node->name() == args[0].first)
428 doc.location().warning(
429 QStringLiteral(
"%1 tries to inherit itself").arg(args[0].first));
430 else if (node->isQmlType()) {
431 auto *qmlType =
static_cast<QmlTypeNode *>(node);
432 qmlType->setQmlBaseName(args[0].first);
435 if (!node->isQmlProperty()) {
436 doc.location().warning(QStringLiteral(
"Ignored '\\%1', applies only to '\\%2'")
438 }
else if (args.isEmpty() || args[0].first.isEmpty()) {
439 doc.location().warning(QStringLiteral(
"Expected an argument for '\\%1' (maybe you meant '\\%2'?)")
442 static_cast<QmlPropertyNode *>(node)->setDefaultValue(args[0].first);
447 if (!node->isQmlProperty()) {
448 doc.location().warning(
"Ignored '\\%1', applies only to '\\%2'"_L1
450 }
else if (!
static_cast<QmlPropertyNode*>(node)->nativeEnum()->resolve(args[0].first, args[0].second)) {
451 doc.location().warning(
"Failed to find C++ enumeration '%2' passed to \\%1"_L1
452 .arg(command, args[0].first),
"Use \\value commands instead"_L1);
455 node->markReadOnly(1);
457 if (node->isQmlProperty())
458 static_cast<QmlPropertyNode *>(node)->setRequired();
460 for (
const auto &argument : args)
461 QDocDatabase::qdocDB()->addToGroup(argument.first, node);
463 node->setStatus(Node::Internal);
465 node->setStatus(Node::Deprecated);
467 node->setStatus(Node::Preliminary);
469 QString arg = args[0].first;
474 doc.location().warning(
475 QStringLiteral(
"The \\%1 command is ignored in QML files").arg(command));
482
483
484
485QString
QmlDocVisitor::getFullyQualifiedId(QQmlJS::AST::UiQualifiedId *id)
489 result = id->name.toString();
491 while (id !=
nullptr) {
492 result += QChar(
'.') + id->name.toString();
500
501
502
503
504
505
506
507
508
511 QString type = getFullyQualifiedId(definition->qualifiedTypeNameId);
514 auto component = applyDocumentation(definition->firstSourceLocation(),
nullptr);
516 auto *qmlTypeNode =
static_cast<
QmlTypeNode *>(component);
517 if (!component->doc().isEmpty())
518 qmlTypeNode->setQmlBaseName(type);
519 qmlTypeNode->setTitle(m_name);
520 qmlTypeNode->setImportList(m_importList);
521 m_importList.clear();
522 m_current = qmlTypeNode;
529
530
531
532
533
536 if (m_nestingLevel > 0) {
539 m_lastEndOffset = definition->lastSourceLocation().end();
544 QString name = m_document.mid(import->fileNameToken.offset, import->fileNameToken.length);
546 name = name.mid(1, name.size() - 2);
548 if (import->version) {
549 const auto start = import->version->firstSourceLocation().begin();
550 const auto end = import->version->lastSourceLocation().end();
551 version = m_document.mid(start, end - start);
553 QString importUri = getFullyQualifiedId(import->importUri);
554 m_importList.append(
ImportRec(
std::move(name),
std::move(version),
std::move(importUri), import->importId));
561 m_lastEndOffset = definition->lastSourceLocation().end();
586 for (QQmlJS::AST::UiQualifiedId *it = node; it; it = it->next) {
590 s.append(QLatin1Char(
'.'));
597
598
599
600
603 if (m_nestingLevel > 1) {
606 switch (member->type) {
607 case QQmlJS::AST::UiPublicMember::Signal: {
609 auto *qmlType =
static_cast<
QmlTypeNode *>(m_current);
612 QString name = member->name.toString();
613 auto *newSignal =
new FunctionNode(metaness, m_current, name);
614 Parameters ¶meters = newSignal->parameters();
615 for (QQmlJS::AST::UiParameterList *it = member->parameters; it; it = it->next) {
616 const QString type = it->type ? it->type->toString() : QString();
617 if (!type.isEmpty() && !it->name.isEmpty())
618 parameters.append(type, it->name.toString());
620 applyDocumentation(member->firstSourceLocation(), newSignal);
625 case QQmlJS::AST::UiPublicMember::Property: {
626 QString type = qualifiedIdToString(member->memberType);
628 auto *qmlType =
static_cast<
QmlTypeNode *>(m_current);
630 QString name = member->name.toString();
632 if (qmlPropNode ==
nullptr)
635 if (member->isDefaultMember())
637 if (member->requiredToken().isValid())
639 qmlPropNode->setIsList(member->typeModifier ==
"list"_L1);
640 applyDocumentation(member->firstSourceLocation(), qmlPropNode);
653
654
657 m_lastEndOffset = member->lastSourceLocation().end();
666
667
668
671 if (m_nestingLevel <= 1) {
675 QString name = fd->name.toString();
676 auto *method =
new FunctionNode(metaness, m_current, name);
677 Parameters ¶meters = method->parameters();
678 QQmlJS::AST::FormalParameterList *formals = fd->formals;
680 QQmlJS::AST::FormalParameterList *fp = formals;
682 QString defaultValue;
683 auto initializer = fp->element->initializer;
685 auto loc = initializer->firstSourceLocation();
686 defaultValue = m_document.mid(loc.begin(), loc.length);
688 parameters.append(QString(), fp->element->bindingIdentifier.toString(),
691 }
while (fp && fp != formals);
693 applyDocumentation(fd->firstSourceLocation(), method);
699
700
703 m_lastEndOffset = fd->lastSourceLocation().end();
707
708
709
710
711
712
713
714
722 m_lastEndOffset = sb->lastSourceLocation().end();
737 hasRecursionDepthError =
true;
742 return hasRecursionDepthError;
const Location & location() const
Returns the starting location of a qdoc comment.
const Location & startLocation() const
Returns the starting location of a qdoc comment.
TopicList topicsUsed() const
Returns a reference to the list of topic commands used in the current qdoc comment.
This node is used to represent any kind of function being documented.
Parameters & parameters()
The Location class provides a way to mark a location in a file.
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
static QDocDatabase * qdocDB()
Creates the singleton.
NamespaceNode * primaryTreeRoot()
Returns a pointer to the root node of the primary tree.
bool visit(QQmlJS::AST::UiImport *import) override
void throwRecursionDepthError() final
void endVisit(QQmlJS::AST::UiImport *definition) override
QmlDocVisitor(const QString &filePath, const QString &code, QQmlJS::Engine *engine, const QSet< QString > &commands, const QSet< QString > &topics)
The constructor stores all the parameters in local data members.
bool isDefault() const override
Returns true if the QML property node is marked as default.
void markReadOnly(bool flag) override
If this node is a QmlPropertyNode, then the property's read-only flag is set to flag.
void markDefault() override
If this node is a QmlPropertyNode, it is marked as the default property.
bool isReadOnly() const
Returns true if this QML property node is marked as a read-only property.
void setIsList(bool isList)
Marks this property as a list if isList is true.
bool match(int target)
If the current token matches target, read the next token and return true.
QmlSignatureParser(FunctionNode *func, const QString &signature, const Location &loc)
bool matchTypeAndName(CodeChunk *type, QString *var)
Parse a QML data type into type and an optional variable name into var.
void setSingleton(bool singleton=true)
QString previousLexeme() const
void setParsingFnOrMacro(bool macro)
#define COMMAND_QMLINHERITS
#define COMMAND_QMLSIGNAL
#define COMMAND_DEPRECATED
#define COMMAND_QMLSINGLETONTYPE
#define COMMAND_PRELIMINARY
#define COMMAND_QMLPROPERTY
#define COMMAND_QMLDEFAULT
#define COMMAND_QMLABSTRACT
#define COMMAND_QMLBASICTYPE
#define COMMAND_QMLMETHOD
#define COMMAND_QMLREADONLY
#define COMMAND_QMLENUMERATORSFROM
#define COMMAND_QMLREQUIRED
#define COMMAND_QMLVALUETYPE
#define COMMAND_INQMLMODULE
#define COMMAND_QMLATTACHEDMETHOD
#define COMMAND_QMLATTACHEDSIGNAL
static QString qualifiedIdToString(QQmlJS::AST::UiQualifiedId *node)
The Node class is the base class for all the nodes in QDoc's parse tree.
bool isQmlNode() const
Returns true if this node's Genus value is QML.
bool isNamespace() const
Returns true if the node type is Namespace.
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
Aggregate * parent() const
Returns the node's parent pointer.
void setLocation(const Location &t)
Sets the node's declaration location, its definition location, or both, depending on the suffix of th...
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
A class for parsing and managing a function parameter list.