diff options
author | Mahmoud Badri <mahmoud.badri@qt.io> | 2024-05-21 00:05:19 +0300 |
---|---|---|
committer | Mahmoud Badri <mahmoud.badri@qt.io> | 2024-05-21 13:48:06 +0000 |
commit | 9a6bd997b57257eaff9e3fcd621c6a508d9da96c (patch) | |
tree | 0a4558f77499843db52267d911719e0e5cb48980 | |
parent | 6cf945af59637107f017e94549ad605b9c4398ca (diff) |
QmlDesigner: Convert UniqueName keywords to array of stringviews
Also change the class to namespace, rename "get" to "generate"
and add unit tests.
Change-Id: Ib52bf7e3e0110e33acb40ca6e3d9bfc61cefdca0
Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
8 files changed, 175 insertions, 47 deletions
diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp index 36299e586bf..7f488bd6153 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -154,7 +154,7 @@ bool AssetsLibraryModel::renameFolder(const QString &folderPath, const QString & QString AssetsLibraryModel::addNewFolder(const QString &folderPath) { - Utils::FilePath uniqueDirPath = Utils::FilePath::fromString(UniqueName::getPath(folderPath)); + Utils::FilePath uniqueDirPath = Utils::FilePath::fromString(UniqueName::generatePath(folderPath)); auto res = uniqueDirPath.ensureWritableDir(); if (!res.has_value()) { diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp index 11439409a71..5e2211ce0d9 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -178,7 +178,7 @@ QString AssetsLibraryWidget::getUniqueEffectPath(const QString &parentFolder, co QString effectsDir = ModelNodeOperations::getEffectsDefaultDirectory(parentFolder); QString effectPath = QLatin1String("%1/%2.qep").arg(effectsDir, effectName); - return UniqueName::getPath(effectPath); + return UniqueName::generatePath(effectPath); } bool AssetsLibraryWidget::createNewEffect(const QString &effectPath, bool openInEffectComposer) diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp index 7f664f0388d..cfe1fcde830 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp @@ -317,11 +317,11 @@ QPair<QString, QString> ContentLibraryUserModel::getUniqueLibItemNames(const QSt baseQml[0] = baseQml.at(0).toUpper(); baseQml.prepend("My"); - QString uniqueQml = UniqueName::get(baseQml, [&] (const QString &name) { + QString uniqueQml = UniqueName::generate(baseQml, [&] (const QString &name) { return itemQmls.contains(name); }); - QString uniqueIcon = UniqueName::get(defaultName, [&] (const QString &name) { + QString uniqueIcon = UniqueName::generate(defaultName, [&] (const QString &name) { return itemIcons.contains(name); }); diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index d95d3e294bb..1100da62d6e 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -1874,7 +1874,7 @@ QString Model::generateNewId(const QString &prefixName, const QString &fallbackP if (!isDuplicate.has_value()) // TODO: to be removed separately to not break build isDuplicate = std::bind(&Model::hasId, this, std::placeholders::_1); - return UniqueName::getId(prefixName, [&] (const QString &id) { + return UniqueName::generateId(prefixName, [&] (const QString &id) { // Properties of the root node are not allowed for ids, because they are available in the // complete context without qualification. return isDuplicate.value()(id) || d->rootNode()->property(id.toUtf8()); diff --git a/src/plugins/qmldesigner/designercore/uniquename.cpp b/src/plugins/qmldesigner/designercore/uniquename.cpp index 7bfa2c74303..06121b326a1 100644 --- a/src/plugins/qmldesigner/designercore/uniquename.cpp +++ b/src/plugins/qmldesigner/designercore/uniquename.cpp @@ -3,10 +3,58 @@ #include "uniquename.h" +#include <utils/span.h> + #include <QFileInfo> #include <QRegularExpression> -namespace QmlDesigner { +namespace QmlDesigner::UniqueName { + +using namespace Qt::Literals; + +constexpr QLatin1StringView keywords[] { + "anchors"_L1, "as"_L1, "baseState"_L1, + "border"_L1, "bottom"_L1, "break"_L1, + "case"_L1, "catch"_L1, "clip"_L1, + "color"_L1, "continue"_L1, "data"_L1, + "debugger"_L1, "default"_L1, "delete"_L1, + "do"_L1, "else"_L1, "enabled"_L1, + "finally"_L1, "flow"_L1, "focus"_L1, + "font"_L1, "for"_L1, "function"_L1, + "height"_L1, "if"_L1, "import"_L1, + "in"_L1, "instanceof"_L1, "item"_L1, + "layer"_L1, "left"_L1, "margin"_L1, + "new"_L1, "opacity"_L1, "padding"_L1, + "parent"_L1, "print"_L1, "rect"_L1, + "return"_L1, "right"_L1, "scale"_L1, + "shaderInfo"_L1, "source"_L1, "sprite"_L1, + "spriteSequence"_L1, "state"_L1, "switch"_L1, + "text"_L1, "this"_L1, "throw"_L1, + "top"_L1, "try"_L1, "typeof"_L1, + "var"_L1, "visible"_L1, "void"_L1, + "while"_L1, "with"_L1, "x"_L1, + "y"_L1 +}; + +namespace { + +QString toCamelCase(const QString &input) +{ + QString result = input.at(0).toLower(); + bool capitalizeNext = false; + + for (const QChar &c : Utils::span{input}.subspan(1)) { + bool isValidChar = c.isLetterOrNumber() || c == '_'; + if (isValidChar) + result += capitalizeNext ? c.toUpper() : c; + + capitalizeNext = !isValidChar; + } + + return result; +} + +} // namespace /** * @brief Generates a unique name based on the provided name. @@ -19,7 +67,7 @@ namespace QmlDesigner { * false if name is unique. * @return A unique name derived from the provided name. */ -QString UniqueName::get(const QString &name, std::function<bool(const QString &)> predicate) +QString generate(const QString &name, std::function<bool(const QString &)> predicate) { if (!predicate(name)) return name; @@ -61,7 +109,7 @@ QString UniqueName::get(const QString &name, std::function<bool(const QString &) * @param path The original path to be made unique. * @return A unique path derived from the provided path. */ -QString UniqueName::getPath(const QString &path) +QString generatePath(const QString &path) { // Remove the trailing slash if it exists (otherwise QFileInfo::path() returns empty) QString adjustedPath = path; @@ -77,8 +125,8 @@ QString UniqueName::getPath(const QString &path) QString parentDir = fileInfo.path(); QString pathTemplate = parentDir + "/%1" + suffix; - QString uniqueBaseName = UniqueName::get(baseName, [&] (const QString &currName) { - return !QFileInfo::exists(pathTemplate.arg(currName)); + QString uniqueBaseName = UniqueName::generate(baseName, [&] (const QString &currName) { + return QFileInfo::exists(pathTemplate.arg(currName)); }); return pathTemplate.arg(uniqueBaseName); @@ -97,25 +145,18 @@ QString UniqueName::getPath(const QString &path) * @param id The original id to be made unique. * @return A unique Id (when predicate() returns false) */ -QString UniqueName::getId(const QString &id, std::function<bool(const QString &)> predicate) +QString generateId(const QString &id, std::function<bool(const QString &)> predicate) { - // remove non word (non A-Z, a-z, 0-9) characters - static const QRegularExpression nonWordCharsRgx("\\W"); - QString newId = id.simplified(); - newId.remove(nonWordCharsRgx); - - // convert to camel case - QStringList idParts = newId.split(" "); - idParts[0] = idParts[0].at(0).toLower() + idParts[0].mid(1); - for (int i = 1; i < idParts.size(); ++i) - idParts[i] = idParts[i].at(0).toUpper() + idParts[i].mid(1); - newId = idParts.join(""); + // remove non word (non A-Z, a-z, 0-9) or space characters + QString newId = id.trimmed(); + + newId = toCamelCase(newId); // prepend _ if starts with a digit or invalid id (such as reserved words) - if (newId.at(0).isDigit() || std::binary_search(keywords.begin(), keywords.end(), newId)) + if (newId.at(0).isDigit() || std::binary_search(std::begin(keywords), std::end(keywords), newId)) newId.prepend('_'); - return UniqueName::get(newId, predicate); + return UniqueName::generate(newId, predicate); } -} // namespace QmlDesigner +} // namespace QmlDesigner::UniqueName diff --git a/src/plugins/qmldesigner/designercore/uniquename.h b/src/plugins/qmldesigner/designercore/uniquename.h index 3d6bc6c0e36..d264233ddd6 100644 --- a/src/plugins/qmldesigner/designercore/uniquename.h +++ b/src/plugins/qmldesigner/designercore/uniquename.h @@ -6,27 +6,11 @@ #include <qmldesignercorelib_exports.h> #include <QString> -#include <QStringList> -namespace QmlDesigner { +namespace QmlDesigner::UniqueName { -class QMLDESIGNERCORE_EXPORT UniqueName -{ -public: - static QString get(const QString &name, std::function<bool(const QString &)> predicate); - static QString getPath(const QString &path); - static QString getId(const QString &id, std::function<bool(const QString &)> predicate); +QString generate(const QString &name, std::function<bool(const QString &)> predicate); +QString generatePath(const QString &path); +QMLDESIGNERCORE_EXPORT QString generateId(const QString &id, std::function<bool(const QString &)> predicate); -private: - static inline const QStringList keywords { - "anchors", "as", "baseState", "border", "bottom", "break", "case", "catch", "clip", "color", - "continue", "data", "debugger", "default", "delete", "do", "else", "enabled", "finally", - "flow", "focus", "font", "for", "function", "height", "if", "import", "in", "instanceof", - "item", "layer", "left", "margin", "new", "opacity", "padding", "parent", "print", "rect", - "return", "right", "scale", "shaderInfo", "source", "sprite", "spriteSequence", "state", - "switch", "text", "this", "throw", "top", "try", "typeof", "var", "visible", "void", - "while", "with", "x", "y" - }; -}; - -} // namespace QmlDesigner +} // namespace QmlDesigner::UniqueName diff --git a/tests/unit/tests/unittests/model/CMakeLists.txt b/tests/unit/tests/unittests/model/CMakeLists.txt index 75c81cca6f5..43d6154c504 100644 --- a/tests/unit/tests/unittests/model/CMakeLists.txt +++ b/tests/unit/tests/unittests/model/CMakeLists.txt @@ -7,5 +7,5 @@ extend_qtc_test(unittest modelresourcemanagement-test.cpp modelutils-test.cpp nodelistproperty-test.cpp - + uniquename-test.cpp ) diff --git a/tests/unit/tests/unittests/model/uniquename-test.cpp b/tests/unit/tests/unittests/model/uniquename-test.cpp new file mode 100644 index 00000000000..a6fe4560903 --- /dev/null +++ b/tests/unit/tests/unittests/model/uniquename-test.cpp @@ -0,0 +1,103 @@ +// Copyright (C) 2024 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 <uniquename.h> + +namespace { + +namespace UniqueName = QmlDesigner::UniqueName; + +TEST(UniqueName, generate_returns_same_input_if_predicate_returns_false) +{ + auto pred = [] (const QString &name) -> bool { + return false; + }; + QString name = "abc"; + + QString uniqueName = UniqueName::generate(name, pred); + + ASSERT_THAT(uniqueName, "abc"); +} + +TEST(UniqueName, generateId_returns_properly_formatted_id) +{ + auto pred = [] (const QString &id) -> bool { + return false; + }; + QString id = " A bc d _"; + + QString uniqueId = UniqueName::generateId(id, pred); + + ASSERT_THAT(uniqueId, "aBcD_"); +} + +TEST(UniqueName, generateId_returns_properly_formatted_unique_id_when_id_exists) +{ + QStringList existingIds = {"aBcD009", "aBcD010"}; + auto pred = [&] (const QString &id) -> bool { + return existingIds.contains(id); + }; + QString id = " A bc d 0 \t 0 9\n"; + + QString uniqueId = UniqueName::generateId(id, pred); + + ASSERT_THAT(uniqueId, "aBcD011"); +} + +TEST(UniqueName, generateId_properly_handles_dot_separated_words) +{ + auto pred = [&] (const QString &id) -> bool { + return false; + }; + QString id = "Foo.bar*foo"; + + QString uniqueId = UniqueName::generateId(id, pred); + + ASSERT_THAT(uniqueId, "fooBarFoo"); +} + +TEST(UniqueName, generateId_prefixes_with_underscore_if_id_is_a_reserved_word) +{ + auto pred = [&] (const QString &id) -> bool { + return false; + }; + QString id = "for"; + + QString uniqueId = UniqueName::generateId(id, pred); + + ASSERT_THAT(uniqueId, "_for"); +} + +TEST(UniqueName, generateId_prefixes_with_underscore_if_id_is_a_number) +{ + auto pred = [&] (const QString &id) -> bool { + return false; + }; + QString id = "436"; + + QString uniqueId = UniqueName::generateId(id, pred); + + ASSERT_THAT(uniqueId, "_436"); +} + +TEST(UniqueName, generatePath_returns_same_path_when_path_doesnt_exist) +{ + QString path = "<<<non/existing/path>>>"; + + QString uniquePath = UniqueName::generatePath(path); + + ASSERT_THAT(uniquePath, path); +} + +TEST(UniqueName, generatePath_returns_unique_path_when_path_exists) +{ + QString path = UNITTEST_DIR; + + QString uniquePath = UniqueName::generatePath(path); + + ASSERT_THAT(uniquePath, QString(UNITTEST_DIR).append("1")); +} + +} // namespace |