qmljslink.cpp 11.5 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
57
58
59
60
61
    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 {
        // 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);
        }
62

63
        // ### TODO: Which type environment do scripts see?
64

65
        _context->pushScope(bind->rootObjectValue());
66
    }
67

68
    if (FunctionDeclaration *fun = cast<FunctionDeclaration *>(currentObject)) {
69
70
71
72
73
74
75
        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);
    }
76
77
78
79
80
81
82
}

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

84
    linkedDocs->append(doc->fileName());
85

86
87
88
89
90
91
92
93
94
95
96
    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);
        }
97
    }
98

99
100
101
102
103
104
    if (bind->rootObjectValue())
        _context->pushScope(bind->rootObjectValue());

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

105
106
107
    const QStringList &includedScripts = bind->includedScripts();
    for (int index = includedScripts.size() - 1; index != -1; --index) {
        const QString &scriptFile = includedScripts.at(index);
108

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

116
117
    _context->pushScope(bind->functionEnvironment());
    _context->pushScope(bind->idEnvironment());
118
119
120
121
122
}

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

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

128
        _context->setTypeEnvironment(doc.data(), typeEnv);
129
130
131
    }
}

132
133
134
135
136
137
138
139
140
141
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;
}

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

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

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

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

        if (otherAbsolutePath != absolutePath)
            continue;

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

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

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

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

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

192
193
    if (!import->fileName)
        return;
194

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

200
    ObjectValue *importNamespace = 0;
201

202
203
204
205
206
207
208
209
    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;
210

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

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

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

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

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

274
275
276
277
278
279
280
281
282
283
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;
}

284
285
286
287
static uint qHash(Document::Ptr doc) {
    return qHash(doc.data());
}

288
QList<Document::Ptr> Link::reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot)
289
{
290
    QSet<Document::Ptr> docs;
291

292
    if (! startDoc)
293
        return docs.values();
294

295
296
297
    QMultiHash<QString, Document::Ptr> documentByPath;
    foreach (Document::Ptr doc, snapshot)
        documentByPath.insert(doc->path(), doc);
298

299
300
301
302
303
    // ### 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;
304

305
306
        while (! todo.isEmpty()) {
            Document::Ptr doc = todo.takeFirst();
307
308

            docs += doc;
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323

            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;
                }
            }
324
        }
325
    }
326

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
    // 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;
        }
362
363
    }

364
    return docs.values();
365
}