QmlDesigner: Unify invalid ids

Fixes: QDS-13056
Change-Id: I8d99e0e9eba148fe4d31c3c9a2d28f46d91b377a
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Ali Kianian
2024-06-20 17:33:12 +03:00
parent e574ba97f1
commit 7caf81117d
6 changed files with 186 additions and 71 deletions

View File

@@ -546,19 +546,33 @@ private:
uint _block = 0;
};
class IdsThatShouldNotBeUsedInDesigner : public QStringList
class IdsThatShouldNotBeUsedInDesigner : public QStringList
{
public:
IdsThatShouldNotBeUsedInDesigner()
: QStringList({"top", "bottom", "left", "right", "width", "height",
"x", "y", "opacity", "parent", "item", "flow",
"color", "margin", "padding", "print", "border", "font",
"text", "source", "state", "visible", "focus", "data",
"clip", "layer", "scale", "enabled", "anchors",
"texture", "shaderInfo", "sprite", "spriteSequence", "baseState"
"vector", "string", "url", "var", "point", "date", "size", "list",
"enumeration"})
: QStringList({
"action", "alias", "anchors", "as", "baseState", "bool",
"border", "bottom", "break", "case", "catch", "clip",
"color", "continue", "data", "date", "debugger", "default",
"delete", "do", "double", "else", "enabled", "enumeration",
"finally", "flow", "focus", "font", "for", "function",
"height", "id", "if", "import", "in", "instanceof",
"int", "item", "layer", "left", "list", "margin",
"matrix4x4", "new", "opacity", "padding", "parent", "point",
"print", "quaternion", "real", "rect", "return", "right",
"scale", "shaderInfo", "size", "source", "sprite", "spriteSequence",
"state", "string", "switch", "text", "texture", "this",
"throw", "time", "top", "try", "typeof", "url",
"var", "variant", "vector", "vector2d", "vector3d", "vector4d",
"visible", "void", "while", "width", "with", "x",
"y", "z",
})
{}
bool containsId(const QString &id) const
{
return std::binary_search(constBegin(), constEnd(), id);
}
};
class VisualAspectsPropertyBlackList : public QStringList
@@ -682,7 +696,7 @@ QList<StaticAnalysis::Type> Check::defaultDisabledMessagesForNonQuickUi()
bool Check::incompatibleDesignerQmlId(const QString &id)
{
return idsThatShouldNotBeUsedInDesigner->contains(id);
return idsThatShouldNotBeUsedInDesigner->containsId(id);
}
Check::Check(Document::Ptr doc, const ContextPtr &context, Utils::QtcSettings *qtcSettings)

View File

@@ -93,42 +93,10 @@ QString ModelNode::validId() const
return id();
}
namespace {
bool isQmlKeyWord(QStringView id)
{
static constexpr auto keywords = Utils::to_array<std::u16string_view>(
{u"as", u"break", u"case", u"catch", u"continue", u"debugger",
u"default", u"delete", u"do", u"else", u"finally", u"for",
u"function", u"if", u"import", u"in", u"instanceof", u"new",
u"print", u"return", u"switch", u"this", u"throw", u"try",
u"typeof", u"var", u"void", u"while", u"with"});
return std::binary_search(keywords.begin(), keywords.end(), ModelUtils::toStdStringView(id));
}
bool isIdToAvoid(QStringView id)
{
static constexpr auto token = Utils::to_array<std::u16string_view>(
{u"anchors", u"baseState", u"border", u"bottom", u"clip", u"color",
u"data", u"enabled", u"flow", u"focus", u"font", u"height",
u"item", u"layer", u"left", u"margin", u"opacity", u"padding",
u"parent", u"rect", u"right", u"scale", u"shaderInfo", u"source",
u"sprite", u"spriteSequence", u"state", u"text", u"texture", u"top",
u"visible", u"width", u"x", u"y"});
return std::binary_search(token.begin(), token.end(), ModelUtils::toStdStringView(id));
}
bool idContainsWrongLetter(const QString &id)
{
static QRegularExpression idExpr(QStringLiteral("^[a-z_][a-zA-Z0-9_]*$"));
return !id.contains(idExpr);
}
} // namespace
bool ModelNode::isValidId(const QString &id)
{
return id.isEmpty() || (!idContainsWrongLetter(id) && !isQmlKeyWord(id) && !isIdToAvoid(id));
using namespace ModelUtils;
return isValidQmlIdentifier(id) && !isBannedQmlId(id);
}
QString ModelNode::getIdValidityErrorMessage(const QString &id)
@@ -145,10 +113,13 @@ QString ModelNode::getIdValidityErrorMessage(const QString &id)
if (id.contains(' '))
return QObject::tr("ID cannot include whitespace (%1).").arg(id);
if (isQmlKeyWord(id))
if (ModelUtils::isQmlKeyword(id))
return QObject::tr("%1 is a reserved QML keyword.").arg(id);
if (isIdToAvoid(id))
if (ModelUtils::isQmlBuiltinType(id))
return QObject::tr("%1 is a reserved Qml type.").arg(id);
if (ModelUtils::isDiscouragedQmlId(id))
return QObject::tr("%1 is a reserved property keyword.").arg(id);
return QObject::tr("ID includes invalid characters (%1).").arg(id);

View File

@@ -16,12 +16,50 @@
#include <algorithm>
#include <QRegularExpression>
namespace QmlDesigner::ModelUtils {
namespace {
enum class ImportError { EmptyImportName, HasAlreadyImport, NoModule };
constexpr std::u16string_view qmlKeywords[]{
u"alias", u"as", u"break", u"case", u"catch", u"continue", u"debugger", u"default",
u"delete", u"do", u"else", u"finally", u"for", u"function", u"if", u"import",
u"in", u"instanceof", u"new", u"print", u"return", u"switch", u"this", u"throw",
u"try", u"typeof", u"var", u"void", u"while", u"with",
};
constexpr std::u16string_view qmlDiscouragedIds[]{
u"action", u"anchors", u"baseState", u"border", u"bottom", u"clip",
u"data", u"enabled", u"flow", u"focus", u"font", u"height",
u"id", u"item", u"layer", u"left", u"margin", u"opacity",
u"padding", u"parent", u"right", u"scale", u"shaderInfo", u"source",
u"sprite", u"spriteSequence", u"state", u"text", u"texture", u"time",
u"top", u"visible", u"width", u"x", u"y", u"z",
};
constexpr std::u16string_view qmlBuiltinTypes[]{
u"bool", u"color", u"date", u"double", u"enumeration", u"font",
u"int", u"list", u"matrix4x4", u"point", u"quaternion", u"real",
u"rect", u"size", u"string", u"url", u"var", u"variant",
u"vector", u"vector2d", u"vector3d", u"vector4d",
};
constexpr auto createBannedQmlIds()
{
std::array<std::u16string_view, sizeof(qmlKeywords) + sizeof(qmlDiscouragedIds) + sizeof(qmlBuiltinTypes)> ids;
auto idsEnd = std::copy(std::begin(qmlKeywords), std::end(qmlKeywords), ids.begin());
idsEnd = std::copy(std::begin(qmlDiscouragedIds), std::end(qmlDiscouragedIds), idsEnd);
std::copy(std::begin(qmlBuiltinTypes), std::end(qmlBuiltinTypes), idsEnd);
std::sort(ids.begin(), ids.end());
return ids;
}
::Utils::expected<Import, ImportError> findImport(const QString &importName,
const std::function<bool(const Import &)> &predicate,
const Imports &imports,
@@ -275,4 +313,31 @@ ModelNode lowestCommonAncestor(Utils::span<const ModelNode> nodes)
return accumulatedNode;
}
bool isQmlKeyword(QStringView id)
{
return std::binary_search(std::begin(qmlKeywords), std::end(qmlKeywords), id);
}
bool isDiscouragedQmlId(QStringView id)
{
return std::binary_search(std::begin(qmlDiscouragedIds), std::end(qmlDiscouragedIds), id);
}
bool isQmlBuiltinType(QStringView id)
{
return std::binary_search(std::begin(qmlBuiltinTypes), std::end(qmlBuiltinTypes), id);
}
bool isBannedQmlId(QStringView id)
{
static constexpr auto invalidIds = createBannedQmlIds();
return std::binary_search(invalidIds.begin(), invalidIds.end(), id);
}
bool isValidQmlIdentifier(QStringView id)
{
static QRegularExpression idExpr(R"(^[a-z_]\w*$)");
return id.contains(idExpr);
}
} // namespace QmlDesigner::ModelUtils

View File

@@ -46,6 +46,12 @@ QMLDESIGNERCORE_EXPORT QList<ModelNode> allModelNodesWithId(AbstractView *view);
QMLDESIGNERCORE_EXPORT bool isThisOrAncestorLocked(const ModelNode &node);
QMLDESIGNERCORE_EXPORT ModelNode lowestCommonAncestor(Utils::span<const ModelNode> nodes);
QMLDESIGNERCORE_EXPORT bool isQmlKeyword(QStringView id);
QMLDESIGNERCORE_EXPORT bool isDiscouragedQmlId(QStringView id);
QMLDESIGNERCORE_EXPORT bool isQmlBuiltinType(QStringView id);
QMLDESIGNERCORE_EXPORT bool isBannedQmlId(QStringView id);
QMLDESIGNERCORE_EXPORT bool isValidQmlIdentifier(QStringView id);
constexpr std::u16string_view toStdStringView(QStringView view)
{
return {view.utf16(), Utils::usize(view)};

View File

@@ -3,6 +3,8 @@
#include "uniquename.h"
#include <model/modelutils.h>
#include <utils/span.h>
#include <QFileInfo>
@@ -12,30 +14,6 @@ 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)
@@ -156,7 +134,7 @@ QString generateId(const QString &id, std::function<bool(const QString &)> predi
newId = toCamelCase(newId);
// prepend _ if starts with a digit or invalid id (such as reserved words)
if (newId.at(0).isDigit() || std::binary_search(std::begin(keywords), std::end(keywords), newId))
if (newId.at(0).isDigit() || ModelUtils::isBannedQmlId(newId))
newId.prepend('_');
if (!predicate)

View File

@@ -159,4 +159,85 @@ TEST_F(ModelUtils, lowest_common_ancestor_for_uncle_and_nephew_should_return_the
ASSERT_THAT(commonAncestor, grandFatherNode);
}
TEST_F(ModelUtils, isValidQmlIdentifier_fails_on_empty_values)
{
QStringView emptyValue = u"";
bool result = QmlDesigner::ModelUtils::isValidQmlIdentifier(emptyValue);
ASSERT_FALSE(result);
}
TEST_F(ModelUtils, isValidQmlIdentifier_fails_on_upper_case_first_letter)
{
QStringView id = u"Lmn";
bool result = QmlDesigner::ModelUtils::isValidQmlIdentifier(id);
ASSERT_FALSE(result);
}
TEST_F(ModelUtils, isValidQmlIdentifier_fails_on_digital_first_letter)
{
QStringView id = u"6mn";
bool result = QmlDesigner::ModelUtils::isValidQmlIdentifier(id);
ASSERT_FALSE(result);
}
TEST_F(ModelUtils, isValidQmlIdentifier_fails_on_unicodes)
{
QStringView id = u"sähköverkko";
bool result = QmlDesigner::ModelUtils::isValidQmlIdentifier(id);
ASSERT_FALSE(result);
}
TEST_F(ModelUtils, isValidQmlIdentifier_passes_on_lower_case_first_letter)
{
QStringView id = u"mn";
bool result = QmlDesigner::ModelUtils::isValidQmlIdentifier(id);
ASSERT_TRUE(result);
}
TEST_F(ModelUtils, isValidQmlIdentifier_passes_on_underscored_first_letter)
{
QStringView id = u"_m";
bool result = QmlDesigner::ModelUtils::isValidQmlIdentifier(id);
ASSERT_TRUE(result);
}
TEST_F(ModelUtils, isValidQmlIdentifier_passes_on_digital_non_first_letter)
{
QStringView id = u"_6";
bool result = QmlDesigner::ModelUtils::isValidQmlIdentifier(id);
ASSERT_TRUE(result);
}
TEST_F(ModelUtils, isValidQmlIdentifier_passes_on_upper_case_non_first_letter)
{
QStringView id = u"mN";
bool result = QmlDesigner::ModelUtils::isValidQmlIdentifier(id);
ASSERT_TRUE(result);
}
TEST_F(ModelUtils, isValidQmlIdentifier_passes_on_underscore_only)
{
QStringView id = u"_";
bool result = QmlDesigner::ModelUtils::isValidQmlIdentifier(id);
ASSERT_TRUE(result);
}
} // namespace