qmljslink.cpp 11.7 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
Link::Link(Context *context, Document::Ptr currentDoc, const Snapshot &snapshot)
16
    : _snapshot(snapshot)
17
    , _context(context)
18
19
20
21
22
23
24
25
26
{
    _docs = reachableDocuments(currentDoc, snapshot);
    linkImports();
}

Link::~Link()
{
}

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

32
33
void Link::scopeChainAt(Document::Ptr doc, Node *currentObject)
{
34
    // ### TODO: This object ought to contain the global namespace additions by QML.
35
    _context->pushScope(engine()->globalObject());
36

37
    if (! doc)
38
        return;
39

Roberto Raggi's avatar
Roberto Raggi committed
40
    Bind *bind = doc->bind();
41
    QStringList linkedDocs; // to prevent cycles
42

43
44
45
46
47
48
49
50
51
52
53
54
55
56
    if (doc->qmlProgram()) {
        _context->setLookupMode(Context::QmlLookup);

        ObjectValue *scopeObject = 0;
        if (UiObjectDefinition *definition = cast<UiObjectDefinition *>(currentObject))
            scopeObject = bind->findQmlObject(definition);
        else if (UiObjectBinding *binding = cast<UiObjectBinding *>(currentObject))
            scopeObject = bind->findQmlObject(binding);

        pushScopeChainForComponent(doc, &linkedDocs, scopeObject);

        if (const ObjectValue *typeEnvironment = _context->typeEnvironment(doc.data()))
            _context->pushScope(typeEnvironment);
    } else {
57
58
59
60
61
62
63
64
65
        // the global scope of a js file does not see the instantiating component
        if (currentObject != 0) {
            // add scope chains for all components that source this document
            foreach (Document::Ptr otherDoc, _docs) {
                if (otherDoc->bind()->includedScripts().contains(doc->fileName()))
                    pushScopeChainForComponent(otherDoc, &linkedDocs);
            }

            // ### TODO: Which type environment do scripts see?
66
        }
67

68
        _context->pushScope(bind->rootObjectValue());
69
    }
70

71
    if (FunctionDeclaration *fun = cast<FunctionDeclaration *>(currentObject)) {
72
73
74
75
76
77
78
        ObjectValue *activation = engine()->newObject(/*prototype = */ 0);
        for (FormalParameterList *it = fun->formals; it; it = it->next) {
            if (it->name)
                activation->setProperty(it->name->asString(), engine()->undefinedValue());
        }
        _context->pushScope(activation);
    }
79
80
81
82
83
84
85
}

void Link::pushScopeChainForComponent(Document::Ptr doc, QStringList *linkedDocs,
                                      ObjectValue *scopeObject)
{
    if (!doc->qmlProgram())
        return;
86

87
    linkedDocs->append(doc->fileName());
88

89
90
91
92
93
94
95
96
97
98
99
    Bind *bind = doc->bind();

    // add scopes for all components instantiating this one
    foreach (Document::Ptr otherDoc, _docs) {
        if (linkedDocs->contains(otherDoc->fileName()))
            continue;

        if (otherDoc->bind()->usesQmlPrototype(bind->rootObjectValue(), _context)) {
            // ### TODO: Depth-first insertion doesn't give us correct name shadowing.
            pushScopeChainForComponent(otherDoc, linkedDocs);
        }
100
    }
101

102
103
104
105
106
107
    if (bind->rootObjectValue())
        _context->pushScope(bind->rootObjectValue());

    if (scopeObject && scopeObject != bind->rootObjectValue())
        _context->pushScope(scopeObject);

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

112
113
        if (Document::Ptr scriptDoc = _snapshot.document(scriptFile)) {
            if (scriptDoc->jsProgram()) {
114
                _context->pushScope(scriptDoc->bind()->rootObjectValue());
115
            }
116
117
        }
    }
118

119
120
    _context->pushScope(bind->functionEnvironment());
    _context->pushScope(bind->idEnvironment());
121
122
123
124
125
}

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

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

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

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

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

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

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

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

        if (otherAbsolutePath != absolutePath)
            continue;

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

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

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

182
183
184
185
186
/*
    import "content"
    import "content" as Xxx
    import "content" 4.6
    import "content" 4.6 as Xxx
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)
{
193
194
    Q_UNUSED(doc)

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

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

203
    ObjectValue *importNamespace = 0;
204

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

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

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

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

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

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

277
278
279
280
281
282
283
284
285
286
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;
}

287
288
289
290
static uint qHash(Document::Ptr doc) {
    return qHash(doc.data());
}

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

295
    if (! startDoc)
296
        return docs.values();
297

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

302
303
304
305
306
    // ### 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;
307

308
309
        while (! todo.isEmpty()) {
            Document::Ptr doc = todo.takeFirst();
310
311

            docs += doc;
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326

            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;
                }
            }
327
        }
328
    }
329

330
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
    // 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;
        }
365
366
    }

367
    return docs.values();
368
}