qmljslink.cpp 10.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "qmljslink.h"

#include "parser/qmljsast_p.h"
#include "qmljsdocument.h"
#include "qmljsbind.h"

#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <QtCore/QDebug>

using namespace QmlJS;
using namespace QmlJS::Interpreter;
using namespace QmlJS::AST;

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
Link::Link(Document::Ptr currentDoc, const Snapshot &snapshot, Interpreter::Engine *interp)
    : _snapshot(snapshot)
    , _interp(interp)
{
    _docs = reachableDocuments(currentDoc, snapshot);

    linkImports();
}

Link::~Link()
{
    // unset all prototypes and scopes
    foreach (Document::Ptr doc, _docs) {
        BindPtr bind = doc->bind();

        if (doc->qmlProgram()) {
            bind->_idEnvironment->setScope(0);
            bind->_functionEnvironment->setScope(0);

            foreach (ObjectValue *object, bind->_qmlObjectBindings) {
                object->setPrototype(0);
                object->setScope(0);
            }
            foreach (ObjectValue *object, bind->_qmlObjectDefinitions) {
                object->setPrototype(0);
                object->setScope(0);
            }
        } else if (doc->jsProgram()) {
            bind->_rootObjectValue->setScope(0);
        }
    }
}

static ObjectValue *pushScope(ObjectValue *next, ObjectValue *onto)
{
    onto->setScope(next);
    return next;
}

ObjectValue *Link::scopeChainAt(Document::Ptr doc, UiObjectMember *currentObject)
{
    BindPtr bind = doc->bind();

    ObjectValue *scopeObject;
    if (UiObjectDefinition *definition = cast<UiObjectDefinition *>(currentObject))
        scopeObject = bind->_qmlObjectDefinitions.value(definition);
    else if (UiObjectBinding *binding = cast<UiObjectBinding *>(currentObject))
        scopeObject = bind->_qmlObjectBindings.value(binding);
    else
        return bind->_interp.globalObject();

    if (!scopeObject)
        return bind->_interp.globalObject();

    // Build the scope chain.
    ObjectValue *scopeStart = _typeEnvironments.value(doc.data());
    ObjectValue *scope = scopeStart;
    scope = pushScope(bind->_idEnvironment, scope);
    scope = pushScope(bind->_functionEnvironment, scope);

    foreach (const QString &scriptFile, doc->bind()->includedScripts()) {
        if (Document::Ptr scriptDoc = _snapshot.document(scriptFile)) {
            if (scriptDoc->jsProgram()) {
                scope = pushScope(scriptDoc->bind()->_rootObjectValue, scope);
            }
        }
    }

    scope = pushScope(scopeObject, scope);
    if (scopeObject != bind->_rootObjectValue)
        scope = pushScope(bind->_rootObjectValue, scope);

    scope = pushScope(bind->_interp.globalObject(), scope);

    // May want to link to instantiating components from here.

    return scopeStart;
}

void Link::linkImports()
{
    foreach (Document::Ptr doc, _docs) {
        BindPtr bind = doc->bind();

        ObjectValue *typeEnv = _interp->newObject(/*prototype =*/0);
        _typeEnvironments.insert(doc.data(), typeEnv);

        // Populate the _typeEnvironment with imports.
        populateImportedTypes(typeEnv, doc);

        // Set the prototypes.
        {
            QHash<UiObjectDefinition *, ObjectValue *>::iterator it = bind->_qmlObjectDefinitions.begin();
            QHash<UiObjectDefinition *, ObjectValue *>::iterator end = bind->_qmlObjectDefinitions.end();
            for (; it != end; ++it) {
                UiObjectDefinition *key = it.key();
                ObjectValue *value = it.value();
                if (!key->qualifiedTypeNameId)
                    continue;

                value->setPrototype(lookupType(typeEnv, key->qualifiedTypeNameId));
            }
        }
        {
            QHash<UiObjectBinding *, ObjectValue *>::iterator it = bind->_qmlObjectBindings.begin();
            QHash<UiObjectBinding *, ObjectValue *>::iterator end = bind->_qmlObjectBindings.end();
            for (; it != end; ++it) {
                UiObjectBinding *key = it.key();
                ObjectValue *value = it.value();
                if (!key->qualifiedTypeNameId)
                    continue;

                value->setPrototype(lookupType(typeEnv, key->qualifiedTypeNameId));
            }
        }
    }
}

133
134
135
136
137
138
139
140
141
142
static QString componentName(const QString &fileName)
{
    QString componentName = fileName;
    int dotIndex = componentName.indexOf(QLatin1Char('.'));
    if (dotIndex != -1)
        componentName.truncate(dotIndex);
    componentName[0] = componentName[0].toUpper();
    return componentName;
}

143
void Link::populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Ptr doc)
144
145
146
147
148
149
150
{
    if (! (doc->qmlProgram() && doc->qmlProgram()->imports))
        return;

    QFileInfo fileInfo(doc->fileName());
    const QString absolutePath = fileInfo.absolutePath();

151
152
    // implicit imports:
    // qml files in the same directory are available without explicit imports
153
154
    foreach (Document::Ptr otherDoc, _docs) {
        if (otherDoc == doc)
155
156
157
158
159
160
161
162
            continue;

        QFileInfo otherFileInfo(otherDoc->fileName());
        const QString otherAbsolutePath = otherFileInfo.absolutePath();

        if (otherAbsolutePath != absolutePath)
            continue;

163
164
        typeEnv->setProperty(componentName(otherFileInfo.fileName()),
                             otherDoc->bind()->_rootObjectValue);
165
166
167
168
    }

    // explicit imports, whether directories or files
    for (UiImportList *it = doc->qmlProgram()->imports; it; it = it->next) {
169
        if (! it->import)
170
171
            continue;

172
173
174
175
176
177
178
        if (it->import->fileName) {
            importFile(typeEnv, doc, it->import, absolutePath);
        } else if (it->import->importUri) {
            importNonFile(typeEnv, doc, it->import);
        }
    }
}
179

180
181
182
183
184
/*
    import "content"
    import "content" as Xxx
    import "content" 4.6
    import "content" 4.6 as Xxx
185

186
187
188
189
190
191
192
    import "http://www.ovi.com/" as Ovi
*/
void Link::importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
                      AST::UiImport *import, const QString &startPath)
{
    if (!import->fileName)
        return;
193

194
195
196
197
    QString path = startPath;
    path += QLatin1Char('/');
    path += import->fileName->asString();
    path = QDir::cleanPath(path);
198

199
    ObjectValue *importNamespace = 0;
200

201
202
203
204
205
206
207
208
    foreach (Document::Ptr otherDoc, _docs) {
        QFileInfo otherFileInfo(otherDoc->fileName());
        const QString otherAbsolutePath = otherFileInfo.absolutePath();

        bool directoryImport = (path == otherAbsolutePath);
        bool fileImport = (path == otherDoc->fileName());
        if (!directoryImport && !fileImport)
            continue;
209

210
211
212
213
        if (directoryImport && import->importId && !importNamespace) {
            importNamespace = _interp->newObject(/*prototype =*/0);
            typeEnv->setProperty(import->importId->asString(), importNamespace);
        }
214

215
216
217
218
219
        QString targetName;
        if (fileImport && import->importId) {
            targetName = import->importId->asString();
        } else {
            targetName = componentName(otherFileInfo.fileName());
220
        }
221
222
223
224
225
226

        ObjectValue *importInto = typeEnv;
        if (importNamespace)
            importInto = importNamespace;

        importInto->setProperty(targetName, otherDoc->bind()->_rootObjectValue);
227
228
229
    }
}

230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
/*
  import Qt 4.6
  import Qt 4.6 as Xxx
  (import com.nokia.qt is the same as the ones above)
*/
void Link::importNonFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc, AST::UiImport *import)
{
    ObjectValue *namespaceObject = 0;

    if (import->importId) { // with namespace we insert an object in the type env. to hold the imported types
        namespaceObject = _interp->newObject(/*prototype */ 0);
        typeEnv->setProperty(import->importId->asString(), namespaceObject);

    } else { // without namespace we insert all types directly into the type env.
        namespaceObject = typeEnv;
    }

    // try the metaobject system
    if (import->importUri) {
        const QString package = Bind::toString(import->importUri, '/');
        int majorVersion = -1; // ### TODO: Check these magic version numbers
        int minorVersion = -1; // ### TODO: Check these magic version numbers

        if (import->versionToken.isValid()) {
            const QString versionString = doc->source().mid(import->versionToken.offset, import->versionToken.length);
            const int dotIdx = versionString.indexOf(QLatin1Char('.'));
            if (dotIdx == -1) {
                // only major (which is probably invalid, but let's handle it anyway)
                majorVersion = versionString.toInt();
                minorVersion = 0; // ### TODO: Check with magic version numbers above
            } else {
                majorVersion = versionString.left(dotIdx).toInt();
                minorVersion = versionString.mid(dotIdx + 1).toInt();
            }
        }
#ifndef NO_DECLARATIVE_BACKEND
        foreach (QmlObjectValue *object, _interp->metaTypeSystem().staticTypesForImport(package, majorVersion, minorVersion)) {
            namespaceObject->setProperty(object->qmlTypeName(), object);
        }
#endif // NO_DECLARATIVE_BACKEND
    }
}

const ObjectValue *Link::lookupType(ObjectValue *env, UiQualifiedId *id)
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
{
    const ObjectValue *objectValue = env;

    for (UiQualifiedId *iter = id; objectValue && iter; iter = iter->next) {
        if (! iter->name)
            return 0;

        const Value *value = objectValue->property(iter->name->asString());
        if (!value)
            return 0;

        objectValue = value->asObjectValue();
    }

    return objectValue;
}

291
QList<Document::Ptr> Link::reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot)
292
{
293
    QList<Document::Ptr> docs;
294

295
296
    QSet<QString> processed;
    QStringList todo;
297

298
299
300
    QMultiHash<QString, Document::Ptr> documentByPath;
    foreach (Document::Ptr doc, snapshot)
        documentByPath.insert(doc->path(), doc);
301

302
    todo.append(startDoc->path());
303

304
305
306
    // Find the reachable documents.
    while (! todo.isEmpty()) {
        const QString path = todo.takeFirst();
307

308
309
        if (processed.contains(path))
            continue;
310

311
312
313
314
315
316
317
318
319
320
        processed.insert(path);

        QStringList localImports;
        foreach (Document::Ptr doc, documentByPath.values(path)) {
            docs += doc;
            localImports += doc->bind()->localImports();
        }

        localImports.removeDuplicates();
        todo += localImports;
321
322
    }

323
    return docs;
324
}