qmljslink.cpp 11.8 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
        scopeChain.qmlComponentScope.clear();
52
53
        makeComponentChain(doc, &scopeChain.qmlComponentScope, &componentScopes);

54
        if (const ObjectValue *typeEnvironment = _context->typeEnvironment(doc.data()))
55
            scopeChain.qmlTypes = typeEnvironment;
56
    } else {
57
        // the global scope of a js file does not see the instantiating component
58
        if (astPath.size() > 0) {
59
60
            // add scope chains for all components that source this document
            foreach (Document::Ptr otherDoc, _docs) {
61
62
63
64
65
66
                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);
                }
67
68
69
            }

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

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

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

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

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

    // add scopes for all components instantiating this one
    foreach (Document::Ptr otherDoc, _docs) {
92
        if (otherDoc == doc)
93
94
            continue;
        if (otherDoc->bind()->usesQmlPrototype(bind->rootObjectValue(), _context)) {
95
96
97
98
99
100
101
102
103
            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);
            }
104
        }
105
    }
106

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

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

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

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

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

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

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

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

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

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

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

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

        if (otherAbsolutePath != absolutePath)
            continue;

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

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

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

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

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

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

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

205
    ObjectValue *importNamespace = 0;
206

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

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

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

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

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

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

279
280
281
282
283
284
285
286
287
288
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
289
290
291
QT_BEGIN_NAMESPACE
static uint qHash(Document::Ptr doc)
{
292
293
    return qHash(doc.data());
}
hjk's avatar
hjk committed
294
QT_END_NAMESPACE
295

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

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

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

307
308
309
310
311
    // ### 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;
312

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

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

            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;
                }
            }
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
369
    // 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;
        }
370
371
    }

372
    return docs.values();
373
}