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)
125 static_cast<QmlTypeNode*>(node)->setSingleton(
true);
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;
145 node =
new QmlTypeNode(m_current, m_name, NodeType::QmlType);
148 if (m_singletonPragmaFound && node->isQmlNode())
149 static_cast<QmlTypeNode*>(node)->setSingleton(
true);
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"))) {
159 auto *qmlProperty =
static_cast<QmlPropertyNode *>(node);
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"));
177 if (qmlProperty->isDefault())
182 qCDebug(lcQdoc) <<
"Failed to parse QML property:" << topic << args;
187 if (nodes.size() > 1) {
188 doc
.location().warning(
"\\%1 cannot be mixed with other topic commands"_L1.arg(topic));
196 if (nodes.size() > 1) {
197 doc
.location().warning(
"\\%1 cannot be mixed with other topic commands"_L1.arg(topic));
206 for (
auto *n : nodes) {
207 applyMetacommands(loc, n, doc);
213 bool isPropertyGroup{
false};
216 std::all_of(
std::next(nodes.cbegin()), nodes.cend(), [node](
const Node *p) {
217 return p->name().startsWith(
"%1."_L1.arg(node->name()));
225 if (isPropertyGroup) {
228 QString group{nodes.takeFirst()->name()};
229 auto *scn =
new SharedCommentNode(
static_cast<QmlTypeNode*>(parent),
230 nodes.size(), group);
233 applyMetacommands(loc, scn, doc);
234 for (
const auto n : std::as_const(nodes))
238 for (
auto *n : nodes) {
240 n->setLocation(doc.location());
244 m_usedComments.insert(loc.offset);
252 QByteArray latin1 = signature.toLatin1();
253 Tokenizer stringTokenizer(location_,
std::move(latin1));
255 tokenizer_ = &stringTokenizer;
261
262
263
264
267 if (tok_ == target) {
275
276
277
283 type->append(previousLexeme());
286 bool hasModifiers =
false;
290 type->append(previousLexeme());
296 type->append(previousLexeme());
297 }
else if (!hasModifiers) {
300 type->append(previousLexeme());
309 type->append(previousLexeme());
316 type->append(previousLexeme());
319
320
321
322 type->appendHotspot();
325 if ((var !=
nullptr) && match(Tok_Ident))
326 *var = previousLexeme();
333 type->append(lexeme());
344 CodeChunk defaultValue;
347 if (name.isEmpty()) {
348 name = type.toString();
359 defaultValue.append(lexeme());
363 func_
->parameters().append(type.toString(), name, defaultValue.toString());
369 CodeChunk returnType;
371 qsizetype firstBlank = signature_.indexOf(QChar(
' '));
372 qsizetype leftParen = signature_.indexOf(QChar(
'('));
373 if ((firstBlank > 0) && (leftParen - firstBlank) > 1) {
379 names_.append(previousLexeme());
390
391
392
396 func_->setReturnType(returnType.toString());
411
412
416 QSet<QString> metacommands = doc.metaCommandsUsed();
417 if (metacommands.size() > 0) {
418 metacommands.subtract(m_topics);
419 for (
const auto &command : std::as_const(metacommands)) {
420 const ArgList args = doc.metaCommandArgs(command);
422 if (node->isQmlType()) {
423 node->setAbstract(
true);
426 node->setDeprecated(args[0].second);
428 qdb->addToQmlModule(args[0].first, node);
430 if (node->name() == args[0].first)
431 doc.location().warning(
432 QStringLiteral(
"%1 tries to inherit itself").arg(args[0].first));
433 else if (node->isQmlType()) {
434 auto *qmlType =
static_cast<QmlTypeNode *>(node);
435 qmlType->setQmlBaseName(args[0].first);
438 if (!node->isQmlProperty()) {
439 doc.location().warning(QStringLiteral(
"Ignored '\\%1', applies only to '\\%2'")
441 }
else if (args.isEmpty() || args[0].first.isEmpty()) {
442 doc.location().warning(QStringLiteral(
"Expected an argument for '\\%1' (maybe you meant '\\%2'?)")
445 static_cast<QmlPropertyNode *>(node)->setDefaultValue(args[0].first);
450 if (!node->isQmlProperty()) {
451 doc.location().warning(
"Ignored '\\%1', applies only to '\\%2'"_L1
453 }
else if (!
static_cast<QmlPropertyNode*>(node)->nativeEnum()->resolve(args[0].first, args[0].second)) {
454 doc.location().warning(
"Failed to find C++ enumeration '%2' passed to \\%1"_L1
455 .arg(command, args[0].first),
"Use \\value commands instead"_L1);
458 node->markReadOnly(1);
460 if (node->isQmlProperty())
461 static_cast<QmlPropertyNode *>(node)->setRequired();
463 for (
const auto &argument : args)
464 QDocDatabase::qdocDB()->addToGroup(argument.first, node);
466 node->setStatus(Status::Internal);
468 node->setStatus(Status::Deprecated);
470 node->setStatus(Status::Preliminary);
472 QString arg = args[0].first;
477 doc.location().warning(
478 QStringLiteral(
"The \\%1 command is ignored in QML files").arg(command));
485
486
487
488QString
QmlDocVisitor::getFullyQualifiedId(QQmlJS::AST::UiQualifiedId *id)
492 result = id->name.toString();
494 while (id !=
nullptr) {
495 result += QChar(
'.') + id->name.toString();
503
504
505
506
507
508
509
510
511
514 QString type = getFullyQualifiedId(definition->qualifiedTypeNameId);
517 auto component = applyDocumentation(definition->firstSourceLocation(),
nullptr);
519 auto *qmlTypeNode =
static_cast<
QmlTypeNode *>(component);
521 if (!component->doc().isEmpty() && qmlTypeNode->qmlBaseName().isEmpty())
522 qmlTypeNode->setQmlBaseName(type);
523 qmlTypeNode->setImportList(m_importList);
524 m_importList.clear();
525 m_current = qmlTypeNode;
532
533
534
535
536
539 if (m_nestingLevel > 0) {
542 m_lastEndOffset = definition->lastSourceLocation().end();
547 QString name = m_document.mid(import->fileNameToken.offset, import->fileNameToken.length);
549 name = name.mid(1, name.size() - 2);
551 if (import->version) {
552 const auto start = import->version->firstSourceLocation().begin();
553 const auto end = import->version->lastSourceLocation().end();
554 version = m_document.mid(start, end - start);
556 QString importUri = getFullyQualifiedId(import->importUri);
557 m_importList.append(
ImportRec(
std::move(name),
std::move(version),
std::move(importUri), import->importId));
564 m_lastEndOffset = definition->lastSourceLocation().end();
589 for (QQmlJS::AST::UiQualifiedId *it = node; it; it = it->next) {
593 s.append(QLatin1Char(
'.'));
600
601
602
603
606 if (m_nestingLevel > 1) {
609 switch (member->type) {
610 case QQmlJS::AST::UiPublicMember::Signal: {
612 auto *qmlType =
static_cast<QmlTypeNode *>(m_current);
615 QString name = member->name.toString();
616 auto *newSignal =
new FunctionNode(metaness, m_current, name);
617 Parameters ¶meters = newSignal->parameters();
618 for (QQmlJS::AST::UiParameterList *it = member->parameters; it; it = it->next) {
619 const QString type = it->type ? it->type->toString() : QString();
620 if (!type.isEmpty() && !it->name.isEmpty())
621 parameters.append(type, it->name.toString());
623 applyDocumentation(member->firstSourceLocation(), newSignal);
628 case QQmlJS::AST::UiPublicMember::Property: {
629 QString type = qualifiedIdToString(member->memberType);
631 auto *qmlType =
static_cast<QmlTypeNode *>(m_current);
633 QString name = member->name.toString();
635 if (qmlPropNode ==
nullptr)
638 if (member->isDefaultMember())
640 if (member->requiredToken().isValid())
642 qmlPropNode
->setIsList(member->typeModifier ==
"list"_L1);
643 applyDocumentation(member->firstSourceLocation(), qmlPropNode);
656
657
660 m_lastEndOffset = member->lastSourceLocation().end();
669
670
671
674 if (m_nestingLevel <= 1) {
678 QString name = fd->name.toString();
679 auto *method =
new FunctionNode(metaness, m_current, name);
680 Parameters ¶meters = method->parameters();
681 QQmlJS::AST::FormalParameterList *formals = fd->formals;
683 QQmlJS::AST::FormalParameterList *fp = formals;
685 QString defaultValue;
686 auto initializer = fp->element->initializer;
688 auto loc = initializer->firstSourceLocation();
689 defaultValue = m_document.mid(loc.begin(), loc.length);
691 parameters.append(QString(), fp->element->bindingIdentifier.toString(),
694 }
while (fp && fp != formals);
696 applyDocumentation(fd->firstSourceLocation(), method);
702
703
706 m_lastEndOffset = fd->lastSourceLocation().end();
710
711
712
713
714
715
716
717
725 m_lastEndOffset = sb->lastSourceLocation().end();
740 hasRecursionDepthError =
true;
745 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.
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.
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
Combined button and popup list for selecting options.
static QString qualifiedIdToString(QQmlJS::AST::UiQualifiedId *node)
The Node class is the base class for all the nodes in QDoc's parse tree.
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.