1
/****************************************************************************
3
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4
** Contact: http://www.qt-project.org/legal
6
** This file is part of the tools applications of the Qt Toolkit.
8
** $QT_BEGIN_LICENSE:LGPL$
9
** Commercial License Usage
10
** Licensees holding valid commercial Qt licenses may use this file in
11
** accordance with the commercial license agreement provided with the
12
** Software or, alternatively, in accordance with the terms contained in
13
** a written agreement between you and Digia. For licensing terms and
14
** conditions see http://qt.digia.com/licensing. For further information
15
** use the contact form at http://qt.digia.com/contact-us.
17
** GNU Lesser General Public License Usage
18
** Alternatively, this file may be used under the terms of the GNU Lesser
19
** General Public License version 2.1 as published by the Free Software
20
** Foundation and appearing in the file LICENSE.LGPL included in the
21
** packaging of this file. Please review the following information to
22
** ensure the GNU Lesser General Public License version 2.1 requirements
23
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25
** In addition, as a special exception, Digia gives you certain additional
26
** rights. These rights are described in the Digia Qt LGPL Exception
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29
** GNU General Public License Usage
30
** Alternatively, this file may be used under the terms of the GNU
31
** General Public License version 3.0 as published by the Free Software
32
** Foundation and appearing in the file LICENSE.GPL included in the
33
** packaging of this file. Please review the following information to
34
** ensure the GNU General Public License version 3.0 requirements will be
35
** met: http://www.gnu.org/copyleft/gpl.html.
40
****************************************************************************/
42
#include <qfileinfo.h>
43
#include <qstringlist.h>
45
#include "qqmljsast_p.h"
46
#include "qqmljsastfwd_p.h"
47
#include "qqmljsengine_p.h"
50
#include "codeparser.h"
51
#include "qmlvisitor.h"
52
#include "qdocdatabase.h"
56
#define COMMAND_DEPRECATED Doc::alias(QLatin1String("deprecated"))
57
#define COMMAND_INGROUP Doc::alias(QLatin1String("ingroup"))
58
#define COMMAND_INTERNAL Doc::alias(QLatin1String("internal"))
59
#define COMMAND_OBSOLETE Doc::alias(QLatin1String("obsolete"))
60
#define COMMAND_PAGEKEYWORDS Doc::alias(QLatin1String("pagekeywords"))
61
#define COMMAND_PRELIMINARY Doc::alias(QLatin1String("preliminary"))
62
#define COMMAND_SINCE Doc::alias(QLatin1String("since"))
64
#define COMMAND_QMLABSTRACT Doc::alias(QLatin1String("qmlabstract"))
65
#define COMMAND_QMLCLASS Doc::alias(QLatin1String("qmlclass"))
66
#define COMMAND_QMLTYPE Doc::alias(QLatin1String("qmltype"))
67
#define COMMAND_QMLMODULE Doc::alias(QLatin1String("qmlmodule"))
68
#define COMMAND_QMLPROPERTY Doc::alias(QLatin1String("qmlproperty"))
69
#define COMMAND_QMLATTACHEDPROPERTY Doc::alias(QLatin1String("qmlattachedproperty"))
70
#define COMMAND_QMLINHERITS Doc::alias(QLatin1String("inherits"))
71
#define COMMAND_QMLINSTANTIATES Doc::alias(QLatin1String("instantiates"))
72
#define COMMAND_INQMLMODULE Doc::alias(QLatin1String("inqmlmodule"))
73
#define COMMAND_QMLSIGNAL Doc::alias(QLatin1String("qmlsignal"))
74
#define COMMAND_QMLATTACHEDSIGNAL Doc::alias(QLatin1String("qmlattachedsignal"))
75
#define COMMAND_QMLMETHOD Doc::alias(QLatin1String("qmlmethod"))
76
#define COMMAND_QMLATTACHEDMETHOD Doc::alias(QLatin1String("qmlattachedmethod"))
77
#define COMMAND_QMLDEFAULT Doc::alias(QLatin1String("default"))
78
#define COMMAND_QMLREADONLY Doc::alias(QLatin1String("readonly"))
79
#define COMMAND_QMLBASICTYPE Doc::alias(QLatin1String("qmlbasictype"))
82
The constructor stores all the parameters in local data members.
84
QmlDocVisitor::QmlDocVisitor(const QString &filePath,
86
QQmlJS::Engine *engine,
87
QSet<QString> &commands,
88
QSet<QString> &topics)
91
this->filePath = filePath;
92
this->name = QFileInfo(filePath).baseName();
94
this->engine = engine;
95
this->commands = commands;
96
this->topics = topics;
97
current = QDocDatabase::qdocDB()->treeRoot();
101
The destructor does nothing.
103
QmlDocVisitor::~QmlDocVisitor()
109
Returns the location of thre nearest comment above the \a offset.
111
QQmlJS::AST::SourceLocation QmlDocVisitor::precedingComment(quint32 offset) const
113
QListIterator<QQmlJS::AST::SourceLocation> it(engine->comments());
116
while (it.hasPrevious()) {
118
QQmlJS::AST::SourceLocation loc = it.previous();
120
if (loc.begin() <= lastEndOffset)
121
// Return if we reach the end of the preceding structure.
124
else if (usedComments.contains(loc.begin()))
125
// Return if we encounter a previously used comment.
128
else if (loc.begin() > lastEndOffset && loc.end() < offset) {
130
// Only examine multiline comments in order to avoid snippet markers.
131
if (document.at(loc.offset - 1) == QLatin1Char('*')) {
132
QString comment = document.mid(loc.offset, loc.length);
133
if (comment.startsWith(QLatin1Char('!')) || comment.startsWith(QLatin1Char('*')))
139
return QQmlJS::AST::SourceLocation();
143
Finds the nearest unused qdoc comment above the QML entity
144
represented by the \a node and processes the qdoc commands
145
in that comment. The proceesed documentation is stored in
148
If a qdoc comment is found about \a location, true is returned.
149
If a comment is not found there, false is returned.
151
bool QmlDocVisitor::applyDocumentation(QQmlJS::AST::SourceLocation location, Node* node)
153
QQmlJS::AST::SourceLocation loc = precedingComment(location.begin());
156
QString source = document.mid(loc.offset, loc.length);
157
Location start(filePath);
158
start.setLineNo(loc.startLine);
159
start.setColumnNo(loc.startColumn);
160
Location finish(filePath);
161
finish.setLineNo(loc.startLine);
162
finish.setColumnNo(loc.startColumn);
164
Doc doc(start, finish, source.mid(1), commands, topics);
166
applyMetacommands(loc, node, doc);
167
usedComments.insert(loc.offset);
172
Location codeLoc(filePath);
173
codeLoc.setLineNo(location.startLine);
174
node->setLocation(codeLoc);
179
A QML property argument has the form...
181
<type> <component>::<name>
182
<type> <QML-module>::<component>::<name>
184
This function splits the argument into one of those
185
two forms. The three part form is the old form, which
186
was used before the creation of QtQuick 2 and Qt
187
Components. A <QML-module> is the QML equivalent of a
188
C++ namespace. So this function splits \a arg on "::"
189
and stores the parts in the \e {type}, \e {module},
190
\e {component}, and \a {name}, fields of \a qpa. If it
191
is successful, it returns true. If not enough parts
192
are found, a qdoc warning is emitted and false is
195
bool QmlDocVisitor::splitQmlPropertyArg(const Doc& doc,
200
QStringList blankSplit = arg.split(QLatin1Char(' '));
201
if (blankSplit.size() > 1) {
202
qpa.type_ = blankSplit[0];
203
QStringList colonSplit(blankSplit[1].split("::"));
204
if (colonSplit.size() == 3) {
205
qpa.module_ = colonSplit[0];
206
qpa.component_ = colonSplit[1];
207
qpa.name_ = colonSplit[2];
210
else if (colonSplit.size() == 2) {
211
qpa.component_ = colonSplit[0];
212
qpa.name_ = colonSplit[1];
215
else if (colonSplit.size() == 1) {
216
qpa.name_ = colonSplit[0];
219
QString msg = "Unrecognizable QML module/component qualifier for " + arg;
220
doc.location().warning(tr(msg.toLatin1().data()));
223
QString msg = "Missing property type for " + arg;
224
doc.location().warning(tr(msg.toLatin1().data()));
230
Applies the metacommands found in the comment.
232
void QmlDocVisitor::applyMetacommands(QQmlJS::AST::SourceLocation,
236
QDocDatabase* qdb = QDocDatabase::qdocDB();
238
const TopicList& topicsUsed = doc.topicsUsed();
239
if (topicsUsed.size() > 0) {
240
if (node->type() == Node::QmlProperty) {
241
QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node);
242
for (int i=0; i<topicsUsed.size(); ++i) {
243
if (topicsUsed.at(i).topic == "qmlproperty") {
245
if (splitQmlPropertyArg(doc, topicsUsed.at(i).args, qpa)) {
246
QmlPropertyNode* n = new QmlPropertyNode(qpn, qpa.name_, qpa.type_, false);
247
n->setLocation(doc.location());
248
qpn->appendQmlPropNode(n);
249
n->setReadOnly(qpn->isReadOnly());
250
if (qpn->isDefault())
254
qDebug() << " FAILED TO PARSE QML PROPERTY:"
255
<< topicsUsed.at(i).topic << topicsUsed.at(i).args;
260
QSet<QString> metacommands = doc.metaCommandsUsed();
261
if (metacommands.count() > 0) {
264
QSet<QString>::iterator i = metacommands.begin();
265
while (i != metacommands.end()) {
266
if (topics.contains(*i)) {
272
if (!topic.isEmpty()) {
273
args = doc.metaCommandArgs(topic);
274
if ((topic == COMMAND_QMLCLASS) || (topic == COMMAND_QMLTYPE)) {
277
else if (topic == COMMAND_QMLPROPERTY) {
278
if (node->type() == Node::QmlProperty) {
279
QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node);
281
if (qpn->dataType() == "alias") {
282
QStringList part = args[0].first.split(QLatin1Char(' '));
283
qpn->setDataType(part[0]);
287
else if (topic == COMMAND_QMLMODULE) {
289
else if (topic == COMMAND_QMLATTACHEDPROPERTY) {
290
if (node->type() == Node::QmlProperty) {
291
QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node);
295
else if (topic == COMMAND_QMLSIGNAL) {
297
else if (topic == COMMAND_QMLATTACHEDSIGNAL) {
299
else if (topic == COMMAND_QMLMETHOD) {
301
else if (topic == COMMAND_QMLATTACHEDMETHOD) {
303
else if (topic == COMMAND_QMLBASICTYPE) {
306
metacommands.subtract(topics);
307
i = metacommands.begin();
308
while (i != metacommands.end()) {
309
QString command = *i;
310
args = doc.metaCommandArgs(command);
311
if (command == COMMAND_QMLABSTRACT) {
312
if ((node->type() == Node::Document) && (node->subType() == Node::QmlClass)) {
313
node->setAbstract(true);
316
else if (command == COMMAND_DEPRECATED) {
317
node->setStatus(Node::Deprecated);
319
else if (command == COMMAND_INQMLMODULE) {
320
qdb->addToQmlModule(args[0].first,node);
322
else if (command == COMMAND_QMLINHERITS) {
323
if (node->name() == args[0].first)
324
doc.location().warning(tr("%1 tries to inherit itself").arg(args[0].first));
326
CodeParser::setLink(node, Node::InheritsLink, args[0].first);
327
if (node->subType() == Node::QmlClass) {
328
QmlClassNode::addInheritedBy(args[0].first,node);
332
else if (command == COMMAND_QMLDEFAULT) {
333
if (node->type() == Node::QmlProperty) {
334
QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node);
338
else if (command == COMMAND_QMLREADONLY) {
339
if (node->type() == Node::QmlProperty) {
340
QmlPropertyNode* qpn = static_cast<QmlPropertyNode*>(node);
344
else if ((command == COMMAND_INGROUP) && !args.isEmpty()) {
345
ArgList::ConstIterator argsIter = args.constBegin();
346
while (argsIter != args.constEnd()) {
347
QDocDatabase::qdocDB()->addToGroup(argsIter->first, node);
351
else if (command == COMMAND_INTERNAL) {
352
node->setAccess(Node::Private);
353
node->setStatus(Node::Internal);
355
else if (command == COMMAND_OBSOLETE) {
356
if (node->status() != Node::Compat)
357
node->setStatus(Node::Obsolete);
359
else if (command == COMMAND_PAGEKEYWORDS) {
360
// Not done yet. Do we need this?
362
else if (command == COMMAND_PRELIMINARY) {
363
node->setStatus(Node::Preliminary);
365
else if (command == COMMAND_SINCE) {
366
QString arg = args[0].first; //.join(' ');
370
doc.location().warning(tr("The \\%1 command is ignored in QML files").arg(command));
378
Begin the visit of the object \a definition, recording it in the
379
qdoc database. Increment the object nesting level, which is used
380
to test whether we are at the public API level. The public level
383
bool QmlDocVisitor::visit(QQmlJS::AST::UiObjectDefinition *definition)
385
QString type = definition->qualifiedTypeNameId->name.toString();
388
if (current->type() == Node::Namespace) {
389
QmlClassNode *component = new QmlClassNode(current, name);
390
component->setTitle(name);
391
component->setImportList(importList);
393
if (applyDocumentation(definition->firstSourceLocation(), component)) {
394
QmlClassNode::addInheritedBy(type, component);
395
if (!component->links().contains(Node::InheritsLink))
396
component->setLink(Node::InheritsLink, type, type);
405
End the visit of the object \a definition. In particular,
406
decrement the object nesting level, which is used to test
407
whether we are at the public API level. The public API
408
level is level 1. It won't decrement below 0.
410
void QmlDocVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *definition)
412
if (nestingLevel > 0)
414
lastEndOffset = definition->lastSourceLocation().end();
418
Note that the imports list can be traversed by iteration to obtain
419
all the imports in the document at once, having found just one:
421
*it = imports; it; it = it->next
424
bool QmlDocVisitor::visit(QQmlJS::AST::UiImportList *imports)
426
QQmlJS::AST::UiImport* imp = imports->import;
427
quint32 length = imp->versionToken.offset - imp->fileNameToken.offset - 1;
428
QString module = document.mid(imp->fileNameToken.offset,length);
429
QString version = document.mid(imp->versionToken.offset, imp->versionToken.length);
430
if (version.size() > 1) {
431
int dot = version.lastIndexOf(QChar('.'));
433
version = version.left(dot);
435
importList.append(QPair<QString, QString>(module, version));
441
End the visit of the imports list.
443
void QmlDocVisitor::endVisit(QQmlJS::AST::UiImportList *definition)
445
lastEndOffset = definition->lastSourceLocation().end();
449
Visits the public \a member declaration, which can be a
450
signal or a property. It is a custom signal or property.
451
Only visit the \a member if the nestingLevel is 1.
453
bool QmlDocVisitor::visit(QQmlJS::AST::UiPublicMember *member)
455
if (nestingLevel > 1)
457
switch (member->type) {
458
case QQmlJS::AST::UiPublicMember::Signal:
460
if (current->type() == Node::Document) {
461
QmlClassNode *qmlClass = static_cast<QmlClassNode *>(current);
464
QString name = member->name.toString();
465
FunctionNode *qmlSignal = new FunctionNode(Node::QmlSignal, current, name, false);
467
QList<Parameter> parameters;
468
for (QQmlJS::AST::UiParameterList *it = member->parameters; it; it = it->next) {
469
if (!it->type.isEmpty() && !it->name.isEmpty())
470
parameters.append(Parameter(it->type.toString(), QString(), it->name.toString()));
473
qmlSignal->setParameters(parameters);
474
applyDocumentation(member->firstSourceLocation(), qmlSignal);
479
case QQmlJS::AST::UiPublicMember::Property:
481
QString type = member->memberType.toString();
482
QString name = member->name.toString();
483
if (current->type() == Node::Document) {
484
QmlClassNode *qmlClass = static_cast<QmlClassNode *>(current);
486
QString name = member->name.toString();
487
QmlPropertyNode *qmlPropNode = new QmlPropertyNode(qmlClass, name, type, false);
488
qmlPropNode->setReadOnly(member->isReadonlyMember);
489
if (member->isDefaultMember)
490
qmlPropNode->setDefault();
491
applyDocumentation(member->firstSourceLocation(), qmlPropNode);
504
End the visit of the \a member.
506
void QmlDocVisitor::endVisit(QQmlJS::AST::UiPublicMember* member)
508
lastEndOffset = member->lastSourceLocation().end();
511
bool QmlDocVisitor::visit(QQmlJS::AST::IdentifierPropertyName *)
517
Begin the visit of the function declaration \a fd, but only
518
if the nesting level is 1.
520
bool QmlDocVisitor::visit(QQmlJS::AST::FunctionDeclaration* fd)
522
if (nestingLevel > 1)
524
if (current->type() == Node::Document) {
525
QmlClassNode* qmlClass = static_cast<QmlClassNode*>(current);
527
QString name = fd->name.toString();
528
FunctionNode* qmlMethod = new FunctionNode(Node::QmlMethod, current, name, false);
530
NodeList::ConstIterator overloadIterator = current->childNodes().constBegin();
531
while (overloadIterator != current->childNodes().constEnd()) {
532
if ((*overloadIterator)->name() == name)
537
qmlMethod->setOverload(true);
538
QList<Parameter> parameters;
539
QQmlJS::AST::FormalParameterList* formals = fd->formals;
541
QQmlJS::AST::FormalParameterList* fpl = formals;
543
parameters.append(Parameter(QString(), QString(), fpl->name.toString()));
545
} while (fpl && fpl != formals);
546
qmlMethod->setParameters(parameters);
548
applyDocumentation(fd->firstSourceLocation(), qmlMethod);
555
End the visit of the function declaration, \a fd.
557
void QmlDocVisitor::endVisit(QQmlJS::AST::FunctionDeclaration* fd)
559
lastEndOffset = fd->lastSourceLocation().end();
563
Begin the visit of the signal handler declaration \a sb, but only
564
if the nesting level is 1.
566
bool QmlDocVisitor::visit(QQmlJS::AST::UiScriptBinding* sb)
568
if (nestingLevel > 1)
570
if (current->type() == Node::Document) {
571
QString handler = sb->qualifiedId->name.toString();
572
if (handler.length() > 2 && handler.startsWith("on") && handler.at(2).isUpper()) {
573
QmlClassNode* qmlClass = static_cast<QmlClassNode*>(current);
575
FunctionNode* qmlSH = new FunctionNode(Node::QmlSignalHandler,current,handler,false);
576
applyDocumentation(sb->firstSourceLocation(), qmlSH);
583
void QmlDocVisitor::endVisit(QQmlJS::AST::UiScriptBinding* sb)
585
lastEndOffset = sb->lastSourceLocation().end();
588
bool QmlDocVisitor::visit(QQmlJS::AST::UiQualifiedId* )
593
void QmlDocVisitor::endVisit(QQmlJS::AST::UiQualifiedId* )