qmljslink.cpp 11.7 KB
Newer Older
1
2
3
4
5
#include "qmljslink.h"

#include "parser/qmljsast_p.h"
#include "qmljsdocument.h"
#include "qmljsbind.h"
6
#include "qmljsscopebuilder.h"
7
8
9
10
11
12
13
14
15

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

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

16
Link::Link(Context *context, Document::Ptr currentDoc, const Snapshot &snapshot)
17
    : _snapshot(snapshot)
18
    , _context(context)
19
20
21
22
23
24
25
26
27
{
    _docs = reachableDocuments(currentDoc, snapshot);
    linkImports();
}

Link::~Link()
{
}

28
Interpreter::Engine *Link::engine()
29
{
30
    return _context->engine();
31
}
32

33
void Link::scopeChainAt(Document::Ptr doc, const QList<Node *> &astPath)
34
{
35
36
    ScopeChain &scopeChain = _context->scopeChain();

37
    // ### TODO: This object ought to contain the global namespace additions by QML.
38
    scopeChain.globalScope = engine()->globalObject();
39

40
41
    if (! doc) {
        scopeChain.update();
42
        return;
43
    }
44

Roberto Raggi's avatar
Roberto Raggi committed
45
    Bind *bind = doc->bind();
46
    QHash<Document *, ScopeChain::QmlComponentChain *> componentScopes;
47

48
49
50
    if (doc->qmlProgram()) {
        _context->setLookupMode(Context::QmlLookup);

51
52
        makeComponentChain(doc, &scopeChain.qmlComponentScope, &componentScopes);

53
        if (const ObjectValue *typeEnvironment = _context->typeEnvironment(doc.data()))
54
            scopeChain.qmlTypes = typeEnvironment;
55
    } else {
56
        // the global scope of a js file does not see the instantiating component
57
        if (astPath.size() > 0) {
58
59
            // add scope chains for all components that source this document
            foreach (Document::Ptr otherDoc, _docs) {
60
61
62
63
64
65
                if (otherDoc->bind()->includedScripts().contains(doc->fileName())) {
                    ScopeChain::QmlComponentChain *component = new ScopeChain::QmlComponentChain;
                    componentScopes.insert(otherDoc.data(), component);
                    scopeChain.qmlComponentScope.instantiatingComponents += component;
                    makeComponentChain(otherDoc, component, &componentScopes);
                }
66
67
68
            }

            // ### TODO: Which type environment do scripts see?
69
        }
70

71
        scopeChain.jsScopes += bind->rootObjectValue();
72
    }
73

74
75
76
    ScopeBuilder scopeBuilder(doc, _context);
    foreach (Node *node, astPath)
        scopeBuilder.push(node);
77
78
}

79
80
81
82
void Link::makeComponentChain(
        Document::Ptr doc,
        ScopeChain::QmlComponentChain *target,
        QHash<Document *, ScopeChain::QmlComponentChain *> *components)
83
84
85
{
    if (!doc->qmlProgram())
        return;
86

87
88
89
90
    Bind *bind = doc->bind();

    // add scopes for all components instantiating this one
    foreach (Document::Ptr otherDoc, _docs) {
91
        if (otherDoc == doc)
92
93
            continue;
        if (otherDoc->bind()->usesQmlPrototype(bind->rootObjectValue(), _context)) {
94
95
96
97
98
99
100
101
102
            if (components->contains(otherDoc.data())) {
                target->instantiatingComponents += components->value(otherDoc.data());
            } else {
                ScopeChain::QmlComponentChain *component = new ScopeChain::QmlComponentChain;
                components->insert(otherDoc.data(), component);
                target->instantiatingComponents += component;

                makeComponentChain(otherDoc, component, components);
            }
103
        }
104
    }
105

106
    // build this component scope
107
    if (bind->rootObjectValue())
108
        target->rootObject = bind->rootObjectValue();
109

110
111
112
    const QStringList &includedScripts = bind->includedScripts();
    for (int index = includedScripts.size() - 1; index != -1; --index) {
        const QString &scriptFile = includedScripts.at(index);
113

114
        if (Document::Ptr scriptDoc = _snapshot.document(scriptFile)) {
115
116
            if (scriptDoc->jsProgram())
                target->functionScopes += scriptDoc->bind()->rootObjectValue();
117
118
        }
    }
119

120
121
    target->functionScopes += bind->functionEnvironment();
    target->ids = bind->idEnvironment();
122
123
124
125
126
}

void Link::linkImports()
{
    foreach (Document::Ptr doc, _docs) {
127
        ObjectValue *typeEnv = engine()->newObject(/*prototype =*/0); // ### FIXME
128
129
130

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

132
        _context->setTypeEnvironment(doc.data(), typeEnv);
133
134
135
    }
}

136
137
138
139
140
141
142
143
144
145
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;
}

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

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

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

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

        if (otherAbsolutePath != absolutePath)
            continue;

166
        typeEnv->setProperty(componentName(otherFileInfo.fileName()),
167
                             otherDoc->bind()->rootObjectValue());
168
169
170
171
    }

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

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

183
184
185
186
187
/*
    import "content"
    import "content" as Xxx
    import "content" 4.6
    import "content" 4.6 as Xxx
188

189
190
191
192
193
    import "http://www.ovi.com/" as Ovi
*/
void Link::importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
                      AST::UiImport *import, const QString &startPath)
{
194
195
    Q_UNUSED(doc)

196
197
    if (!import->fileName)
        return;
198

199
200
201
202
    QString path = startPath;
    path += QLatin1Char('/');
    path += import->fileName->asString();
    path = QDir::cleanPath(path);
203

204
    ObjectValue *importNamespace = 0;
205

206
207
208
209
210
211
212
213
    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;
214

215
        if (directoryImport && import->importId && !importNamespace) {
216
            importNamespace = engine()->newObject(/*prototype =*/0);
217
218
            typeEnv->setProperty(import->importId->asString(), importNamespace);
        }
219

220
221
222
223
224
        QString targetName;
        if (fileImport && import->importId) {
            targetName = import->importId->asString();
        } else {
            targetName = componentName(otherFileInfo.fileName());
225
        }
226
227
228
229
230

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

231
        importInto->setProperty(targetName, otherDoc->bind()->rootObjectValue());
232
233
234
    }
}

235
236
237
238
239
240
241
242
243
244
/*
  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
245
        namespaceObject = engine()->newObject(/*prototype */ 0);
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
        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
271
        foreach (QmlObjectValue *object, engine()->metaTypeSystem().staticTypesForImport(package, majorVersion, minorVersion)) {
272
273
274
275
276
277
            namespaceObject->setProperty(object->qmlTypeName(), object);
        }
#endif // NO_DECLARATIVE_BACKEND
    }
}

278
279
280
281
282
283
284
285
286
287
UiQualifiedId *Link::qualifiedTypeNameId(Node *node)
{
    if (UiObjectBinding *binding = AST::cast<UiObjectBinding *>(node))
        return binding->qualifiedTypeNameId;
    else if (UiObjectDefinition *binding = AST::cast<UiObjectDefinition *>(node))
        return binding->qualifiedTypeNameId;
    else
        return 0;
}

hjk's avatar
hjk committed
288
289
290
QT_BEGIN_NAMESPACE
static uint qHash(Document::Ptr doc)
{
291
292
    return qHash(doc.data());
}
hjk's avatar
hjk committed
293
QT_END_NAMESPACE
294

295
QList<Document::Ptr> Link::reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot)
296
{
297
    QSet<Document::Ptr> docs;
298

299
    if (! startDoc)
300
        return docs.values();
301

302
303
304
    QMultiHash<QString, Document::Ptr> documentByPath;
    foreach (Document::Ptr doc, snapshot)
        documentByPath.insert(doc->path(), doc);
305

306
307
308
309
310
    // ### TODO: This doesn't scale well. Maybe just use the whole snapshot?
    // Find all documents that (indirectly) include startDoc
    {
        QList<Document::Ptr> todo;
        todo += startDoc;
311

312
313
        while (! todo.isEmpty()) {
            Document::Ptr doc = todo.takeFirst();
314
315

            docs += doc;
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330

            Snapshot::const_iterator it, end = snapshot.end();
            for (it = snapshot.begin(); it != end; ++it) {
                Document::Ptr otherDoc = *it;
                if (docs.contains(otherDoc))
                    continue;

                QStringList localImports = otherDoc->bind()->localImports();
                if (localImports.contains(doc->fileName())
                    || localImports.contains(doc->path())
                    || otherDoc->bind()->includedScripts().contains(doc->fileName())
                ) {
                    todo += otherDoc;
                }
            }
331
        }
332
    }
333

334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
    // Find all documents that are included by these (even if indirectly).
    {
        QSet<QString> processed;
        QStringList todo;
        foreach (Document::Ptr doc, docs)
            todo.append(doc->fileName());

        while (! todo.isEmpty()) {
            QString path = todo.takeFirst();

            if (processed.contains(path))
                continue;
            processed.insert(path);

            if (Document::Ptr doc = snapshot.document(path)) {
                docs += doc;

                if (doc->qmlProgram())
                    path = doc->path();
                else
                    continue;
            }

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

            localImports.removeDuplicates();
            todo += localImports;
        }
369
370
    }

371
    return docs.values();
372
}