qmljscheck.cpp 8.1 KB
Newer Older
Christian Kamm's avatar
Christian Kamm committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** 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 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "qmljscheck.h"
#include "qmljsbind.h"
#include "qmljsinterpreter.h"
33
#include "qmljsevaluate.h"
Christian Kamm's avatar
Christian Kamm committed
34
35
#include "parser/qmljsast_p.h"

36
#include <QtGui/QApplication>
Christian Kamm's avatar
Christian Kamm committed
37
38
#include <QtCore/QDebug>

39
40
41
42
43
44
45
46
47
48
49
namespace QmlJS {
namespace Messages {
static const char *invalidPropertyName =  QT_TRANSLATE_NOOP("QmlJS::Check", "'%1' is not a valid property name");
static const char *unknownType = QT_TRANSLATE_NOOP("QmlJS::Check", "unknown type");
} // namespace Messages

static inline QString tr(const char *msg)
{ return qApp->translate("QmlJS::Check", msg); }

} // namespace QmlJS

Christian Kamm's avatar
Christian Kamm committed
50
51
52
53
54
55
56
57
58
using namespace QmlJS;
using namespace QmlJS::AST;
using namespace QmlJS::Interpreter;

Check::Check(Document::Ptr doc, const Snapshot &snapshot)
    : _doc(doc)
    , _snapshot(snapshot)
    , _context(&_engine)
    , _link(&_context, doc, snapshot)
59
60
    , _extraScope(0)
    , _allowAnyProperty(false)
Christian Kamm's avatar
Christian Kamm committed
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
{
}

Check::~Check()
{
}

QList<DiagnosticMessage> Check::operator()()
{
    _messages.clear();
    Node::accept(_doc->ast(), this);
    return _messages;
}

bool Check::visit(UiProgram *ast)
{
    // build the initial scope chain
    if (ast->members && ast->members->member)
        _link.scopeChainAt(_doc, ast->members->member);

    return true;
}

bool Check::visit(UiObjectDefinition *ast)
{
86
    visitQmlObject(ast, ast->qualifiedTypeNameId, ast->initializer);
Christian Kamm's avatar
Christian Kamm committed
87
88
89
90
91
92
93
    return false;
}

bool Check::visit(UiObjectBinding *ast)
{
    checkScopeObjectMember(ast->qualifiedId);

94
    visitQmlObject(ast, ast->qualifiedTypeNameId, ast->initializer);
95
96
97
    return false;
}

98
99
void Check::visitQmlObject(Node *ast, UiQualifiedId *typeId,
                           UiObjectInitializer *initializer)
100
{
101
102
103
104
105
106
107
108
    // If the 'typeId' starts with a lower-case letter, it doesn't define
    // a new object instance. For instance: anchors { ... }
    if (typeId->name->asString().at(0).isLower() && ! typeId->next) {
        checkScopeObjectMember(typeId);
        // ### don't give up!
        return;
    }

109
110
    const bool oldAllowAnyProperty = _allowAnyProperty;

111
    if (! _context.lookupType(_doc.data(), typeId)) {
112
113
        warning(typeId->identifierToken, tr(Messages::unknownType));
        _allowAnyProperty = true; // suppress subsequent "unknown property" errors
114
115
    }

Christian Kamm's avatar
Christian Kamm committed
116
    const ObjectValue *oldScopeObject = _context.qmlScopeObject();
117
118
119
120
121
    const ObjectValue *oldExtraScope = _extraScope;
    const ObjectValue *scopeObject = _doc->bind()->findQmlObject(ast);
    _context.setQmlScopeObject(scopeObject);

#ifndef NO_DECLARATIVE_BACKEND
122
    // check if the object has a Qt.ListElement ancestor
123
124
125
126
    const ObjectValue *prototype = scopeObject->prototype(&_context);
    while (prototype) {
        if (const QmlObjectValue *qmlMetaObject = dynamic_cast<const QmlObjectValue *>(prototype)) {
            // ### Also check for Qt package. Involves changes in QmlObjectValue.
127
128
            if (qmlMetaObject->qmlTypeName() == QLatin1String("ListElement")) {
                _allowAnyProperty = true;
129
                break;
130
            }
131
132
133
134
        }
        prototype = prototype->prototype(&_context);
    }

135
136
137
138
139
140
141
142
143
144
    // check if the object has a Qt.PropertyChanges ancestor
    prototype = scopeObject->prototype(&_context);
    while (prototype) {
        if (const QmlObjectValue *qmlMetaObject = dynamic_cast<const QmlObjectValue *>(prototype)) {
            // ### Also check for Qt package. Involves changes in QmlObjectValue.
            if (qmlMetaObject->qmlTypeName() == QLatin1String("PropertyChanges"))
                break;
        }
        prototype = prototype->prototype(&_context);
    }
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
    // find the target script binding
    if (prototype && initializer) {
        for (UiObjectMemberList *m = initializer->members; m; m = m->next) {
            if (UiScriptBinding *scriptBinding = cast<UiScriptBinding *>(m->member)) {
                if (scriptBinding->qualifiedId
                        && scriptBinding->qualifiedId->name->asString() == QLatin1String("target")
                        && ! scriptBinding->qualifiedId->next) {
                    if (ExpressionStatement *expStmt = cast<ExpressionStatement *>(scriptBinding->statement)) {
                        Evaluate evaluator(&_context);
                        const Value *targetValue = evaluator(expStmt->expression);

                        if (const ObjectValue *target = value_cast<const ObjectValue *>(targetValue)) {
                            _extraScope = target;
                        } else {
                            _allowAnyProperty = true;
                        }
                    }
                }
            }
        }
    }
#endif
Christian Kamm's avatar
Christian Kamm committed
167

168
    Node::accept(initializer, this);
Christian Kamm's avatar
Christian Kamm committed
169
170

    _context.setQmlScopeObject(oldScopeObject);
171
172
    _extraScope = oldExtraScope;
    _allowAnyProperty = oldAllowAnyProperty;
Christian Kamm's avatar
Christian Kamm committed
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
}

bool Check::visit(UiScriptBinding *ast)
{
    checkScopeObjectMember(ast->qualifiedId);

    return true;
}

bool Check::visit(UiArrayBinding *ast)
{
    checkScopeObjectMember(ast->qualifiedId);

    return true;
}

189
void Check::checkScopeObjectMember(const UiQualifiedId *id)
Christian Kamm's avatar
Christian Kamm committed
190
{
191
192
193
    if (_allowAnyProperty)
        return;

Christian Kamm's avatar
Christian Kamm committed
194
195
196
197
198
    const ObjectValue *scopeObject = _context.qmlScopeObject();

    if (! id)
        return; // ### error?

199
    QString propertyName = id->name->asString();
Christian Kamm's avatar
Christian Kamm committed
200
201
202
203
204

    if (propertyName == QLatin1String("id") && ! id->next)
        return;

    // attached properties
205
206
207
    bool isAttachedProperty = false;
    if (! propertyName.isEmpty() && propertyName[0].isUpper()) {
        isAttachedProperty = true;
Christian Kamm's avatar
Christian Kamm committed
208
        scopeObject = _context.typeEnvironment(_doc.data());
209
    }
210
211
212

    if (! scopeObject)
        return;
Christian Kamm's avatar
Christian Kamm committed
213

214
    // global lookup for first part of id
Christian Kamm's avatar
Christian Kamm committed
215
    const Value *value = scopeObject->lookupMember(propertyName, &_context);
216
217
    if (_extraScope && !value)
        value = _extraScope->lookupMember(propertyName, &_context);
Christian Kamm's avatar
Christian Kamm committed
218
219
    if (!value) {
        error(id->identifierToken,
220
              tr(Messages::invalidPropertyName).arg(propertyName));
Christian Kamm's avatar
Christian Kamm committed
221
222
    }

223
224
225
226
    // can't look up members for attached properties
    if (isAttachedProperty)
        return;

227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
    // member lookup
    const UiQualifiedId *idPart = id;
    while (idPart->next) {
        const ObjectValue *objectValue = value_cast<const ObjectValue *>(value);
        if (! objectValue) {
            error(idPart->identifierToken,
                  QString("'%1' does not have members").arg(propertyName));
            return;
        }

        idPart = idPart->next;
        propertyName = idPart->name->asString();

        value = objectValue->lookupMember(propertyName, &_context);
        if (! value) {
            error(idPart->identifierToken,
                  QString("'%1' is not a member of '%2'").arg(propertyName, objectValue->className()));
            return;
        }
    }
Christian Kamm's avatar
Christian Kamm committed
247
248
249
250
251
252
253
254
255
256
257
}

void Check::error(const AST::SourceLocation &loc, const QString &message)
{
    _messages.append(DiagnosticMessage(DiagnosticMessage::Error, loc, message));
}

void Check::warning(const AST::SourceLocation &loc, const QString &message)
{
    _messages.append(DiagnosticMessage(DiagnosticMessage::Warning, loc, message));
}