#include "qmljslink.h" #include "parser/qmljsast_p.h" #include "qmljsdocument.h" #include "qmljsbind.h" #include #include #include using namespace QmlJS; using namespace QmlJS::Interpreter; using namespace QmlJS::AST; Link::Link(Context *context, Document::Ptr currentDoc, const Snapshot &snapshot) : _snapshot(snapshot) , _context(context) { _docs = reachableDocuments(currentDoc, snapshot); linkImports(); } Link::~Link() { } Interpreter::Engine *Link::engine() { return _context->engine(); } void Link::scopeChainAt(Document::Ptr doc, Node *currentObject) { // ### TODO: This object ought to contain the global namespace additions by QML. _context->pushScope(engine()->globalObject()); if (! doc) return; Bind *bind = doc->bind(); QStringList linkedDocs; // to prevent cycles if (doc->qmlProgram()) { _context->setLookupMode(Context::QmlLookup); ObjectValue *scopeObject = 0; if (UiObjectDefinition *definition = cast(currentObject)) scopeObject = bind->findQmlObject(definition); else if (UiObjectBinding *binding = cast(currentObject)) scopeObject = bind->findQmlObject(binding); pushScopeChainForComponent(doc, &linkedDocs, scopeObject); if (const ObjectValue *typeEnvironment = _context->typeEnvironment(doc.data())) _context->pushScope(typeEnvironment); } else { // 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? } _context->pushScope(bind->rootObjectValue()); } if (FunctionDeclaration *fun = cast(currentObject)) { 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); } } void Link::pushScopeChainForComponent(Document::Ptr doc, QStringList *linkedDocs, ObjectValue *scopeObject) { if (!doc->qmlProgram()) return; linkedDocs->append(doc->fileName()); 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); } } if (bind->rootObjectValue()) _context->pushScope(bind->rootObjectValue()); if (scopeObject && scopeObject != bind->rootObjectValue()) _context->pushScope(scopeObject); const QStringList &includedScripts = bind->includedScripts(); for (int index = includedScripts.size() - 1; index != -1; --index) { const QString &scriptFile = includedScripts.at(index); if (Document::Ptr scriptDoc = _snapshot.document(scriptFile)) { if (scriptDoc->jsProgram()) { _context->pushScope(scriptDoc->bind()->rootObjectValue()); } } } _context->pushScope(bind->functionEnvironment()); _context->pushScope(bind->idEnvironment()); } void Link::linkImports() { foreach (Document::Ptr doc, _docs) { ObjectValue *typeEnv = engine()->newObject(/*prototype =*/0); // ### FIXME // Populate the _typeEnvironment with imports. populateImportedTypes(typeEnv, doc); _context->setTypeEnvironment(doc.data(), typeEnv); } } 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; } void Link::populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Ptr doc) { if (! (doc->qmlProgram() && doc->qmlProgram()->imports)) return; QFileInfo fileInfo(doc->fileName()); const QString absolutePath = fileInfo.absolutePath(); // implicit imports: // qml files in the same directory are available without explicit imports foreach (Document::Ptr otherDoc, _docs) { if (otherDoc == doc) continue; QFileInfo otherFileInfo(otherDoc->fileName()); const QString otherAbsolutePath = otherFileInfo.absolutePath(); if (otherAbsolutePath != absolutePath) continue; typeEnv->setProperty(componentName(otherFileInfo.fileName()), otherDoc->bind()->rootObjectValue()); } // explicit imports, whether directories or files for (UiImportList *it = doc->qmlProgram()->imports; it; it = it->next) { if (! it->import) continue; if (it->import->fileName) { importFile(typeEnv, doc, it->import, absolutePath); } else if (it->import->importUri) { importNonFile(typeEnv, doc, it->import); } } } /* import "content" import "content" as Xxx import "content" 4.6 import "content" 4.6 as Xxx import "http://www.ovi.com/" as Ovi */ void Link::importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc, AST::UiImport *import, const QString &startPath) { Q_UNUSED(doc) if (!import->fileName) return; QString path = startPath; path += QLatin1Char('/'); path += import->fileName->asString(); path = QDir::cleanPath(path); ObjectValue *importNamespace = 0; 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; if (directoryImport && import->importId && !importNamespace) { importNamespace = engine()->newObject(/*prototype =*/0); typeEnv->setProperty(import->importId->asString(), importNamespace); } QString targetName; if (fileImport && import->importId) { targetName = import->importId->asString(); } else { targetName = componentName(otherFileInfo.fileName()); } ObjectValue *importInto = typeEnv; if (importNamespace) importInto = importNamespace; importInto->setProperty(targetName, otherDoc->bind()->rootObjectValue()); } } /* 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 namespaceObject = engine()->newObject(/*prototype */ 0); 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 foreach (QmlObjectValue *object, engine()->metaTypeSystem().staticTypesForImport(package, majorVersion, minorVersion)) { namespaceObject->setProperty(object->qmlTypeName(), object); } #endif // NO_DECLARATIVE_BACKEND } } UiQualifiedId *Link::qualifiedTypeNameId(Node *node) { if (UiObjectBinding *binding = AST::cast(node)) return binding->qualifiedTypeNameId; else if (UiObjectDefinition *binding = AST::cast(node)) return binding->qualifiedTypeNameId; else return 0; } QT_BEGIN_NAMESPACE static uint qHash(Document::Ptr doc) { return qHash(doc.data()); } QT_END_NAMESPACE QList Link::reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot) { QSet docs; if (! startDoc) return docs.values(); QMultiHash documentByPath; foreach (Document::Ptr doc, snapshot) documentByPath.insert(doc->path(), doc); // ### TODO: This doesn't scale well. Maybe just use the whole snapshot? // Find all documents that (indirectly) include startDoc { QList todo; todo += startDoc; while (! todo.isEmpty()) { Document::Ptr doc = todo.takeFirst(); docs += doc; 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; } } } } // Find all documents that are included by these (even if indirectly). { QSet 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; } } return docs.values(); }