QmlDesigner: Add function to locate import insertion point in QML text

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 <henning.gruendl@qt.io>
This commit is contained in:
Marco Bubke
2025-05-22 14:12:33 +02:00
parent 44936a8968
commit 608b065378
5 changed files with 155 additions and 0 deletions

View File

@@ -18,6 +18,7 @@ add_qtc_library(QmlDesignerUtils STATIC
version.cpp version.h version.cpp version.h
maputils.h maputils.h
stringutils.cpp stringutils.h stringutils.cpp stringutils.h
importutils.cpp importutils.h
) )
extend_qtc_library(QmlDesignerUtils extend_qtc_library(QmlDesignerUtils

View File

@@ -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 <QRegularExpression>
#include <algorithm>
#include <ranges>
template<>
inline constexpr bool std::ranges::enable_borrowed_range<QStringView> = true;
template<>
inline constexpr bool std::ranges::enable_view<QStringView> = true;
namespace QmlDesigner::ImportUtils {
using namespace Qt::StringLiterals;
std::optional<QStringView::iterator> 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<QStringView::iterator>(std::ranges::next(endLine));
}
} // namespace QmlDesigner::ImportUtils

View File

@@ -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 <optional>
#include <QStringView>
namespace QmlDesigner::ImportUtils {
QMLDESIGNERUTILS_EXPORT std::optional<QStringView::iterator> find_import_location(QStringView text,
QStringView directory);
} // namespace QmlDesigner::ImportUtils

View File

@@ -4,4 +4,5 @@ extend_qtc_test(unittest
SOURCES SOURCES
version-test.cpp version-test.cpp
stringutils-test.cpp stringutils-test.cpp
importutils-test.cpp
) )

View File

@@ -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 <googletest.h>
#include <qmldesignerutils/importutils.h>
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