qmljslink.cpp 9.6 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
16
Link::Link(Document::Ptr currentDoc, const Snapshot &snapshot, Interpreter::Engine *interp)
    : _snapshot(snapshot)
Roberto Raggi's avatar
Roberto Raggi committed
17
    , _context(interp)
18
19
20
21
22
23
24
25
{
    _docs = reachableDocuments(currentDoc, snapshot);

    linkImports();
}

Link::~Link()
{
26
    // unset all prototypes
27
28
29
30
    foreach (Document::Ptr doc, _docs) {
        BindPtr bind = doc->bind();

        if (doc->qmlProgram()) {
31
            foreach (ObjectValue *object, bind->_qmlObjects) {
32
33
34
35
36
37
                object->setPrototype(0);
            }
        }
    }
}

38
Context *Link::context()
39
{
40
    return &_context;
41
42
}

43
Interpreter::Engine *Link::engine()
44
{
45
    return _context.engine();
46
}
47

48
49
void Link::scopeChainAt(Document::Ptr doc, Node *currentObject)
{
50
    _context.pushScope(engine()->globalObject());
51

52
    if (! doc)
53
        return;
54
55
56

    if (doc->qmlProgram() != 0)
        _context.setLookupMode(Context::QmlLookup);
57
58

    BindPtr bind = doc->bind();
59
60
61

    // Build the scope chain.

62
    // ### FIXME: May want to link to instantiating components from here.
63

64
65
    if (bind->_rootObjectValue)
        _context.pushScope(bind->_rootObjectValue);
66

67
68
69
70
71
    ObjectValue *scopeObject = 0;
    if (UiObjectDefinition *definition = cast<UiObjectDefinition *>(currentObject))
        scopeObject = bind->_qmlObjects.value(definition);
    else if (UiObjectBinding *binding = cast<UiObjectBinding *>(currentObject))
        scopeObject = bind->_qmlObjects.value(binding);
72

73
74
    if (scopeObject && scopeObject != bind->_rootObjectValue)
        _context.pushScope(scopeObject);
75

76
77
78
    const QStringList &includedScripts = bind->includedScripts();
    for (int index = includedScripts.size() - 1; index != -1; --index) {
        const QString &scriptFile = includedScripts.at(index);
79

80
81
82
83
        if (Document::Ptr scriptDoc = _snapshot.document(scriptFile)) {
            if (scriptDoc->jsProgram()) {
                _context.pushScope(scriptDoc->bind()->_rootObjectValue);
            }
84
85
        }
    }
86

87
88
89
90
91
92
93
94
    if (bind->_functionEnvironment)
        _context.pushScope(bind->_functionEnvironment);

    if (bind->_idEnvironment)
        _context.pushScope(bind->_idEnvironment);

    if (const ObjectValue *typeEnvironment = _typeEnvironments.value(doc.data()))
        _context.pushScope(typeEnvironment);
95
96
97
98
99
100
101
}

void Link::linkImports()
{
    foreach (Document::Ptr doc, _docs) {
        BindPtr bind = doc->bind();

102
        ObjectValue *typeEnv = engine()->newObject(/*prototype =*/0); // ### FIXME
103
104
105
106
107
108
        _typeEnvironments.insert(doc.data(), typeEnv);

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

        // Set the prototypes.
109
110
111
112
113
114
115
116
        QHashIterator<Node *, ObjectValue *> it(bind->_qmlObjects);
        while (it.hasNext()) {
            it.next();
            Node *binding = it.key();
            ObjectValue *value = it.value();

            if (UiQualifiedId *qualifiedId = qualifiedTypeNameId(binding))
                value->setPrototype(lookupType(typeEnv, qualifiedId));
117
118
119
120
        }
    }
}

121
122
123
124
125
126
127
128
129
130
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;
}

131
void Link::populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Ptr doc)
132
133
134
135
136
137
138
{
    if (! (doc->qmlProgram() && doc->qmlProgram()->imports))
        return;

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

139
140
    // implicit imports:
    // qml files in the same directory are available without explicit imports
141
142
    foreach (Document::Ptr otherDoc, _docs) {
        if (otherDoc == doc)
143
144
145
146
147
148
149
150
            continue;

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

        if (otherAbsolutePath != absolutePath)
            continue;

151
152
        typeEnv->setProperty(componentName(otherFileInfo.fileName()),
                             otherDoc->bind()->_rootObjectValue);
153
154
155
156
    }

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

160
161
162
163
164
165
166
        if (it->import->fileName) {
            importFile(typeEnv, doc, it->import, absolutePath);
        } else if (it->import->importUri) {
            importNonFile(typeEnv, doc, it->import);
        }
    }
}
167

168
169
170
171
172
/*
    import "content"
    import "content" as Xxx
    import "content" 4.6
    import "content" 4.6 as Xxx
173

174
175
176
177
178
    import "http://www.ovi.com/" as Ovi
*/
void Link::importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
                      AST::UiImport *import, const QString &startPath)
{
179
180
    Q_UNUSED(doc)

181
182
    if (!import->fileName)
        return;
183

184
185
186
187
    QString path = startPath;
    path += QLatin1Char('/');
    path += import->fileName->asString();
    path = QDir::cleanPath(path);
188

189
    ObjectValue *importNamespace = 0;
190

191
192
193
194
195
196
197
198
    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;
199

200
        if (directoryImport && import->importId && !importNamespace) {
201
            importNamespace = engine()->newObject(/*prototype =*/0);
202
203
            typeEnv->setProperty(import->importId->asString(), importNamespace);
        }
204

205
206
207
208
209
        QString targetName;
        if (fileImport && import->importId) {
            targetName = import->importId->asString();
        } else {
            targetName = componentName(otherFileInfo.fileName());
210
        }
211
212
213
214
215
216

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

        importInto->setProperty(targetName, otherDoc->bind()->_rootObjectValue);
217
218
219
    }
}

220
221
222
223
224
225
226
227
228
229
/*
  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
230
        namespaceObject = engine()->newObject(/*prototype */ 0);
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
        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
256
        foreach (QmlObjectValue *object, engine()->metaTypeSystem().staticTypesForImport(package, majorVersion, minorVersion)) {
257
258
259
260
261
262
263
            namespaceObject->setProperty(object->qmlTypeName(), object);
        }
#endif // NO_DECLARATIVE_BACKEND
    }
}

const ObjectValue *Link::lookupType(ObjectValue *env, UiQualifiedId *id)
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
{
    const ObjectValue *objectValue = env;

    for (UiQualifiedId *iter = id; objectValue && iter; iter = iter->next) {
        if (! iter->name)
            return 0;

        const Value *value = objectValue->property(iter->name->asString());
        if (!value)
            return 0;

        objectValue = value->asObjectValue();
    }

    return objectValue;
}

281
282
283
284
285
286
287
288
289
290
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;
}

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

295
296
    QSet<QString> processed;
    QStringList todo;
297

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

302
    todo.append(startDoc->path());
303

304
305
306
    // Find the reachable documents.
    while (! todo.isEmpty()) {
        const QString path = todo.takeFirst();
307

308
309
        if (processed.contains(path))
            continue;
310

311
312
313
314
315
316
317
318
319
320
        processed.insert(path);

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

        localImports.removeDuplicates();
        todo += localImports;
321
322
    }

323
    return docs;
324
}