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