Commit 9ea01cf5 authored by Christian Kamm's avatar Christian Kamm
Browse files

Improve Qml code model import handling.

* Fill the snapshot with files that could be imported.
* Implement package imports.

* The qmldir file is not parsed yet.

Reviewed-by: Erik Verbruggen
parent 62c41def
......@@ -59,11 +59,6 @@ QStringList Bind::includedScripts() const
return _includedScripts;
}
QStringList Bind::localImports() const
{
return _localImports;
}
Interpreter::ObjectValue *Bind::currentObjectValue() const
{
return _currentObjectValue;
......@@ -210,16 +205,8 @@ bool Bind::visit(AST::Program *)
return true;
}
bool Bind::visit(UiImport *ast)
bool Bind::visit(UiImport *)
{
if (ast->fileName) {
QString path = _doc->path();
path += QLatin1Char('/');
path += ast->fileName->asString();
path = QDir::cleanPath(path);
_localImports.append(path);
}
return false;
}
......
......@@ -51,7 +51,6 @@ public:
virtual ~Bind();
QStringList includedScripts() const;
QStringList localImports() const;
Interpreter::ObjectValue *currentObjectValue() const;
Interpreter::ObjectValue *idEnvironment() const;
......@@ -101,7 +100,6 @@ private:
QHash<AST::Node *, Interpreter::ObjectValue *> _qmlObjects;
QStringList _includedScripts;
QStringList _localImports;
};
} // end of namespace Qml
......
......@@ -170,11 +170,11 @@ public:
} // end of anonymous namespace
Check::Check(Document::Ptr doc, const Snapshot &snapshot)
Check::Check(Document::Ptr doc, const Snapshot &snapshot, const QStringList &importPaths)
: _doc(doc)
, _snapshot(snapshot)
, _context(&_engine)
, _link(&_context, doc, snapshot)
, _link(&_context, snapshot, importPaths)
, _scopeBuilder(doc, &_context)
{
}
......
......@@ -41,7 +41,7 @@ namespace QmlJS {
class QMLJS_EXPORT Check: protected AST::Visitor
{
public:
Check(Document::Ptr doc, const Snapshot &snapshot);
Check(Document::Ptr doc, const Snapshot &snapshot, const QStringList &importPaths);
virtual ~Check();
QList<DiagnosticMessage> operator()();
......
......@@ -1300,9 +1300,10 @@ Context::~Context()
{
}
void Context::build(const QList<Node *> &astPath, QmlJS::Document::Ptr doc, const QmlJS::Snapshot &snapshot)
void Context::build(const QList<Node *> &astPath, QmlJS::Document::Ptr doc,
const QmlJS::Snapshot &snapshot, const QStringList &importPaths)
{
Link link(this, doc, snapshot);
Link link(this, snapshot, importPaths);
link.scopeChainAt(doc, astPath);
}
......@@ -1333,12 +1334,12 @@ void Context::setLookupMode(LookupMode lookupMode)
const ObjectValue *Context::typeEnvironment(const QmlJS::Document *doc) const
{
return _typeEnvironments.value(doc, 0);
return _typeEnvironments.value(doc->fileName(), 0);
}
void Context::setTypeEnvironment(const QmlJS::Document *doc, const ObjectValue *typeEnvironment)
{
_typeEnvironments[doc] = typeEnvironment;
_typeEnvironments[doc->fileName()] = typeEnvironment;
}
const Value *Context::lookup(const QString &name)
......@@ -1953,6 +1954,11 @@ QmlObjectValue *MetaTypeSystem::staticTypeForImport(const QString &qualifiedName
return previousCandidate;
}
bool MetaTypeSystem::hasPackage(const QString &package) const
{
return _importedTypes.contains(package);
}
ConvertToNumber::ConvertToNumber(Engine *engine)
: _engine(engine), _result(0)
{
......
......@@ -289,7 +289,8 @@ public:
Context(Engine *engine);
~Context();
void build(const QList<AST::Node *> &astPath, const Document::Ptr doc, const Snapshot &snapshot);
void build(const QList<AST::Node *> &astPath, const Document::Ptr doc,
const Snapshot &snapshot, const QStringList &importPaths);
Engine *engine() const;
const ScopeChain &scopeChain() const;
......@@ -313,7 +314,7 @@ private:
Engine *_engine;
LookupMode _lookupMode;
QHash<const ObjectValue *, Properties> _properties;
QHash<const Document *, const ObjectValue *> _typeEnvironments;
QHash<QString, const ObjectValue *> _typeEnvironments;
ScopeChain _scopeChain;
int _qmlScopeObjectIndex;
bool _qmlScopeObjectSet;
......@@ -526,6 +527,8 @@ public:
QList<Interpreter::QmlObjectValue *> staticTypesForImport(const QString &prefix, int majorVersion, int minorVersion) const;
Interpreter::QmlObjectValue *staticTypeForImport(const QString &qualifiedName) const;
bool hasPackage(const QString &package) const;
private:
QHash<QString, QList<QmlObjectValue *> > _importedTypes;
};
......
......@@ -13,11 +13,14 @@ using namespace QmlJS;
using namespace QmlJS::Interpreter;
using namespace QmlJS::AST;
Link::Link(Context *context, Document::Ptr currentDoc, const Snapshot &snapshot)
Link::Link(Context *context, const Snapshot &snapshot,
const QStringList &importPaths)
: _snapshot(snapshot)
, _context(context)
, _importPaths(importPaths)
{
_docs = reachableDocuments(currentDoc, snapshot);
foreach (Document::Ptr doc, snapshot)
_documentByPath.insert(doc->path(), doc);
linkImports();
}
......@@ -58,7 +61,7 @@ void Link::scopeChainAt(Document::Ptr doc, const QList<Node *> &astPath)
// the global scope of a js file does not see the instantiating component
if (astPath.size() > 0) {
// add scope chains for all components that source this document
foreach (Document::Ptr otherDoc, _docs) {
foreach (Document::Ptr otherDoc, _snapshot) {
if (otherDoc->bind()->includedScripts().contains(doc->fileName())) {
ScopeChain::QmlComponentChain *component = new ScopeChain::QmlComponentChain;
componentScopes.insert(otherDoc.data(), component);
......@@ -93,7 +96,7 @@ void Link::makeComponentChain(
Bind *bind = doc->bind();
// add scopes for all components instantiating this one
foreach (Document::Ptr otherDoc, _docs) {
foreach (Document::Ptr otherDoc, _snapshot) {
if (otherDoc == doc)
continue;
if (otherDoc->bind()->usesQmlPrototype(bind->rootObjectValue(), _context)) {
......@@ -129,7 +132,7 @@ void Link::makeComponentChain(
void Link::linkImports()
{
foreach (Document::Ptr doc, _docs) {
foreach (Document::Ptr doc, _snapshot) {
ObjectValue *typeEnv = engine()->newObject(/*prototype =*/0); // ### FIXME
// Populate the _typeEnvironment with imports.
......@@ -142,6 +145,9 @@ void Link::linkImports()
static QString componentName(const QString &fileName)
{
QString componentName = fileName;
int sepIndex = componentName.lastIndexOf(QDir::separator());
if (sepIndex != -1)
componentName.remove(0, sepIndex + 1);
int dotIndex = componentName.indexOf(QLatin1Char('.'));
if (dotIndex != -1)
componentName.truncate(dotIndex);
......@@ -159,22 +165,13 @@ void Link::populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Pt
if (scriptValue)
typeEnv->setProperty("Script", scriptValue);
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) {
foreach (Document::Ptr otherDoc, _documentByPath.values(doc->path())) {
if (otherDoc == doc)
continue;
QFileInfo otherFileInfo(otherDoc->fileName());
const QString otherAbsolutePath = otherFileInfo.absolutePath();
if (otherAbsolutePath != absolutePath)
continue;
typeEnv->setProperty(componentName(otherFileInfo.fileName()),
typeEnv->setProperty(componentName(otherDoc->fileName()),
otherDoc->bind()->rootObjectValue());
}
......@@ -184,7 +181,7 @@ void Link::populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Pt
continue;
if (it->import->fileName) {
importFile(typeEnv, doc, it->import, absolutePath);
importFile(typeEnv, doc, it->import);
} else if (it->import->importUri) {
importNonFile(typeEnv, doc, it->import);
}
......@@ -200,46 +197,44 @@ void Link::populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Pt
import "http://www.ovi.com/" as Ovi
*/
void Link::importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
AST::UiImport *import, const QString &startPath)
AST::UiImport *import)
{
Q_UNUSED(doc)
if (!import->fileName)
return;
QString path = startPath;
QString path = doc->path();
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;
ObjectValue *importNamespace = typeEnv;
if (directoryImport && import->importId && !importNamespace) {
// directory import
if (_documentByPath.contains(path)) {
if (import->importId) {
importNamespace = engine()->newObject(/*prototype =*/0);
typeEnv->setProperty(import->importId->asString(), importNamespace);
}
foreach (Document::Ptr importedDoc, _documentByPath.values(path)) {
const QString targetName = componentName(importedDoc->fileName());
importNamespace->setProperty(targetName, importedDoc->bind()->rootObjectValue());
}
}
// file import
else if (Document::Ptr importedDoc = _snapshot.document(path)) {
QString targetName;
if (fileImport && import->importId) {
if (import->importId) {
targetName = import->importId->asString();
} else {
targetName = componentName(otherFileInfo.fileName());
targetName = componentName(importedDoc->fileName());
}
ObjectValue *importInto = typeEnv;
if (importNamespace)
importInto = importNamespace;
importInto->setProperty(targetName, otherDoc->bind()->rootObjectValue());
importNamespace->setProperty(targetName, importedDoc->bind()->rootObjectValue());
} else {
// error!
}
}
......@@ -250,6 +245,9 @@ void Link::importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
*/
void Link::importNonFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc, AST::UiImport *import)
{
if (! import->importUri)
return;
ObjectValue *namespaceObject = 0;
if (import->importId) { // with namespace we insert an object in the type env. to hold the imported types
......@@ -260,28 +258,47 @@ void Link::importNonFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc, A
namespaceObject = typeEnv;
}
// try the metaobject system
if (import->importUri) {
const QString package = Bind::toString(import->importUri, '/');
int majorVersion = QmlObjectValue::NoVersion;
int minorVersion = QmlObjectValue::NoVersion;
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();
}
const QString package = Bind::toString(import->importUri, '/');
int majorVersion = QmlObjectValue::NoVersion;
int minorVersion = QmlObjectValue::NoVersion;
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();
}
}
// if the package is in the meta type system, use it
if (engine()->metaTypeSystem().hasPackage(package)) {
foreach (QmlObjectValue *object, engine()->metaTypeSystem().staticTypesForImport(package, majorVersion, minorVersion)) {
namespaceObject->setProperty(object->className(), object);
}
} else {
// check the filesystem
QStringList localImportPaths = _importPaths;
localImportPaths.prepend(doc->path());
foreach (const QString &importPath, localImportPaths) {
QDir dir(importPath);
if (!dir.cd(package))
continue;
if (!dir.exists("qmldir"))
continue;
// ### Should read qmldir file and import accordingly.
foreach (Document::Ptr otherDoc, _documentByPath.values(dir.path())) {
namespaceObject->setProperty(componentName(otherDoc->fileName()), otherDoc->bind()->rootObjectValue());
}
break;
}
}
}
......@@ -294,89 +311,3 @@ UiQualifiedId *Link::qualifiedTypeNameId(Node *node)
else
return 0;
}
QT_BEGIN_NAMESPACE
static uint qHash(Document::Ptr doc)
{
return qHash(doc.data());
}
QT_END_NAMESPACE
QList<Document::Ptr> Link::reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot)
{
QSet<Document::Ptr> docs;
if (! startDoc)
return docs.values();
QMultiHash<QString, Document::Ptr> 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<Document::Ptr> 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<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;
}
}
return docs.values();
}
......@@ -7,6 +7,7 @@
#include <QtCore/QList>
#include <QtCore/QHash>
#include <QtCore/QStringList>
namespace QmlJS {
......@@ -18,8 +19,8 @@ class NameId;
class Link
{
public:
// Link all documents in snapshot reachable from doc.
Link(Interpreter::Context *context, Document::Ptr doc, const Snapshot &snapshot);
// Link all documents in snapshot
Link(Interpreter::Context *context, const Snapshot &snapshot, const QStringList &importPaths);
~Link();
// Get the scope chain for the currentObject inside doc.
......@@ -33,14 +34,15 @@ private:
Interpreter::ScopeChain::QmlComponentChain *target,
QHash<Document *, Interpreter::ScopeChain::QmlComponentChain *> *components);
static QList<Document::Ptr> reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot);
static QList<Document::Ptr> reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot,
const QStringList &importPaths);
static AST::UiQualifiedId *qualifiedTypeNameId(AST::Node *node);
void linkImports();
void populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Ptr doc);
void importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
AST::UiImport *import, const QString &startPath);
AST::UiImport *import);
void importNonFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
AST::UiImport *import);
void importObject(Bind *bind, const QString &name, Interpreter::ObjectValue *object, NameId *targetNamespace);
......@@ -48,7 +50,8 @@ private:
private:
Snapshot _snapshot;
Interpreter::Context *_context;
QList<Document::Ptr> _docs;
QMultiHash<QString, Document::Ptr> _documentByPath;
const QStringList _importPaths;
};
} // namespace QmlJS
......
......@@ -671,7 +671,7 @@ int CodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
// Set up the current scope chain.
QList<AST::Node *> astPath = semanticInfo.astPath(editor->position());
context.build(astPath , document, snapshot);
context.build(astPath , document, snapshot, m_modelManager->importPaths());
// Search for the operator that triggered the completion.
QChar completionOperator;
......
......@@ -643,6 +643,7 @@ QmlJSTextEditor::QmlJSTextEditor(QWidget *parent) :
m_modelManager = ExtensionSystem::PluginManager::instance()->getObject<ModelManagerInterface>();
if (m_modelManager) {
m_semanticHighlighter->setModelManager(m_modelManager);
connect(m_modelManager, SIGNAL(documentUpdated(QmlJS::Document::Ptr)),
this, SLOT(onDocumentUpdated(QmlJS::Document::Ptr)));
}
......@@ -980,7 +981,8 @@ TextEditor::BaseTextEditor::Link QmlJSTextEditor::findLinkAt(const QTextCursor &
Interpreter::Engine interp;
Interpreter::Context context(&interp);
context.build(semanticInfo.astPath(cursorPosition), semanticInfo.document, semanticInfo.snapshot);
context.build(semanticInfo.astPath(cursorPosition), semanticInfo.document,
semanticInfo.snapshot, m_modelManager->importPaths());
Evaluate check(&context);
const Interpreter::Value *value = check.reference(node);
......@@ -1266,7 +1268,8 @@ SemanticHighlighter::Source QmlJSTextEditor::currentSource(bool force)
SemanticHighlighter::SemanticHighlighter(QObject *parent)
: QThread(parent),
m_done(false)
m_done(false),
m_modelManager(0)
{
}
......@@ -1357,8 +1360,16 @@ SemanticInfo SemanticHighlighter::semanticInfo(const Source &source)
semanticInfo.snapshot = snapshot;
semanticInfo.document = doc;
Check checker(doc, snapshot);
QStringList importPaths;
if (m_modelManager)
importPaths = m_modelManager->importPaths();
Check checker(doc, snapshot, importPaths);
semanticInfo.semanticMessages = checker();
return semanticInfo;
}
void SemanticHighlighter::setModelManager(ModelManagerInterface *modelManager)
{
m_modelManager = modelManager;
}
......@@ -176,6 +176,7 @@ public:
};
void rehighlight(const Source &source);
void setModelManager(ModelManagerInterface *modelManager);
Q_SIGNALS:
void changed(const QmlJSEditor::Internal::SemanticInfo &semanticInfo);
......@@ -193,6 +194,7 @@ private:
bool m_done;
Source m_source;
SemanticInfo m_lastSemanticInfo;
ModelManagerInterface *m_modelManager;
};
class QmlJSTextEditor : public TextEditor::BaseTextEditor
......
......@@ -172,7 +172,7 @@ void HoverHandler::updateHelpIdAndTooltip(TextEditor::ITextEditor *editor, int p
Interpreter::Engine interp;
Interpreter::Context context(&interp);
context.build(astPath, qmlDocument, snapshot);
context.build(astPath, qmlDocument, snapshot, m_modelManager->importPaths());
Evaluate check(&context);
const Interpreter::Value *value = check(node);
......
......@@ -39,8 +39,10 @@
#include <texteditor/itexteditor.h>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QLibraryInfo>
#include <QtConcurrentRun>
#include <qtconcurrent/runextensions.h>
#include <QTextStream>
......@@ -51,6 +53,8 @@ using namespace QmlJS;
using namespace QmlJSEditor;
using namespace QmlJSEditor::Internal;
static QStringList environmentImportPaths();
ModelManager::ModelManager(QObject *parent):
ModelManagerInterface(parent),
m_core(Core::ICore::instance())
......@@ -63,6 +67,10 @@ ModelManager::ModelManager(QObject *parent):
this, SLOT(onDocumentUpdated(QmlJS::Document::Ptr)));
loadQmlTypeDescriptions();
m_defaultImportPaths << environmentImportPaths