diff options
23 files changed, 1109 insertions, 19 deletions
diff --git a/src/libs/qmljs/qmljs-lib.pri b/src/libs/qmljs/qmljs-lib.pri index f31879a6f0..12913de0a5 100644 --- a/src/libs/qmljs/qmljs-lib.pri +++ b/src/libs/qmljs/qmljs-lib.pri @@ -39,7 +39,8 @@ HEADERS += \ $$PWD/consoleitem.h \ $$PWD/iscriptevaluator.h \ $$PWD/qmljssimplereader.h \ - $$PWD/persistenttrie.h + $$PWD/persistenttrie.h \ + $$PWD/qmljsqrcparser.h SOURCES += \ $$PWD/qmljsbind.cpp \ @@ -69,7 +70,8 @@ SOURCES += \ $$PWD/consolemanagerinterface.cpp \ $$PWD/consoleitem.cpp \ $$PWD/qmljssimplereader.cpp \ - $$PWD/persistenttrie.cpp + $$PWD/persistenttrie.cpp \ + $$PWD/qmljsqrcparser.cpp RESOURCES += \ $$PWD/qmljs.qrc diff --git a/src/libs/qmljs/qmljs.pro b/src/libs/qmljs/qmljs.pro index c9c018de5c..5db252b290 100644 --- a/src/libs/qmljs/qmljs.pro +++ b/src/libs/qmljs/qmljs.pro @@ -1,5 +1,5 @@ DEFINES += QMLJS_BUILD_DIR -QT +=script +QT +=script xml include(../../qtcreatorlibrary.pri) include(qmljs-lib.pri) diff --git a/src/libs/qmljs/qmljs.qbs b/src/libs/qmljs/qmljs.qbs index 2bef9864c4..7029b1149f 100644 --- a/src/libs/qmljs/qmljs.qbs +++ b/src/libs/qmljs/qmljs.qbs @@ -12,7 +12,7 @@ QtcLibrary { Depends { name: "Utils" } Depends { name: "LanguageUtils" } - Depends { name: "Qt"; submodules: ["widgets", "script"] } + Depends { name: "Qt"; submodules: ["widgets", "script", "xml"] } files: [ "jsoncheck.cpp", @@ -52,6 +52,8 @@ QtcLibrary { "qmljsmodelmanagerinterface.h", "qmljspropertyreader.cpp", "qmljspropertyreader.h", + "qmljsqrcparser.cpp", + "qmljsqrcparser.h", "qmljsreformatter.cpp", "qmljsreformatter.h", "qmljsrewriter.cpp", diff --git a/src/libs/qmljs/qmljsdocument.cpp b/src/libs/qmljs/qmljsdocument.cpp index 3e55ccc42a..a7eb00c354 100644 --- a/src/libs/qmljs/qmljsdocument.cpp +++ b/src/libs/qmljs/qmljsdocument.cpp @@ -115,6 +115,22 @@ bool Document::isFullySupportedLanguage(Document::Language language) return false; } +bool Document::isQmlLikeOrJsLanguage(Document::Language language) +{ + switch (language) { + case QmlLanguage: + case QmlQtQuick1Language: + case QmlQtQuick2Language: + case QmlQbsLanguage: + case QmlProjectLanguage: + case QmlTypeInfoLanguage: + case JavaScriptLanguage: + return true; + default: + return false; + } +} + Document::Document(const QString &fileName, Language language) : _engine(0) , _ast(0) diff --git a/src/libs/qmljs/qmljsdocument.h b/src/libs/qmljs/qmljsdocument.h index e18f95d475..56ebb7061e 100644 --- a/src/libs/qmljs/qmljsdocument.h +++ b/src/libs/qmljs/qmljsdocument.h @@ -65,6 +65,7 @@ public: static bool isQmlLikeLanguage(Language languge); static bool isFullySupportedLanguage(Language language); + static bool isQmlLikeOrJsLanguage(Language language); protected: Document(const QString &fileName, Language language); diff --git a/src/libs/qmljs/qmljsinterpreter.cpp b/src/libs/qmljs/qmljsinterpreter.cpp index 24d68d4948..c9b1f4381a 100644 --- a/src/libs/qmljs/qmljsinterpreter.cpp +++ b/src/libs/qmljs/qmljsinterpreter.cpp @@ -35,6 +35,7 @@ #include "qmljstypedescriptionreader.h" #include "qmljsvalueowner.h" #include "qmljscontext.h" +#include "qmljsmodelmanagerinterface.h" #include "parser/qmljsast_p.h" #include <utils/qtcassert.h> @@ -2107,13 +2108,19 @@ ImportInfo ImportInfo::pathImport(const QString &docPath, const QString &path, importFileInfo = QFileInfo(docPath + QDir::separator() + path); info._path = importFileInfo.absoluteFilePath(); - if (importFileInfo.isFile()) + if (importFileInfo.isFile()) { info._type = FileImport; - else if (importFileInfo.isDir()) + } else if (importFileInfo.isDir()) { info._type = DirectoryImport; - else + } else if (path.startsWith(QLatin1String("qrc:"))) { + info._path = path; + if (ModelManagerInterface::instance()->filesAtQrcPath(info.path()).isEmpty()) + info._type = QrcDirectoryImport; + else + info._type = QrcFileImport; + } else { info._type = UnknownFileImport; - + } info._version = version; info._as = as; info._ast = ast; @@ -2192,7 +2199,7 @@ const Value *TypeScope::lookupMember(const QString &name, const Context *context const ImportInfo &info = i.info; // JS import has no types - if (info.type() == ImportInfo::FileImport) + if (info.type() == ImportInfo::FileImport || info.type() == ImportInfo::QrcFileImport) continue; if (!info.as().isEmpty()) { @@ -2222,7 +2229,7 @@ void TypeScope::processMembers(MemberProcessor *processor) const const ImportInfo &info = i.info; // JS import has no types - if (info.type() == ImportInfo::FileImport) + if (info.type() == ImportInfo::FileImport || info.type() == ImportInfo::QrcFileImport) continue; if (!info.as().isEmpty()) @@ -2249,7 +2256,7 @@ const Value *JSImportScope::lookupMember(const QString &name, const Context *, const ImportInfo &info = i.info; // JS imports are always: import "somefile.js" as Foo - if (info.type() != ImportInfo::FileImport) + if (info.type() != ImportInfo::FileImport && info.type() != ImportInfo::QrcFileImport) continue; if (info.as() == name) { @@ -2272,7 +2279,7 @@ void JSImportScope::processMembers(MemberProcessor *processor) const const ObjectValue *import = i.object; const ImportInfo &info = i.info; - if (info.type() == ImportInfo::FileImport) + if (info.type() == ImportInfo::FileImport || info.type() == ImportInfo::QrcFileImport) processor->processProperty(info.as(), import); } } @@ -2329,7 +2336,7 @@ ImportInfo Imports::info(const QString &name, const Context *context) const continue; } - if (info.type() == ImportInfo::FileImport) { + if (info.type() == ImportInfo::FileImport || info.type() == ImportInfo::QrcFileImport) { if (import->className() == firstId) return info; } else { @@ -2349,7 +2356,7 @@ QString Imports::nameForImportedObject(const ObjectValue *value, const Context * const ObjectValue *import = i.object; const ImportInfo &info = i.info; - if (info.type() == ImportInfo::FileImport) { + if (info.type() == ImportInfo::FileImport || info.type() == ImportInfo::QrcFileImport) { if (import == value) return import->className(); } else { diff --git a/src/libs/qmljs/qmljsinterpreter.h b/src/libs/qmljs/qmljsinterpreter.h index 56f914713f..356f52bb38 100644 --- a/src/libs/qmljs/qmljsinterpreter.h +++ b/src/libs/qmljs/qmljsinterpreter.h @@ -872,6 +872,8 @@ public: LibraryImport, FileImport, DirectoryImport, + QrcFileImport, + QrcDirectoryImport, UnknownFileImport // refers a file/directory that wasn't found }; diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp index e90dd9a9ae..2d5672fa20 100644 --- a/src/libs/qmljs/qmljslink.cpp +++ b/src/libs/qmljs/qmljslink.cpp @@ -34,6 +34,7 @@ #include "qmljsbind.h" #include "qmljsutils.h" #include "qmljsmodelmanagerinterface.h" +#include <qmljs/qmljsqrcparser.h> #include <QDir> #include <QDebug> @@ -245,6 +246,8 @@ void LinkPrivate::populateImportedTypes(Imports *imports, Document::Ptr doc) switch (info.type()) { case ImportInfo::FileImport: case ImportInfo::DirectoryImport: + case ImportInfo::QrcFileImport: + case ImportInfo::QrcDirectoryImport: import = importFileOrDirectory(doc, info); break; case ImportInfo::LibraryImport: @@ -285,7 +288,7 @@ Import LinkPrivate::importFileOrDirectory(Document::Ptr doc, const ImportInfo &i import.object = 0; import.valid = true; - const QString &path = importInfo.path(); + QString path = importInfo.path(); if (importInfo.type() == ImportInfo::DirectoryImport || importInfo.type() == ImportInfo::ImplicitDirectoryImport) { @@ -304,8 +307,36 @@ Import LinkPrivate::importFileOrDirectory(Document::Ptr doc, const ImportInfo &i Document::Ptr importedDoc = snapshot.document(path); if (importedDoc) import.object = importedDoc->bind()->rootObjectValue(); - } + } else if (importInfo.type() == ImportInfo::QrcFileImport) { + QLocale locale; + QStringList filePaths = ModelManagerInterface::instance() + ->filesAtQrcPath(path, &locale, 0, ModelManagerInterface::ActiveQrcResources); + if (filePaths.isEmpty()) { + filePaths = ModelManagerInterface::instance()->filesAtQrcPath(path); + } + if (!filePaths.isEmpty()) { + Document::Ptr importedDoc = snapshot.document(filePaths.at(0)); + if (importedDoc) + import.object = importedDoc->bind()->rootObjectValue(); + } + } else if (importInfo.type() == ImportInfo::QrcDirectoryImport){ + import.object = new ObjectValue(valueOwner); + + importLibrary(doc, path, &import); + QMapIterator<QString,QStringList> iter(ModelManagerInterface::instance() + ->filesInQrcPath(path)); + while (iter.hasNext()) { + iter.next(); + if (Document::isQmlLikeLanguage(Document::guessLanguageFromSuffix(iter.key()))) { + Document::Ptr importedDoc = snapshot.document(iter.value().at(0)); + if (importedDoc && importedDoc->bind()->rootObjectValue()) { + const QString targetName = QFileInfo(iter.key()).baseName(); + import.object->setMember(targetName, importedDoc->bind()->rootObjectValue()); + } + } + } + } return import; } diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.h b/src/libs/qmljs/qmljsmodelmanagerinterface.h index b708c3ca41..cdcaef362d 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.h +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.h @@ -53,6 +53,11 @@ class QMLJS_EXPORT ModelManagerInterface: public QObject Q_OBJECT public: + enum QrcResourceSelector { + ActiveQrcResources, + AllQrcResources + }; + class ProjectInfo { public: @@ -80,6 +85,8 @@ public: QPointer<ProjectExplorer::Project> project; QStringList sourceFiles; QStringList importPaths; + QStringList activeResourceFiles; + QStringList allResourceFiles; // whether trying to run qmldump makes sense bool tryQmlDump; @@ -142,6 +149,14 @@ public: bool emitDocumentOnDiskChanged) = 0; virtual void fileChangedOnDisk(const QString &path) = 0; virtual void removeFiles(const QStringList &files) = 0; + virtual QStringList filesAtQrcPath(const QString &path, const QLocale *locale = 0, + ProjectExplorer::Project *project = 0, + QrcResourceSelector resources = AllQrcResources) = 0; + virtual QMap<QString,QStringList> filesInQrcPath(const QString &path, + const QLocale *locale = 0, + ProjectExplorer::Project *project = 0, + bool addDirs = false, + QrcResourceSelector resources = AllQrcResources) = 0; virtual QList<ProjectInfo> projectInfos() const = 0; virtual ProjectInfo projectInfo(ProjectExplorer::Project *project) const = 0; diff --git a/src/libs/qmljs/qmljsqrcparser.cpp b/src/libs/qmljs/qmljsqrcparser.cpp new file mode 100644 index 0000000000..cdecbcd735 --- /dev/null +++ b/src/libs/qmljs/qmljsqrcparser.cpp @@ -0,0 +1,517 @@ +/**************************************************************************** +** +** 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 "qmljsqrcparser.h" +#include <QFile> +#include <QDir> +#include <QFileInfo> +#include <QStringList> +#include <QDomDocument> +#include <QLocale> +#include <QMutex> +#include <QSet> +#include <QMutexLocker> +#include <QMultiHash> +#include <QCoreApplication> +#include <utils/qtcassert.h> +#include <QDebug> + +namespace QmlJS { + +namespace Internal { +/*! + * \class QrcParser + * \brief Parses one or more qrc files, and keeps their content cached + * + * A Qrc resource contains files read from the filesystem but organized in a possibly different way. + * + * To easily describe that with a simple structure we use a map from qrc paths to the paths in the + * filesystem. + * By using a map we can easily find all qrc paths that start with a given prefix, and thus loop + * on a qrc directory. + * + * Qrc files also support languages, those are mapped to a prefix of the qrc path. + * For example the french /image/bla.png (lang=fr) will have the path "fr/image/bla.png". + * The empty language represent the default resource. + * Languages are looked up using the locale uiLanguages() property + * + * For a single qrc a given path maps to a single file, but when one has multiple + * (platform specific exclusive) qrc files, then multiple files match, so QStringList are used. + * + * Especially the collect* methods are thought as low level interface. + */ +class QrcParserPrivate +{ + Q_DECLARE_TR_FUNCTIONS(QmlJS::QrcParser) +public: + typedef QMap<QString,QStringList> SMap; + QrcParserPrivate(QrcParser *q); + bool parseFile(const QString &path); + QString firstFileAtPath(const QString &path, const QLocale &locale) const; + void collectFilesAtPath(const QString &path, QStringList *res, const QLocale *locale = 0) const; + bool hasDirAtPath(const QString &path, const QLocale *locale = 0) const; + void collectFilesInPath(const QString &path, QMap<QString,QStringList> *res, bool addDirs = false, + const QLocale *locale = 0) const; + QStringList errorMessages() const; + QStringList languages() const; +private: + static QString fixPrefix(const QString &prefix); + QStringList allUiLanguages(const QLocale *locale) const; + + SMap m_resources; + QStringList m_languages; + QStringList m_errorMessages; +}; + +class QrcCachePrivate +{ + Q_DECLARE_TR_FUNCTIONS(QmlJS::QrcCachePrivate) +public: + QrcCachePrivate(QrcCache *q); + QrcParser::Ptr addPath(const QString &path); + void removePath(const QString &path); + QrcParser::Ptr updatePath(const QString &path); + QrcParser::Ptr parsedPath(const QString &path); + void clear(); +private: + QHash<QString, QPair<QrcParser::Ptr,int> > m_cache; + QMutex m_mutex; +}; +} // namespace Internal + +/*! \brief normalizes the path to a file in a qrc resource by dropping the "qrc:/" or ":" and + * any extra slashes at the beginning + */ +QString QrcParser::normalizedQrcFilePath(const QString &path) { + QString normPath = path; + int endPrefix = 0; + if (path.startsWith(QLatin1String("qrc:/"))) { + endPrefix = 4; + } else if (path.startsWith(QLatin1String(":/"))) { + endPrefix = 1; + } + if (endPrefix < path.size() && path.at(endPrefix) == QLatin1Char('/')) + while (endPrefix + 1 < path.size() && path.at(endPrefix+1) == QLatin1Char('/')) + ++endPrefix; + normPath = path.right(path.size()-endPrefix); + if (!normPath.startsWith(QLatin1String("/"))) + normPath.insert(0, QLatin1Char('/')); + return normPath; +} + +/*! \brief normalizes the path to a directory in a qrc resource by dropping the "qrc:/" or ":" and + * any extra slashes at the beginning, and ensuring it ends with a slash + */ +QString QrcParser::normalizedQrcDirectoryPath(const QString &path) { + QString normPath = normalizedQrcFilePath(path); + if (!normPath.endsWith(QLatin1Char('/'))) + normPath.append(QLatin1Char('/')); + return normPath; +} + +QrcParser::QrcParser() +{ + d = new Internal::QrcParserPrivate(this); +} + +QrcParser::~QrcParser() +{ + delete d; +} + +bool QrcParser::parseFile(const QString &path) +{ + return d->parseFile(path); +} + +/*! \brief returns fs path of the first (active) file at the given qrc path + */ +QString QrcParser::firstFileAtPath(const QString &path, const QLocale &locale) const +{ + return d->firstFileAtPath(path, locale); +} + +/*! \brief adds al the fs paths for the given qrc path to *res + * If locale is null all possible files are added, otherwise just the first match + * using that locale. + */ +void QrcParser::collectFilesAtPath(const QString &path, QStringList *res, const QLocale *locale) const +{ + d->collectFilesAtPath(path, res, locale); +} + +/*! \brief returns true if the given path is a non empty directory + */ +bool QrcParser::hasDirAtPath(const QString &path, const QLocale *locale) const +{ + return d->hasDirAtPath(path, locale); +} + +/*! \brief adds the directory contents of the given qrc path to res + * + * adds the qrcFileName => fs paths associations contained in the given qrc path + * to res. If addDirs is true directories are also added. + * If locale is null all possible files are added, otherwise just the first match + * using that locale. + */ +void QrcParser::collectFilesInPath(const QString &path, QMap<QString,QStringList> *res, bool addDirs, + const QLocale *locale) const +{ + d->collectFilesInPath(path, res, addDirs, locale); +} + +/*! \brief returns the errors found while parsing + */ +QStringList QrcParser::errorMessages() const +{ + return d->errorMessages(); +} + +/*! \brief returns all languages used in this qrc resource + */ +QStringList QrcParser::languages() const +{ + return d->languages(); +} + +/*! \brief if the contents are valid + */ +bool QrcParser::isValid() const +{ + return errorMessages().isEmpty(); +} + +QrcParser::Ptr QrcParser::parseQrcFile(const QString &path) +{ + Ptr res(new QrcParser); + if (!path.isEmpty()) + res->parseFile(path); + return res; +} + +// ---------------- + +QrcCache::QrcCache() +{ + d = new Internal::QrcCachePrivate(this); +} + +QrcCache::~QrcCache() +{ + delete d; +} + +QrcParser::ConstPtr QrcCache::addPath(const QString &path) +{ + return d->addPath(path); +} + +void QrcCache::removePath(const QString &path) +{ + d->removePath(path); +} + +QrcParser::ConstPtr QrcCache::updatePath(const QString &path) +{ + return d->updatePath(path); +} + +QrcParser::ConstPtr QrcCache::parsedPath(const QString &path) +{ + return d->parsedPath(path); +} + +void QrcCache::clear() +{ + d->clear(); +} + +// -------------------- + +namespace Internal { + +QrcParserPrivate::QrcParserPrivate(QrcParser *) +{ } + +bool QrcParserPrivate::parseFile(const QString &path) +{ + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + m_errorMessages.append(file.errorString()); + return false; + } + + QDomDocument doc; + + QString error_msg; + int error_line, error_col; + if (!doc.setContent(&file, &error_msg, &error_line, &error_col)) { + m_errorMessages.append(tr("XML error on line %1, col %2: %3") + .arg(error_line).arg(error_col).arg(error_msg)); + return false; + } + + QDomElement root = doc.firstChildElement(QLatin1String("RCC")); + if (root.isNull()) { + m_errorMessages.append(tr("The <RCC> root element is missing.")); + return false; + } + + QDomElement relt = root.firstChildElement(QLatin1String("qresource")); + for (; !relt.isNull(); relt = relt.nextSiblingElement(QLatin1String("qresource"))) { + + QString prefix = fixPrefix(relt.attribute(QLatin1String("prefix"))); + const QString language = relt.attribute(QLatin1String("lang")); + if (!m_languages.contains(language)) + m_languages.append(language); + + QDomElement felt = relt.firstChildElement(QLatin1String("file")); + for (; !felt.isNull(); felt = felt.nextSiblingElement(QLatin1String("file"))) { + const QString fileName = felt.text(); + QTC_CHECK(!QDir::isAbsolutePath(fileName)); + const QString alias = felt.attribute(QLatin1String("alias")); + QString filePath = QFileInfo(path).path() + QLatin1Char('/') + fileName; + QString accessPath; + if (!alias.isEmpty()) + accessPath = language + prefix + alias; + else + accessPath = language + prefix + fileName; + if (m_resources.contains(accessPath)) { + QStringList &val = m_resources[accessPath]; + if (!val.contains(filePath)) + val.append(filePath); + } else { + m_resources.insert(accessPath, QStringList(filePath)); + } + } + } + return true; +} + +// path is assumed to be a normalized absolute path +QString QrcParserPrivate::firstFileAtPath(const QString &path, const QLocale &locale) const +{ + QTC_CHECK(path.startsWith(QLatin1Char('/'))); + QStringList langs = allUiLanguages(&locale); + foreach (const QString &language, langs) { + if (m_languages.contains(language)) { + SMap::const_iterator res = m_resources.find(language + path); + if (res != m_resources.end()) + return res.value().at(0); + } + } + return QString(); +} + +void QrcParserPrivate::collectFilesAtPath(const QString &path, QStringList *files, + const QLocale *locale) const +{ + QTC_CHECK(path.startsWith(QLatin1Char('/'))); + QStringList langs = allUiLanguages(locale); + foreach (const QString &language, langs) { + if (m_languages.contains(language)) { + SMap::const_iterator res = m_resources.find(language + path); + if (res != m_resources.end()) + (*files) << res.value(); + } + } +} + +// path is expected to be normalized and start and end with a slash +bool QrcParserPrivate::hasDirAtPath(const QString &path, const QLocale *locale) const +{ + QTC_CHECK(path.startsWith(QLatin1Char('/'))); + QTC_CHECK(path.endsWith(QLatin1Char('/'))); + QStringList langs = allUiLanguages(locale); + foreach (const QString &language, langs) { + if (m_languages.contains(language)) { + QString key = language + path; + SMap::const_iterator res = m_resources.lowerBound(key); + if (res != m_resources.end() && res.key().startsWith(key)) + return true; + } + } + return false; +} + +void QrcParserPrivate::collectFilesInPath(const QString &path, QMap<QString,QStringList> *contents, + bool addDirs, const QLocale *locale) const +{ + QTC_CHECK(path.startsWith(QLatin1Char('/'))); + QTC_CHECK(path.endsWith(QLatin1Char('/'))); + SMap::const_iterator end = m_resources.end(); + QStringList langs = allUiLanguages(locale); + foreach (const QString &language, langs) { + QString key = language + path; + SMap::const_iterator res = m_resources.lowerBound(key); + while (res != end && res.key().startsWith(key)) { + const QString &actualKey = res.key(); + int endDir = actualKey.indexOf(QLatin1Char('/'), key.size()); + if (endDir == -1) { + QString fileName = res.key().right(res.key().size()-key.size()); + QStringList &els = (*contents)[fileName]; + foreach (const QString &val, res.value()) + if (!els.contains(val)){ + els << val; + } + ++res; + } else { + QString dirName = res.key().mid(key.size(), endDir - key.size() + 1); + if (addDirs) + contents->insert(dirName, QStringList()); + QString key2 = key + dirName; + do { + ++res; + } while (res != end && res.key().startsWith(key2)); + } + } + } +} + +QStringList QrcParserPrivate::errorMessages() const +{ + return m_errorMessages; +} + +QStringList QrcParserPrivate::languages() const +{ + return m_languages; +} + +QString QrcParserPrivate::fixPrefix(const QString &prefix) +{ + const QChar slash = QLatin1Char('/'); + QString result = QString(slash); + for (int i = 0; i < prefix.size(); ++i) { + const QChar c = prefix.at(i); + if (c == slash && result.at(result.size() - 1) == slash) + continue; + result.append(c); + } + + if (!result.endsWith(slash)) + result.append(slash); + + return result; +} + +QStringList QrcParserPrivate::allUiLanguages(const QLocale *locale) const +{ + if (!locale) + return languages(); + QStringList langs = locale->uiLanguages(); + foreach (const QString &language, langs) { // qt4 support + if (language.contains(QLatin1Char('_')) || language.contains(QLatin1Char('-'))) { + QStringList splits = QString(language).replace(QLatin1Char('_'), QLatin1Char('-')) + .split(QLatin1Char('-')); + if (splits.size() > 1 && !langs.contains(splits.at(0))) + langs.append(splits.at(0)); + } + } + if (!langs.contains(QString())) + langs.append(QString()); + return langs; +} + +// ---------------- + +QrcCachePrivate::QrcCachePrivate(QrcCache *) +{ } + +QrcParser::Ptr QrcCachePrivate::addPath(const QString &path) +{ + QPair<QrcParser::Ptr,int> currentValue; + { + QMutexLocker l(&m_mutex); + currentValue = m_cache.value(path, qMakePair(QrcParser::Ptr(0), 0)); + currentValue.second += 1; + if (currentValue.second > 1) { + m_cache.insert(path, currentValue); + return currentValue.first; + } + } + QrcParser::Ptr newParser = QrcParser::parseQrcFile(path); + if (!newParser->isValid()) + qDebug() << "adding invalid qrc " << path << " to the cache:" << newParser->errorMessages(); + { + QMutexLocker l(&m_mutex); + QPair<QrcParser::Ptr,int> currentValue = m_cache.value(path, qMakePair(QrcParser::Ptr(0), 0)); + if (currentValue.first.isNull()) + currentValue.first = newParser; + currentValue.second += 1; + m_cache.insert(path, currentValue); + return currentValue.first; + } +} + +void QrcCachePrivate::removePath(const QString &path) +{ + QPair<QrcParser::Ptr,int> currentValue; + { + QMutexLocker l(&m_mutex); + currentValue = m_cache.value(path, qMakePair(QrcParser::Ptr(0), 0)); + if (currentValue.second == 1) { + m_cache.remove(path); + } else if (currentValue.second > 1) { + currentValue.second -= 1; + m_cache.insert(path, currentValue); + } else { + QTC_CHECK(!m_cache.contains(path)); + } + } +} + +QrcParser::Ptr QrcCachePrivate::updatePath(const QString &path) +{ + QrcParser::Ptr newParser = QrcParser::parseQrcFile(path); + { + QMutexLocker l(&m_mutex); + QPair<QrcParser::Ptr,int> currentValue = m_cache.value(path, qMakePair(QrcParser::Ptr(0), 0)); + QTC_CHECK(currentValue.second > 0); + currentValue.first = newParser; + m_cache.insert(path, currentValue); + return currentValue.first; + } +} + +QrcParser::Ptr QrcCachePrivate::parsedPath(const QString &path) +{ + QMutexLocker l(&m_mutex); + QPair<QrcParser::Ptr,int> currentValue = m_cache.value(path, qMakePair(QrcParser::Ptr(0), 0)); + return currentValue.first; +} + +void QrcCachePrivate::clear() +{ + QMutexLocker l(&m_mutex); + m_cache.clear(); +} + +} // namespace Internal +} // namespace QmlJS diff --git a/src/libs/qmljs/qmljsqrcparser.h b/src/libs/qmljs/qmljsqrcparser.h new file mode 100644 index 0000000000..e25baa2c73 --- /dev/null +++ b/src/libs/qmljs/qmljsqrcparser.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef QMLJSQRCPARSER_H +#define QMLJSQRCPARSER_H +#include "qmljs_global.h" + +#include <QMap> +#include <QSharedPointer> +#include <QString> +#include <QStringList> + +QT_FORWARD_DECLARE_CLASS(QLocale) + +namespace QmlJS { + +namespace Internal { +class QrcParserPrivate; +class QrcCachePrivate; +} + +class QMLJS_EXPORT QrcParser +{ +public: + typedef QSharedPointer<QrcParser> Ptr; + typedef QSharedPointer<const QrcParser> ConstPtr; + ~QrcParser(); + bool parseFile(const QString &path); + QString firstFileAtPath(const QString &path, const QLocale &locale) const; + void collectFilesAtPath(const QString &path, QStringList *res, const QLocale *locale = 0) const; + bool hasDirAtPath(const QString &path, const QLocale *locale = 0) const; + void collectFilesInPath(const QString &path, QMap<QString,QStringList> *res, bool addDirs = false, + const QLocale *locale = 0) const; + QStringList errorMessages() const; + QStringList languages() const; + bool isValid() const; + + static Ptr parseQrcFile(const QString &path); + static QString normalizedQrcFilePath(const QString &path); + static QString normalizedQrcDirectoryPath(const QString &path); +private: + QrcParser(); + QrcParser(const QrcParser &); + Internal::QrcParserPrivate *d; +}; + +class QMLJS_EXPORT QrcCache +{ +public: + QrcCache(); + ~QrcCache(); + QrcParser::ConstPtr addPath(const QString &path); + void removePath(const QString &path); + QrcParser::ConstPtr updatePath(const QString &path); + QrcParser::ConstPtr parsedPath(const QString &path); + void clear(); +private: + Internal::QrcCachePrivate *d; +}; +} +#endif // QMLJSQRCPARSER_H diff --git a/src/libs/qmljs/qmljsscopechain.cpp b/src/libs/qmljs/qmljsscopechain.cpp index 4c6ff96c93..a0a9bf0f91 100644 --- a/src/libs/qmljs/qmljsscopechain.cpp +++ b/src/libs/qmljs/qmljsscopechain.cpp @@ -30,6 +30,7 @@ #include "qmljsscopechain.h" #include "qmljsbind.h" #include "qmljsevaluate.h" +#include "qmljsmodelmanagerinterface.h" using namespace QmlJS; @@ -310,7 +311,10 @@ void ScopeChain::initializeRootScope() 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()) { + if ((import.type() == ImportInfo::FileImport && m_document->fileName() == import.path()) + || (import.type() == ImportInfo::QrcFileImport + && ModelManagerInterface::instance()->filesAtQrcPath(import.path()) + .contains(m_document->fileName()))) { QmlComponentChain *component = new QmlComponentChain(otherDoc); componentScopes.insert(otherDoc.data(), component); chain->addInstantiatingComponent(component); diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index 50120f9fa4..9956ecab1a 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -47,6 +47,7 @@ #include <qmljs/qmljscheck.h> #include <qmljs/qmljsutils.h> #include <qmljs/qmljsmodelmanagerinterface.h> +#include <qmljs/qmljsqrcparser.h> #include <QSet> #include <QDir> @@ -362,10 +363,17 @@ public: } else if (importInfo.isValid() && importInfo.type() == ImportInfo::DirectoryImport) { QString path = importInfo.path(); QDir dir(m_doc->path()); + // should probably try to make it relatve to some import path, not to the document path QString relativeDir = dir.relativeFilePath(path); QString name = relativeDir.replace(QLatin1Char('/'), QLatin1Char('.')); if (!name.isEmpty()) typeName.prepend(name + QLatin1Char('.')); + } else if (importInfo.isValid() && importInfo.type() == ImportInfo::QrcDirectoryImport) { + QString path = QrcParser::normalizedQrcDirectoryPath(importInfo.path()); + path = path.mid(1, path.size() - ((path.size() > 1) ? 2 : 1)); + const QString name = path.replace(QLatin1Char('/'), QLatin1Char('.')); + if (!name.isEmpty()) + typeName.prepend(name + QLatin1Char('.')); } } } diff --git a/src/plugins/qmljseditor/qmljshoverhandler.cpp b/src/plugins/qmljseditor/qmljshoverhandler.cpp index 7e816cf397..c33c1d13cd 100644 --- a/src/plugins/qmljseditor/qmljshoverhandler.cpp +++ b/src/plugins/qmljseditor/qmljshoverhandler.cpp @@ -43,6 +43,7 @@ #include <qmljs/parser/qmljsast_p.h> #include <qmljs/parser/qmljsastfwd_p.h> #include <qmljs/qmljsutils.h> +#include <qmljs/qmljsqrcparser.h> #include <texteditor/itexteditor.h> #include <texteditor/basetexteditor.h> #include <texteditor/helpitem.h> @@ -134,9 +135,15 @@ static inline QString getModuleName(const ScopeChain &scopeChain, const Document } else if (importInfo.isValid() && importInfo.type() == ImportInfo::DirectoryImport) { const QString path = importInfo.path(); const QDir dir(qmlDocument->path()); + // should probably try to make it relatve to some import path, not to the document path QString relativeDir = dir.relativeFilePath(path); const QString name = relativeDir.replace(QLatin1Char('/'), QLatin1Char('.')); return name; + } else if (importInfo.isValid() && importInfo.type() == ImportInfo::QrcDirectoryImport) { + QString path = QrcParser::normalizedQrcDirectoryPath(importInfo.path()); + path = path.mid(1, path.size() - ((path.size() > 1) ? 2 : 1)); + const QString name = path.replace(QLatin1Char('/'), QLatin1Char('.')); + return name; } } return QString(); diff --git a/src/plugins/qmljstools/qmljsmodelmanager.cpp b/src/plugins/qmljstools/qmljsmodelmanager.cpp index 07e9ec06c1..d2c25d49f7 100644 --- a/src/plugins/qmljstools/qmljsmodelmanager.cpp +++ b/src/plugins/qmljstools/qmljsmodelmanager.cpp @@ -58,6 +58,7 @@ #include <QTextStream> #include <QTimer> #include <QRegExp> +#include <QtAlgorithms> #include <QDebug> @@ -406,6 +407,114 @@ void ModelManager::removeFiles(const QStringList &files) } } +namespace { +bool pInfoLessThanActive(const ModelManager::ProjectInfo &p1, const ModelManager::ProjectInfo &p2) +{ + QStringList s1 = p1.activeResourceFiles; + QStringList s2 = p2.activeResourceFiles; + if (s1.size() < s2.size()) + return true; + if (s1.size() > s2.size()) + return false; + for (int i = 0; i < s1.size(); ++i) { + if (s1.at(i) < s2.at(i)) + return true; + else if (s1.at(i) > s2.at(i)) + return false; + } + return false; +} + +bool pInfoLessThanAll(const ModelManager::ProjectInfo &p1, const ModelManager::ProjectInfo &p2) +{ + QStringList s1 = p1.allResourceFiles; + QStringList s2 = p2.allResourceFiles; + if (s1.size() < s2.size()) + return true; + if (s1.size() > s2.size()) + return false; + for (int i = 0; i < s1.size(); ++i) { + if (s1.at(i) < s2.at(i)) + return true; + else if (s1.at(i) > s2.at(i)) + return false; + } + return false; +} +} + +QStringList ModelManager::filesAtQrcPath(const QString &path, const QLocale *locale, + ProjectExplorer::Project *project, + QrcResourceSelector resources) +{ + QString normPath = QrcParser::normalizedQrcFilePath(path); + QList<ProjectInfo> pInfos; + if (project) + pInfos.append(projectInfo(project)); + else + pInfos = projectInfos(); + + QStringList res; + QSet<QString> pathsChecked; + foreach (const ModelManager::ProjectInfo &pInfo, pInfos) { + QStringList qrcFilePaths; + if (resources == ActiveQrcResources) + qrcFilePaths = pInfo.activeResourceFiles; + else + qrcFilePaths = pInfo.allResourceFiles; + foreach (const QString &qrcFilePath, qrcFilePaths) { + if (pathsChecked.contains(qrcFilePath)) + continue; + pathsChecked.insert(qrcFilePath); + QrcParser::ConstPtr qrcFile = m_qrcCache.parsedPath(qrcFilePath); + if (qrcFile.isNull()) + continue; + qrcFile->collectFilesAtPath(normPath, &res, locale); + } + } + res.sort(); // make the result predictable + return res; +} + +QMap<QString, QStringList> ModelManager::filesInQrcPath(const QString &path, + const QLocale *locale, + ProjectExplorer::Project *project, + bool addDirs, + QrcResourceSelector resources) +{ + QString normPath = QrcParser::normalizedQrcDirectoryPath(path); + QList<ProjectInfo> pInfos; + if (project) { + pInfos.append(projectInfo(project)); + } else { + pInfos = projectInfos(); + if (resources == ActiveQrcResources) // make the result predictable + qSort(pInfos.begin(), pInfos.end(), &pInfoLessThanActive); + else + qSort(pInfos.begin(), pInfos.end(), &pInfoLessThanAll); + } + QMap<QString, QStringList> res; + QSet<QString> pathsChecked; + foreach (const ModelManager::ProjectInfo &pInfo, pInfos) { + QStringList qrcFilePaths; + if (resources == ActiveQrcResources) + qrcFilePaths = pInfo.activeResourceFiles; + else + qrcFilePaths = pInfo.allResourceFiles; + foreach (const QString &qrcFilePath, qrcFilePaths) { + if (pathsChecked.contains(qrcFilePath)) + continue; + pathsChecked.insert(qrcFilePath); + QrcParser::ConstPtr qrcFile = m_qrcCache.parsedPath(qrcFilePath); + + if (qrcFile.isNull()) + continue; + qrcFile->collectFilesInPath(normPath, &res, addDirs, locale); + } + } + return res; +} + QList<ModelManager::ProjectInfo> ModelManager::projectInfos() const { QMutexLocker locker(&m_mutex); @@ -462,6 +571,12 @@ void ModelManager::updateProjectInfo(const ProjectInfo &pinfo) } updateSourceFiles(newFiles, false); + // update qrc cache + foreach (const QString &newQrc, pinfo.allResourceFiles) + m_qrcCache.addPath(newQrc); + foreach (const QString &oldQrc, oldInfo.allResourceFiles) + m_qrcCache.removePath(oldQrc); + // dump builtin types if the shipped definitions are probably outdated and the // Qt version ships qmlplugindump if (QtSupport::QtVersionNumber(pinfo.qtVersionString) > QtSupport::QtVersionNumber(4, 8, 5)) @@ -497,6 +612,11 @@ ModelManagerInterface::ProjectInfo ModelManager::projectInfoForPath(QString path void ModelManager::emitDocumentChangedOnDisk(Document::Ptr doc) { emit documentChangedOnDisk(doc); } +void ModelManager::updateQrcFile(const QString &path) +{ + m_qrcCache.updatePath(path); +} + void ModelManager::updateDocument(Document::Ptr doc) { { @@ -560,6 +680,23 @@ static void findNewFileImports(const Document::Ptr &doc, const Snapshot &snapsho scannedPaths->insert(importName); } } + } else if (import.type() == ImportInfo::QrcFileImport) { + QStringList importPaths = ModelManagerInterface::instance()->filesAtQrcPath(importName); + foreach (const QString &importPath, importPaths) { + if (! snapshot.document(importPath)) + *importedFiles += importPath; + } + } else if (import.type() == ImportInfo::QrcDirectoryImport) { + QMapIterator<QString,QStringList> dirContents(ModelManagerInterface::instance()->filesInQrcPath(importName)); + while (dirContents.hasNext()) { + dirContents.next(); + if (Document::isQmlLikeOrJsLanguage(Document::guessLanguageFromSuffix(dirContents.key()))) { + foreach (const QString &filePath, dirContents.value()) { + if (! snapshot.document(filePath)) + *importedFiles += filePath; + } + } + } } } } @@ -697,8 +834,11 @@ void ModelManager::parse(QFutureInterface<void> &future, const QString fileName = files.at(i); Document::Language language = languageOfFile(fileName); - if (language == Document::UnknownLanguage) + if (language == Document::UnknownLanguage) { + if (fileName.endsWith(QLatin1String(".qrc"))) + modelManager->updateQrcFile(fileName); continue; + } QString contents; int documentRevision = 0; diff --git a/src/plugins/qmljstools/qmljsmodelmanager.h b/src/plugins/qmljstools/qmljsmodelmanager.h index 3b0763f904..1c42c03882 100644 --- a/src/plugins/qmljstools/qmljsmodelmanager.h +++ b/src/plugins/qmljstools/qmljsmodelmanager.h @@ -33,6 +33,7 @@ #include "qmljstools_global.h" #include <qmljs/qmljsmodelmanagerinterface.h> +#include <qmljs/qmljsqrcparser.h> #include <cplusplus/CppDocument.h> @@ -41,6 +42,7 @@ #include <QMutex> QT_FORWARD_DECLARE_CLASS(QTimer) +QT_FORWARD_DECLARE_CLASS(QLocale) namespace Core { class MimeType; @@ -50,6 +52,10 @@ namespace CPlusPlus { class CppModelManagerInterface; } +namespace QmlJS { +class QrcParser; +} + namespace QmlJSTools { QMLJSTOOLS_EXPORT QmlJS::Document::Language languageOfFile(const QString &fileName); @@ -77,6 +83,14 @@ public: bool emitDocumentOnDiskChanged); virtual void fileChangedOnDisk(const QString &path); virtual void removeFiles(const QStringList &files); + virtual QStringList filesAtQrcPath(const QString &path, const QLocale *locale = 0, + ProjectExplorer::Project *project = 0, + QrcResourceSelector resources = AllQrcResources); + virtual QMap<QString,QStringList> filesInQrcPath(const QString &path, + const QLocale *locale = 0, + ProjectExplorer::Project *project = 0, + bool addDirs = false, + QrcResourceSelector resources = AllQrcResources); virtual QList<ProjectInfo> projectInfos() const; virtual ProjectInfo projectInfo(ProjectExplorer::Project *project) const; @@ -87,6 +101,7 @@ public: void updateDocument(QmlJS::Document::Ptr doc); void updateLibraryInfo(const QString &path, const QmlJS::LibraryInfo &info); void emitDocumentChangedOnDisk(QmlJS::Document::Ptr doc); + void updateQrcFile(const QString &path); virtual QStringList importPaths() const; virtual QmlJS::QmlLanguageBundles activeBundles() const; @@ -147,6 +162,7 @@ private: QTimer *m_updateCppQmlTypesTimer; QHash<QString, QPair<CPlusPlus::Document::Ptr, bool> > m_queuedCppDocuments; QFuture<void> m_cppQmlTypesUpdater; + QmlJS::QrcCache m_qrcCache; CppDataHash m_cppDataHash; mutable QMutex m_cppDataMutex; diff --git a/src/plugins/qt4projectmanager/qt4nodes.cpp b/src/plugins/qt4projectmanager/qt4nodes.cpp index 92954b6be8..2553c1f25d 100644 --- a/src/plugins/qt4projectmanager/qt4nodes.cpp +++ b/src/plugins/qt4projectmanager/qt4nodes.cpp @@ -1886,6 +1886,10 @@ void Qt4ProFileNode::applyEvaluate(EvalResult evalResult, bool async) QLatin1String("OBJECTIVE_HEADERS"), m_projectDir, buildDirectory); newVarValues[UiDirVar] = QStringList() << uiDirPath(m_readerExact); newVarValues[MocDirVar] = QStringList() << mocDirPath(m_readerExact); + newVarValues[ResourceVar] = fileListForVar(m_readerExact, m_readerCumulative, + QLatin1String("RESOURCES"), m_projectDir, buildDirectory); + newVarValues[ExactResourceVar] = fileListForVar(m_readerExact, 0, + QLatin1String("RESOURCES"), m_projectDir, buildDirectory); newVarValues[PkgConfigVar] = m_readerExact->values(QLatin1String("PKGCONFIG")); newVarValues[PrecompiledHeaderVar] = m_readerExact->absoluteFileValues(QLatin1String("PRECOMPILED_HEADER"), diff --git a/src/plugins/qt4projectmanager/qt4nodes.h b/src/plugins/qt4projectmanager/qt4nodes.h index c4d01190bb..745b745966 100644 --- a/src/plugins/qt4projectmanager/qt4nodes.h +++ b/src/plugins/qt4projectmanager/qt4nodes.h @@ -86,6 +86,8 @@ enum Qt4Variable { CppSourceVar, ObjCSourceVar, ObjCHeaderVar, + ResourceVar, + ExactResourceVar, UiDirVar, MocDirVar, PkgConfigVar, diff --git a/src/plugins/qt4projectmanager/qt4project.cpp b/src/plugins/qt4projectmanager/qt4project.cpp index 123dbca22d..b2efd28323 100644 --- a/src/plugins/qt4projectmanager/qt4project.cpp +++ b/src/plugins/qt4projectmanager/qt4project.cpp @@ -627,6 +627,8 @@ void Qt4Project::updateQmlJSCodeModel() bool hasQmlLib = false; foreach (Qt4ProFileNode *node, proFiles) { projectInfo.importPaths.append(node->variableValue(QmlImportPathVar)); + projectInfo.activeResourceFiles.append(node->variableValue(ExactResourceVar)); + projectInfo.allResourceFiles.append(node->variableValue(ResourceVar)); if (!hasQmlLib) { QStringList qtLibs = node->variableValue(QtVar); hasQmlLib = qtLibs.contains(QLatin1String("declarative")) || @@ -645,6 +647,8 @@ void Qt4Project::updateQmlJSCodeModel() setProjectLanguages(pl); projectInfo.importPaths.removeDuplicates(); + projectInfo.activeResourceFiles.removeDuplicates(); + projectInfo.allResourceFiles.removeDuplicates(); setProjectLanguage(ProjectExplorer::Constants::LANG_QMLJS, !projectInfo.sourceFiles.isEmpty()); diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro index 466be4b27b..5b320abd5e 100644 --- a/tests/auto/qml/qml.pro +++ b/tests/auto/qml/qml.pro @@ -4,4 +4,5 @@ SUBDIRS += qmldesigner \ qmleditor \ qmlprojectmanager \ codemodel \ - reformatter + reformatter \ + qrcparser diff --git a/tests/auto/qml/qrcparser/qrcparser.pro b/tests/auto/qml/qrcparser/qrcparser.pro new file mode 100644 index 0000000000..a051c8926c --- /dev/null +++ b/tests/auto/qml/qrcparser/qrcparser.pro @@ -0,0 +1,11 @@ +#include(../shared/shared.pri) +QTC_PLUGIN_DEPENDS += qmljstools +include(../../qttest.pri) +#DEFINES+=CPLUSPLUS_BUILD_STATIC_LIB +include($$IDE_SOURCE_TREE/src/rpath.pri) + +LIBS += -L$$IDE_PLUGIN_PATH/QtProject +#DEFINES += Q_PLUGIN_PATH=\"\\\"$$IDE_PLUGIN_PATH/QtProject\\\"\" + +DEFINES += TESTSRCDIR=\\\"$$PWD\\\" +SOURCES += $$PWD/tst_qrcparser.cpp diff --git a/tests/auto/qml/qrcparser/simple.qrc b/tests/auto/qml/qrcparser/simple.qrc new file mode 100644 index 0000000000..add0b4e0cf --- /dev/null +++ b/tests/auto/qml/qrcparser/simple.qrc @@ -0,0 +1,17 @@ + <!DOCTYPE RCC><RCC version="1.0"> + <qresource> + <file>images/copy.png</file> + <file>images/cut.png</file> + <file>images/new.png</file> + <file>images/open.png</file> + <file>images/paste.png</file> + <file>images/save.png</file> + <file>cut.jpg</file> +</qresource> + <qresource lang="fr"> + <file alias="cut.jpg">cut_fr.jpg</file> + </qresource> + <qresource prefix="/myresources"> + <file alias="cut-img.png">images/cut.png</file> + </qresource> + </RCC>
\ No newline at end of file diff --git a/tests/auto/qml/qrcparser/tst_qrcparser.cpp b/tests/auto/qml/qrcparser/tst_qrcparser.cpp new file mode 100644 index 0000000000..b642e5a163 --- /dev/null +++ b/tests/auto/qml/qrcparser/tst_qrcparser.cpp @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** 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 <QtTest> +#include <QDebug> +#include <QLocale> + +#include <qmljs/qmljsqrcparser.h> + +using namespace QmlJS; + +class tst_QrcParser: public QObject +{ + Q_OBJECT +public: + void readInData(); + QStringList allPaths(QrcParser::ConstPtr p); +private slots: + void firstAtTest_data() { readInData(); } + void firstInTest_data() { readInData(); } + void cacheTest_data() { readInData(); } + void firstAtTest(); + void firstInTest(); + void cacheTest(); + void simpleTest(); + void cleanupTestCase(); +private: + QLocale m_locale; + QrcCache m_cache; +}; + +void tst_QrcParser::readInData() +{ + QTest::addColumn<QString>("path"); + + QDirIterator it(TESTSRCDIR, QStringList("*.qrc"), QDir::Files); + while (it.hasNext()) { + const QString fileName = it.next(); + QTest::newRow(fileName.toLatin1()) << it.filePath(); + } +} + +QStringList tst_QrcParser::allPaths(QrcParser::ConstPtr p) +{ + QStringList res; + res << QLatin1String("/"); + int iPos = 0; + while (iPos < res.size()) { + QString pAtt = res.at(iPos++); + if (!pAtt.endsWith(QLatin1Char('/'))) + continue; + QMap<QString,QStringList> content; + p->collectFilesInPath(pAtt, &content, true); + foreach (const QString &fileName, content.keys()) + res.append(pAtt+fileName); + } + return res; +} + +void tst_QrcParser::firstAtTest() +{ + QFETCH(QString, path); + QrcParser::Ptr p = QrcParser::parseQrcFile(path); + foreach (const QString &qrcPath, allPaths(p)) { + QString s1 = p->firstFileAtPath(qrcPath, m_locale); + if (s1.isEmpty()) + continue; + QStringList l; + p->collectFilesAtPath(qrcPath, &l, &m_locale); + QCOMPARE(l.value(0), s1); + l.clear(); + p->collectFilesAtPath(qrcPath, &l); + QVERIFY(l.contains(s1)); + } +} + +void tst_QrcParser::firstInTest() +{ + QFETCH(QString, path); + QrcParser::Ptr p = QrcParser::parseQrcFile(path); + foreach (const QString &qrcPath, allPaths(p)) { + if (!qrcPath.endsWith(QLatin1Char('/'))) + continue; + for (int addDirs = 0; addDirs < 2; ++addDirs) { + QMap<QString,QStringList> s1; + p->collectFilesInPath(qrcPath, &s1, addDirs, &m_locale); + foreach (const QString &k, s1.keys()) { + if (!k.endsWith(QLatin1Char('/'))) { + QCOMPARE(s1.value(k).value(0), p->firstFileAtPath(qrcPath + k, m_locale)); + } + } + QMap<QString,QStringList> s2; + p->collectFilesInPath(qrcPath, &s2, addDirs); + foreach (const QString &k, s1.keys()) { + if (!k.endsWith(QLatin1Char('/'))) { + QVERIFY(s2.value(k).contains(s1.value(k).at(0))); + } else { + QVERIFY(s2.contains(k)); + } + } + foreach (const QString &k, s2.keys()) { + if (!k.endsWith(QLatin1Char('/'))) { + QStringList l; + p->collectFilesAtPath(qrcPath + k, &l); + QCOMPARE(s2.value(k), l); + } else { + QVERIFY(s2.value(k).isEmpty()); + } + } + } + } +} + +void tst_QrcParser::cacheTest() +{ + QFETCH(QString, path); + QVERIFY(m_cache.parsedPath(path).isNull()); + QrcParser::ConstPtr p0 = m_cache.addPath(path); + QVERIFY(!p0.isNull()); + QrcParser::ConstPtr p1 = m_cache.parsedPath(path); + QVERIFY(p1.data() == p0.data()); + QrcParser::ConstPtr p2 = m_cache.addPath(path); + QVERIFY(p2.data() == p1.data()); + QrcParser::ConstPtr p3 = m_cache.parsedPath(path); + QVERIFY(p3.data() == p2.data()); + QrcParser::ConstPtr p4 = m_cache.updatePath(path); + QVERIFY(p4.data() != p3.data()); + QrcParser::ConstPtr p5 = m_cache.parsedPath(path); + QVERIFY(p5.data() == p4.data()); + m_cache.removePath(path); + QrcParser::ConstPtr p6 = m_cache.parsedPath(path); + QVERIFY(p6.data() == p5.data()); + m_cache.removePath(path); + QrcParser::ConstPtr p7 = m_cache.parsedPath(path); + QVERIFY(p7.isNull()); +} + +void tst_QrcParser::simpleTest() +{ + QrcParser::Ptr p = QrcParser::parseQrcFile(QString::fromLatin1(TESTSRCDIR).append(QLatin1String("/simple.qrc"))); + QStringList paths = allPaths(p); + paths.sort(); + QVERIFY(paths == QStringList() + << QLatin1String("/") + << QLatin1String("/cut.jpg") + << QLatin1String("/images/") + << QLatin1String("/images/copy.png") + << QLatin1String("/images/cut.png") + << QLatin1String("/images/new.png") + << QLatin1String("/images/open.png") + << QLatin1String("/images/paste.png") + << QLatin1String("/images/save.png") + << QLatin1String("/myresources/") + << QLatin1String("/myresources/cut-img.png")); + QString frPath = p->firstFileAtPath(QLatin1String("/cut.jpg"), QLocale(QLatin1String("fr_FR"))); + QString refFrPath = QString::fromLatin1(TESTSRCDIR).append(QLatin1String("/cut_fr.jpg")); + QCOMPARE(frPath, refFrPath); + QString dePath = p->firstFileAtPath(QLatin1String("/cut.jpg"), QLocale(QLatin1String("de_DE"))); + QString refDePath = QString::fromLatin1(TESTSRCDIR).append(QLatin1String("/cut.jpg")); + QCOMPARE(dePath, refDePath); +} + +void tst_QrcParser::cleanupTestCase() +{ + m_cache.clear(); +} + +QTEST_APPLESS_MAIN(tst_QrcParser) + +#include "tst_qrcparser.moc" |