OSDN Git Service

073d9b69337fd0ef5241a9c34542537bba4d8ec8
[qt-creator-jp/qt-creator-jp.git] / src / libs / qmljs / qmljsbind.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "parser/qmljsast_p.h"
35 #include "qmljsbind.h"
36 #include "qmljscheck.h"
37 #include "qmljsdocument.h"
38
39 #include <languageutils/componentversion.h>
40
41 #include <QtCore/QDir>
42 #include <QtCore/QFileInfo>
43 #include <QtCore/QDebug>
44
45 using namespace LanguageUtils;
46 using namespace QmlJS;
47 using namespace QmlJS::AST;
48 using namespace QmlJS::Interpreter;
49
50 /*!
51     \class QmlJS::Bind
52     \brief Collected information about a single Document.
53     \sa QmlJS::Document QmlJS::Link
54
55     Each QmlJS::Document owns a instance of Bind. It provides access to data
56     that can be derived by looking at the document in isolation. If you need
57     information that goes beyond that, you need to create a
58     \l{QmlJS::Interpreter::Context} using \l{QmlJS::Link}.
59
60     The document's imports are classified and available through imports().
61
62     It allows AST to code model lookup through findQmlObject() and findFunctionScope().
63 */
64
65 Bind::Bind(Document *doc, QList<DiagnosticMessage> *messages)
66     : _doc(doc),
67       _currentObjectValue(0),
68       _idEnvironment(0),
69       _rootObjectValue(0),
70       _diagnosticMessages(messages)
71 {
72     if (_doc)
73         accept(_doc->ast());
74 }
75
76 Bind::~Bind()
77 {
78 }
79
80 QList<ImportInfo> Bind::imports() const
81 {
82     return _imports;
83 }
84
85 Interpreter::ObjectValue *Bind::idEnvironment() const
86 {
87     return _idEnvironment;
88 }
89
90 Interpreter::ObjectValue *Bind::rootObjectValue() const
91 {
92     return _rootObjectValue;
93 }
94
95 Interpreter::ObjectValue *Bind::findQmlObject(AST::Node *node) const
96 {
97     return _qmlObjects.value(node);
98 }
99
100 bool Bind::usesQmlPrototype(ObjectValue *prototype,
101                             const Context *context) const
102 {
103     if (!prototype)
104         return false;
105
106     const QString componentName = prototype->className();
107
108     // all component objects have classname set
109     if (componentName.isEmpty())
110         return false;
111
112     // get a list of all the names that may refer to this component
113     // this can only happen for file imports with an 'as' clause
114     // if there aren't any, possibleNames will be left empty
115     QSet<QString> possibleNames;
116     foreach (const ImportInfo &import, imports()) {
117         if (import.type() == ImportInfo::FileImport
118                 && !import.id().isEmpty()
119                 && import.name().contains(componentName)) {
120             possibleNames.insert(import.id());
121         }
122     }
123     if (!possibleNames.isEmpty())
124         possibleNames.insert(componentName);
125
126     // if there are no renamed imports and the document does not use
127     // the className string anywhere, it's out
128     if (possibleNames.isEmpty()) {
129         NameId nameId(componentName.data(), componentName.size());
130         if (!_doc->engine()->literals().contains(nameId))
131             return false;
132     }
133
134     QHashIterator<Node *, ObjectValue *> it(_qmlObjects);
135     while (it.hasNext()) {
136         it.next();
137
138         // if the type id does not contain one of the possible names, skip
139         Node *node = it.key();
140         UiQualifiedId *id = 0;
141         if (UiObjectDefinition *n = cast<UiObjectDefinition *>(node)) {
142             id = n->qualifiedTypeNameId;
143         } else if (UiObjectBinding *n = cast<UiObjectBinding *>(node)) {
144             id = n->qualifiedTypeNameId;
145         }
146         if (!id)
147             continue;
148
149         bool skip = false;
150         // optimize the common case of no renamed imports
151         if (possibleNames.isEmpty()) {
152             for (UiQualifiedId *idIt = id; idIt; idIt = idIt->next) {
153                 if (!idIt->next && idIt->name->asString() != componentName)
154                     skip = true;
155             }
156         } else {
157             for (UiQualifiedId *idIt = id; idIt; idIt = idIt->next) {
158                 if (!idIt->next && !possibleNames.contains(idIt->name->asString()))
159                     skip = true;
160             }
161         }
162         if (skip)
163             continue;
164
165         // resolve and check the prototype
166         const ObjectValue *object = it.value();
167         const ObjectValue *resolvedPrototype = object->prototype(context);
168         if (resolvedPrototype == prototype)
169             return true;
170     }
171
172     return false;
173 }
174
175 Interpreter::ObjectValue *Bind::findFunctionScope(AST::FunctionExpression *node) const
176 {
177     return _functionScopes.value(node);
178 }
179
180 bool Bind::isGroupedPropertyBinding(AST::Node *node) const
181 {
182     return _groupedPropertyBindings.contains(node);
183 }
184
185 ObjectValue *Bind::switchObjectValue(ObjectValue *newObjectValue)
186 {
187     ObjectValue *oldObjectValue = _currentObjectValue;
188     _currentObjectValue = newObjectValue;
189     return oldObjectValue;
190 }
191
192 QString Bind::toString(UiQualifiedId *qualifiedId, QChar delimiter)
193 {
194     QString result;
195
196     for (UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
197         if (iter != qualifiedId)
198             result += delimiter;
199
200         if (iter->name)
201             result += iter->name->asString();
202     }
203
204     return result;
205 }
206
207 ObjectValue *Bind::bindObject(UiQualifiedId *qualifiedTypeNameId, UiObjectInitializer *initializer)
208 {
209     ObjectValue *parentObjectValue = 0;
210
211     // normal component instance
212     ASTObjectValue *objectValue = new ASTObjectValue(qualifiedTypeNameId, initializer, _doc, &_engine);
213     QmlPrototypeReference *prototypeReference =
214             new QmlPrototypeReference(qualifiedTypeNameId, _doc, &_engine);
215     objectValue->setPrototype(prototypeReference);
216
217     parentObjectValue = switchObjectValue(objectValue);
218
219     if (parentObjectValue)
220         objectValue->setProperty(QLatin1String("parent"), parentObjectValue);
221     else {
222         _rootObjectValue = objectValue;
223         _rootObjectValue->setClassName(_doc->componentName());
224     }
225
226     accept(initializer);
227
228     return switchObjectValue(parentObjectValue);
229 }
230
231 void Bind::accept(Node *node)
232 {
233     Node::accept(node, this);
234 }
235
236 bool Bind::visit(AST::UiProgram *)
237 {
238     _idEnvironment = _engine.newObject(/*prototype =*/ 0);
239     return true;
240 }
241
242 bool Bind::visit(AST::Program *)
243 {
244     _currentObjectValue = _engine.newObject(/*prototype =*/ 0);
245     _rootObjectValue = _currentObjectValue;
246     return true;
247 }
248
249 bool Bind::visit(UiImport *ast)
250 {
251     ComponentVersion version;
252     ImportInfo::Type type = ImportInfo::InvalidImport;
253     QString name;
254
255     if (ast->versionToken.isValid()) {
256         const QString versionString = _doc->source().mid(ast->versionToken.offset, ast->versionToken.length);
257         version = ComponentVersion(versionString);
258         if (!version.isValid()) {
259             _diagnosticMessages->append(
260                         errorMessage(ast->versionToken, tr("expected two numbers separated by a dot")));
261         }
262     }
263
264     if (ast->importUri) {
265         type = ImportInfo::LibraryImport;
266         name = toString(ast->importUri, QDir::separator());
267
268         if (!version.isValid()) {
269             _diagnosticMessages->append(
270                         errorMessage(ast, tr("package import requires a version number")));
271         }
272     } else if (ast->fileName) {
273         const QFileInfo importFileInfo(_doc->path() + QDir::separator() + ast->fileName->asString());
274         name = importFileInfo.absoluteFilePath();
275         if (importFileInfo.isFile())
276             type = ImportInfo::FileImport;
277         else if (importFileInfo.isDir())
278             type = ImportInfo::DirectoryImport;
279         else {
280             _diagnosticMessages->append(
281                         errorMessage(ast, tr("file or directory not found")));
282             type = ImportInfo::UnknownFileImport;
283         }
284     }
285     _imports += ImportInfo(type, name, version, ast);
286
287     return false;
288 }
289
290 bool Bind::visit(UiPublicMember *)
291 {
292     // nothing to do.
293     return true;
294 }
295
296 bool Bind::visit(UiObjectDefinition *ast)
297 {
298     // an UiObjectDefinition may be used to group property bindings
299     // think anchors { ... }
300     bool isGroupedBinding = false;
301     for (UiQualifiedId *it = ast->qualifiedTypeNameId; it; it = it->next) {
302         if (!it->next && it->name)
303             isGroupedBinding = it->name->asString().at(0).isLower();
304     }
305
306     if (!isGroupedBinding) {
307         ObjectValue *value = bindObject(ast->qualifiedTypeNameId, ast->initializer);
308         _qmlObjects.insert(ast, value);
309     } else {
310         _groupedPropertyBindings.insert(ast);
311     }
312
313     return false;
314 }
315
316 bool Bind::visit(UiObjectBinding *ast)
317 {
318 //    const QString name = serialize(ast->qualifiedId);
319     ObjectValue *value = bindObject(ast->qualifiedTypeNameId, ast->initializer);
320     _qmlObjects.insert(ast, value);
321     // ### FIXME: we don't handle dot-properties correctly (i.e. font.size)
322 //    _currentObjectValue->setProperty(name, value);
323
324     return false;
325 }
326
327 bool Bind::visit(UiScriptBinding *ast)
328 {
329     if (toString(ast->qualifiedId) == QLatin1String("id")) {
330         if (ExpressionStatement *e = cast<ExpressionStatement*>(ast->statement))
331             if (IdentifierExpression *i = cast<IdentifierExpression*>(e->expression))
332                 if (i->name)
333                     _idEnvironment->setProperty(i->name->asString(), _currentObjectValue);
334     }
335
336     return true;
337 }
338
339 bool Bind::visit(UiArrayBinding *)
340 {
341     // ### FIXME: do we need to store the members into the property? Or, maybe the property type is an JS Array?
342
343     return true;
344 }
345
346 bool Bind::visit(VariableDeclaration *ast)
347 {
348     if (! ast->name)
349         return false;
350
351     ASTVariableReference *ref = new ASTVariableReference(ast, &_engine);
352     _currentObjectValue->setProperty(ast->name->asString(), ref);
353     return true;
354 }
355
356 bool Bind::visit(FunctionExpression *ast)
357 {
358     // ### FIXME: the first declaration counts
359     //if (_currentObjectValue->property(ast->name->asString(), 0))
360     //    return false;
361
362     ASTFunctionValue *function = new ASTFunctionValue(ast, _doc, &_engine);
363     if (ast->name && cast<FunctionDeclaration *>(ast))
364         _currentObjectValue->setProperty(ast->name->asString(), function);
365
366     // build function scope
367     ObjectValue *functionScope = _engine.newObject(/*prototype=*/0);
368     _functionScopes.insert(ast, functionScope);
369     ObjectValue *parent = switchObjectValue(functionScope);
370
371     // The order of the following is important. Example: A function with name "arguments"
372     // overrides the arguments object, a variable doesn't.
373
374     // 1. Function formal arguments
375     for (FormalParameterList *it = ast->formals; it; it = it->next) {
376         if (it->name)
377             functionScope->setProperty(it->name->asString(), _engine.undefinedValue());
378     }
379
380     // 2. Functions defined inside the function body
381     // ### TODO, currently covered by the accept(body)
382
383     // 3. Arguments object
384     ObjectValue *arguments = _engine.newObject(/*prototype=*/0);
385     arguments->setProperty(QLatin1String("callee"), function);
386     arguments->setProperty(QLatin1String("length"), _engine.numberValue());
387     functionScope->setProperty(QLatin1String("arguments"), arguments);
388
389     // 4. Variables defined inside the function body
390     // ### TODO, currently covered by the accept(body)
391
392     // visit body
393     accept(ast->body);
394     switchObjectValue(parent);
395
396     return false;
397 }
398
399 bool Bind::visit(FunctionDeclaration *ast)
400 {
401     return visit(static_cast<FunctionExpression *>(ast));
402 }