diff options
Diffstat (limited to 'src/qdoc/qmlvisitor.cpp')
-rw-r--r-- | src/qdoc/qmlvisitor.cpp | 827 |
1 files changed, 827 insertions, 0 deletions
diff --git a/src/qdoc/qmlvisitor.cpp b/src/qdoc/qmlvisitor.cpp new file mode 100644 index 000000000..654922929 --- /dev/null +++ b/src/qdoc/qmlvisitor.cpp @@ -0,0 +1,827 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlvisitor.h" + +#include "node.h" +#include "codeparser.h" +#include "qdocdatabase.h" +#include "tokenizer.h" + +#include <qfileinfo.h> +#include <qstringlist.h> +#include <qglobal.h> +#include <qdebug.h> +#include <private/qqmljsast_p.h> +#include <private/qqmljsastfwd_p.h> +#include <private/qqmljsengine_p.h> + +QT_BEGIN_NAMESPACE + +#define COMMAND_DEPRECATED Doc::alias(QLatin1String("deprecated")) +#define COMMAND_INGROUP Doc::alias(QLatin1String("ingroup")) +#define COMMAND_INTERNAL Doc::alias(QLatin1String("internal")) +#define COMMAND_OBSOLETE Doc::alias(QLatin1String("obsolete")) +#define COMMAND_PAGEKEYWORDS Doc::alias(QLatin1String("pagekeywords")) +#define COMMAND_PRELIMINARY Doc::alias(QLatin1String("preliminary")) +#define COMMAND_SINCE Doc::alias(QLatin1String("since")) +#define COMMAND_WRAPPER Doc::alias(QLatin1String("wrapper")) +#define COMMAND_NOAUTOLIST Doc::alias(QLatin1String("noautolist")) + +#define COMMAND_ABSTRACT Doc::alias(QLatin1String("abstract")) +#define COMMAND_QMLABSTRACT Doc::alias(QLatin1String("qmlabstract")) +#define COMMAND_QMLCLASS Doc::alias(QLatin1String("qmlclass")) +#define COMMAND_QMLTYPE Doc::alias(QLatin1String("qmltype")) +#define COMMAND_QMLMODULE Doc::alias(QLatin1String("qmlmodule")) +#define COMMAND_QMLPROPERTY Doc::alias(QLatin1String("qmlproperty")) +#define COMMAND_QMLPROPERTYGROUP Doc::alias(QLatin1String("qmlpropertygroup")) +#define COMMAND_QMLATTACHEDPROPERTY Doc::alias(QLatin1String("qmlattachedproperty")) +#define COMMAND_QMLINHERITS Doc::alias(QLatin1String("inherits")) +#define COMMAND_QMLINSTANTIATES Doc::alias(QLatin1String("instantiates")) +#define COMMAND_INQMLMODULE Doc::alias(QLatin1String("inqmlmodule")) +#define COMMAND_QMLSIGNAL Doc::alias(QLatin1String("qmlsignal")) +#define COMMAND_QMLATTACHEDSIGNAL Doc::alias(QLatin1String("qmlattachedsignal")) +#define COMMAND_QMLMETHOD Doc::alias(QLatin1String("qmlmethod")) +#define COMMAND_QMLATTACHEDMETHOD Doc::alias(QLatin1String("qmlattachedmethod")) +#define COMMAND_QMLDEFAULT Doc::alias(QLatin1String("default")) +#define COMMAND_QMLREADONLY Doc::alias(QLatin1String("readonly")) +#define COMMAND_QMLBASICTYPE Doc::alias(QLatin1String("qmlbasictype")) + +#define COMMAND_JSTYPE Doc::alias(QLatin1String("jstype")) +#define COMMAND_JSMODULE Doc::alias(QLatin1String("jsmodule")) +#define COMMAND_JSPROPERTY Doc::alias(QLatin1String("jsproperty")) +#define COMMAND_JSPROPERTYGROUP Doc::alias(QLatin1String("jspropertygroup")) +#define COMMAND_JSATTACHEDPROPERTY Doc::alias(QLatin1String("jsattachedproperty")) +#define COMMAND_INJSMODULE Doc::alias(QLatin1String("injsmodule")) +#define COMMAND_JSSIGNAL Doc::alias(QLatin1String("jssignal")) +#define COMMAND_JSATTACHEDSIGNAL Doc::alias(QLatin1String("jsattachedsignal")) +#define COMMAND_JSMETHOD Doc::alias(QLatin1String("jsmethod")) +#define COMMAND_JSATTACHEDMETHOD Doc::alias(QLatin1String("jsattachedmethod")) +#define COMMAND_JSBASICTYPE Doc::alias(QLatin1String("jsbasictype")) + +/*! + The constructor stores all the parameters in local data members. + */ +QmlDocVisitor::QmlDocVisitor(const QString &filePath, + const QString &code, + QQmlJS::Engine *engine, + const QSet<QString> &commands, + const QSet<QString> &topics) + : nestingLevel(0) +{ + lastEndOffset = 0; + this->filePath_ = filePath; + this->name = QFileInfo(filePath).baseName(); + document = code; + this->engine = engine; + this->commands_ = commands; + this->topics_ = topics; + current = QDocDatabase::qdocDB()->primaryTreeRoot(); +} + +/*! + The destructor does nothing. + */ +QmlDocVisitor::~QmlDocVisitor() +{ + // nothing. +} + +/*! + Returns the location of the nearest comment above the \a offset. + */ +QQmlJS::AST::SourceLocation QmlDocVisitor::precedingComment(quint32 offset) const +{ + QListIterator<QQmlJS::AST::SourceLocation> it(engine->comments()); + it.toBack(); + + while (it.hasPrevious()) { + + QQmlJS::AST::SourceLocation loc = it.previous(); + + if (loc.begin() <= lastEndOffset) { + // Return if we reach the end of the preceding structure. + break; + } + else if (usedComments.contains(loc.begin())) { + // Return if we encounter a previously used comment. + break; + } + else if (loc.begin() > lastEndOffset && loc.end() < offset) { + // Only examine multiline comments in order to avoid snippet markers. + if (document.at(loc.offset - 1) == QLatin1Char('*')) { + QString comment = document.mid(loc.offset, loc.length); + if (comment.startsWith(QLatin1Char('!')) || comment.startsWith(QLatin1Char('*'))) { + return loc; + } + } + } + } + + return QQmlJS::AST::SourceLocation(); +} + +class QmlSignatureParser +{ + public: + QmlSignatureParser(FunctionNode* func, const QString& signature, const Location& loc); + void readToken() { tok_ = tokenizer_->getToken(); } + QString lexeme() { return tokenizer_->lexeme(); } + QString previousLexeme() { return tokenizer_->previousLexeme(); } + + bool match(int target); + bool matchDataType(CodeChunk* dataType, QString* var); + bool matchParameter(); + bool matchFunctionDecl(); + + private: + QString signature_; + QStringList names_; + QString funcName_; + Tokenizer* tokenizer_; + int tok_; + FunctionNode* func_; + const Location& location_; +}; + +/*! + Finds the nearest unused qdoc comment above the QML entity + represented by the \a node and processes the qdoc commands + in that comment. The processed documentation is stored in + the \a node. + + If a qdoc comment is found for \a location, true is returned. + If a comment is not found there, false is returned. + */ +bool QmlDocVisitor::applyDocumentation(QQmlJS::AST::SourceLocation location, Node* node) +{ + QQmlJS::AST::SourceLocation loc = precedingComment(location.begin()); + + if (loc.isValid()) { + QString source = document.mid(loc.offset, loc.length); + Location start(filePath_); + start.setLineNo(loc.startLine); + start.setColumnNo(loc.startColumn); + Location finish(filePath_); + finish.setLineNo(loc.startLine); + finish.setColumnNo(loc.startColumn); + + Doc doc(start, finish, source.mid(1), commands_, topics_); + const TopicList& topicsUsed = doc.topicsUsed(); + NodeList nodes; + Node* nodePassedIn = node; + Aggregate* parent = nodePassedIn->parent(); + node->setDoc(doc); + nodes.append(node); + if (topicsUsed.size() > 0) { + for (int i=0; i<topicsUsed.size(); ++i) { + QString topic = topicsUsed.at(i).topic; + QString args = topicsUsed.at(i).args; + if ((topic == COMMAND_QMLPROPERTY) || (topic == COMMAND_QMLATTACHEDPROPERTY) || + (topic == COMMAND_JSPROPERTY) || (topic == COMMAND_JSATTACHEDPROPERTY)) { + QmlPropArgs qpa; + if (splitQmlPropertyArg(doc, args, qpa)) { + if (qpa.name_ == nodePassedIn->name()) { + if (nodePassedIn->isAlias()) + nodePassedIn->setDataType(qpa.type_); + } + else { + bool isAttached = (topic == COMMAND_QMLATTACHEDPROPERTY) || + (topic == COMMAND_JSATTACHEDPROPERTY); + QmlPropertyNode* n = parent->hasQmlProperty(qpa.name_, isAttached); + if (n == 0) + n = new QmlPropertyNode(parent, qpa.name_, qpa.type_, isAttached); + n->setLocation(doc.location()); + n->setDoc(doc); + n->setReadOnly(nodePassedIn->isReadOnly()); + if (nodePassedIn->isDefault()) + n->setDefault(); + if (isAttached) + n->setReadOnly(0); + if ((topic == COMMAND_JSPROPERTY) || + (topic == COMMAND_JSATTACHEDPROPERTY)) + n->setGenus(Node::JS); + nodes.append(n); + } + } + else + qDebug() << " FAILED TO PARSE QML OR JS PROPERTY:" << topic << args; + } + else if ((topic == COMMAND_QMLMETHOD) || (topic == COMMAND_QMLATTACHEDMETHOD) || + (topic == COMMAND_JSMETHOD) || (topic == COMMAND_JSATTACHEDMETHOD)) { + if (node->isFunction()) { + FunctionNode* fn = static_cast<FunctionNode*>(node); + QmlSignatureParser qsp(fn, args, doc.location()); + } + } + } + } + for (int i=0; i<nodes.size(); ++i) + applyMetacommands(loc, nodes.at(i), doc); + usedComments.insert(loc.offset); + if (doc.isEmpty()) { + return false; + } + return true; + } + Location codeLoc(filePath_); + codeLoc.setLineNo(location.startLine); + node->setLocation(codeLoc); + return false; +} + +QmlSignatureParser::QmlSignatureParser(FunctionNode* func, const QString& signature, const Location& loc) + : signature_(signature), func_(func), location_(loc) +{ + QByteArray latin1 = signature.toLatin1(); + Tokenizer stringTokenizer(location_, latin1); + stringTokenizer.setParsingFnOrMacro(true); + tokenizer_ = &stringTokenizer; + readToken(); + matchFunctionDecl(); +} + +/*! + If the current token matches \a target, read the next + token and return true. Otherwise, don't read the next + token, and return false. + */ +bool QmlSignatureParser::match(int target) +{ + if (tok_ == target) { + readToken(); + return true; + } + return false; +} + +/*! + Parse a QML data type into \a dataType and an optional + variable name into \a var. + */ +bool QmlSignatureParser::matchDataType(CodeChunk* dataType, QString* var) +{ + /* + This code is really hard to follow... sorry. The loop is there to match + Alpha::Beta::Gamma::...::Omega. + */ + for (;;) { + bool virgin = true; + + if (tok_ != Tok_Ident) { + while (match(Tok_signed) || + match(Tok_unsigned) || + match(Tok_short) || + match(Tok_long) || + match(Tok_int64)) { + dataType->append(previousLexeme()); + virgin = false; + } + } + + if (virgin) { + if (match(Tok_Ident)) { + dataType->append(previousLexeme()); + } + else if (match(Tok_void) || + match(Tok_int) || + match(Tok_char) || + match(Tok_double) || + match(Tok_Ellipsis)) + dataType->append(previousLexeme()); + else + return false; + } + else if (match(Tok_int) || + match(Tok_char) || + match(Tok_double)) { + dataType->append(previousLexeme()); + } + + if (match(Tok_Gulbrandsen)) + dataType->append(previousLexeme()); + else + break; + } + + while (match(Tok_Ampersand) || + match(Tok_Aster) || + match(Tok_const) || + match(Tok_Caret)) + dataType->append(previousLexeme()); + + /* + The usual case: Look for an optional identifier, then for + some array brackets. + */ + dataType->appendHotspot(); + + if ((var != 0) && match(Tok_Ident)) + *var = previousLexeme(); + + if (tok_ == Tok_LeftBracket) { + int bracketDepth0 = tokenizer_->bracketDepth(); + while ((tokenizer_->bracketDepth() >= bracketDepth0 && tok_ != Tok_Eoi) || + tok_ == Tok_RightBracket) { + dataType->append(lexeme()); + readToken(); + } + } + return true; +} + +bool QmlSignatureParser::matchParameter() +{ + QString name; + CodeChunk dataType; + CodeChunk defaultValue; + + bool result = matchDataType(&dataType, &name); + if (name.isEmpty()) { + name = dataType.toString(); + dataType.clear(); + } + + if (!result) + return false; + if (match(Tok_Equal)) { + int parenDepth0 = tokenizer_->parenDepth(); + while (tokenizer_->parenDepth() >= parenDepth0 && + (tok_ != Tok_Comma || + tokenizer_->parenDepth() > parenDepth0) && + tok_ != Tok_Eoi) { + defaultValue.append(lexeme()); + readToken(); + } + } + func_->addParameter(Parameter(dataType.toString(), "", name, defaultValue.toString())); + return true; +} + +bool QmlSignatureParser::matchFunctionDecl() +{ + CodeChunk returnType; + + int firstBlank = signature_.indexOf(QChar(' ')); + int leftParen = signature_.indexOf(QChar('(')); + if ((firstBlank > 0) && (leftParen - firstBlank) > 1) { + if (!matchDataType(&returnType, 0)) + return false; + } + + while (match(Tok_Ident)) { + names_.append(previousLexeme()); + if (!match(Tok_Gulbrandsen)) { + funcName_ = previousLexeme(); + names_.pop_back(); + break; + } + } + + if (tok_ != Tok_LeftParen) + return false; + + readToken(); + + func_->setLocation(location_); + func_->setReturnType(returnType.toString()); + + if (tok_ != Tok_RightParen) { + func_->clearParams(); + do { + if (!matchParameter()) + return false; + } while (match(Tok_Comma)); + } + if (!match(Tok_RightParen)) + return false; + return true; +} + +/*! + A QML property argument has the form... + + <type> <component>::<name> + <type> <QML-module>::<component>::<name> + + This function splits the argument into one of those + two forms. The three part form is the old form, which + was used before the creation of QtQuick 2 and Qt + Components. A <QML-module> is the QML equivalent of a + C++ namespace. So this function splits \a arg on "::" + and stores the parts in the \e {type}, \e {module}, + \e {component}, and \a {name}, fields of \a qpa. If it + is successful, it returns \c true. If not enough parts + are found, a qdoc warning is emitted and false is + returned. + */ +bool QmlDocVisitor::splitQmlPropertyArg(const Doc& doc, + const QString& arg, + QmlPropArgs& qpa) +{ + qpa.clear(); + QStringList blankSplit = arg.split(QLatin1Char(' ')); + if (blankSplit.size() > 1) { + qpa.type_ = blankSplit[0]; + QStringList colonSplit(blankSplit[1].split("::")); + if (colonSplit.size() == 3) { + qpa.module_ = colonSplit[0]; + qpa.component_ = colonSplit[1]; + qpa.name_ = colonSplit[2]; + return true; + } + else if (colonSplit.size() == 2) { + qpa.component_ = colonSplit[0]; + qpa.name_ = colonSplit[1]; + return true; + } + else if (colonSplit.size() == 1) { + qpa.name_ = colonSplit[0]; + return true; + } + QString msg = "Unrecognizable QML module/component qualifier for " + arg; + doc.location().warning(tr(msg.toLatin1().data())); + } + else { + QString msg = "Missing property type for " + arg; + doc.location().warning(tr(msg.toLatin1().data())); + } + return false; +} + +/*! + Applies the metacommands found in the comment. + */ +void QmlDocVisitor::applyMetacommands(QQmlJS::AST::SourceLocation, + Node* node, + Doc& doc) +{ + QDocDatabase* qdb = QDocDatabase::qdocDB(); + QSet<QString> metacommands = doc.metaCommandsUsed(); + if (metacommands.count() > 0) { + metacommands.subtract(topics_); + QSet<QString>::iterator i = metacommands.begin(); + while (i != metacommands.end()) { + QString command = *i; + ArgList args = doc.metaCommandArgs(command); + if ((command == COMMAND_QMLABSTRACT) || (command == COMMAND_ABSTRACT)) { + if (node->isQmlType() || node->isJsType()) { + node->setAbstract(true); + } + } + else if (command == COMMAND_DEPRECATED) { + node->setStatus(Node::Obsolete); + } + else if ((command == COMMAND_INQMLMODULE) || (command == COMMAND_INJSMODULE)) { + qdb->addToQmlModule(args[0].first,node); + } + else if (command == COMMAND_QMLINHERITS) { + if (node->name() == args[0].first) + doc.location().warning(tr("%1 tries to inherit itself").arg(args[0].first)); + else if (node->isQmlType() || node->isJsType()) { + QmlTypeNode *qmlType = static_cast<QmlTypeNode*>(node); + qmlType->setQmlBaseName(args[0].first); + QmlTypeNode::addInheritedBy(args[0].first,node); + } + } + else if (command == COMMAND_QMLDEFAULT) { + if (node->isQmlProperty() || node->isJsProperty()) { + QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node); + qpn->setDefault(); + } + } + else if (command == COMMAND_QMLREADONLY) { + if (node->isQmlProperty() || node->isJsProperty()) { + QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node); + qpn->setReadOnly(1); + } + } + else if ((command == COMMAND_INGROUP) && !args.isEmpty()) { + ArgList::ConstIterator argsIter = args.constBegin(); + while (argsIter != args.constEnd()) { + QDocDatabase::qdocDB()->addToGroup(argsIter->first, node); + ++argsIter; + } + } + else if (command == COMMAND_INTERNAL) { + node->setStatus(Node::Internal); + } + else if (command == COMMAND_OBSOLETE) { + node->setStatus(Node::Obsolete); + } + else if (command == COMMAND_PAGEKEYWORDS) { + // Not done yet. Do we need this? + } + else if (command == COMMAND_PRELIMINARY) { + node->setStatus(Node::Preliminary); + } + else if (command == COMMAND_SINCE) { + QString arg = args[0].first; //.join(' '); + node->setSince(arg); + } + else if (command == COMMAND_WRAPPER) { + node->setWrapper(); + } + else { + doc.location().warning(tr("The \\%1 command is ignored in QML files").arg(command)); + } + ++i; + } + } +} + +/*! + Reconstruct the qualified \a id using dot notation + and return the fully qualified string. + */ +QString QmlDocVisitor::getFullyQualifiedId(QQmlJS::AST::UiQualifiedId *id) +{ + QString result; + if (id) { + result = id->name.toString(); + id = id->next; + while (id != 0) { + result += QChar('.') + id->name.toString(); + id = id->next; + } + } + return result; +} + +/*! + Begin the visit of the object \a definition, recording it in the + qdoc database. Increment the object nesting level, which is used + to test whether we are at the public API level. The public level + is level 1. +*/ +bool QmlDocVisitor::visit(QQmlJS::AST::UiObjectDefinition *definition) +{ + QString type = getFullyQualifiedId(definition->qualifiedTypeNameId); + nestingLevel++; + + if (current->type() == Node::Namespace) { + QmlTypeNode *component = new QmlTypeNode(current, name); + component->setTitle(name); + component->setImportList(importList); + importList.clear(); + if (applyDocumentation(definition->firstSourceLocation(), component)) { + QmlTypeNode::addInheritedBy(type, component); + component->setQmlBaseName(type); + } + current = component; + } + + return true; +} + +/*! + End the visit of the object \a definition. In particular, + decrement the object nesting level, which is used to test + whether we are at the public API level. The public API + level is level 1. It won't decrement below 0. + */ +void QmlDocVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *definition) +{ + if (nestingLevel > 0) { + --nestingLevel; + } + lastEndOffset = definition->lastSourceLocation().end(); +} + +bool QmlDocVisitor::visit(QQmlJS::AST::UiImport *import) +{ + QString name = document.mid(import->fileNameToken.offset, import->fileNameToken.length); + if (name[0] == '\"') + name = name.mid(1, name.length()-2); + QString version = document.mid(import->versionToken.offset, import->versionToken.length); + QString importId = document.mid(import->importIdToken.offset, import->importIdToken.length); + QString importUri = getFullyQualifiedId(import->importUri); + QString reconstructed = importUri + QLatin1Char(' ') + version; + importList.append(ImportRec(name, version, importId, importUri)); + + return true; +} + +void QmlDocVisitor::endVisit(QQmlJS::AST::UiImport *definition) +{ + lastEndOffset = definition->lastSourceLocation().end(); +} + +bool QmlDocVisitor::visit(QQmlJS::AST::UiObjectBinding *) +{ + ++nestingLevel; + return true; +} + +void QmlDocVisitor::endVisit(QQmlJS::AST::UiObjectBinding *) +{ + --nestingLevel; +} + +bool QmlDocVisitor::visit(QQmlJS::AST::UiArrayBinding *) +{ + return true; +} + +void QmlDocVisitor::endVisit(QQmlJS::AST::UiArrayBinding *) +{ +} + +/*! + Visits the public \a member declaration, which can be a + signal or a property. It is a custom signal or property. + Only visit the \a member if the nestingLevel is 1. +*/ +bool QmlDocVisitor::visit(QQmlJS::AST::UiPublicMember *member) +{ + if (nestingLevel > 1) { + return true; + } + switch (member->type) { + case QQmlJS::AST::UiPublicMember::Signal: + { + if (current->isQmlType() || current->isJsType()) { + QmlTypeNode *qmlType = static_cast<QmlTypeNode *>(current); + if (qmlType) { + + QString name = member->name.toString(); + FunctionNode *qmlSignal = new FunctionNode(Node::QmlSignal, current, name, false); + + QVector<Parameter> parameters; + for (QQmlJS::AST::UiParameterList *it = member->parameters; it; it = it->next) { + if (!it->type.isEmpty() && !it->name.isEmpty()) + parameters.append(Parameter(it->type.toString(), QString(), it->name.toString())); + } + + qmlSignal->setParameters(parameters); + applyDocumentation(member->firstSourceLocation(), qmlSignal); + } + } + break; + } + case QQmlJS::AST::UiPublicMember::Property: + { + QString type = member->memberType.toString(); + QString name = member->name.toString(); + if (current->isQmlType() || current->isJsType()) { + QmlTypeNode *qmlType = static_cast<QmlTypeNode *>(current); + if (qmlType) { + QString name = member->name.toString(); + QmlPropertyNode* qmlPropNode = qmlType->hasQmlProperty(name); + if (qmlPropNode == 0) { + qmlPropNode = new QmlPropertyNode(qmlType, name, type, false); + if (current->isJsType()) + qmlPropNode->setGenus(Node::JS); + } + qmlPropNode->setReadOnly(member->isReadonlyMember); + if (member->isDefaultMember) + qmlPropNode->setDefault(); + applyDocumentation(member->firstSourceLocation(), qmlPropNode); + } + } + break; + } + default: + return false; + } + + return true; +} + +/*! + End the visit of the \a member. + */ +void QmlDocVisitor::endVisit(QQmlJS::AST::UiPublicMember* member) +{ + lastEndOffset = member->lastSourceLocation().end(); +} + +bool QmlDocVisitor::visit(QQmlJS::AST::IdentifierPropertyName *) +{ + return true; +} + +/*! + Begin the visit of the function declaration \a fd, but only + if the nesting level is 1. + */ +bool QmlDocVisitor::visit(QQmlJS::AST::FunctionDeclaration* fd) +{ + if (nestingLevel > 1) { + return true; + } + if (current->isQmlType() || current->isJsType()) { + QmlTypeNode* qmlType = static_cast<QmlTypeNode*>(current); + if (qmlType) { + QString name = fd->name.toString(); + FunctionNode* qmlMethod = new FunctionNode(Node::QmlMethod, current, name, false); + if (current->isJsType()) + qmlMethod->setGenus(Node::JS); + int overloads = 0; + NodeList::ConstIterator i = current->childNodes().constBegin(); + while (i != current->childNodes().constEnd()) { + if ((*i)->name() == name) + overloads++; + i++; + } + if (overloads > 1) + qmlMethod->setOverloadFlag(true); + QVector<Parameter> parameters; + QQmlJS::AST::FormalParameterList* formals = fd->formals; + if (formals) { + QQmlJS::AST::FormalParameterList* fpl = formals; + do { + parameters.append(Parameter(QString(), QString(), fpl->name.toString())); + fpl = fpl->next; + } while (fpl && fpl != formals); + qmlMethod->setParameters(parameters); + } + applyDocumentation(fd->firstSourceLocation(), qmlMethod); + } + } + return true; +} + +/*! + End the visit of the function declaration, \a fd. + */ +void QmlDocVisitor::endVisit(QQmlJS::AST::FunctionDeclaration* fd) +{ + lastEndOffset = fd->lastSourceLocation().end(); +} + +/*! + Begin the visit of the signal handler declaration \a sb, but only + if the nesting level is 1. + + This visit is now deprecated. It has been decided to document + public signals. If a signal handler must be discussed in the + documentation, that discussion must take place in the comment + for the signal. + */ +bool QmlDocVisitor::visit(QQmlJS::AST::UiScriptBinding* ) +{ +#if 0 + if (nestingLevel > 1) { + return true; + } + if (current->isQmlType() || current->isJsType()) { + QString handler = sb->qualifiedId->name.toString(); + if (handler.length() > 2 && handler.startsWith("on") && handler.at(2).isUpper()) { + QmlTypeNode* qmlType = static_cast<QmlTypeNode*>(current); + if (qmlType) { + FunctionNode* qmlSH = new FunctionNode(Node::QmlSignalHandler,current,handler,false); + applyDocumentation(sb->firstSourceLocation(), qmlSH); + } + } + } +#endif + return true; +} + +void QmlDocVisitor::endVisit(QQmlJS::AST::UiScriptBinding* sb) +{ + lastEndOffset = sb->lastSourceLocation().end(); +} + +bool QmlDocVisitor::visit(QQmlJS::AST::UiQualifiedId* ) +{ + return true; +} + +void QmlDocVisitor::endVisit(QQmlJS::AST::UiQualifiedId* ) +{ + // nothing. +} + +QT_END_NAMESPACE |