From 95de5c93b1ca2944558b7c3b4666e7cb093d93b6 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Wed, 23 Jun 2021 10:27:08 +0200 Subject: [PATCH] QmlJS: Fix issues with getting modulePaths This patch - improves performance by removing the usage of QRegularExpression for trivial string operations (this is called 3000 times after configuring Qt Creator) - fixes handling of version number like "2.-1" which are the result of imports with only a major version number like "import QtQuick 2" Task-number: QTCREATORBUG-25899 Fixes: QTCREATORBUG-26178 Fixes: QTCREATORBUG-26216 Change-Id: Ic792909513f4fe25ac72043645f297ee41890375 Reviewed-by: Fawzi Mohamed Reviewed-by: Jarek Kobus --- src/libs/qmljs/qmljsutils.cpp | 44 ++++++++++---- src/libs/qmljs/qmljsutils.h | 1 + tests/auto/qml/CMakeLists.txt | 1 + tests/auto/qml/qmljsutils/CMakeLists.txt | 5 ++ tests/auto/qml/qmljsutils/tst_qmljsutils.cpp | 62 ++++++++++++++++++++ 5 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 tests/auto/qml/qmljsutils/CMakeLists.txt create mode 100644 tests/auto/qml/qmljsutils/tst_qmljsutils.cpp diff --git a/src/libs/qmljs/qmljsutils.cpp b/src/libs/qmljs/qmljsutils.cpp index d1dd0d8b1c6..db2bcc69689 100644 --- a/src/libs/qmljs/qmljsutils.cpp +++ b/src/libs/qmljs/qmljsutils.cpp @@ -213,6 +213,29 @@ bool QmlJS::maybeModuleVersion(const QString &version) { return version.isEmpty() || version == undefinedVersion || re.match(version).hasMatch(); } +const QStringList QmlJS::splitVersion(const QString &version) +{ + // Successively removing minor and major version numbers. + QStringList result; + int versionEnd = version.length(); + while (versionEnd > 0) { + result.append(version.left(versionEnd)); + // remove numbers and then potential . at the end + const int oldVersionEnd = versionEnd; + while (versionEnd > 0 && version.at(versionEnd - 1).isDigit()) + --versionEnd; + // handle e.g. -1, because an import "QtQuick 2" results in version "2.-1" + if (versionEnd > 0 && version.at(versionEnd - 1) == '-') + --versionEnd; + if (versionEnd > 0 && version.at(versionEnd - 1) == '.') + --versionEnd; + // bail out if we didn't proceed because version string contains invalid characters + if (versionEnd == oldVersionEnd) + break; + } + return result; +} + /*! * \brief Get the path of a module * \param name @@ -242,24 +265,19 @@ QStringList QmlJS::modulePaths(const QString &name, const QString &version, const QString sanitizedVersion = version == undefinedVersion ? QString() : version; const QStringList parts = name.split('.', Qt::SkipEmptyParts); - auto mkpath = [] (const QStringList &xs) -> QString { return xs.join(QLatin1Char('/')); }; - - // Regular expression for building candidates by successively removing minor and major - // version numbers. It does not match the undefined version, so it has to be applied to the - // sanitized version. - const QRegularExpression re("\\.?\\d+$"); + auto mkpath = [](const QStringList &xs) -> QString { return xs.join(QLatin1Char('/')); }; QStringList result; QString candidate; - for (QString ver = sanitizedVersion; !ver.isEmpty(); ver.remove(re)) { - for (const QString &path: importPaths) { + for (const QString &versionPart : splitVersion(sanitizedVersion)) { + for (const QString &path : importPaths) { 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)))); + candidate = QDir::cleanPath(QString::fromLatin1("%1/%2.%3/%4") + .arg(path, + mkpath(parts.mid(0, i + 1)), + versionPart, + mkpath(parts.mid(i + 1)))); if (QDir(candidate).exists()) result << candidate; } diff --git a/src/libs/qmljs/qmljsutils.h b/src/libs/qmljs/qmljsutils.h index 23656ac2cba..5f645994d3c 100644 --- a/src/libs/qmljs/qmljsutils.h +++ b/src/libs/qmljs/qmljsutils.h @@ -57,6 +57,7 @@ QMLJS_EXPORT DiagnosticMessage errorMessage(const SourceLocation &loc, QMLJS_EXPORT bool maybeModuleVersion(const QString &version); +QMLJS_EXPORT const QStringList splitVersion(const QString &version); QMLJS_EXPORT QStringList modulePaths(const QString &moduleImportName, const QString &version, const QStringList &importPaths); diff --git a/tests/auto/qml/CMakeLists.txt b/tests/auto/qml/CMakeLists.txt index 2a7ed89ff14..a1fbe474570 100644 --- a/tests/auto/qml/CMakeLists.txt +++ b/tests/auto/qml/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(persistenttrie) add_subdirectory(qmldesigner) add_subdirectory(qmleditor) add_subdirectory(qmljssimplereader) +add_subdirectory(qmljsutils) add_subdirectory(qmlprojectmanager) add_subdirectory(qrcparser) add_subdirectory(reformatter) diff --git a/tests/auto/qml/qmljsutils/CMakeLists.txt b/tests/auto/qml/qmljsutils/CMakeLists.txt new file mode 100644 index 00000000000..e4be6be53f5 --- /dev/null +++ b/tests/auto/qml/qmljsutils/CMakeLists.txt @@ -0,0 +1,5 @@ +add_qtc_test(tst_qmljstools + DEPENDS QmlJS + DEFINES + SOURCES tst_qmljsutils.cpp +) diff --git a/tests/auto/qml/qmljsutils/tst_qmljsutils.cpp b/tests/auto/qml/qmljsutils/tst_qmljsutils.cpp new file mode 100644 index 00000000000..311acc64908 --- /dev/null +++ b/tests/auto/qml/qmljsutils/tst_qmljsutils.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include +#include + +#include + +class tst_QmlJSUtils: public QObject +{ + Q_OBJECT + +private slots: + void moduleVersionNumbers_data(); + void moduleVersionNumbers(); +}; + +void tst_QmlJSUtils::moduleVersionNumbers_data() +{ + QTest::addColumn("version"); + QTest::addColumn("result"); + + QTest::newRow("empty") << "" << QStringList(); + QTest::newRow("full") << "2.15" << QStringList{"2.15", "2"}; + QTest::newRow("single") << "2" << QStringList{"2"}; + // result if "import QtQuick 2": + QTest::newRow("major") << "2.-1" << QStringList{"2.-1", "2"}; + QTest::newRow("broken") << "2.+3" << QStringList{"2.+3", "2.+"}; +} + +void tst_QmlJSUtils::moduleVersionNumbers() +{ + QFETCH(QString, version); + QFETCH(QStringList, result); + QCOMPARE(QmlJS::splitVersion(version), result); +} + +QTEST_GUILESS_MAIN(tst_QmlJSUtils) + +#include "tst_qmljsutils.moc"