summaryrefslogtreecommitdiffstats
path: root/src/tools/qdoc/qmlvisitor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/qdoc/qmlvisitor.cpp')
-rw-r--r--src/tools/qdoc/qmlvisitor.cpp594
1 files changed, 594 insertions, 0 deletions
diff --git a/src/tools/qdoc/qmlvisitor.cpp b/src/tools/qdoc/qmlvisitor.cpp
new file mode 100644
index 0000000000..9c934ebcd1
--- /dev/null
+++ b/src/tools/qdoc/qmlvisitor.cpp
@@ -0,0 +1,594 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QFileInfo>
+#include <QStringList>
+#include <QtGlobal>
+#include "qqmljsast_p.h"
+#include "qqmljsastfwd_p.h"
+#include "qqmljsengine_p.h"
+#include <qdebug.h>
+#include "node.h"
+#include "codeparser.h"
+#include "qmlvisitor.h"
+
+QT_BEGIN_NAMESPACE
+
+#define COMMAND_DEPRECATED Doc::alias(QLatin1String("deprecated")) // ### don't document
+#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_QMLABSTRACT Doc::alias(QLatin1String("qmlabstract"))
+#define COMMAND_QMLCLASS Doc::alias(QLatin1String("qmlclass"))
+#define COMMAND_QMLMODULE Doc::alias(QLatin1String("qmlmodule"))
+#define COMMAND_QMLPROPERTY Doc::alias(QLatin1String("qmlproperty"))
+#define COMMAND_QMLATTACHEDPROPERTY Doc::alias(QLatin1String("qmlattachedproperty"))
+#define COMMAND_QMLINHERITS Doc::alias(QLatin1String("inherits"))
+#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_QMLMODULE Doc::alias(QLatin1String("qmlmodule"))
+
+/*!
+ The constructor stores all the parameters in local data members.
+ */
+QmlDocVisitor::QmlDocVisitor(const QString &filePath,
+ const QString &code,
+ QQmlJS::Engine *engine,
+ Tree *tree,
+ QSet<QString> &commands,
+ QSet<QString> &topics)
+ : nestingLevel(0)
+{
+ this->filePath = filePath;
+ this->name = QFileInfo(filePath).baseName();
+ document = code;
+ this->engine = engine;
+ this->tree = tree;
+ this->commands = commands;
+ this->topics = topics;
+ current = tree->root();
+}
+
+/*!
+ The destructor does nothing.
+ */
+QmlDocVisitor::~QmlDocVisitor()
+{
+ // nothing.
+}
+
+/*!
+ Returns the location of thre 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.mid(loc.offset - 1, 1) == "*") {
+ QString comment = document.mid(loc.offset, loc.length);
+ if (comment.startsWith(QLatin1Char('!')) || comment.startsWith(QLatin1Char('*')))
+ return loc;
+ }
+ }
+ }
+
+ return QQmlJS::AST::SourceLocation();
+}
+
+/*!
+ Finds the nearest unused qdoc comment above the QML entity
+ represented by the \a node and processes the qdoc commands
+ in that comment. The proceesed documentation is stored in
+ the \a node.
+
+ If a qdoc comment is found about \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);
+ node->setDoc(doc);
+ applyMetacommands(loc, node, doc);
+ usedComments.insert(loc.offset);
+ if (doc.isEmpty())
+ return false;
+ return true;
+ }
+ Location codeLoc(filePath);
+ codeLoc.setLineNo(location.startLine);
+ node->setLocation(codeLoc);
+ return false;
+}
+
+/*!
+ 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 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)
+{
+ const TopicList& topicsUsed = doc.topicsUsed();
+ if (topicsUsed.size() > 0) {
+ if (node->type() == Node::QmlProperty) {
+ QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node);
+ for (int i=0; i<topicsUsed.size(); ++i) {
+ if (topicsUsed.at(i).topic == "qmlproperty") {
+ QmlPropArgs qpa;
+ if (splitQmlPropertyArg(doc, topicsUsed.at(i).args, qpa)) {
+ QmlPropertyNode* n = new QmlPropertyNode(qpn, qpa.name_, qpa.type_, false);
+ qpn->appendQmlPropNode(n);
+ }
+ else
+ qDebug() << " FAILED TO PARSE QML PROPERTY:"
+ << topicsUsed.at(i).topic << topicsUsed.at(i).args;
+ }
+ }
+ }
+ }
+ QSet<QString> metacommands = doc.metaCommandsUsed();
+ if (metacommands.count() > 0) {
+ QString topic;
+ QStringList args;
+ QSet<QString>::iterator i = metacommands.begin();
+ while (i != metacommands.end()) {
+ if (topics.contains(*i)) {
+ topic = *i;
+ break;
+ }
+ ++i;
+ }
+ if (!topic.isEmpty()) {
+ args = doc.metaCommandArgs(topic);
+ if (topic == COMMAND_QMLCLASS) {
+ }
+ else if (topic == COMMAND_QMLPROPERTY) {
+ if (node->type() == Node::QmlProperty) {
+ QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node);
+ qpn->setReadOnly(0);
+ if (qpn->dataType() == "alias") {
+ QStringList part = args[0].split(QLatin1Char(' '));
+ qpn->setDataType(part[0]);
+ }
+ }
+ }
+ else if (topic == COMMAND_QMLMODULE) {
+ }
+ else if (topic == COMMAND_QMLATTACHEDPROPERTY) {
+ if (node->type() == Node::QmlProperty) {
+ QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node);
+ qpn->setReadOnly(0);
+ }
+ }
+ else if (topic == COMMAND_QMLSIGNAL) {
+ }
+ else if (topic == COMMAND_QMLATTACHEDSIGNAL) {
+ }
+ else if (topic == COMMAND_QMLMETHOD) {
+ }
+ else if (topic == COMMAND_QMLATTACHEDMETHOD) {
+ }
+ else if (topic == COMMAND_QMLBASICTYPE) {
+ }
+ }
+ metacommands.subtract(topics);
+ i = metacommands.begin();
+ while (i != metacommands.end()) {
+ QString command = *i;
+ args = doc.metaCommandArgs(command);
+ if (command == COMMAND_QMLABSTRACT) {
+ if ((node->type() == Node::Fake) && (node->subType() == Node::QmlClass)) {
+ node->setAbstract(true);
+ }
+ }
+ else if (command == COMMAND_DEPRECATED) {
+ node->setStatus(Node::Deprecated);
+ }
+ else if (command == COMMAND_INQMLMODULE) {
+ node->setQmlModuleName(args[0]);
+ tree->addToQmlModule(node,args[0]);
+ QString qmid = node->qmlModuleIdentifier();
+ QmlClassNode* qcn = static_cast<QmlClassNode*>(node);
+ QmlClassNode::moduleMap.insert(qmid + "::" + node->name(), qcn);
+ }
+ else if (command == COMMAND_QMLINHERITS) {
+ if (node->name() == args[0])
+ doc.location().warning(tr("%1 tries to inherit itself").arg(args[0]));
+ else {
+ qDebug() << "QML Component:" << node->name() << "inherits:" << args[0];
+ CodeParser::setLink(node, Node::InheritsLink, args[0]);
+ if (node->subType() == Node::QmlClass) {
+ QmlClassNode::addInheritedBy(args[0],node);
+ }
+ }
+ }
+ else if (command == COMMAND_QMLDEFAULT) {
+ if (node->type() == Node::QmlProperty) {
+ QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node);
+ qpn->setDefault();
+ }
+ }
+ else if (command == COMMAND_QMLREADONLY) {
+ if (node->type() == Node::QmlProperty) {
+ QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node);
+ qpn->setReadOnly(1);
+ }
+ }
+ else if (command == COMMAND_INGROUP) {
+ tree->addToGroup(node, args[0]);
+ }
+ else if (command == COMMAND_INTERNAL) {
+ node->setAccess(Node::Private);
+ node->setStatus(Node::Internal);
+ }
+ else if (command == COMMAND_OBSOLETE) {
+ if (node->status() != Node::Compat)
+ 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.join(" ");
+ node->setSince(arg);
+ }
+ else {
+ doc.location().warning(tr("The \\%1 command is ignored in QML files").arg(command));
+ }
+ ++i;
+ }
+ }
+}
+
+/*!
+ Begin the visit of the object \a definition, recording it in a tree
+ structure. 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 = definition->qualifiedTypeNameId->name.toString();
+ nestingLevel++;
+
+ if (current->type() == Node::Namespace) {
+ QmlClassNode *component = new QmlClassNode(current, name, 0);
+ component->setTitle(name);
+ component->setImportList(importList);
+
+ if (applyDocumentation(definition->firstSourceLocation(), component)) {
+ QmlClassNode::addInheritedBy(type, component);
+ if (!component->links().contains(Node::InheritsLink))
+ component->setLink(Node::InheritsLink, type, 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();
+}
+
+/*!
+ Note that the imports list can be traversed by iteration to obtain
+ all the imports in the document at once, having found just one:
+
+ *it = imports; it; it = it->next
+
+ */
+bool QmlDocVisitor::visit(QQmlJS::AST::UiImportList *imports)
+{
+ QQmlJS::AST::UiImport* imp = imports->import;
+ quint32 length = imp->versionToken.offset - imp->fileNameToken.offset - 1;
+ QString module = document.mid(imp->fileNameToken.offset,length);
+ QString version = document.mid(imp->versionToken.offset, imp->versionToken.length);
+ if (version.size() > 1) {
+ int dot = version.lastIndexOf(QChar('.'));
+ if (dot > 0)
+ version = version.left(dot);
+ }
+ importList.append(QPair<QString, QString>(module, version));
+
+ return true;
+}
+
+/*!
+ End the visit of the imports list.
+ */
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiImportList *definition)
+{
+ lastEndOffset = definition->lastSourceLocation().end();
+}
+
+/*!
+ 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->type() == Node::Fake) {
+ QmlClassNode *qmlClass = static_cast<QmlClassNode *>(current);
+ if (qmlClass) {
+
+ QString name = member->name.toString();
+ FunctionNode *qmlSignal = new FunctionNode(Node::QmlSignal, current, name, false);
+
+ QList<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(), "", 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->type() == Node::Fake) {
+ QmlClassNode *qmlClass = static_cast<QmlClassNode *>(current);
+ if (qmlClass) {
+ QString name = member->name.toString();
+ QmlPropertyNode *qmlPropNode = new QmlPropertyNode(qmlClass, name, type, false);
+ qmlPropNode->setReadOnly(member->isReadonlyMember);
+ if (member->isDefaultMember)
+ qmlPropNode->setDefault();
+ applyDocumentation(member->firstSourceLocation(), qmlPropNode);
+ }
+#if 0
+ if (qmlClass) {
+ QString name = member->name->asString();
+ QmlPropGroupNode *qmlPropGroup = new QmlPropGroupNode(qmlClass, name, false);
+ if (member->isDefaultMember)
+ qmlPropGroup->setDefault();
+ QmlPropertyNode *qmlPropNode = new QmlPropertyNode(qmlPropGroup, name, type, false);
+ qmlPropNode->setWritable(!member->isReadonlyMember);
+ applyDocumentation(member->firstSourceLocation(), qmlPropGroup);
+ }
+#endif
+ }
+ 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->type() == Node::Fake) {
+ QmlClassNode* qmlClass = static_cast<QmlClassNode*>(current);
+ if (qmlClass) {
+ QString name = fd->name.toString();
+ FunctionNode* qmlMethod = new FunctionNode(Node::QmlMethod, current, name, false);
+ QList<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.
+ */
+bool QmlDocVisitor::visit(QQmlJS::AST::UiScriptBinding* sb)
+{
+ if (nestingLevel > 1)
+ return true;
+ if (current->type() == Node::Fake) {
+ QString handler = sb->qualifiedId->name.toString();
+ if (handler.length() > 2 && handler.startsWith("on") && handler.at(2).isUpper()) {
+ QmlClassNode* qmlClass = static_cast<QmlClassNode*>(current);
+ if (qmlClass) {
+ FunctionNode* qmlSH = new FunctionNode(Node::QmlSignalHandler,current,handler,false);
+ applyDocumentation(sb->firstSourceLocation(), qmlSH);
+ }
+ }
+ }
+ 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