Commit 82cbd0a9 authored by Christian Kamm's avatar Christian Kamm
Browse files

QmlJS: Support .import directives in js files.

* Allow .import...
* Fix parsing of JS files when using Lexer::scanDirectives()
* Clean up ImportInfo construction.
* Rename ImportInfo::id to ImportInfo::as.

Change-Id: I888da248f06dc6184db99aa74c3b50d7f2f5e491
Reviewed-on: http://codereview.qt-project.org/5625

Reviewed-by: default avatarRoberto Raggi <roberto.raggi@nokia.com>
parent faa792f3
......@@ -113,7 +113,7 @@ double integerFromString(const QString &str, int radix)
Engine::Engine()
: _lexer(0)
: _lexer(0), _directives(0)
{ }
Engine::~Engine()
......@@ -134,6 +134,12 @@ Lexer *Engine::lexer() const
void Engine::setLexer(Lexer *lexer)
{ _lexer = lexer; }
void Engine::setDirectives(Directives *directives)
{ _directives = directives; }
Directives *Engine::directives() const
{ return _directives; }
MemoryPool *Engine::pool()
{ return &_pool; }
......
......@@ -56,6 +56,7 @@ QT_QML_BEGIN_NAMESPACE
namespace QmlJS {
class Lexer;
class Directives;
class MemoryPool;
class QML_PARSER_EXPORT DiagnosticMessage
......@@ -83,6 +84,7 @@ public:
class QML_PARSER_EXPORT Engine
{
Lexer *_lexer;
Directives *_directives;
MemoryPool _pool;
QList<AST::SourceLocation> _comments;
QString _extraCode;
......@@ -100,6 +102,9 @@ public:
Lexer *lexer() const;
void setLexer(Lexer *lexer);
void setDirectives(Directives *directives);
Directives *directives() const;
MemoryPool *pool();
inline QStringRef midRef(int position, int size) { return _code.midRef(position, size); }
......
......@@ -138,7 +138,20 @@ bool Parser::parse(int startToken)
token_buffer[0].token = startToken;
first_token = &token_buffer[0];
last_token = &token_buffer[1];
if (startToken == T_FEED_JS_PROGRAM) {
Directives ignoreDirectives;
Directives *directives = driver->directives();
if (!directives)
directives = &ignoreDirectives;
lexer->scanDirectives(directives);
token_buffer[1].token = lexer->tokenKind();
token_buffer[1].dval = lexer->tokenValue();
token_buffer[1].loc = location(lexer);
token_buffer[1].spell = lexer->tokenSpell();
last_token = &token_buffer[2];
} else {
last_token = &token_buffer[1];
}
tos = -1;
program = 0;
......
......@@ -60,11 +60,13 @@ using namespace QmlJS::AST;
It allows AST to code model lookup through findQmlObject() and findFunctionScope().
*/
Bind::Bind(Document *doc, QList<DiagnosticMessage> *messages)
Bind::Bind(Document *doc, QList<DiagnosticMessage> *messages, bool isJsLibrary, const QList<ImportInfo> &jsImports)
: _doc(doc),
_currentObjectValue(0),
_idEnvironment(0),
_rootObjectValue(0),
_isJsLibrary(isJsLibrary),
_imports(jsImports),
_diagnosticMessages(messages)
{
if (_doc)
......@@ -75,6 +77,11 @@ Bind::~Bind()
{
}
bool Bind::isJsLibrary() const
{
return _isJsLibrary;
}
QList<ImportInfo> Bind::imports() const
{
return _imports;
......@@ -199,10 +206,6 @@ bool Bind::visit(AST::Program *)
bool Bind::visit(UiImport *ast)
{
ComponentVersion version;
ImportInfo::Type type = ImportInfo::InvalidImport;
QString path;
QString name;
if (ast->versionToken.isValid()) {
const QString versionString = _doc->source().mid(ast->versionToken.offset, ast->versionToken.length);
version = ComponentVersion(versionString);
......@@ -213,37 +216,18 @@ bool Bind::visit(UiImport *ast)
}
if (ast->importUri) {
type = ImportInfo::LibraryImport;
path = toString(ast->importUri, QDir::separator());
name = toString(ast->importUri, QLatin1Char('.'));
// treat Qt 4.7 as QtQuick 1.0
if (path == QLatin1String("Qt") && version == ComponentVersion(4, 7)) {
path = QLatin1String("QtQuick");
name = path;
version = ComponentVersion(1, 0);
}
if (!version.isValid()) {
_diagnosticMessages->append(
errorMessage(ast, tr("package import requires a version number")));
}
_imports += ImportInfo::moduleImport(toString(ast->importUri), version,
ast->importId.toString(), ast);
} else if (!ast->fileName.isEmpty()) {
name = ast->fileName.toString();
QFileInfo importFileInfo(name);
if (!importFileInfo.isAbsolute()) {
importFileInfo = QFileInfo(_doc->path() + QDir::separator() + name);
}
path = importFileInfo.absoluteFilePath();
if (importFileInfo.isFile())
type = ImportInfo::FileImport;
else if (importFileInfo.isDir())
type = ImportInfo::DirectoryImport;
else {
type = ImportInfo::UnknownFileImport;
}
_imports += ImportInfo::pathImport(_doc->path(), ast->fileName.toString(),
version, ast->importId.toString(), ast);
} else {
_imports += ImportInfo::invalidImport(ast);
}
_imports += ImportInfo(type, path, name, version, ast);
return false;
}
......
......@@ -52,9 +52,11 @@ class QMLJS_EXPORT Bind: protected AST::Visitor
Q_DECLARE_TR_FUNCTIONS(QmlJS::Bind)
public:
Bind(Document *doc, QList<DiagnosticMessage> *messages);
Bind(Document *doc, QList<DiagnosticMessage> *messages,
bool isJsLibrary, const QList<ImportInfo> &jsImports);
virtual ~Bind();
bool isJsLibrary() const;
QList<ImportInfo> imports() const;
ObjectValue *idEnvironment() const;
......@@ -105,8 +107,8 @@ private:
QMultiHash<QString, const ObjectValue *> _qmlObjectsByPrototypeName;
QSet<AST::Node *> _groupedPropertyBindings;
QHash<AST::Node *, ObjectValue *> _attachedJSScopes;
QStringList _includedScripts;
bool _isJsLibrary;
QList<ImportInfo> _imports;
QList<DiagnosticMessage> *_diagnosticMessages;
......
......@@ -211,6 +211,35 @@ QString Document::componentName() const
return _componentName;
}
namespace {
class CollectDirectives : public Directives
{
QString documentPath;
public:
CollectDirectives(const QString &documentPath)
: documentPath(documentPath)
, isLibrary(false)
{}
virtual void pragmaLibrary() { isLibrary = true; }
virtual void importFile(const QString &jsfile, const QString &module)
{
imports += ImportInfo::pathImport(
documentPath, jsfile, LanguageUtils::ComponentVersion(), module);
}
virtual void importModule(const QString &uri, const QString &version, const QString &module)
{
imports += ImportInfo::moduleImport(uri, LanguageUtils::ComponentVersion(version), module);
}
bool isLibrary;
QList<ImportInfo> imports;
};
} // anonymous namespace
bool Document::parse_helper(int startToken)
{
Q_ASSERT(! _engine);
......@@ -225,11 +254,8 @@ bool Document::parse_helper(int startToken)
QString source = _source;
lexer.setCode(source, /*line = */ 1, /*qmlMode = */_language == QmlLanguage);
if (startToken == QmlJSGrammar::T_FEED_JS_PROGRAM) {
// ### use directives
Directives directives;
lexer.scanDirectives(&directives);
}
CollectDirectives collectDirectives(path());
_engine->setDirectives(&collectDirectives);
switch (startToken) {
case QmlJSGrammar::T_FEED_UI_PROGRAM:
......@@ -248,7 +274,7 @@ bool Document::parse_helper(int startToken)
_ast = parser.rootNode();
_diagnosticMessages = parser.diagnosticMessages();
_bind = new Bind(this, &_diagnosticMessages);
_bind = new Bind(this, &_diagnosticMessages, collectDirectives.isLibrary, collectDirectives.imports);
return _parsedCorrectly;
}
......
......@@ -1932,14 +1932,66 @@ ImportInfo::ImportInfo()
{
}
ImportInfo::ImportInfo(Type type, const QString &path, const QString &name,
ComponentVersion version, UiImport *ast)
: _type(type)
, _name(name)
, _path(path)
, _version(version)
, _ast(ast)
ImportInfo ImportInfo::moduleImport(QString uri, ComponentVersion version,
const QString &as, UiImport *ast)
{
// treat Qt 4.7 as QtQuick 1.0
if (uri == QLatin1String("Qt") && version == ComponentVersion(4, 7)) {
uri = QLatin1String("QtQuick");
version = ComponentVersion(1, 0);
}
ImportInfo info;
info._type = LibraryImport;
info._name = uri;
info._path = uri;
info._path.replace(QLatin1Char('.'), QDir::separator());
info._version = version;
info._as = as;
info._ast = ast;
return info;
}
ImportInfo ImportInfo::pathImport(const QString &docPath, const QString &path,
ComponentVersion version, const QString &as, UiImport *ast)
{
ImportInfo info;
info._name = path;
QFileInfo importFileInfo(path);
if (!importFileInfo.isAbsolute()) {
importFileInfo = QFileInfo(docPath + QDir::separator() + path);
}
info._path = importFileInfo.absoluteFilePath();
if (importFileInfo.isFile()) {
info._type = FileImport;
} else if (importFileInfo.isDir()) {
info._type = DirectoryImport;
} else {
info._type = UnknownFileImport;
}
info._version = version;
info._as = as;
info._ast = ast;
return info;
}
ImportInfo ImportInfo::invalidImport(UiImport *ast)
{
ImportInfo info;
info._type = InvalidImport;
info._ast = ast;
return info;
}
ImportInfo ImportInfo::implicitDirectoryImport(const QString &directory)
{
ImportInfo info;
info._type = ImplicitDirectoryImport;
info._path = directory;
return info;
}
bool ImportInfo::isValid() const
......@@ -1962,11 +2014,9 @@ QString ImportInfo::path() const
return _path;
}
QString ImportInfo::id() const
QString ImportInfo::as() const
{
if (_ast)
return _ast->importId.toString();
return QString();
return _as;
}
ComponentVersion ImportInfo::version() const
......@@ -2003,8 +2053,8 @@ const Value *TypeScope::lookupMember(const QString &name, const Context *context
if (info.type() == ImportInfo::FileImport)
continue;
if (!info.id().isEmpty()) {
if (info.id() == name) {
if (!info.as().isEmpty()) {
if (info.as() == name) {
if (foundInObject)
*foundInObject = this;
return import;
......@@ -2033,8 +2083,8 @@ void TypeScope::processMembers(MemberProcessor *processor) const
if (info.type() == ImportInfo::FileImport)
continue;
if (!info.id().isEmpty()) {
processor->processProperty(info.id(), import);
if (!info.as().isEmpty()) {
processor->processProperty(info.as(), import);
} else {
import->processMembers(processor);
}
......@@ -2061,7 +2111,7 @@ const Value *JSImportScope::lookupMember(const QString &name, const Context *,
if (info.type() != ImportInfo::FileImport)
continue;
if (info.id() == name) {
if (info.as() == name) {
if (foundInObject)
*foundInObject = this;
return import;
......@@ -2082,7 +2132,7 @@ void JSImportScope::processMembers(MemberProcessor *processor) const
const ImportInfo &info = i.info;
if (info.type() == ImportInfo::FileImport)
processor->processProperty(info.id(), import);
processor->processProperty(info.as(), import);
}
}
......@@ -2094,12 +2144,12 @@ Imports::Imports(ValueOwner *valueOwner)
void Imports::append(const Import &import)
{
// when doing lookup, imports with 'as' clause are looked at first
if (!import.info.id().isEmpty()) {
if (!import.info.as().isEmpty()) {
_imports.append(import);
} else {
// find first as-import and prepend
for (int i = 0; i < _imports.size(); ++i) {
if (!_imports.at(i).info.id().isEmpty()) {
if (!_imports.at(i).info.as().isEmpty()) {
_imports.insert(i, import);
return;
}
......@@ -2123,8 +2173,8 @@ ImportInfo Imports::info(const QString &name, const Context *context) const
const ObjectValue *import = i.object;
const ImportInfo &info = i.info;
if (!info.id().isEmpty()) {
if (info.id() == firstId)
if (!info.as().isEmpty()) {
if (info.as() == firstId)
return info;
continue;
}
......@@ -2156,9 +2206,9 @@ QString Imports::nameForImportedObject(const ObjectValue *value, const Context *
const Value *v = import->lookupMember(value->className(), context);
if (v == value) {
QString result = value->className();
if (!info.id().isEmpty()) {
if (!info.as().isEmpty()) {
result.prepend(QLatin1Char('.'));
result.prepend(info.id());
result.prepend(info.as());
}
return result;
}
......@@ -2230,7 +2280,7 @@ void Imports::dump() const
const ObjectValue *import = i.object;
const ImportInfo &info = i.info;
qDebug() << " " << info.path() << " " << info.version().toString() << " as " << info.id() << " : " << import;
qDebug() << " " << info.path() << " " << info.version().toString() << " as " << info.as() << " : " << import;
MemberDumper dumper;
import->processMembers(&dumper);
}
......
......@@ -822,9 +822,14 @@ public:
};
ImportInfo();
ImportInfo(Type type, const QString &path, const QString &name = QString(),
LanguageUtils::ComponentVersion version = LanguageUtils::ComponentVersion(),
AST::UiImport *ast = 0);
static ImportInfo moduleImport(QString uri, LanguageUtils::ComponentVersion version,
const QString &as, AST::UiImport *ast = 0);
static ImportInfo pathImport(const QString &docPath, const QString &path,
LanguageUtils::ComponentVersion version,
const QString &as, AST::UiImport *ast = 0);
static ImportInfo invalidImport(AST::UiImport *ast = 0);
static ImportInfo implicitDirectoryImport(const QString &directory);
bool isValid() const;
Type type() const;
......@@ -838,7 +843,7 @@ public:
QString path() const;
// null if the import has no 'as', otherwise the target id
QString id() const;
QString as() const;
LanguageUtils::ComponentVersion version() const;
AST::UiImport *ast() const;
......@@ -848,6 +853,7 @@ private:
QString _name;
QString _path;
LanguageUtils::ComponentVersion _version;
QString _as;
AST::UiImport *_ast;
};
......
......@@ -233,15 +233,13 @@ Context::ImportsPerDocument LinkPrivate::linkImports()
void LinkPrivate::populateImportedTypes(Imports *imports, Document::Ptr doc)
{
if (! doc->qmlProgram())
return;
// implicit imports: the <default> package is always available
loadImplicitDefaultImports(imports);
// implicit imports:
// qml files in the same directory are available without explicit imports
loadImplicitDirectoryImports(imports, doc);
if (doc->isQmlDocument())
loadImplicitDirectoryImports(imports, doc);
// explicit imports, whether directories, files or libraries
foreach (const ImportInfo &info, doc->bind()->imports()) {
......@@ -261,8 +259,10 @@ void LinkPrivate::populateImportedTypes(Imports *imports, Document::Ptr doc)
import = importNonFile(doc, info);
break;
case ImportInfo::UnknownFileImport:
error(doc, info.ast()->fileNameToken,
Link::tr("file or directory not found"));
if (info.ast()) {
error(doc, info.ast()->fileNameToken,
Link::tr("file or directory not found"));
}
break;
default:
break;
......@@ -400,9 +400,8 @@ bool LinkPrivate::importLibrary(Document::Ptr doc,
import->libraryPath = libraryPath;
const ComponentVersion version = importInfo.version();
const UiImport *ast = importInfo.ast();
SourceLocation errorLoc;
if (ast)
if (const UiImport *ast = importInfo.ast())
errorLoc = locationFromRange(ast->firstSourceLocation(), ast->lastSourceLocation());
if (!libraryInfo.plugins().isEmpty()) {
......@@ -505,8 +504,7 @@ void LinkPrivate::loadQmldirComponents(ObjectValue *import, ComponentVersion ver
void LinkPrivate::loadImplicitDirectoryImports(Imports *imports, Document::Ptr doc)
{
ImportInfo implcitDirectoryImportInfo(
ImportInfo::ImplicitDirectoryImport, doc->path());
ImportInfo implcitDirectoryImportInfo = ImportInfo::implicitDirectoryImport(doc->path());
Import directoryImport = importCache.value(ImportCacheKey(implcitDirectoryImportInfo));
if (!directoryImport.object) {
......@@ -523,15 +521,15 @@ void LinkPrivate::loadImplicitDefaultImports(Imports *imports)
{
const QString defaultPackage = CppQmlTypes::defaultPackage;
if (valueOwner->cppQmlTypes().hasModule(defaultPackage)) {
ImportInfo info(ImportInfo::LibraryImport, defaultPackage);
const ComponentVersion maxVersion(ComponentVersion::MaxVersion, ComponentVersion::MaxVersion);
const ImportInfo info = ImportInfo::moduleImport(defaultPackage, maxVersion, QString());
Import import = importCache.value(ImportCacheKey(info));
if (!import.object) {
import.info = info;
import.object = new ObjectValue(valueOwner);
foreach (const QmlObjectValue *object,
valueOwner->cppQmlTypes().createObjectsForImport(
defaultPackage,
ComponentVersion(ComponentVersion::MaxVersion, ComponentVersion::MaxVersion))) {
defaultPackage, maxVersion)) {
import.object->setMember(object->className(), object);
}
importCache.insert(ImportCacheKey(info), import);
......
......@@ -254,29 +254,30 @@ void ScopeChain::initializeRootScope()
QmlComponentChain *chain = new QmlComponentChain(m_document);
m_qmlComponentScope = QSharedPointer<const QmlComponentChain>(chain);
if (const Imports *imports = m_context->imports(m_document.data())) {
m_qmlTypes = imports->typeScope();
m_jsImports = imports->jsImportScope();
}
if (m_document->qmlProgram()) {
componentScopes.insert(m_document.data(), chain);
makeComponentChain(chain, snapshot, &componentScopes);
if (const Imports *imports = m_context->imports(m_document.data())) {
m_qmlTypes = imports->typeScope();
m_jsImports = imports->jsImportScope();
}
} else {
// add scope chains for all components that import this file
foreach (Document::Ptr otherDoc, snapshot) {
foreach (const ImportInfo &import, otherDoc->bind()->imports()) {
if (import.type() == ImportInfo::FileImport && m_document->fileName() == import.path()) {
QmlComponentChain *component = new QmlComponentChain(otherDoc);
componentScopes.insert(otherDoc.data(), component);
chain->addInstantiatingComponent(component);
makeComponentChain(component, snapshot, &componentScopes);
// unless there's .pragma library
if (!m_document->bind()->isJsLibrary()) {
foreach (Document::Ptr otherDoc, snapshot) {
foreach (const ImportInfo &import, otherDoc->bind()->imports()) {
if (import.type() == ImportInfo::FileImport && m_document->fileName() == import.path()) {
QmlComponentChain *component = new QmlComponentChain(otherDoc);
componentScopes.insert(otherDoc.data(), component);
chain->addInstantiatingComponent(component);
makeComponentChain(component, snapshot, &componentScopes);
}
}
}
}
// ### TODO: Which type environment do scripts see?
if (bind->rootObjectValue())
m_jsScopes += bind->rootObjectValue();
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment