2010-01-28 14:53:53 +01:00
|
|
|
#include "qmljslink.h"
|
|
|
|
|
|
|
|
#include "parser/qmljsast_p.h"
|
|
|
|
#include "qmljsdocument.h"
|
|
|
|
#include "qmljsbind.h"
|
2010-02-19 15:55:11 +01:00
|
|
|
#include "qmljsscopebuilder.h"
|
2010-01-28 14:53:53 +01:00
|
|
|
|
|
|
|
#include <QtCore/QFileInfo>
|
|
|
|
#include <QtCore/QDir>
|
|
|
|
#include <QtCore/QDebug>
|
2010-03-25 14:47:28 +01:00
|
|
|
#include <QtCore/QCoreApplication>
|
2010-01-28 14:53:53 +01:00
|
|
|
|
|
|
|
using namespace QmlJS;
|
|
|
|
using namespace QmlJS::Interpreter;
|
|
|
|
using namespace QmlJS::AST;
|
|
|
|
|
2010-03-29 11:32:11 +02:00
|
|
|
Link::Link(Context *context, const Document::Ptr &doc, const Snapshot &snapshot,
|
2010-03-16 16:34:33 +01:00
|
|
|
const QStringList &importPaths)
|
2010-03-29 11:32:11 +02:00
|
|
|
: _doc(doc)
|
|
|
|
, _snapshot(snapshot)
|
2010-02-04 10:19:37 +01:00
|
|
|
, _context(context)
|
2010-03-16 16:34:33 +01:00
|
|
|
, _importPaths(importPaths)
|
2010-02-02 15:55:17 +01:00
|
|
|
{
|
2010-03-16 16:34:33 +01:00
|
|
|
foreach (Document::Ptr doc, snapshot)
|
|
|
|
_documentByPath.insert(doc->path(), doc);
|
2010-02-02 15:55:17 +01:00
|
|
|
linkImports();
|
|
|
|
}
|
|
|
|
|
|
|
|
Link::~Link()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2010-02-03 10:59:52 +01:00
|
|
|
Interpreter::Engine *Link::engine()
|
2010-02-02 15:55:17 +01:00
|
|
|
{
|
2010-02-04 10:19:37 +01:00
|
|
|
return _context->engine();
|
2010-02-03 10:59:52 +01:00
|
|
|
}
|
2010-02-02 15:55:17 +01:00
|
|
|
|
2010-03-25 14:47:28 +01:00
|
|
|
QList<DiagnosticMessage> Link::diagnosticMessages() const
|
|
|
|
{
|
|
|
|
return _diagnosticMessages;
|
|
|
|
}
|
|
|
|
|
2010-02-19 12:25:26 +01:00
|
|
|
void Link::scopeChainAt(Document::Ptr doc, const QList<Node *> &astPath)
|
2010-02-03 10:59:52 +01:00
|
|
|
{
|
2010-02-19 10:14:34 +01:00
|
|
|
ScopeChain &scopeChain = _context->scopeChain();
|
|
|
|
|
2010-02-11 18:58:17 +01:00
|
|
|
// ### TODO: This object ought to contain the global namespace additions by QML.
|
2010-02-19 10:14:34 +01:00
|
|
|
scopeChain.globalScope = engine()->globalObject();
|
2010-02-02 15:55:17 +01:00
|
|
|
|
2010-02-19 12:25:26 +01:00
|
|
|
if (! doc) {
|
|
|
|
scopeChain.update();
|
2010-02-03 10:59:52 +01:00
|
|
|
return;
|
2010-02-19 12:25:26 +01:00
|
|
|
}
|
2010-02-03 14:31:03 +01:00
|
|
|
|
2010-02-03 15:59:15 +01:00
|
|
|
Bind *bind = doc->bind();
|
2010-02-19 10:14:34 +01:00
|
|
|
QHash<Document *, ScopeChain::QmlComponentChain *> componentScopes;
|
2010-02-02 15:55:17 +01:00
|
|
|
|
2010-02-11 18:58:17 +01:00
|
|
|
if (doc->qmlProgram()) {
|
|
|
|
_context->setLookupMode(Context::QmlLookup);
|
|
|
|
|
2010-02-22 11:21:03 +01:00
|
|
|
scopeChain.qmlComponentScope.clear();
|
2010-02-25 12:56:59 +01:00
|
|
|
componentScopes.insert(doc.data(), &scopeChain.qmlComponentScope);
|
2010-02-19 10:14:34 +01:00
|
|
|
makeComponentChain(doc, &scopeChain.qmlComponentScope, &componentScopes);
|
|
|
|
|
2010-02-11 18:58:17 +01:00
|
|
|
if (const ObjectValue *typeEnvironment = _context->typeEnvironment(doc.data()))
|
2010-02-19 10:14:34 +01:00
|
|
|
scopeChain.qmlTypes = typeEnvironment;
|
2010-02-11 18:58:17 +01:00
|
|
|
} else {
|
2010-02-12 10:05:13 +01:00
|
|
|
// the global scope of a js file does not see the instantiating component
|
2010-02-19 15:55:11 +01:00
|
|
|
if (astPath.size() > 0) {
|
2010-02-12 10:05:13 +01:00
|
|
|
// add scope chains for all components that source this document
|
2010-03-16 16:34:33 +01:00
|
|
|
foreach (Document::Ptr otherDoc, _snapshot) {
|
2010-02-19 10:14:34 +01:00
|
|
|
if (otherDoc->bind()->includedScripts().contains(doc->fileName())) {
|
|
|
|
ScopeChain::QmlComponentChain *component = new ScopeChain::QmlComponentChain;
|
|
|
|
componentScopes.insert(otherDoc.data(), component);
|
|
|
|
scopeChain.qmlComponentScope.instantiatingComponents += component;
|
|
|
|
makeComponentChain(otherDoc, component, &componentScopes);
|
|
|
|
}
|
2010-02-12 10:05:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// ### TODO: Which type environment do scripts see?
|
2010-02-11 18:58:17 +01:00
|
|
|
}
|
2010-02-02 15:55:17 +01:00
|
|
|
|
2010-02-19 10:14:34 +01:00
|
|
|
scopeChain.jsScopes += bind->rootObjectValue();
|
2010-02-11 18:58:17 +01:00
|
|
|
}
|
2010-02-03 10:59:52 +01:00
|
|
|
|
2010-03-10 14:50:56 +01:00
|
|
|
if (astPath.isEmpty()) {
|
|
|
|
scopeChain.update();
|
|
|
|
} else {
|
|
|
|
ScopeBuilder scopeBuilder(doc, _context);
|
|
|
|
foreach (Node *node, astPath)
|
|
|
|
scopeBuilder.push(node);
|
|
|
|
}
|
2010-02-11 18:58:17 +01:00
|
|
|
}
|
|
|
|
|
2010-02-19 10:14:34 +01:00
|
|
|
void Link::makeComponentChain(
|
|
|
|
Document::Ptr doc,
|
|
|
|
ScopeChain::QmlComponentChain *target,
|
|
|
|
QHash<Document *, ScopeChain::QmlComponentChain *> *components)
|
2010-02-11 18:58:17 +01:00
|
|
|
{
|
|
|
|
if (!doc->qmlProgram())
|
|
|
|
return;
|
2010-02-08 21:37:59 +01:00
|
|
|
|
2010-02-11 18:58:17 +01:00
|
|
|
Bind *bind = doc->bind();
|
|
|
|
|
|
|
|
// add scopes for all components instantiating this one
|
2010-03-16 16:34:33 +01:00
|
|
|
foreach (Document::Ptr otherDoc, _snapshot) {
|
2010-02-19 10:14:34 +01:00
|
|
|
if (otherDoc == doc)
|
2010-02-11 18:58:17 +01:00
|
|
|
continue;
|
|
|
|
if (otherDoc->bind()->usesQmlPrototype(bind->rootObjectValue(), _context)) {
|
2010-02-19 10:14:34 +01:00
|
|
|
if (components->contains(otherDoc.data())) {
|
2010-02-25 12:56:59 +01:00
|
|
|
// target->instantiatingComponents += components->value(otherDoc.data());
|
2010-02-19 10:14:34 +01:00
|
|
|
} else {
|
|
|
|
ScopeChain::QmlComponentChain *component = new ScopeChain::QmlComponentChain;
|
|
|
|
components->insert(otherDoc.data(), component);
|
|
|
|
target->instantiatingComponents += component;
|
|
|
|
|
|
|
|
makeComponentChain(otherDoc, component, components);
|
|
|
|
}
|
2010-02-11 18:58:17 +01:00
|
|
|
}
|
2010-02-08 21:37:59 +01:00
|
|
|
}
|
2010-02-02 15:55:17 +01:00
|
|
|
|
2010-02-19 10:14:34 +01:00
|
|
|
// build this component scope
|
2010-02-11 18:58:17 +01:00
|
|
|
if (bind->rootObjectValue())
|
2010-02-19 10:14:34 +01:00
|
|
|
target->rootObject = bind->rootObjectValue();
|
2010-02-11 18:58:17 +01:00
|
|
|
|
2010-02-03 14:31:03 +01:00
|
|
|
const QStringList &includedScripts = bind->includedScripts();
|
|
|
|
for (int index = includedScripts.size() - 1; index != -1; --index) {
|
|
|
|
const QString &scriptFile = includedScripts.at(index);
|
2010-02-03 10:59:52 +01:00
|
|
|
|
2010-02-03 14:31:03 +01:00
|
|
|
if (Document::Ptr scriptDoc = _snapshot.document(scriptFile)) {
|
2010-02-19 10:14:34 +01:00
|
|
|
if (scriptDoc->jsProgram())
|
|
|
|
target->functionScopes += scriptDoc->bind()->rootObjectValue();
|
2010-02-03 10:59:52 +01:00
|
|
|
}
|
|
|
|
}
|
2010-02-02 15:55:17 +01:00
|
|
|
|
2010-02-19 10:14:34 +01:00
|
|
|
target->functionScopes += bind->functionEnvironment();
|
|
|
|
target->ids = bind->idEnvironment();
|
2010-02-02 15:55:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Link::linkImports()
|
|
|
|
{
|
2010-03-16 16:34:33 +01:00
|
|
|
foreach (Document::Ptr doc, _snapshot) {
|
2010-02-03 14:31:03 +01:00
|
|
|
ObjectValue *typeEnv = engine()->newObject(/*prototype =*/0); // ### FIXME
|
2010-02-02 15:55:17 +01:00
|
|
|
|
|
|
|
// Populate the _typeEnvironment with imports.
|
|
|
|
populateImportedTypes(typeEnv, doc);
|
2010-02-04 09:44:43 +01:00
|
|
|
|
2010-02-04 10:19:37 +01:00
|
|
|
_context->setTypeEnvironment(doc.data(), typeEnv);
|
2010-02-02 15:55:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Link::populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Ptr doc)
|
2010-01-28 14:53:53 +01:00
|
|
|
{
|
|
|
|
if (! (doc->qmlProgram() && doc->qmlProgram()->imports))
|
|
|
|
return;
|
|
|
|
|
2010-03-03 11:36:42 +01:00
|
|
|
// Add the implicitly available Script type
|
|
|
|
const ObjectValue *scriptValue = engine()->metaTypeSystem().staticTypeForImport("Script");
|
|
|
|
if (scriptValue)
|
|
|
|
typeEnv->setProperty("Script", scriptValue);
|
|
|
|
|
2010-01-29 13:21:50 +01:00
|
|
|
// implicit imports:
|
|
|
|
// qml files in the same directory are available without explicit imports
|
2010-03-16 16:34:33 +01:00
|
|
|
foreach (Document::Ptr otherDoc, _documentByPath.values(doc->path())) {
|
2010-02-02 15:55:17 +01:00
|
|
|
if (otherDoc == doc)
|
2010-01-28 14:53:53 +01:00
|
|
|
continue;
|
|
|
|
|
2010-03-31 15:49:19 +02:00
|
|
|
typeEnv->setProperty(otherDoc->componentName(),
|
2010-02-10 17:05:45 +01:00
|
|
|
otherDoc->bind()->rootObjectValue());
|
2010-01-28 14:53:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// explicit imports, whether directories or files
|
|
|
|
for (UiImportList *it = doc->qmlProgram()->imports; it; it = it->next) {
|
2010-02-02 15:55:17 +01:00
|
|
|
if (! it->import)
|
2010-01-28 14:53:53 +01:00
|
|
|
continue;
|
|
|
|
|
2010-02-02 15:55:17 +01:00
|
|
|
if (it->import->fileName) {
|
2010-03-16 16:34:33 +01:00
|
|
|
importFile(typeEnv, doc, it->import);
|
2010-02-02 15:55:17 +01:00
|
|
|
} else if (it->import->importUri) {
|
|
|
|
importNonFile(typeEnv, doc, it->import);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-01-28 14:53:53 +01:00
|
|
|
|
2010-02-02 15:55:17 +01:00
|
|
|
/*
|
|
|
|
import "content"
|
|
|
|
import "content" as Xxx
|
|
|
|
import "content" 4.6
|
|
|
|
import "content" 4.6 as Xxx
|
2010-01-29 13:36:07 +01:00
|
|
|
|
2010-02-02 15:55:17 +01:00
|
|
|
import "http://www.ovi.com/" as Ovi
|
|
|
|
*/
|
|
|
|
void Link::importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
|
2010-03-16 16:34:33 +01:00
|
|
|
AST::UiImport *import)
|
2010-02-02 15:55:17 +01:00
|
|
|
{
|
2010-02-02 16:36:14 +01:00
|
|
|
Q_UNUSED(doc)
|
|
|
|
|
2010-02-02 15:55:17 +01:00
|
|
|
if (!import->fileName)
|
|
|
|
return;
|
2010-01-28 14:53:53 +01:00
|
|
|
|
2010-03-16 16:34:33 +01:00
|
|
|
QString path = doc->path();
|
2010-02-02 15:55:17 +01:00
|
|
|
path += QLatin1Char('/');
|
|
|
|
path += import->fileName->asString();
|
|
|
|
path = QDir::cleanPath(path);
|
2010-01-28 14:53:53 +01:00
|
|
|
|
2010-03-16 16:34:33 +01:00
|
|
|
ObjectValue *importNamespace = typeEnv;
|
2010-01-28 14:53:53 +01:00
|
|
|
|
2010-03-16 16:34:33 +01:00
|
|
|
// directory import
|
|
|
|
if (_documentByPath.contains(path)) {
|
|
|
|
if (import->importId) {
|
2010-02-03 14:31:03 +01:00
|
|
|
importNamespace = engine()->newObject(/*prototype =*/0);
|
2010-02-02 15:55:17 +01:00
|
|
|
typeEnv->setProperty(import->importId->asString(), importNamespace);
|
|
|
|
}
|
2010-01-29 13:36:07 +01:00
|
|
|
|
2010-03-16 16:34:33 +01:00
|
|
|
foreach (Document::Ptr importedDoc, _documentByPath.values(path)) {
|
2010-03-31 15:49:19 +02:00
|
|
|
const QString targetName = importedDoc->componentName();
|
2010-03-16 16:34:33 +01:00
|
|
|
importNamespace->setProperty(targetName, importedDoc->bind()->rootObjectValue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// file import
|
|
|
|
else if (Document::Ptr importedDoc = _snapshot.document(path)) {
|
2010-02-02 15:55:17 +01:00
|
|
|
QString targetName;
|
2010-03-16 16:34:33 +01:00
|
|
|
if (import->importId) {
|
2010-02-02 15:55:17 +01:00
|
|
|
targetName = import->importId->asString();
|
|
|
|
} else {
|
2010-03-31 15:49:19 +02:00
|
|
|
targetName = importedDoc->componentName();
|
2010-01-28 14:53:53 +01:00
|
|
|
}
|
2010-02-02 15:55:17 +01:00
|
|
|
|
2010-03-16 16:34:33 +01:00
|
|
|
importNamespace->setProperty(targetName, importedDoc->bind()->rootObjectValue());
|
|
|
|
} else {
|
2010-03-29 11:32:11 +02:00
|
|
|
error(doc, import->fileNameToken,
|
|
|
|
QCoreApplication::translate("QmlJS::Link", "could not find file or directory"));
|
2010-01-28 14:53:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-03-25 14:47:28 +01:00
|
|
|
static SourceLocation locationFromRange(const SourceLocation &start,
|
|
|
|
const SourceLocation &end)
|
|
|
|
{
|
|
|
|
return SourceLocation(start.offset,
|
|
|
|
end.end() - start.begin(),
|
|
|
|
start.startLine,
|
|
|
|
start.startColumn);
|
|
|
|
}
|
|
|
|
|
2010-02-02 15:55:17 +01:00
|
|
|
/*
|
|
|
|
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)
|
|
|
|
{
|
2010-03-16 16:34:33 +01:00
|
|
|
if (! import->importUri)
|
|
|
|
return;
|
|
|
|
|
2010-02-02 15:55:17 +01:00
|
|
|
ObjectValue *namespaceObject = 0;
|
|
|
|
|
|
|
|
if (import->importId) { // with namespace we insert an object in the type env. to hold the imported types
|
2010-02-03 14:31:03 +01:00
|
|
|
namespaceObject = engine()->newObject(/*prototype */ 0);
|
2010-02-02 15:55:17 +01:00
|
|
|
typeEnv->setProperty(import->importId->asString(), namespaceObject);
|
|
|
|
|
|
|
|
} else { // without namespace we insert all types directly into the type env.
|
|
|
|
namespaceObject = typeEnv;
|
|
|
|
}
|
|
|
|
|
2010-03-25 15:23:57 +01:00
|
|
|
const QString packageName = Bind::toString(import->importUri, '.');
|
2010-03-16 16:34:33 +01:00
|
|
|
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) {
|
2010-03-29 11:32:11 +02:00
|
|
|
error(doc, import->versionToken,
|
|
|
|
QCoreApplication::translate("QmlJS::Link", "expected two numbers separated by a dot"));
|
2010-03-25 14:47:28 +01:00
|
|
|
return;
|
2010-03-16 16:34:33 +01:00
|
|
|
} else {
|
|
|
|
majorVersion = versionString.left(dotIdx).toInt();
|
|
|
|
minorVersion = versionString.mid(dotIdx + 1).toInt();
|
2010-02-02 15:55:17 +01:00
|
|
|
}
|
2010-03-25 14:47:28 +01:00
|
|
|
} else {
|
2010-03-29 11:32:11 +02:00
|
|
|
error(doc, locationFromRange(import->firstSourceLocation(), import->lastSourceLocation()),
|
|
|
|
QCoreApplication::translate("QmlJS::Link", "package import requires a version number"));
|
2010-03-25 14:47:28 +01:00
|
|
|
return;
|
2010-03-16 16:34:33 +01:00
|
|
|
}
|
2010-03-01 13:01:05 +01:00
|
|
|
|
2010-03-16 16:34:33 +01:00
|
|
|
// if the package is in the meta type system, use it
|
2010-03-25 15:23:57 +01:00
|
|
|
if (engine()->metaTypeSystem().hasPackage(packageName)) {
|
|
|
|
foreach (QmlObjectValue *object, engine()->metaTypeSystem().staticTypesForImport(packageName, majorVersion, minorVersion)) {
|
2010-03-01 13:01:05 +01:00
|
|
|
namespaceObject->setProperty(object->className(), object);
|
2010-02-02 15:55:17 +01:00
|
|
|
}
|
2010-03-25 14:47:28 +01:00
|
|
|
return;
|
2010-03-16 16:34:33 +01:00
|
|
|
} else {
|
|
|
|
// check the filesystem
|
2010-03-25 15:23:57 +01:00
|
|
|
const QString packagePath = Bind::toString(import->importUri, QDir::separator());
|
2010-03-16 16:34:33 +01:00
|
|
|
QStringList localImportPaths = _importPaths;
|
|
|
|
localImportPaths.prepend(doc->path());
|
|
|
|
foreach (const QString &importPath, localImportPaths) {
|
|
|
|
QDir dir(importPath);
|
2010-03-25 15:23:57 +01:00
|
|
|
if (!dir.cd(packagePath))
|
2010-03-16 16:34:33 +01:00
|
|
|
continue;
|
|
|
|
|
2010-03-18 15:43:33 +01:00
|
|
|
const LibraryInfo libraryInfo = _snapshot.libraryInfo(dir.path());
|
|
|
|
if (!libraryInfo.isValid())
|
|
|
|
continue;
|
2010-03-18 12:06:43 +01:00
|
|
|
|
|
|
|
QSet<QString> importedTypes;
|
2010-03-18 15:43:33 +01:00
|
|
|
foreach (const QmlDirParser::Component &component, libraryInfo.components()) {
|
2010-03-18 12:06:43 +01:00
|
|
|
if (importedTypes.contains(component.typeName))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (component.majorVersion > majorVersion
|
|
|
|
|| (component.majorVersion == majorVersion
|
|
|
|
&& component.minorVersion > minorVersion))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
importedTypes.insert(component.typeName);
|
|
|
|
if (Document::Ptr importedDoc = _snapshot.document(dir.filePath(component.fileName))) {
|
|
|
|
namespaceObject->setProperty(component.typeName, importedDoc->bind()->rootObjectValue());
|
|
|
|
}
|
2010-03-16 16:34:33 +01:00
|
|
|
}
|
|
|
|
|
2010-03-25 14:47:28 +01:00
|
|
|
return;
|
2010-03-16 16:34:33 +01:00
|
|
|
}
|
2010-02-02 15:55:17 +01:00
|
|
|
}
|
2010-03-25 14:47:28 +01:00
|
|
|
|
2010-03-29 11:32:11 +02:00
|
|
|
error(doc, locationFromRange(import->firstSourceLocation(), import->lastSourceLocation()),
|
|
|
|
QCoreApplication::translate("QmlJS::Link", "package not found"));
|
2010-02-02 15:55:17 +01:00
|
|
|
}
|
|
|
|
|
2010-02-02 17:06:48 +01:00
|
|
|
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;
|
|
|
|
}
|
2010-03-29 11:32:11 +02:00
|
|
|
|
|
|
|
void Link::error(const Document::Ptr &doc, const AST::SourceLocation &loc, const QString &message)
|
|
|
|
{
|
|
|
|
if (doc->fileName() == _doc->fileName())
|
|
|
|
_diagnosticMessages.append(DiagnosticMessage(DiagnosticMessage::Error, loc, message));
|
|
|
|
}
|