From 03fa188b41e46d9b0f6ad4d4e6e97f2d3711568c Mon Sep 17 00:00:00 2001 From: Christian Kamm <christian.d.kamm@nokia.com> Date: Tue, 2 Feb 2010 15:55:17 +0100 Subject: [PATCH] Bind each QML document after parsing. Link them before use. Reviewed-by: Roberto Raggi <roberto.raggi@nokia.com> --- src/libs/qmljs/qmljsbind.cpp | 203 +---------- src/libs/qmljs/qmljsbind.h | 19 +- src/libs/qmljs/qmljsdocument.cpp | 10 + src/libs/qmljs/qmljsdocument.h | 6 + src/libs/qmljs/qmljslink.cpp | 329 +++++++++++++----- src/libs/qmljs/qmljslink.h | 42 ++- src/plugins/qmljseditor/qmlcodecompletion.cpp | 3 +- src/plugins/qmljseditor/qmlhoverhandler.cpp | 4 +- 8 files changed, 311 insertions(+), 305 deletions(-) diff --git a/src/libs/qmljs/qmljsbind.cpp b/src/libs/qmljs/qmljsbind.cpp index bdd626e22e5..01f489c336f 100644 --- a/src/libs/qmljs/qmljsbind.cpp +++ b/src/libs/qmljs/qmljsbind.cpp @@ -121,76 +121,27 @@ public: } }; -class ProcessSourceElements: protected AST::Visitor -{ - Interpreter::Engine *_interp; - -public: - ProcessSourceElements(Interpreter::Engine *interp) - : _interp(interp), - typeOfExpression(interp) - { - } - - void operator()(AST::Node *node) - { - if (node) - node->accept(this); - } - -protected: - using AST::Visitor::visit; - - virtual bool visit(FunctionDeclaration *ast) - { - if (ast->name) - _interp->globalObject()->setProperty(ast->name->asString(), new ASTFunctionValue(ast, _interp)); - - return false; - } - - virtual bool visit(VariableDeclaration *ast) - { - if (ast->name) { - const Value *value = _interp->undefinedValue(); - - if (ast->expression) - value = typeOfExpression(ast->expression, _interp->globalObject()); - - _interp->globalObject()->setProperty(ast->name->asString(), value); - } - - return false; - } - - Check typeOfExpression; -}; - } // end of anonymous namespace -Bind::Bind(Document::Ptr doc, Interpreter::Engine *interp) +Bind::Bind(Document *doc) : _doc(doc), - _interp(interp), _currentObjectValue(0), - _typeEnvironment(0), _idEnvironment(0), _functionEnvironment(0), _rootObjectValue(0) { - if (_doc && _doc->qmlProgram() != 0) { - UiProgram *program = _doc->qmlProgram(); - - _currentObjectValue = 0; - _typeEnvironment = _interp->newObject(/*prototype =*/ 0); - _idEnvironment = _interp->newObject(/*prototype =*/ 0); - _functionEnvironment = _interp->newObject(/*prototype =*/ 0); - _rootObjectValue = 0; - - _qmlObjectDefinitions.clear(); - _qmlObjectBindings.clear(); + if (!_doc) + return; - accept(program); + if (_doc->qmlProgram()) { + _idEnvironment = _interp.newObject(/*prototype =*/ 0); + _functionEnvironment = _interp.newObject(/*prototype =*/ 0); + } else if (_doc->jsProgram()) { + _currentObjectValue = _interp.globalObject(); + _rootObjectValue = _interp.globalObject(); } + + accept(_doc->ast()); } Bind::~Bind() @@ -214,77 +165,6 @@ ObjectValue *Bind::switchObjectValue(ObjectValue *newObjectValue) return oldObjectValue; } -ObjectValue *Bind::scopeChainAt(Document::Ptr currentDocument, const Snapshot &snapshot, - Interpreter::Engine *interp, AST::UiObjectMember *currentObject) -{ - Bind *currentBind = 0; - QList<Bind *> binds; - - QSet<QString> processed; - QStringList todo; - - QMultiHash<QString, Document::Ptr> documentByPath; - foreach (Document::Ptr doc, snapshot) - documentByPath.insert(doc->path(), doc); - - todo.append(currentDocument->path()); - - // Bind the reachable documents. - while (! todo.isEmpty()) { - const QString path = todo.takeFirst(); - - if (processed.contains(path)) - continue; - - processed.insert(path); - - QStringList localImports; - foreach (Document::Ptr doc, documentByPath.values(path)) { - Bind *newBind = new Bind(doc, interp); - binds += newBind; - - localImports += newBind->localImports(); - - if (doc == currentDocument) - currentBind = newBind; - } - - localImports.removeDuplicates(); - todo += localImports; - } - - LinkImports linkImports; - Link link; - - // link the import directives - linkImports(binds); - - // link the scripts - QStringList includedScriptFiles; - foreach (Bind *bind, binds) - includedScriptFiles += bind->includedScripts(); - - includedScriptFiles.removeDuplicates(); - - ProcessSourceElements processSourceElements(interp); - - foreach (const QString &scriptFile, includedScriptFiles) { - if (Document::Ptr scriptDoc = snapshot.document(scriptFile)) { - if (AST::Program *program = scriptDoc->jsProgram()) { - processSourceElements(program); - } - } - } - - ObjectValue *scope = interp->globalObject(); - if (currentBind) - scope = link(binds, currentBind, currentObject); - - qDeleteAll(binds); - - return scope; -} - QString Bind::toString(UiQualifiedId *qualifiedId, QChar delimiter) { QString result; @@ -350,7 +230,7 @@ ObjectValue *Bind::bindObject(UiQualifiedId *qualifiedTypeNameId, UiObjectInitia } // normal component instance - ASTObjectValue *objectValue = new ASTObjectValue(qualifiedTypeNameId, initializer, _interp); + ASTObjectValue *objectValue = new ASTObjectValue(qualifiedTypeNameId, initializer, &_interp); parentObjectValue = switchObjectValue(objectValue); if (parentObjectValue) @@ -368,64 +248,9 @@ void Bind::accept(Node *node) Node::accept(node, this); } -/* - import Qt 4.6 - import Qt 4.6 as Xxx - (import com.nokia.qt is the same as the ones above) - - import "content" - import "content" as Xxx - import "content" 4.6 - import "content" 4.6 as Xxx - - import "http://www.ovi.com/" as Ovi - */ -// ### TODO: Move to LinkImports bool Bind::visit(UiImport *ast) { - if (! (ast->importUri || ast->fileName)) - return false; // nothing to do. - - ObjectValue *namespaceObject = 0; - - if (ast->asToken.isValid()) { // with namespace we insert an object in the type env. to hold the imported types - if (!ast->importId) - return false; // this should never happen, but better be safe than sorry - - namespaceObject = _interp->newObject(/*prototype */ 0); - - _typeEnvironment->setProperty(ast->importId->asString(), namespaceObject); - - } else { // without namespace we insert all types directly into the type env. - namespaceObject = _typeEnvironment; - } - - // look at files first - - // else try the metaobject system - if (ast->importUri) { - const QString package = toString(ast->importUri, '/'); - int majorVersion = -1; // ### TODO: Check these magic version numbers - int minorVersion = -1; // ### TODO: Check these magic version numbers - - if (ast->versionToken.isValid()) { - const QString versionString = _doc->source().mid(ast->versionToken.offset, ast->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, _interp->metaTypeSystem().staticTypesForImport(package, majorVersion, minorVersion)) { - namespaceObject->setProperty(object->qmlTypeName(), object); - } -#endif // NO_DECLARATIVE_BACKEND - } else if (ast->fileName) { + if (ast->fileName) { QString path = _doc->path(); path += QLatin1Char('/'); path += ast->fileName->asString(); @@ -488,7 +313,7 @@ bool Bind::visit(FunctionDeclaration *ast) if (_currentObjectValue->property(ast->name->asString())) return false; - ASTFunctionValue *function = new ASTFunctionValue(ast, _interp); + ASTFunctionValue *function = new ASTFunctionValue(ast, &_interp); _currentObjectValue->setProperty(ast->name->asString(), function); return false; // ### eventually want to visit function bodies } diff --git a/src/libs/qmljs/qmljsbind.h b/src/libs/qmljs/qmljsbind.h index f9f35cbdc23..d5ce0e6f0af 100644 --- a/src/libs/qmljs/qmljsbind.h +++ b/src/libs/qmljs/qmljsbind.h @@ -31,11 +31,12 @@ #define QMLBIND_H #include <qmljs/parser/qmljsastvisitor_p.h> -#include <qmljs/qmljsdocument.h> #include <qmljs/qmljsinterpreter.h> +#include <qmljs/qmljsdocument.h> #include <QtCore/QHash> #include <QtCore/QStringList> +#include <QtCore/QSharedPointer> namespace QmlJS { @@ -44,21 +45,13 @@ class Link; class QMLJS_EXPORT Bind: protected AST::Visitor { -protected: - Bind(Document::Ptr doc, Interpreter::Engine *interp); - public: + Bind(Document *doc); virtual ~Bind(); QStringList includedScripts() const; QStringList localImports() const; - // ### TODO: This methods should go. Bind each document after parsing, link later. - static Interpreter::ObjectValue *scopeChainAt(Document::Ptr currentDocument, - const Snapshot &snapshot, - Interpreter::Engine *interp, - AST::UiObjectMember *currentObject); - protected: using AST::Visitor::visit; @@ -83,11 +76,10 @@ protected: Interpreter::ObjectValue *bindObject(AST::UiQualifiedId *qualifiedTypeNameId, AST::UiObjectInitializer *initializer); private: - Document::Ptr _doc; - Interpreter::Engine *_interp; + Document *_doc; + Interpreter::Engine _interp; Interpreter::ObjectValue *_currentObjectValue; - Interpreter::ObjectValue *_typeEnvironment; Interpreter::ObjectValue *_idEnvironment; Interpreter::ObjectValue *_functionEnvironment; Interpreter::ObjectValue *_rootObjectValue; @@ -100,6 +92,7 @@ private: friend class LinkImports; friend class Link; }; +typedef QSharedPointer<Bind> BindPtr; } // end of namespace Qml diff --git a/src/libs/qmljs/qmljsdocument.cpp b/src/libs/qmljs/qmljsdocument.cpp index 262d7745e31..a215198fe98 100644 --- a/src/libs/qmljs/qmljsdocument.cpp +++ b/src/libs/qmljs/qmljsdocument.cpp @@ -28,6 +28,7 @@ **************************************************************************/ #include "qmljsdocument.h" +#include "qmljsbind.h" #include <qmljs/parser/qmljsast_p.h> #include <qmljs/parser/qmljslexer_p.h> #include <qmljs/parser/qmljsparser_p.h> @@ -143,6 +144,8 @@ bool Document::parseQml() _ast = parser.ast(); _diagnosticMessages = parser.diagnosticMessages(); + _bind = BindPtr(new Bind(this)); + return _parsedCorrectly; } @@ -164,6 +167,8 @@ bool Document::parseJavaScript() _ast = cast<Program*>(parser.rootNode()); _diagnosticMessages = parser.diagnosticMessages(); + _bind = BindPtr(new Bind(this)); + return _parsedCorrectly; } @@ -190,6 +195,11 @@ bool Document::parseExpression() return _parsedCorrectly; } +BindPtr Document::bind() const +{ + return _bind; +} + Snapshot::Snapshot() { } diff --git a/src/libs/qmljs/qmljsdocument.h b/src/libs/qmljs/qmljsdocument.h index 4ba69aa7c7a..bef0fd986e4 100644 --- a/src/libs/qmljs/qmljsdocument.h +++ b/src/libs/qmljs/qmljsdocument.h @@ -40,6 +40,9 @@ namespace QmlJS { +class Bind; +typedef QSharedPointer<Bind> BindPtr; + class QMLJS_EXPORT Document { public: @@ -71,6 +74,8 @@ public: bool isParsedCorrectly() const { return _parsedCorrectly; } + BindPtr bind() const; + int documentRevision() const; void setDocumentRevision(int documentRevision); @@ -89,6 +94,7 @@ private: QString _path; QString _componentName; QString _source; + BindPtr _bind; }; class QMLJS_EXPORT Snapshot diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp index e86db8799a3..57c99b33ab2 100644 --- a/src/libs/qmljs/qmljslink.cpp +++ b/src/libs/qmljs/qmljslink.cpp @@ -12,6 +12,124 @@ using namespace QmlJS; using namespace QmlJS::Interpreter; using namespace QmlJS::AST; +Link::Link(Document::Ptr currentDoc, const Snapshot &snapshot, Interpreter::Engine *interp) + : _snapshot(snapshot) + , _interp(interp) +{ + _docs = reachableDocuments(currentDoc, snapshot); + + linkImports(); +} + +Link::~Link() +{ + // unset all prototypes and scopes + foreach (Document::Ptr doc, _docs) { + BindPtr bind = doc->bind(); + + if (doc->qmlProgram()) { + bind->_idEnvironment->setScope(0); + bind->_functionEnvironment->setScope(0); + + foreach (ObjectValue *object, bind->_qmlObjectBindings) { + object->setPrototype(0); + object->setScope(0); + } + foreach (ObjectValue *object, bind->_qmlObjectDefinitions) { + object->setPrototype(0); + object->setScope(0); + } + } else if (doc->jsProgram()) { + bind->_rootObjectValue->setScope(0); + } + } +} + +static ObjectValue *pushScope(ObjectValue *next, ObjectValue *onto) +{ + onto->setScope(next); + return next; +} + +ObjectValue *Link::scopeChainAt(Document::Ptr doc, UiObjectMember *currentObject) +{ + BindPtr bind = doc->bind(); + + ObjectValue *scopeObject; + if (UiObjectDefinition *definition = cast<UiObjectDefinition *>(currentObject)) + scopeObject = bind->_qmlObjectDefinitions.value(definition); + else if (UiObjectBinding *binding = cast<UiObjectBinding *>(currentObject)) + scopeObject = bind->_qmlObjectBindings.value(binding); + else + return bind->_interp.globalObject(); + + if (!scopeObject) + return bind->_interp.globalObject(); + + // Build the scope chain. + ObjectValue *scopeStart = _typeEnvironments.value(doc.data()); + ObjectValue *scope = scopeStart; + scope = pushScope(bind->_idEnvironment, scope); + scope = pushScope(bind->_functionEnvironment, scope); + + foreach (const QString &scriptFile, doc->bind()->includedScripts()) { + if (Document::Ptr scriptDoc = _snapshot.document(scriptFile)) { + if (scriptDoc->jsProgram()) { + scope = pushScope(scriptDoc->bind()->_rootObjectValue, scope); + } + } + } + + scope = pushScope(scopeObject, scope); + if (scopeObject != bind->_rootObjectValue) + scope = pushScope(bind->_rootObjectValue, scope); + + scope = pushScope(bind->_interp.globalObject(), scope); + + // May want to link to instantiating components from here. + + return scopeStart; +} + +void Link::linkImports() +{ + foreach (Document::Ptr doc, _docs) { + BindPtr bind = doc->bind(); + + ObjectValue *typeEnv = _interp->newObject(/*prototype =*/0); + _typeEnvironments.insert(doc.data(), typeEnv); + + // Populate the _typeEnvironment with imports. + populateImportedTypes(typeEnv, doc); + + // Set the prototypes. + { + QHash<UiObjectDefinition *, ObjectValue *>::iterator it = bind->_qmlObjectDefinitions.begin(); + QHash<UiObjectDefinition *, ObjectValue *>::iterator end = bind->_qmlObjectDefinitions.end(); + for (; it != end; ++it) { + UiObjectDefinition *key = it.key(); + ObjectValue *value = it.value(); + if (!key->qualifiedTypeNameId) + continue; + + value->setPrototype(lookupType(typeEnv, key->qualifiedTypeNameId)); + } + } + { + QHash<UiObjectBinding *, ObjectValue *>::iterator it = bind->_qmlObjectBindings.begin(); + QHash<UiObjectBinding *, ObjectValue *>::iterator end = bind->_qmlObjectBindings.end(); + for (; it != end; ++it) { + UiObjectBinding *key = it.key(); + ObjectValue *value = it.value(); + if (!key->qualifiedTypeNameId) + continue; + + value->setPrototype(lookupType(typeEnv, key->qualifiedTypeNameId)); + } + } + } +} + static QString componentName(const QString &fileName) { QString componentName = fileName; @@ -22,11 +140,8 @@ static QString componentName(const QString &fileName) return componentName; } -void LinkImports::linkImports(Bind *bind, const QList<Bind *> &binds) +void Link::populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Ptr doc) { - // ### TODO: remove all properties from _typeEnv - - Document::Ptr doc = bind->_doc; if (! (doc->qmlProgram() && doc->qmlProgram()->imports)) return; @@ -35,65 +150,127 @@ void LinkImports::linkImports(Bind *bind, const QList<Bind *> &binds) // implicit imports: // qml files in the same directory are available without explicit imports - foreach (Bind *otherBind, binds) { - if (otherBind == bind) + foreach (Document::Ptr otherDoc, _docs) { + if (otherDoc == doc) continue; - Document::Ptr otherDoc = otherBind->_doc; QFileInfo otherFileInfo(otherDoc->fileName()); const QString otherAbsolutePath = otherFileInfo.absolutePath(); if (otherAbsolutePath != absolutePath) continue; - bind->_typeEnvironment->setProperty(componentName(otherFileInfo.fileName()), - otherBind->_rootObjectValue); + 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 && it->import->fileName)) + if (! it->import) continue; - QString path = absolutePath; - path += QLatin1Char('/'); - path += it->import->fileName->asString(); - path = QDir::cleanPath(path); + if (it->import->fileName) { + importFile(typeEnv, doc, it->import, absolutePath); + } else if (it->import->importUri) { + importNonFile(typeEnv, doc, it->import); + } + } +} - ObjectValue *importNamespace = 0; +/* + import "content" + import "content" as Xxx + import "content" 4.6 + import "content" 4.6 as Xxx - foreach (Bind *otherBind, binds) { - Document::Ptr otherDoc = otherBind->_doc; - QFileInfo otherFileInfo(otherDoc->fileName()); - const QString otherAbsolutePath = otherFileInfo.absolutePath(); + import "http://www.ovi.com/" as Ovi +*/ +void Link::importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc, + AST::UiImport *import, const QString &startPath) +{ + if (!import->fileName) + return; - bool directoryImport = (path == otherAbsolutePath); - bool fileImport = (path == otherDoc->fileName()); - if (!directoryImport && !fileImport) - continue; + QString path = startPath; + path += QLatin1Char('/'); + path += import->fileName->asString(); + path = QDir::cleanPath(path); - if (directoryImport && it->import->importId && !importNamespace) { - importNamespace = bind->_interp->newObject(/*prototype =*/0); - bind->_typeEnvironment->setProperty(it->import->importId->asString(), importNamespace); - } + ObjectValue *importNamespace = 0; - QString targetName; - if (fileImport && it->import->importId) { - targetName = it->import->importId->asString(); - } else { - targetName = componentName(otherFileInfo.fileName()); - } + 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; - ObjectValue *importInto = bind->_typeEnvironment; - if (importNamespace) - importInto = importNamespace; + if (directoryImport && import->importId && !importNamespace) { + importNamespace = _interp->newObject(/*prototype =*/0); + typeEnv->setProperty(import->importId->asString(), importNamespace); + } - importInto->setProperty(targetName, otherBind->_rootObjectValue); + 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); } } -static const ObjectValue *lookupType(ObjectValue *env, UiQualifiedId *id) +/* + 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 = _interp->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, _interp->metaTypeSystem().staticTypesForImport(package, majorVersion, minorVersion)) { + namespaceObject->setProperty(object->qmlTypeName(), object); + } +#endif // NO_DECLARATIVE_BACKEND + } +} + +const ObjectValue *Link::lookupType(ObjectValue *env, UiQualifiedId *id) { const ObjectValue *objectValue = env; @@ -111,65 +288,37 @@ static const ObjectValue *lookupType(ObjectValue *env, UiQualifiedId *id) return objectValue; } -void LinkImports::operator()(const QList<Bind *> &binds) +QList<Document::Ptr> Link::reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot) { - foreach (Bind *bind, binds) { - // Populate the _typeEnvironment with imports. - linkImports(bind, binds); + QList<Document::Ptr> docs; - // Set the prototypes. - { - QHash<UiObjectDefinition *, ObjectValue *>::iterator it = bind->_qmlObjectDefinitions.begin(); - QHash<UiObjectDefinition *, ObjectValue *>::iterator end = bind->_qmlObjectDefinitions.end(); - for (; it != end; ++it) { - UiObjectDefinition *key = it.key(); - ObjectValue *value = it.value(); - if (!key->qualifiedTypeNameId) - continue; + QSet<QString> processed; + QStringList todo; - value->setPrototype(lookupType(bind->_typeEnvironment, key->qualifiedTypeNameId)); - } - } - { - QHash<UiObjectBinding *, ObjectValue *>::iterator it = bind->_qmlObjectBindings.begin(); - QHash<UiObjectBinding *, ObjectValue *>::iterator end = bind->_qmlObjectBindings.end(); - for (; it != end; ++it) { - UiObjectBinding *key = it.key(); - ObjectValue *value = it.value(); - if (!key->qualifiedTypeNameId) - continue; + QMultiHash<QString, Document::Ptr> documentByPath; + foreach (Document::Ptr doc, snapshot) + documentByPath.insert(doc->path(), doc); - value->setPrototype(lookupType(bind->_typeEnvironment, key->qualifiedTypeNameId)); - } - } - } -} + todo.append(startDoc->path()); -ObjectValue *Link::operator()(const QList<Bind *> &binds, Bind *currentBind, UiObjectMember *currentObject) -{ - Q_UNUSED(binds); - ObjectValue *scopeObject; - if (UiObjectDefinition *definition = cast<UiObjectDefinition *>(currentObject)) - scopeObject = currentBind->_qmlObjectDefinitions.value(definition); - else if (UiObjectBinding *binding = cast<UiObjectBinding *>(currentObject)) - scopeObject = currentBind->_qmlObjectBindings.value(binding); - else - return currentBind->_interp->globalObject(); + // Find the reachable documents. + while (! todo.isEmpty()) { + const QString path = todo.takeFirst(); - if (!scopeObject) - return currentBind->_interp->globalObject(); + if (processed.contains(path)) + continue; - // Build the scope chain. - currentBind->_typeEnvironment->setScope(currentBind->_idEnvironment); - currentBind->_idEnvironment->setScope(currentBind->_functionEnvironment); - currentBind->_functionEnvironment->setScope(scopeObject); - if (scopeObject != currentBind->_rootObjectValue) { - scopeObject->setScope(currentBind->_rootObjectValue); - currentBind->_rootObjectValue->setScope(currentBind->_interp->globalObject()); - } else { - scopeObject->setScope(currentBind->_interp->globalObject()); + processed.insert(path); + + QStringList localImports; + foreach (Document::Ptr doc, documentByPath.values(path)) { + docs += doc; + localImports += doc->bind()->localImports(); + } + + localImports.removeDuplicates(); + todo += localImports; } - // May want to link to instantiating components from here. - return currentBind->_typeEnvironment; + return docs; } diff --git a/src/libs/qmljs/qmljslink.h b/src/libs/qmljs/qmljslink.h index 799d1f967f4..dc37c106700 100644 --- a/src/libs/qmljs/qmljslink.h +++ b/src/libs/qmljs/qmljslink.h @@ -1,29 +1,49 @@ #ifndef QMLJSLINK_H #define QMLJSLINK_H +#include <qmljs/qmljsdocument.h> +#include <qmljs/qmljsbind.h> #include <qmljs/qmljsinterpreter.h> #include <qmljs/parser/qmljsastfwd_p.h> #include <qmljs/parser/qmljsengine_p.h> #include <QtCore/QList> +#include <QtCore/QHash> namespace QmlJS { -class Bind; - -class LinkImports +/* + Temporarily links a set of bound documents together to allow resolving cross-document + dependencies. The link is undone when this object is destoyed. +*/ +class QMLJS_EXPORT Link { public: - void operator()(const QList<Bind *> &binds); + // Link all documents in snapshot reachable from doc. + Link(Document::Ptr doc, const Snapshot &snapshot, Interpreter::Engine *interp); + ~Link(); + + // Get the scope chain for the currentObject inside doc. + Interpreter::ObjectValue *scopeChainAt(Document::Ptr doc, AST::UiObjectMember *currentObject); + private: - void importObject(Bind *bind, const QString &name, Interpreter::ObjectValue *object, NameId* targetNamespace); - void linkImports(Bind *bind, const QList<Bind *> &binds); -}; + static QList<Document::Ptr> reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot); + static const Interpreter::ObjectValue *lookupType(Interpreter::ObjectValue *env, AST::UiQualifiedId *id); -class Link -{ -public: - Interpreter::ObjectValue *operator()(const QList<Bind *> &binds, Bind *currentBind, AST::UiObjectMember *currentObject); + void linkImports(); + + void populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Ptr doc); + void importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc, + AST::UiImport *import, const QString &startPath); + void importNonFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc, + AST::UiImport *import); + void importObject(BindPtr bind, const QString &name, Interpreter::ObjectValue *object, NameId* targetNamespace); + +private: + Snapshot _snapshot; + Interpreter::Engine *_interp; + QList<Document::Ptr> _docs; + QHash<Document *, Interpreter::ObjectValue *> _typeEnvironments; }; } // namespace QmlJS diff --git a/src/plugins/qmljseditor/qmlcodecompletion.cpp b/src/plugins/qmljseditor/qmlcodecompletion.cpp index 2d6340de713..043bdd28fa9 100644 --- a/src/plugins/qmljseditor/qmlcodecompletion.cpp +++ b/src/plugins/qmljseditor/qmlcodecompletion.cpp @@ -632,10 +632,11 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor) // Set up the current scope chain. Interpreter::ObjectValue *scope = interp.globalObject(); + Link link(qmlDocument, snapshot, &interp); if (isQmlFile && qmlDocument) { AST::UiObjectMember *declaringMember = semanticInfo.declaringMember(editor->position()); - scope = Bind::scopeChainAt(qmlDocument, snapshot, &interp, declaringMember); + scope = link.scopeChainAt(qmlDocument, declaringMember); } // Search for the operator that triggered the completion. diff --git a/src/plugins/qmljseditor/qmlhoverhandler.cpp b/src/plugins/qmljseditor/qmlhoverhandler.cpp index 71e7012bfc3..fc2dd793d1f 100644 --- a/src/plugins/qmljseditor/qmlhoverhandler.cpp +++ b/src/plugins/qmljseditor/qmlhoverhandler.cpp @@ -37,6 +37,7 @@ #include <debugger/debuggerconstants.h> #include <extensionsystem/pluginmanager.h> #include <qmljs/qmljsbind.h> +#include <qmljs/qmljslink.h> #include <qmljs/qmljscheck.h> #include <qmljs/qmljsinterpreter.h> #include <qmljs/parser/qmljsast_p.h> @@ -171,7 +172,8 @@ void QmlHoverHandler::updateHelpIdAndTooltip(TextEditor::ITextEditor *editor, in AST::UiObjectMember *declaringMember = semanticInfo.declaringMember(pos); Interpreter::Engine interp; - const Interpreter::ObjectValue *scope = Bind::scopeChainAt(qmlDocument, snapshot, &interp, declaringMember); + Link link(qmlDocument, snapshot, &interp); + const Interpreter::ObjectValue *scope = link.scopeChainAt(qmlDocument, declaringMember); Check check(&interp); const Interpreter::Value *value = check(node, scope); -- GitLab