From 1d39377fb1235b90c54dedc66f0f41ace0968ab1 Mon Sep 17 00:00:00 2001
From: Christian Kamm <christian.d.kamm@nokia.com>
Date: Thu, 28 Jan 2010 14:53:53 +0100
Subject: [PATCH] Separate bind into bind, link imports and building the scope
 chain.

---
 src/libs/qmljs/qmljs-lib.pri                  |   6 +-
 src/libs/qmljs/qmljsbind.cpp                  | 104 +++--------
 src/libs/qmljs/qmljsbind.h                    |  24 ++-
 src/libs/qmljs/qmljslink.cpp                  | 174 ++++++++++++++++++
 src/libs/qmljs/qmljslink.h                    |  31 ++++
 src/plugins/qmljseditor/qmlcodecompletion.cpp |  16 +-
 src/plugins/qmljseditor/qmlhoverhandler.cpp   |   3 +-
 7 files changed, 261 insertions(+), 97 deletions(-)
 create mode 100644 src/libs/qmljs/qmljslink.cpp
 create mode 100644 src/libs/qmljs/qmljslink.h

diff --git a/src/libs/qmljs/qmljs-lib.pri b/src/libs/qmljs/qmljs-lib.pri
index 37be93dd0b9..bbc6e14e9c4 100644
--- a/src/libs/qmljs/qmljs-lib.pri
+++ b/src/libs/qmljs/qmljs-lib.pri
@@ -17,7 +17,8 @@ HEADERS += \
     $$PWD/qmljscheck.h \
     $$PWD/qmljsdocument.h \
     $$PWD/qmljsscanner.h \
-    $$PWD/qmljsinterpreter.h
+    $$PWD/qmljsinterpreter.h \
+    $$PWD/qmljslink.h
 
 SOURCES += \
     $$PWD/qmljsbind.cpp \
@@ -25,7 +26,8 @@ SOURCES += \
     $$PWD/qmljsdocument.cpp \
     $$PWD/qmljsscanner.cpp \
     $$PWD/qmljsinterpreter.cpp \
-    $$PWD/qmljsmetatypesystem.cpp
+    $$PWD/qmljsmetatypesystem.cpp \
+    $$PWD/qmljslink.cpp
 
 contains(QT_CONFIG, declarative) {
     QT += declarative
diff --git a/src/libs/qmljs/qmljsbind.cpp b/src/libs/qmljs/qmljsbind.cpp
index 9d5b523bdf0..e82e3b044be 100644
--- a/src/libs/qmljs/qmljsbind.cpp
+++ b/src/libs/qmljs/qmljsbind.cpp
@@ -29,6 +29,7 @@
 
 #include "parser/qmljsast_p.h"
 #include "qmljsbind.h"
+#include "qmljslink.h"
 #include "qmljsmetatypesystem.h"
 #include <QtCore/QDebug>
 
@@ -36,53 +37,60 @@ using namespace QmlJS;
 using namespace QmlJS::AST;
 using namespace QmlJS::Interpreter;
 
-Bind::Bind(Document::Ptr doc, const Snapshot &snapshot, Interpreter::Engine *interp)
+Bind::Bind(Document::Ptr doc, Interpreter::Engine *interp)
     : _doc(doc),
-      _snapshot(snapshot),
       _interp(interp),
-      _interestingMember(0),
       _currentObjectValue(0),
       _typeEnvironment(0),
       _idEnvironment(0),
       _functionEnvironment(0),
-      _interestingObjectValue(0),
       _rootObjectValue(0)
 {
+    (*this)();
 }
 
 Bind::~Bind()
 {
 }
 
-Interpreter::ObjectValue *Bind::operator()(UiObjectMember *member)
+void Bind::operator()()
 {
     UiProgram *program = _doc->qmlProgram();
     if (!program)
-        return 0;
-
-    _interestingMember = member;
+        return;
 
     _currentObjectValue = 0;
     _typeEnvironment = _interp->newObject(/*prototype =*/ 0);
     _idEnvironment = _interp->newObject(/*prototype =*/ 0);
     _functionEnvironment = _interp->newObject(/*prototype =*/ 0);
-    _interestingObjectValue = 0;
     _rootObjectValue = 0;
 
+    _qmlObjectDefinitions.clear();
+    _qmlObjectBindings.clear();
+
     accept(program);
+}
 
-    if (_interestingObjectValue) {
-        _functionEnvironment->setScope(_interestingObjectValue);
+ObjectValue *Bind::scopeChainAt(Document::Ptr currentDocument, const Snapshot &snapshot,
+                                Interpreter::Engine *interp, AST::UiObjectMember *currentObject)
+{
+    Bind *currentBind = 0;
+    QList<Bind *> binds;
 
-        if (_interestingObjectValue != _rootObjectValue)
-            _interestingObjectValue->setScope(_rootObjectValue);
-    } else {
-        _functionEnvironment->setScope(_rootObjectValue);
+    Snapshot::const_iterator end = snapshot.end();
+    for (Snapshot::const_iterator iter = snapshot.begin(); iter != end; ++iter) {
+        Document::Ptr doc = *iter;
+        Bind *newBind = new Bind(doc, interp);
+        binds += newBind;
+        if (doc == currentDocument)
+            currentBind = newBind;
     }
-    _idEnvironment->setScope(_functionEnvironment);
-    _typeEnvironment->setScope(_idEnvironment);
 
-    return _typeEnvironment;
+    LinkImports()(binds);
+    ObjectValue *scope = Link()(binds, currentBind, currentObject);
+    qDeleteAll(binds);
+
+    return scope;
 }
 
 void Bind::accept(Node *node)
@@ -127,6 +135,7 @@ static QString serialize(UiQualifiedId *qualifiedId, QChar delimiter)
 
   import "http://www.ovi.com/" as Ovi
  */
+// ### TODO: Move to LinkImports
 bool Bind::visit(UiImport *ast)
 {
     if (! (ast->importUri || ast->fileName))
@@ -171,38 +180,6 @@ bool Bind::visit(UiImport *ast)
             namespaceObject->setProperty(object->qmlTypeName(), object);
         }
 #endif // NO_DECLARATIVE_BACKEND
-    } else if (ast->fileName) {
-        // got an import "contents"
-        const QString relativePath = ast->fileName->asString();
-        const QList<Document::Ptr> userComponents = _snapshot.importedDocuments(_doc, relativePath);
-        foreach (Document::Ptr userComponent, userComponents) {
-            if (UiProgram *program = userComponent->qmlProgram()) {
-                if (UiObjectMemberList *members = program->members) {
-                    if (UiObjectDefinition *def = cast<UiObjectDefinition *>(members->member)) {
-                        const ObjectValue *prototype = lookupType(def->qualifiedTypeNameId);
-                        ObjectValue *objectValue = _interp->newObject(prototype);
-                        if (def->initializer) {
-                            for (AST::UiObjectMemberList *it = def->initializer->members; it; it = it->next) {
-                                if (AST::UiPublicMember *prop = AST::cast<AST::UiPublicMember *>(it->member)) {
-                                    if (prop->name && prop->memberType) {
-                                        const QString propName = prop->name->asString();
-                                        const QString propType = prop->memberType->asString();
-                                        objectValue->setProperty(propName, _interp->defaultValueForBuiltinType(propType));
-                                    }
-                                }
-                            }
-                        }
-
-                        const QString componentName = userComponent->componentName();
-
-                        if (! componentName.isEmpty()) {
-                            objectValue->setClassName(componentName);
-                            namespaceObject->setProperty(componentName, objectValue);
-                        }
-                    }
-                }
-            }
-        }
     }
 
     return false;
@@ -225,24 +202,6 @@ bool Bind::visit(UiSourceElement *)
     return true;
 }
 
-const ObjectValue *Bind::lookupType(UiQualifiedId *qualifiedTypeNameId)
-{
-    const ObjectValue *objectValue = _typeEnvironment;
-
-    for (UiQualifiedId *iter = qualifiedTypeNameId; 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;
-}
-
 ObjectValue *Bind::bindObject(UiQualifiedId *qualifiedTypeNameId, UiObjectInitializer *initializer)
 {
     ObjectValue *parentObjectValue;
@@ -253,8 +212,7 @@ ObjectValue *Bind::bindObject(UiQualifiedId *qualifiedTypeNameId, UiObjectInitia
         // Script blocks all contribute to the same scope
         parentObjectValue = switchObjectValue(_functionEnvironment);
     } else { // normal component instance
-        const ObjectValue *prototype = lookupType(qualifiedTypeNameId);
-        ObjectValue *objectValue = _interp->newObject(prototype);
+        ObjectValue *objectValue = _interp->newObject(/*prototype =*/0);
         parentObjectValue = switchObjectValue(objectValue);
         if (parentObjectValue)
             objectValue->setProperty("parent", parentObjectValue);
@@ -270,9 +228,8 @@ ObjectValue *Bind::bindObject(UiQualifiedId *qualifiedTypeNameId, UiObjectInitia
 bool Bind::visit(UiObjectDefinition *ast)
 {
     ObjectValue *value = bindObject(ast->qualifiedTypeNameId, ast->initializer);
+    _qmlObjectDefinitions.insert(ast, value);
 
-    if (_interestingMember == ast)
-        _interestingObjectValue = value;
     return false;
 }
 
@@ -280,11 +237,10 @@ bool Bind::visit(UiObjectBinding *ast)
 {
 //    const QString name = serialize(ast->qualifiedId);
     ObjectValue *value = bindObject(ast->qualifiedTypeNameId, ast->initializer);
+    _qmlObjectBindings.insert(ast, value);
     // ### FIXME: we don't handle dot-properties correctly (i.e. font.size)
 //    _currentObjectValue->setProperty(name, value);
 
-    if (_interestingMember == ast)
-        _interestingObjectValue = value;
     return false;
 }
 
diff --git a/src/libs/qmljs/qmljsbind.h b/src/libs/qmljs/qmljsbind.h
index 258e02d4622..b128dc0d6e0 100644
--- a/src/libs/qmljs/qmljsbind.h
+++ b/src/libs/qmljs/qmljsbind.h
@@ -34,15 +34,26 @@
 #include <qmljs/qmljsdocument.h>
 #include <qmljs/qmljsinterpreter.h>
 
+#include <QtCore/QHash>
+
 namespace QmlJS {
 
+class LinkImports;
+class Link;
+
 class QMLJS_EXPORT Bind: protected AST::Visitor
 {
 public:
-    Bind(Document::Ptr doc, const Snapshot &snapshot, Interpreter::Engine *interp);
+    Bind(Document::Ptr doc, Interpreter::Engine *interp);
     virtual ~Bind();
 
-    Interpreter::ObjectValue* operator()(AST::UiObjectMember *member);
+    void operator()();
+
+    // ### 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:
     void accept(AST::Node *node);
@@ -148,17 +159,20 @@ protected:
 
 private:
     Document::Ptr _doc;
-    Snapshot _snapshot;
     Interpreter::Engine *_interp;
-    AST::UiObjectMember *_interestingMember;
 
     Interpreter::ObjectValue *_currentObjectValue;
 
     Interpreter::ObjectValue *_typeEnvironment;
     Interpreter::ObjectValue *_idEnvironment;
     Interpreter::ObjectValue *_functionEnvironment;
-    Interpreter::ObjectValue *_interestingObjectValue;
     Interpreter::ObjectValue *_rootObjectValue;
+
+    QHash<AST::UiObjectDefinition *, Interpreter::ObjectValue *> _qmlObjectDefinitions;
+    QHash<AST::UiObjectBinding *, Interpreter::ObjectValue *> _qmlObjectBindings;
+
+    friend class LinkImports;
+    friend class Link;
 };
 
 } // end of namespace Qml
diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp
new file mode 100644
index 00000000000..52f90e3e744
--- /dev/null
+++ b/src/libs/qmljs/qmljslink.cpp
@@ -0,0 +1,174 @@
+#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;
+
+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 LinkImports::linkImports(Bind *bind, const QList<Bind *> &binds)
+{
+    // ### TODO: remove all properties from _typeEnv
+
+    Document::Ptr doc = bind->_doc;
+    if (! (doc->qmlProgram() && doc->qmlProgram()->imports))
+        return;
+
+    QFileInfo fileInfo(doc->fileName());
+    const QString absolutePath = fileInfo.absolutePath();
+
+    // implicit imports
+    foreach (Bind *otherBind, binds) {
+        if (otherBind == bind)
+            continue;
+
+        Document::Ptr otherDoc = otherBind->_doc;
+        QFileInfo otherFileInfo(otherDoc->fileName());
+        const QString otherAbsolutePath = otherFileInfo.absolutePath();
+
+        if (otherAbsolutePath.size() < absolutePath.size()
+            || otherAbsolutePath.left(absolutePath.size()) != absolutePath)
+            continue;
+
+        // ### TODO: implicit directory access not implemented
+        if (otherAbsolutePath != absolutePath)
+            continue;
+
+        bind->_typeEnvironment->setProperty(componentName(otherFileInfo.fileName()), otherBind->_rootObjectValue);
+    }
+
+    // explicit imports, whether directories or files
+    for (UiImportList *it = doc->qmlProgram()->imports; it; it = it->next) {
+        if (! (it->import && it->import->fileName))
+            continue;
+
+        QString path = absolutePath;
+        path += QLatin1Char('/');
+        path += it->import->fileName->asString();
+        path = QDir::cleanPath(path);
+
+        foreach (Bind *otherBind, binds) {
+            Document::Ptr otherDoc = otherBind->_doc;
+            QFileInfo otherFileInfo(otherDoc->fileName());
+            const QString otherAbsolutePath = otherFileInfo.absolutePath();
+
+            if (path != otherDoc->fileName() && path != otherAbsolutePath)
+                continue;
+
+            bool directoryImport = (path == otherAbsolutePath);
+            bool fileImport = (path == otherDoc->fileName());
+            if (!directoryImport && !fileImport)
+                continue;
+
+            ObjectValue *importInto = bind->_typeEnvironment;
+            if (directoryImport && it->import->importId) {
+                // ### TODO: set importInto to a namespace object value
+            }
+
+            QString targetName;
+            if (fileImport && it->import->importId) {
+                targetName = it->import->importId->asString();
+            } else {
+                targetName = componentName(otherFileInfo.fileName());
+            }
+
+            importInto->setProperty(targetName, otherBind->_rootObjectValue);
+        }
+    }
+}
+
+static const ObjectValue *lookupType(ObjectValue *env, UiQualifiedId *id)
+{
+    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;
+}
+
+void LinkImports::operator()(const QList<Bind *> &binds)
+{
+    foreach (Bind *bind, binds) {
+        // Populate the _typeEnvironment with imports.
+        linkImports(bind, binds);
+
+        // 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(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;
+
+                value->setPrototype(lookupType(bind->_typeEnvironment, key->qualifiedTypeNameId));
+            }
+        }
+    }
+}
+
+ObjectValue *Link::operator()(const QList<Bind *> &binds, Bind *currentBind, UiObjectMember *currentObject)
+{
+    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 0;
+
+    if (!scopeObject)
+        return 0;
+
+    // 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());
+    }
+    // May want to link to instantiating components from here.
+
+    return currentBind->_typeEnvironment;
+}
diff --git a/src/libs/qmljs/qmljslink.h b/src/libs/qmljs/qmljslink.h
new file mode 100644
index 00000000000..799d1f967f4
--- /dev/null
+++ b/src/libs/qmljs/qmljslink.h
@@ -0,0 +1,31 @@
+#ifndef QMLJSLINK_H
+#define QMLJSLINK_H
+
+#include <qmljs/qmljsinterpreter.h>
+#include <qmljs/parser/qmljsastfwd_p.h>
+#include <qmljs/parser/qmljsengine_p.h>
+
+#include <QtCore/QList>
+
+namespace QmlJS {
+
+class Bind;
+
+class LinkImports
+{
+public:
+    void operator()(const QList<Bind *> &binds);
+private:
+    void importObject(Bind *bind, const QString &name, Interpreter::ObjectValue *object, NameId* targetNamespace);
+    void linkImports(Bind *bind, const QList<Bind *> &binds);
+};
+
+class Link
+{
+public:
+    Interpreter::ObjectValue *operator()(const QList<Bind *> &binds, Bind *currentBind, AST::UiObjectMember *currentObject);
+};
+
+} // namespace QmlJS
+
+#endif // QMLJSLINK_H
diff --git a/src/plugins/qmljseditor/qmlcodecompletion.cpp b/src/plugins/qmljseditor/qmlcodecompletion.cpp
index e115fcd1459..73e88bae9f7 100644
--- a/src/plugins/qmljseditor/qmlcodecompletion.cpp
+++ b/src/plugins/qmljseditor/qmlcodecompletion.cpp
@@ -34,6 +34,7 @@
 
 #include <qmljs/parser/qmljsast_p.h>
 #include <qmljs/qmljsbind.h>
+#include <qmljs/qmljslink.h>
 #include <qmljs/qmljsinterpreter.h>
 #include <qmljs/qmljsscanner.h>
 #include <qmljs/qmljscheck.h>
@@ -589,8 +590,6 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
     Interpreter::ObjectValue *scope = interp.globalObject();
 
     if (isQmlFile) {
-        scope = interp.newObject(/* prototype = */ 0);
-
         AST::UiObjectMember *declaringMember = 0;
 
         const int cursorPosition = editor->position();
@@ -600,8 +599,7 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
             }
         }
 
-        Bind bind(qmlDocument, snapshot, &interp);
-        scope = bind(declaringMember);
+        scope = Bind::scopeChainAt(qmlDocument, snapshot, &interp, declaringMember);
     }
 
     // Search for the operator that triggered the completion.
@@ -612,16 +610,6 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
     if (completionOperator.isSpace() || completionOperator.isNull() || isDelimiter(completionOperator) ||
             (completionOperator == QLatin1Char('(') && m_startPosition != editor->position())) {
         // It's a global completion.
-        // Process the visible user defined components.
-        QHashIterator<QString, Document::Ptr> componentIt(userComponents);
-        while (componentIt.hasNext()) {
-            componentIt.next();
-            TextEditor::CompletionItem item(this);
-            item.text = componentIt.key();
-            item.icon = componentIcon;
-            m_completions.append(item);
-        }
-
         EnumerateProperties enumerateProperties;
         enumerateProperties.setGlobalCompletion(true);
         QHashIterator<QString, const Interpreter::Value *> it(enumerateProperties(scope, /* lookAtScope = */ true));
diff --git a/src/plugins/qmljseditor/qmlhoverhandler.cpp b/src/plugins/qmljseditor/qmlhoverhandler.cpp
index eb874a04399..1ac1094bef7 100644
--- a/src/plugins/qmljseditor/qmlhoverhandler.cpp
+++ b/src/plugins/qmljseditor/qmlhoverhandler.cpp
@@ -190,8 +190,7 @@ void QmlHoverHandler::updateHelpIdAndTooltip(TextEditor::ITextEditor *editor, in
         }
 
         Interpreter::Engine interp;
-        Bind bind(qmlDocument, snapshot, &interp);
-        Interpreter::ObjectValue *scope = bind(declaringMember);
+        Interpreter::ObjectValue *scope = Bind::scopeChainAt(qmlDocument, snapshot, &interp, declaringMember);
         Check check(&interp);
         const Interpreter::Value *value = check(expression, scope);
         QStringList baseClasses;
-- 
GitLab