From 608b065378faaf0154a75a6db7372ed7cd7c8c3f Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Thu, 22 May 2025 14:12:33 +0200 Subject: [PATCH] QmlDesigner: Add function to locate import insertion point in QML text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch introduces `find_import_location`, which determines the appropriate location to insert a new `import` statement in a QML file. It scans the header section of the file, checks for existing imports, and avoids inserting duplicates when the target import already exists without an alias. The function returns an iterator pointing to the correct insertion point. Change-Id: If1c55da7e56066b5395cf47f589e1e3566c119c6 Reviewed-by: Henning Gründl --- .../libs/qmldesignerutils/CMakeLists.txt | 1 + .../libs/qmldesignerutils/importutils.cpp | 48 ++++++++++ .../libs/qmldesignerutils/importutils.h | 16 ++++ .../unittests/qmldesignerutils/CMakeLists.txt | 1 + .../qmldesignerutils/importutils-test.cpp | 89 +++++++++++++++++++ 5 files changed, 155 insertions(+) create mode 100644 src/plugins/qmldesigner/libs/qmldesignerutils/importutils.cpp create mode 100644 src/plugins/qmldesigner/libs/qmldesignerutils/importutils.h create mode 100644 tests/unit/tests/unittests/qmldesignerutils/importutils-test.cpp diff --git a/src/plugins/qmldesigner/libs/qmldesignerutils/CMakeLists.txt b/src/plugins/qmldesigner/libs/qmldesignerutils/CMakeLists.txt index 042061c8697..3ce2f68154b 100644 --- a/src/plugins/qmldesigner/libs/qmldesignerutils/CMakeLists.txt +++ b/src/plugins/qmldesigner/libs/qmldesignerutils/CMakeLists.txt @@ -18,6 +18,7 @@ add_qtc_library(QmlDesignerUtils STATIC version.cpp version.h maputils.h stringutils.cpp stringutils.h + importutils.cpp importutils.h ) extend_qtc_library(QmlDesignerUtils diff --git a/src/plugins/qmldesigner/libs/qmldesignerutils/importutils.cpp b/src/plugins/qmldesigner/libs/qmldesignerutils/importutils.cpp new file mode 100644 index 00000000000..dd65055d933 --- /dev/null +++ b/src/plugins/qmldesigner/libs/qmldesignerutils/importutils.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "importutils.h" + +#include + +#include +#include + +template<> +inline constexpr bool std::ranges::enable_borrowed_range = true; +template<> +inline constexpr bool std::ranges::enable_view = true; + +namespace QmlDesigner::ImportUtils { + +using namespace Qt::StringLiterals; + +std::optional find_import_location(QStringView text, QStringView directory) +{ + const auto begin = text.begin(); + const auto headerEnd = std::ranges::find(text, u'{'); + QStringView header = {begin, headerEnd}; + QStringView import{u"import"}; + + auto importRange = std::ranges::search(header.rbegin(), + header.rend(), + import.rbegin(), + import.rend()); + + if (importRange.empty()) + return {begin}; + + auto endLine = std::ranges::find(importRange.begin().base(), headerEnd, u'\n'); + + QRegularExpression regex{uR"(^import\s+")"_s + directory + uR"("\s+(?!as))"_s}; + + if (regex.matchView({begin, endLine}).hasMatch()) + return {}; + + if (endLine == text.end()) + return {text.end()}; + + return std::make_optional(std::ranges::next(endLine)); +} + +} // namespace QmlDesigner::ImportUtils diff --git a/src/plugins/qmldesigner/libs/qmldesignerutils/importutils.h b/src/plugins/qmldesigner/libs/qmldesignerutils/importutils.h new file mode 100644 index 00000000000..6e3bd17bcf6 --- /dev/null +++ b/src/plugins/qmldesigner/libs/qmldesignerutils/importutils.h @@ -0,0 +1,16 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "qmldesignerutils_global.h" + +#include +#include + +namespace QmlDesigner::ImportUtils { + +QMLDESIGNERUTILS_EXPORT std::optional find_import_location(QStringView text, + QStringView directory); + +} // namespace QmlDesigner::ImportUtils diff --git a/tests/unit/tests/unittests/qmldesignerutils/CMakeLists.txt b/tests/unit/tests/unittests/qmldesignerutils/CMakeLists.txt index c15c8eed464..d6a9277cce7 100644 --- a/tests/unit/tests/unittests/qmldesignerutils/CMakeLists.txt +++ b/tests/unit/tests/unittests/qmldesignerutils/CMakeLists.txt @@ -4,4 +4,5 @@ extend_qtc_test(unittest SOURCES version-test.cpp stringutils-test.cpp + importutils-test.cpp ) diff --git a/tests/unit/tests/unittests/qmldesignerutils/importutils-test.cpp b/tests/unit/tests/unittests/qmldesignerutils/importutils-test.cpp new file mode 100644 index 00000000000..ed0fa0ed0cb --- /dev/null +++ b/tests/unit/tests/unittests/qmldesignerutils/importutils-test.cpp @@ -0,0 +1,89 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include + +namespace { + +using namespace Qt::StringLiterals; +using QmlDesigner::ImportUtils::find_import_location; + +TEST(ImportUtils, begin_for_no_imports) +{ + auto content = QStringView{uR"("Item {})"}; + QStringView directory = u"foo"; + + auto found = find_import_location(content, directory); + + ASSERT_THAT(found, content.begin()); +} + +TEST(ImportUtils, begin_for_imports_after_last_import) +{ + auto content = QStringView{u"import foo\n" + u"import bar\n" + u"\n" + u"Item {}\n"}; + QStringView directory = u"foo"; + + auto found = find_import_location(content, directory); + + QStringView rest{*found, content.end()}; + ASSERT_THAT(rest, u"\nItem {}\n"); +} + +TEST(ImportUtils, skip_already_existing_directory_import) +{ + auto content = QStringView{u"import \"foo\"\n" + u"import bar\n" + u"\n" + u"Item {}\n"}; + QStringView directory = u"foo"; + + auto found = find_import_location(content, directory); + + ASSERT_THAT(found, std::nullopt); +} + +TEST(ImportUtils, dont_already_existing_directory_import_with_as) +{ + auto content = QStringView{u"import \"foo\" as foo\n" + u"import bar\n" + u"\n" + u"Item {}\n"}; + QStringView directory = u"foo"; + + auto found = find_import_location(content, directory); + + QStringView rest{*found, content.end()}; + ASSERT_THAT(rest, u"\nItem {}\n"); +} + +TEST(ImportUtils, ignore_import_properties) +{ + auto content = QStringView{u"import foo\n" + u"import bar\n" + u"\n" + u"Item { import :}\n"}; + QStringView directory = u"foo"; + + auto found = find_import_location(content, directory); + + QStringView rest{*found, content.end()}; + ASSERT_THAT(rest, u"\nItem { import :}\n"); +} + +TEST(ImportUtils, handle_nothing_after_import) +{ + auto content = QStringView{u"import foo\n" + u"import bar"}; + QStringView directory = u"foo"; + + auto found = find_import_location(content, directory); + + ASSERT_THAT(found, content.end()); +} + +} // namespace