Commit 942326ae authored by Fawzi Mohamed's avatar Fawzi Mohamed
Browse files

qmljs: add infrastructure handling qml dialects better



QmlBundles enables us to treat the different qml
dialects differently.

Add imports completion.

Change-log: [Qml/JS Support] Corrected handling of QtQuick2 only features.
Change-log: [Qml/JS Support] Added import completion in editor.
Task-number: QTCREATORBUG-8750
Task-number: QTCREATORBUG-8624
Task-number: QTCREATORBUG-8584
Task-number: QTCREATORBUG-8583
Task-number: QTCREATORBUG-8429

Change-Id: I1384b1b23136a85b4d077895ea86f92960da9e71
Reviewed-by: default avatarKai Koehne <kai.koehne@digia.com>
parent 6d3c271d
{
"name": "qbs",
"searchPaths": [
"$(QBS_IMPORT_PATH)"],
"installPaths": [
"$(QBS_IMPORT_PATH)"],
"implicitImports": [
"__javascriptQt5__"],
"supportedImports": [
"qbs.base 1.0",
"qbs 1.0",
"qbs.fileinfo 1.0",
"qbs.probe 1.0"]
}
{
"name": "qmlproject",
"searchPaths": [
""],
"installPaths": [
""],
"implicitImports": [
""],
"supportedImports": [
"QmlProject 1.0",
"QmlProject 1.1"]
}
{
"name":"qmltypes",
"searchPaths":[],
"installPaths":[],
"implicitImports":[],
"supportedImports":[
"QtQuick.tooling 1.1",
"QtQuick.tooling 1.0"]
}
{
"name": "QtQuick1",
"searchPaths": [
"$(QT_INSTALL_IMPORTS)"],
"installPaths": [
"$(QT_INSTALL_IMPORTS)"],
"implicitImports": [
"__javascriptQt4__"],
"supportedImports": [
"QtQuick 1.0",
"QtQuick 1.1",
"Qt.labs.gestures 1.0",
"Qt.labs.particles 1.0",
"Qt.labs.shaders 1.0",
"Qt.labs.folderlistmodel 1.0",
"QtWebKit 1.0"]
}
{
"name": "QtQuick1",
"searchPaths": [
"$(QT_INSTALL_IMPORTS)"],
"installPaths": [
"$(QT_INSTALL_IMPORTS)"],
"implicitImports": [
"__javascriptQt5__"],
"supportedImports": [
"QtQuick 1.1",
"Qt.labs.gestures 1.0",
"Qt.labs.particles 1.0",
"Qt.labs.shaders 1.0",
"Qt.labs.folderlistmodel 1.0",
"QtWebKit 1.0"]
}
{
"name": "QtQuick2",
"searchPaths": [
"$(QT_INSTALL_QML)"],
"installPaths": [
"$(QT_INSTALL_QML)"],
"implicitImports": [
"__javascriptQt5__"],
"supportedImports": [
"Qt.labs.folderlistmodel 2.0",
"QtAudioEngine 1.0",
"QtMultimedia 5.0",
"QtQuick.LocalStorage 2.0",
"QtQuick.Particles 2.0",
"QtQuick.Window 2.0",
"QtQuick.XmlListModel 2.0",
"QtQuick 2.0",
"QtTest 1.0",
"QtWebKit 3.0"]
}
......@@ -261,6 +261,54 @@ QStringList TrieNode::stringList(const TrieNode::Ptr &trie)
return a.res;
}
bool TrieNode::isSame(const TrieNode::Ptr &trie1, const TrieNode::Ptr &trie2)
{
if (trie1.data() == trie2.data())
return true;
if (trie1.isNull() || trie2.isNull())
return false; // assume never to generate non null empty tries
if (trie1->prefix != trie2->prefix)
return false; // assume no excess splitting
QList<TrieNode::Ptr> t1 = trie1->postfixes, t2 =trie2->postfixes;
int nEl = t1.size();
if (nEl != t2.size()) return false;
// different order = different trie
for (int i = 0; i < nEl; ++i) {
if (!isSame(t1.value(i), t2.value(i)))
return false;
}
return true;
}
namespace {
class ReplaceInTrie
{
public:
TrieNode::Ptr trie;
QHash<QString, QString> replacements;
ReplaceInTrie() { }
void operator()(QString s)
{
QHashIterator<QString, QString> i(replacements);
QString res = s;
while (i.hasNext()) {
i.next();
res.replace(i.key(), i.value());
}
trie = TrieNode::insertF(trie,res);
}
};
}
TrieNode::Ptr TrieNode::replaceF(const TrieNode::Ptr &trie, const QHash<QString, QString> &replacements)
{
// inefficient...
ReplaceInTrie rep;
rep.replacements = replacements;
enumerateTrieNode<ReplaceInTrie>(trie, rep, QString());
return rep.trie;
}
std::pair<TrieNode::Ptr,int> TrieNode::intersectF(
const TrieNode::Ptr &v1, const TrieNode::Ptr &v2, int index1)
{
......@@ -553,6 +601,26 @@ void Trie::merge(const Trie &v)
trie = TrieNode::mergeF(trie, v.trie).first;
}
void Trie::replace(const QHash<QString, QString> &replacements)
{
trie = TrieNode::replaceF(trie, replacements);
}
bool Trie::isEmpty() const
{
return trie.isNull(); // assuming to never generate an empty non null trie
}
bool Trie::operator==(const Trie &o)
{
return TrieNode::isSame(trie,o.trie);
}
bool Trie::operator!=(const Trie &o)
{
return !TrieNode::isSame(trie,o.trie);
}
Trie Trie::insertF(const QString &value) const
{
return Trie(TrieNode::insertF(trie, value));
......@@ -568,13 +636,19 @@ Trie Trie::mergeF(const Trie &v) const
return Trie(TrieNode::mergeF(trie, v.trie).first);
}
Trie Trie::replaceF(const QHash<QString, QString> &replacements) const
{
return Trie(TrieNode::replaceF(trie, replacements));
}
/*!
\fn int matchStrength(const QString &searchStr, const QString &str)
Returns a number defining how well the serachStr matches str.
Quite simplistic, looks only at the first match, and prefers contiguos
matches, or matches to ca capitalized or separated word.
matches, or matches to capitalized or separated words.
Match to the last char is also preferred.
*/
int matchStrength(const QString &searchStr, const QString &str)
{
......@@ -601,6 +675,8 @@ int matchStrength(const QString &searchStr, const QString &str)
}
if (i != iEnd)
return iEnd - i;
if (j == jEnd)
++res;
return res;
}
......
......@@ -32,6 +32,7 @@
#include <qmljs/qmljs_global.h>
#include <QHash>
#include <QList>
#include <QSharedPointer>
......@@ -67,7 +68,9 @@ public:
const QString &base = QString(), LookupFlags flags = LookupFlags(CaseInsensitive|Partial));
static bool contains(const Ptr &trie, const QString &value, LookupFlags flags = LookupFlags(0));
static QStringList stringList(const Ptr &trie);
static bool isSame(const Ptr &trie1, const Ptr &trie2);
static Ptr replaceF(const Ptr &trie, const QHash<QString, QString> &replacements);
static Ptr insertF(const Ptr &trie, const QString &value);
static std::pair<Ptr,int> intersectF(const Ptr &v1, const Ptr &v2, int index1=0);
static std::pair<Ptr,int> mergeF(const Ptr &v1, const Ptr &v2);
......@@ -91,10 +94,16 @@ public:
Trie insertF(const QString &value) const;
Trie intersectF(const Trie &v) const;
Trie mergeF(const Trie &v) const;
Trie replaceF(const QHash<QString, QString> &replacements) const;
void insert(const QString &value);
void intersect(const Trie &v);
void merge(const Trie &v);
void replace(const QHash<QString, QString> &replacements);
bool isEmpty() const;
bool operator==(const Trie &o);
bool operator!=(const Trie &o);
friend QMLJS_EXPORT QDebug &operator<<(QDebug &dbg, const TrieNode::Ptr &trie);
friend QMLJS_EXPORT QDebug &operator<<(QDebug &dbg, const Trie &trie);
......
......@@ -11,6 +11,7 @@ INCLUDEPATH += $$PWD/..
HEADERS += \
$$PWD/qmljs_global.h \
$$PWD/qmljsbind.h \
$$PWD/qmljsbundle.h \
$$PWD/qmljsevaluate.h \
$$PWD/qmljsdocument.h \
$$PWD/qmljsscanner.h \
......@@ -42,6 +43,7 @@ HEADERS += \
SOURCES += \
$$PWD/qmljsbind.cpp \
$$PWD/qmljsbundle.cpp \
$$PWD/qmljsevaluate.cpp \
$$PWD/qmljsdocument.cpp \
$$PWD/qmljsscanner.cpp \
......
......@@ -23,6 +23,8 @@ QtcLibrary {
"qmljs_global.h",
"qmljsbind.cpp",
"qmljsbind.h",
"qmljsbundle.cpp",
"qmljsbundle.h",
"qmljscheck.cpp",
"qmljscheck.h",
"qmljscodeformatter.cpp",
......
......@@ -31,6 +31,7 @@
#include "qmljsbind.h"
#include "qmljsutils.h"
#include "qmljsdocument.h"
#include "qmljsmodelmanagerinterface.h"
#include <languageutils/componentversion.h>
......@@ -204,15 +205,27 @@ bool Bind::visit(UiImport *ast)
_diagnosticMessages->append(
errorMessage(ast, tr("package import requires a version number")));
}
_imports += ImportInfo::moduleImport(toString(ast->importUri), version,
ast->importId.toString(), ast);
ImportInfo import = ImportInfo::moduleImport(toString(ast->importUri), version,
ast->importId.toString(), ast);
if (_doc->language() == Document::QmlLanguage) {
QString importStr = import.name() + ast->importId.toString();
QmlLanguageBundles langBundles = ModelManagerInterface::instance()->extendedBundles();
QmlBundle qq1 = langBundles.bundleForLanguage(Document::QmlQtQuick1Language);
QmlBundle qq2 = langBundles.bundleForLanguage(Document::QmlQtQuick2Language);
bool isQQ1 = qq1.supportedImports().contains(importStr);
bool isQQ2 = qq2.supportedImports().contains(importStr);
if (isQQ1 && ! isQQ2)
_doc->setLanguage(Document::QmlQtQuick1Language);
if (isQQ2 && ! isQQ1)
_doc->setLanguage(Document::QmlQtQuick2Language);
}
_imports += import;
} else if (!ast->fileName.isEmpty()) {
_imports += ImportInfo::pathImport(_doc->path(), ast->fileName.toString(),
version, ast->importId.toString(), ast);
} else {
_imports += ImportInfo::invalidImport(ast);
}
return false;
}
......
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "qmljsbundle.h"
#include <utils/json.h>
#include <QString>
#include <QFile>
#include <QTextStream>
#include <QHash>
namespace QmlJS {
typedef PersistentTrie::Trie Trie;
QmlBundle::QmlBundle(const QmlBundle &o)
: m_name(o.m_name), m_searchPaths(o.searchPaths()), m_installPaths(o.installPaths()),
m_supportedImports(o.m_supportedImports), m_implicitImports(o.m_implicitImports)
{ }
QmlBundle::QmlBundle()
{ }
QmlBundle::QmlBundle(const QString &bundleName, const Trie &searchPaths,
const Trie &installPaths, const Trie &supportedImports,
const Trie &implicitImports)
: m_name(bundleName), m_searchPaths(searchPaths), m_installPaths(installPaths),
m_supportedImports(supportedImports), m_implicitImports(implicitImports)
{ }
QString QmlBundle::name() const
{
return m_name;
}
Trie QmlBundle::installPaths() const
{
return m_installPaths;
}
Trie QmlBundle::searchPaths() const
{
return m_searchPaths;
}
Trie QmlBundle::implicitImports() const
{
return m_implicitImports;
}
Trie QmlBundle::supportedImports() const
{
return m_supportedImports;
}
void QmlBundle::merge(const QmlBundle &o)
{
*this = mergeF(o);
}
void QmlBundle::intersect(const QmlBundle &o)
{
*this = intersectF(o);
}
QmlBundle QmlBundle::mergeF(const QmlBundle &o) const
{
return QmlBundle(QString::fromLatin1("(%1)||(%2)").arg(name()).arg(o.name()),
searchPaths().mergeF(o.searchPaths()),
installPaths().mergeF(o.installPaths()),
supportedImports().mergeF(o.supportedImports()),
implicitImports().mergeF(o.implicitImports()));
}
QmlBundle QmlBundle::intersectF(const QmlBundle &o) const
{
return QmlBundle(QString::fromLatin1("(%1)&&(%2)").arg(name()).arg(o.name()),
searchPaths().mergeF(o.searchPaths()),
installPaths().mergeF(o.installPaths()),
supportedImports().intersectF(o.supportedImports()),
implicitImports().mergeF(o.implicitImports()));
}
bool QmlBundle::isEmpty() const
{
return m_implicitImports.isEmpty() && m_installPaths.isEmpty()
&& m_searchPaths.isEmpty() && m_supportedImports.isEmpty();
}
void QmlBundle::replaceVars(const QHash<QString, QString> &replacements)
{
m_implicitImports.replace(replacements);
m_installPaths.replace(replacements);
m_searchPaths.replace(replacements);
m_supportedImports.replace(replacements);
}
QmlBundle QmlBundle::replaceVarsF(const QHash<QString, QString> &replacements) const
{
QmlBundle res(*this);
res.replaceVars(replacements);
return res;
}
bool QmlBundle::writeTo(const QString &path) const
{
QFile f(path);
if (!f.open(QIODevice::WriteOnly | QIODevice::Text))
return false;
QTextStream stream(&f);
return writeTo(stream);
}
bool QmlBundle::operator==(const QmlBundle &o) const
{
return o.implicitImports() == implicitImports()
&& o.installPaths() == installPaths()
&& o.supportedImports() == supportedImports(); // name is not considered
}
bool QmlBundle::operator!=(const QmlBundle &o) const
{
return !((*this) == o);
}
void QmlBundle::printEscaped(QTextStream &s, const QString &str)
{
s << QLatin1Char('"');
QString::const_iterator i = str.constBegin(), iLast = str.constBegin(),
iEnd = str.constEnd();
while (i != iEnd) {
if ((*i) != QLatin1Char('"')) {
s << QStringRef(&str, static_cast<int>(iLast - str.constBegin())
, static_cast<int>(i - iLast) ).toString()
<< QLatin1Char('\\');
iLast = i;
}
++i;
}
s << QStringRef(&str, static_cast<int>(iLast - str.constBegin())
, static_cast<int>(i - iLast) ).toString();
}
void QmlBundle::writeTrie(QTextStream &stream, const Trie &t, const QString &indent) {
stream << QLatin1Char('[');
bool firstLine = true;
foreach (const QString &i, t.stringList()) {
if (firstLine)
firstLine = false;
else
stream << QLatin1Char(',');
stream << QLatin1String("\n") << indent << QLatin1String(" ");
printEscaped(stream, i);
}
stream << QLatin1Char(']');
}
bool QmlBundle::writeTo(QTextStream &stream, const QString &indent) const
{
QString innerIndent = QString::fromLatin1(" ").append(indent);
stream << indent << QLatin1String("{\n")
<< indent << QLatin1String(" \"name\": ");
printEscaped(stream, name());
stream << QLatin1String(",\n")
<< indent << QLatin1String(" \"searchPaths\": ");
writeTrie(stream, searchPaths(), innerIndent);
stream << QLatin1String(",\n")
<< indent << QLatin1String(" \"installPaths\": ");
writeTrie(stream, installPaths(), innerIndent);
stream << QLatin1String(",\n")
<< indent << QLatin1String(" \"supportedImports\": ");
writeTrie(stream, supportedImports(), innerIndent);
stream << QLatin1String(",\n")
<< QLatin1String(" \"implicitImports\": ");
writeTrie(stream, implicitImports(), innerIndent);
stream << QLatin1String("\n")
<< indent << QLatin1Char('}');
return true;
}
QString QmlBundle::toString(const QString &indent)
{
QString res;
QTextStream s(&res);
writeTo(s, indent);
return res;
}
QStringList QmlBundle::maybeReadTrie(Trie &trie, Utils::JsonObjectValue *config,
const QString &path, const QString &propertyName, bool required)
{
QStringList res;
if (!config->hasMember(propertyName)) {
if (required)
res << tr("Missing required property \"%1\" from %2").arg(propertyName, path);
return res;
}
Utils::JsonValue *imp0 = config->member(propertyName);
Utils::JsonArrayValue *imp = ((imp0 != 0) ? imp0->toArray() : 0);
if (imp != 0) {
foreach (Utils::JsonValue *v, imp->elements()) {
Utils::JsonStringValue *impStr = ((v != 0) ? v->toString() : 0);
if (impStr != 0) {
trie.insert(impStr->value());
} else {
res.append(tr("Expected all elements of array in property \"%1\" to be strings in QmlBundle at %2.")
.arg(propertyName).arg(path));
break;
}
}
} else {
res.append(tr("Expected string array in property \"%1\" in QmlBundle at %2.")
.arg(propertyName).arg(path));
}
return res;
}
bool QmlBundle::readFrom(QString path, QStringList *errors)
{
using namespace Utils;
QFile f(path);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
if (errors)
(*errors) << tr("Could not open QmlBundle at %1 .").arg(path);
return false;
}
JsonObjectValue *config = JsonValue::create(QString::fromUtf8(f.readAll()))->toObject();
if (config == 0) {
if (errors)
(*errors) << tr("Could not parse json object in QmlBundle at %1 .").arg(path);
return false;
}
QStringList errs;
if (config->hasMember(QLatin1String("name"))) {
JsonValue *n0 = config->member(QLatin1String("name"));
JsonStringValue *n = ((n0 != 0) ? n0->toString() : 0);
if (n != 0)
m_name = n->value();
else
errs.append(tr("Property \"name\" in QmlBundle at %1 is expected to be a string.")
.arg(path));
} else {
errs.append(tr("Missing required property \"name\" in QmlBundle at %1 .").arg(path));
}
errs << maybeReadTrie(m_searchPaths, config, path, QLatin1String("searchPaths"));
errs << maybeReadTrie(m_installPaths, config, path, QLatin1String("installPaths"));
errs << maybeReadTrie(m_supportedImports, config, path, QLatin1String("supportedImports")
, true);
errs << maybeReadTrie(m_implicitImports, config, path, QLatin1String("implicitImports"));
if (errors)
(*errors) << errs;
return errs.isEmpty();
}
QmlBundle QmlLanguageBundles::bundleForLanguage(Document::Language l) const
{