qmljslink.cpp 11.8 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
    if (bind->rootObjectValue())
        _context->pushScope(bind->rootObjectValue());

Christian Kamm's avatar
Christian Kamm committed
105
106
107
108
109
    if (scopeObject) {
        _context->markQmlScopeObject();
        if (scopeObject != bind->rootObjectValue())
            _context->setQmlScopeObject(scopeObject);
    }
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
116
        if (Document::Ptr scriptDoc = _snapshot.document(scriptFile)) {
            if (scriptDoc->jsProgram()) {
117
                _context->pushScope(scriptDoc->bind()->rootObjectValue());
118
            }
119
120
        }
    }
121

122
123
    _context->pushScope(bind->functionEnvironment());
    _context->pushScope(bind->idEnvironment());
124
125
126
127
128
}

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

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

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

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

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

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

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

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

        if (otherAbsolutePath != absolutePath)
            continue;

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

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

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

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

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

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

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

206
    ObjectValue *importNamespace = 0;
207

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

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

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

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

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

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

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

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

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

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

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

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

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

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

373
    return docs.values();
374
}