forked from qt-creator/qt-creator
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:
@@ -550,15 +550,29 @@ 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)
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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)};
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user