1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
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
16 ** 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, 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.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "parser/qmljsast_p.h"
35 #include "qmljsbind.h"
36 #include "qmljscheck.h"
37 #include "qmljsdocument.h"
39 #include <languageutils/componentversion.h>
41 #include <QtCore/QDir>
42 #include <QtCore/QFileInfo>
43 #include <QtCore/QDebug>
45 using namespace LanguageUtils;
46 using namespace QmlJS;
47 using namespace QmlJS::AST;
48 using namespace QmlJS::Interpreter;
52 \brief Collected information about a single Document.
53 \sa QmlJS::Document QmlJS::Link
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}.
60 The document's imports are classified and available through imports().
62 It allows AST to code model lookup through findQmlObject() and findFunctionScope().
65 Bind::Bind(Document *doc, QList<DiagnosticMessage> *messages)
67 _currentObjectValue(0),
70 _diagnosticMessages(messages)
80 QList<ImportInfo> Bind::imports() const
85 Interpreter::ObjectValue *Bind::idEnvironment() const
87 return _idEnvironment;
90 Interpreter::ObjectValue *Bind::rootObjectValue() const
92 return _rootObjectValue;
95 Interpreter::ObjectValue *Bind::findQmlObject(AST::Node *node) const
97 return _qmlObjects.value(node);
100 bool Bind::usesQmlPrototype(ObjectValue *prototype,
101 const Context *context) const
106 const QString componentName = prototype->className();
108 // all component objects have classname set
109 if (componentName.isEmpty())
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());
123 if (!possibleNames.isEmpty())
124 possibleNames.insert(componentName);
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))
134 QHashIterator<Node *, ObjectValue *> it(_qmlObjects);
135 while (it.hasNext()) {
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;
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)
157 for (UiQualifiedId *idIt = id; idIt; idIt = idIt->next) {
158 if (!idIt->next && !possibleNames.contains(idIt->name->asString()))
165 // resolve and check the prototype
166 const ObjectValue *object = it.value();
167 const ObjectValue *resolvedPrototype = object->prototype(context);
168 if (resolvedPrototype == prototype)
175 Interpreter::ObjectValue *Bind::findFunctionScope(AST::FunctionExpression *node) const
177 return _functionScopes.value(node);
180 bool Bind::isGroupedPropertyBinding(AST::Node *node) const
182 return _groupedPropertyBindings.contains(node);
185 ObjectValue *Bind::switchObjectValue(ObjectValue *newObjectValue)
187 ObjectValue *oldObjectValue = _currentObjectValue;
188 _currentObjectValue = newObjectValue;
189 return oldObjectValue;
192 QString Bind::toString(UiQualifiedId *qualifiedId, QChar delimiter)
196 for (UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
197 if (iter != qualifiedId)
201 result += iter->name->asString();
207 ObjectValue *Bind::bindObject(UiQualifiedId *qualifiedTypeNameId, UiObjectInitializer *initializer)
209 ObjectValue *parentObjectValue = 0;
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);
217 parentObjectValue = switchObjectValue(objectValue);
219 if (parentObjectValue)
220 objectValue->setProperty(QLatin1String("parent"), parentObjectValue);
222 _rootObjectValue = objectValue;
223 _rootObjectValue->setClassName(_doc->componentName());
228 return switchObjectValue(parentObjectValue);
231 void Bind::accept(Node *node)
233 Node::accept(node, this);
236 bool Bind::visit(AST::UiProgram *)
238 _idEnvironment = _engine.newObject(/*prototype =*/ 0);
242 bool Bind::visit(AST::Program *)
244 _currentObjectValue = _engine.newObject(/*prototype =*/ 0);
245 _rootObjectValue = _currentObjectValue;
249 bool Bind::visit(UiImport *ast)
251 ComponentVersion version;
252 ImportInfo::Type type = ImportInfo::InvalidImport;
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")));
264 if (ast->importUri) {
265 type = ImportInfo::LibraryImport;
266 name = toString(ast->importUri, QDir::separator());
268 if (!version.isValid()) {
269 _diagnosticMessages->append(
270 errorMessage(ast, tr("package import requires a version number")));
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;
280 _diagnosticMessages->append(
281 errorMessage(ast, tr("file or directory not found")));
282 type = ImportInfo::UnknownFileImport;
285 _imports += ImportInfo(type, name, version, ast);
290 bool Bind::visit(UiPublicMember *)
296 bool Bind::visit(UiObjectDefinition *ast)
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();
306 if (!isGroupedBinding) {
307 ObjectValue *value = bindObject(ast->qualifiedTypeNameId, ast->initializer);
308 _qmlObjects.insert(ast, value);
310 _groupedPropertyBindings.insert(ast);
316 bool Bind::visit(UiObjectBinding *ast)
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);
327 bool Bind::visit(UiScriptBinding *ast)
329 if (toString(ast->qualifiedId) == QLatin1String("id")) {
330 if (ExpressionStatement *e = cast<ExpressionStatement*>(ast->statement))
331 if (IdentifierExpression *i = cast<IdentifierExpression*>(e->expression))
333 _idEnvironment->setProperty(i->name->asString(), _currentObjectValue);
339 bool Bind::visit(UiArrayBinding *)
341 // ### FIXME: do we need to store the members into the property? Or, maybe the property type is an JS Array?
346 bool Bind::visit(VariableDeclaration *ast)
351 ASTVariableReference *ref = new ASTVariableReference(ast, &_engine);
352 _currentObjectValue->setProperty(ast->name->asString(), ref);
356 bool Bind::visit(FunctionExpression *ast)
358 // ### FIXME: the first declaration counts
359 //if (_currentObjectValue->property(ast->name->asString(), 0))
362 ASTFunctionValue *function = new ASTFunctionValue(ast, _doc, &_engine);
363 if (ast->name && cast<FunctionDeclaration *>(ast))
364 _currentObjectValue->setProperty(ast->name->asString(), function);
366 // build function scope
367 ObjectValue *functionScope = _engine.newObject(/*prototype=*/0);
368 _functionScopes.insert(ast, functionScope);
369 ObjectValue *parent = switchObjectValue(functionScope);
371 // The order of the following is important. Example: A function with name "arguments"
372 // overrides the arguments object, a variable doesn't.
374 // 1. Function formal arguments
375 for (FormalParameterList *it = ast->formals; it; it = it->next) {
377 functionScope->setProperty(it->name->asString(), _engine.undefinedValue());
380 // 2. Functions defined inside the function body
381 // ### TODO, currently covered by the accept(body)
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);
389 // 4. Variables defined inside the function body
390 // ### TODO, currently covered by the accept(body)
394 switchObjectValue(parent);
399 bool Bind::visit(FunctionDeclaration *ast)
401 return visit(static_cast<FunctionExpression *>(ast));