From 1556a707c32a0b101edc11e5e04c257f353ef718 Mon Sep 17 00:00:00 2001 From: Marco Benelli Date: Fri, 29 Apr 2016 16:25:17 +0200 Subject: [PATCH] QmlJs: search for version in parent modules. Modules used to be searched by name with optionally an attached version. Now, if a module with version is not found, the version is attached to the parent module. Task-number: QTCREATORBUG-16145 Change-Id: Ie0f30d4df64d13b3ec4c5ee38e9ad9215ae56420 Reviewed-by: Thomas Hartmann --- src/libs/qmljs/qmljslink.cpp | 34 ++----------------- src/libs/qmljs/qmljsutils.cpp | 63 +++++++++++++++++++++++++++++++++++ src/libs/qmljs/qmljsutils.h | 5 +++ 3 files changed, 70 insertions(+), 32 deletions(-) diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp index 1d9b633370a..1d084bf395d 100644 --- a/src/libs/qmljs/qmljslink.cpp +++ b/src/libs/qmljs/qmljslink.cpp @@ -377,38 +377,8 @@ Import LinkPrivate::importNonFile(Document::Ptr doc, const ImportInfo &importInf const QString packageName = importInfo.name(); const ComponentVersion version = importInfo.version(); - bool importFound = false; - - const QString &packagePath = importInfo.path(); - // check the filesystem with full version - foreach (const QString &importPath, importPaths) { - QString libraryPath = QString::fromLatin1("%1/%2.%3").arg(importPath, packagePath, version.toString()); - if (importLibrary(doc, libraryPath, &import, importPath)) { - importFound = true; - break; - } - } - if (!importFound) { - // check the filesystem with major version - foreach (const QString &importPath, importPaths) { - QString libraryPath = QString::fromLatin1("%1/%2.%3").arg(importPath, packagePath, - QString::number(version.majorVersion())); - if (importLibrary(doc, libraryPath, &import, importPath)) { - importFound = true; - break; - } - } - } - if (!importFound) { - // check the filesystem with no version - foreach (const QString &importPath, importPaths) { - QString libraryPath = QString::fromLatin1("%1/%2").arg(importPath, packagePath); - if (importLibrary(doc, libraryPath, &import, importPath)) { - importFound = true; - break; - } - } - } + QString libraryPath = modulePath(packageName, version.toString(), importPaths); + bool importFound = importLibrary(doc, libraryPath, &import); // if there are cpp-based types for this package, use them too if (valueOwner->cppQmlTypes().hasModule(packageName)) { diff --git a/src/libs/qmljs/qmljsutils.cpp b/src/libs/qmljs/qmljsutils.cpp index 8fbfaf39be9..98deff530ac 100644 --- a/src/libs/qmljs/qmljsutils.cpp +++ b/src/libs/qmljs/qmljsutils.cpp @@ -28,6 +28,8 @@ #include "parser/qmljsast_p.h" #include +#include +#include using namespace QmlJS; using namespace QmlJS::AST; @@ -195,6 +197,67 @@ DiagnosticMessage QmlJS::errorMessage(const AST::SourceLocation &loc, const QStr return DiagnosticMessage(Severity::Error, loc, message); } +/*! + * \brief Permissive validation of a string representing a module version. + * \param version + * \return True if \p version is a valid version format (.) or if it is empty. + * False otherwise. + */ +bool QmlJS::maybeModuleVersion(const QString &version) { + QRegularExpression re(QLatin1String("^\\d+\\.\\d+$")); + return version.isEmpty() || re.match(version).hasMatch(); +} + +/*! + * \brief Get the path of a module + * \param name + * \param version + * \param importPaths + * + * Given the qualified \p name and \p version of a module, look for a valid path in \p importPaths. + * Most specific version are searched first, the version is searched also in parent modules. + * For example, given the \p name QtQml.Models and \p version 2.0, the following directories are + * searched in every element of \p importPath: + * + * - QtQml/Models.2.0 + * - QtQml.2.0/Models + * - QtQml/Models.2 + * - QtQml.2/Models + * - QtQml/Models + * + * \return The module paths if found, an empty string otherwise + * \see qmlimportscanner in qtdeclarative/tools + */ +QString QmlJS::modulePath(const QString &name, const QString &version, + const QStringList &importPaths) +{ + Q_ASSERT(maybeModuleVersion(version)); + + const QStringList parts = name.split(QLatin1Char('.'), QString::SkipEmptyParts); + auto mkpath = [] (const QStringList &xs) -> QString { return xs.join(QLatin1Char('/')); }; + + QString candidate; + + for (QString ver = version; ; ver.remove(QRegularExpression(QLatin1String("\\.?\\d+$")))) { + for (const QString &path: importPaths) { + if (ver.isEmpty()) { + candidate = QDir::cleanPath(QString::fromLatin1("%1/%2").arg(path, mkpath(parts))); + return QDir(candidate).exists() ? candidate : QString(); + } else { + for (int i = parts.count() - 1; i >= 0; --i) { + candidate = QDir::cleanPath( + QString::fromLatin1("%1/%2.%3/%4").arg(path, + mkpath(parts.mid(0, i + 1)), + ver, + mkpath(parts.mid(i + 1)))); + if (QDir(candidate).exists()) + return candidate; + } + } + } + } +} + bool QmlJS::isValidBuiltinPropertyType(const QString &name) { return sharedData()->validBuiltinPropertyNames.contains(name); diff --git a/src/libs/qmljs/qmljsutils.h b/src/libs/qmljs/qmljsutils.h index dbbfbd39957..a9da56ed2d2 100644 --- a/src/libs/qmljs/qmljsutils.h +++ b/src/libs/qmljs/qmljsutils.h @@ -54,6 +54,11 @@ QMLJS_EXPORT bool isValidBuiltinPropertyType(const QString &name); QMLJS_EXPORT DiagnosticMessage errorMessage(const AST::SourceLocation &loc, const QString &message); +QMLJS_EXPORT bool maybeModuleVersion(const QString &version); + +QMLJS_EXPORT QString modulePath(const QString &moduleImportName, const QString &version, + const QStringList &importPaths); + template AST::SourceLocation locationFromRange(const T *node) {