forked from qt-creator/qt-creator
QmlDesigner: Handle more corner cases for alias relinking
Synchronization should always throw an exception if it gets in an inconsitent state. Task-number: QDS-4551 Change-Id: I8c55198115aa79b676a13fe0cd7ab225fb6723d5 Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
@@ -550,6 +550,16 @@ private:
|
||||
auto [typeId, aliasTypeNameId] = fetchTypeIdByNameUngarded(alias.aliasTypeName,
|
||||
alias.sourceId);
|
||||
|
||||
if (!typeId) {
|
||||
auto hasPropertyDeclaration = selectPropertyDeclarationIdStatement
|
||||
.template optionalValue<PropertyDeclarationId>(
|
||||
&alias.propertyDeclarationId);
|
||||
if (hasPropertyDeclaration)
|
||||
throw TypeNameDoesNotExists{};
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto [propertyTypeId, aliasId, propertyTraits] = fetchPropertyDeclarationByTypeIdAndNameUngarded(
|
||||
typeId, alias.aliasPropertyName);
|
||||
|
||||
@@ -1025,7 +1035,8 @@ private:
|
||||
|
||||
void syncPrototypes(Storage::Type &type)
|
||||
{
|
||||
if (Utils::visit([](auto &&type) -> bool { return type.name.isEmpty(); }, type.prototype)) {
|
||||
if (Utils::visit([](auto &&typeName) -> bool { return typeName.name.isEmpty(); },
|
||||
type.prototype)) {
|
||||
updatePrototypeStatement.write(&type.typeId, Sqlite::NullValue{});
|
||||
} else {
|
||||
auto [prototypeId, prototypeTypeNameId] = fetchTypeIdByNameUngarded(type.prototype,
|
||||
@@ -1482,8 +1493,9 @@ public:
|
||||
" FROM propertyDeclarations JOIN typeSelection USING(typeId) "
|
||||
" WHERE name=?2 ORDER BY level LIMIT 1",
|
||||
database};
|
||||
WriteStatement upsertTypeNamesStatement{"INSERT INTO typeNames(importId, name, typeId, kind) "
|
||||
"VALUES(?1, ?2, ?3, ?4) ON CONFLICT DO NOTHING",
|
||||
WriteStatement upsertTypeNamesStatement{
|
||||
"INSERT INTO typeNames(importId, name, typeId, kind) VALUES(?1, ?2, ?3, ?4) ON CONFLICT DO "
|
||||
"UPDATE SET typeId=excluded.typeId",
|
||||
database};
|
||||
mutable ReadStatement<1> selectPrototypeIdsStatement{
|
||||
"WITH RECURSIVE "
|
||||
@@ -1721,7 +1733,8 @@ public:
|
||||
"SELECT alias.propertyDeclarationId, alias.propertyTypeNameId, alias.typeId, "
|
||||
"target.propertyDeclarationId FROM propertyDeclarations AS alias JOIN propertyDeclarations "
|
||||
"AS target ON alias.aliasPropertyDeclarationId=target.propertyDeclarationId WHERE "
|
||||
"alias.propertyTypeId=?1",
|
||||
"alias.propertyTypeId=?1 OR alias.propertyTypeNameId IN (SELECT typeNameId FROM typeNames "
|
||||
"WHERE typeId=?1)",
|
||||
database};
|
||||
ReadWriteStatement<2> updatesPropertyDeclarationPropertyTypeToNullStatement{
|
||||
"UPDATE propertyDeclarations SET propertyTypeId=NULL WHERE propertyTypeId=?1 AND "
|
||||
|
@@ -531,7 +531,7 @@ protected:
|
||||
|
||||
types.push_back(Storage::Type{importId2,
|
||||
"QAliasItem",
|
||||
Storage::NativeType{"QQuickItem"},
|
||||
Storage::ExportedType{"Item"},
|
||||
TypeAccessSemantics::Reference,
|
||||
sourceId3,
|
||||
importIds,
|
||||
@@ -541,9 +541,48 @@ protected:
|
||||
Storage::NativeType{"QObject"},
|
||||
Storage::PropertyDeclarationTraits::IsList});
|
||||
types.back().aliasDeclarations.push_back(
|
||||
Storage::AliasPropertyDeclaration{"items", Storage::NativeType{"QQuickItem"}, "children"});
|
||||
Storage::AliasPropertyDeclaration{"items", Storage::ExportedType{"Item"}, "children"});
|
||||
types.back().aliasDeclarations.push_back(
|
||||
Storage::AliasPropertyDeclaration{"objects", Storage::NativeType{"QQuickItem"}, "objects"});
|
||||
Storage::AliasPropertyDeclaration{"objects", Storage::ExportedType{"Item"}, "objects"});
|
||||
|
||||
types.push_back(
|
||||
Storage::Type{importId3,
|
||||
"QObject2",
|
||||
Storage::NativeType{},
|
||||
TypeAccessSemantics::Reference,
|
||||
sourceId4,
|
||||
importIds,
|
||||
{Storage::ExportedType{"Object2"}, Storage::ExportedType{"Obj2"}}});
|
||||
types[3].propertyDeclarations.push_back(
|
||||
Storage::PropertyDeclaration{"objects",
|
||||
Storage::NativeType{"QObject"},
|
||||
Storage::PropertyDeclarationTraits::IsList});
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
auto createTypesWithAliases2()
|
||||
{
|
||||
auto types = createTypes();
|
||||
|
||||
types[1].propertyDeclarations.push_back(
|
||||
Storage::PropertyDeclaration{"objects",
|
||||
Storage::NativeType{"QObject"},
|
||||
Storage::PropertyDeclarationTraits::IsList});
|
||||
|
||||
types.push_back(Storage::Type{importId2,
|
||||
"QAliasItem",
|
||||
Storage::NativeType{"QObject"},
|
||||
TypeAccessSemantics::Reference,
|
||||
sourceId3,
|
||||
importIds,
|
||||
{Storage::ExportedType{"AliasItem"}}});
|
||||
types.back().propertyDeclarations.push_back(
|
||||
Storage::PropertyDeclaration{"data",
|
||||
Storage::NativeType{"QObject"},
|
||||
Storage::PropertyDeclarationTraits::IsList});
|
||||
types.back().aliasDeclarations.push_back(
|
||||
Storage::AliasPropertyDeclaration{"objects", Storage::ExportedType{"Item"}, "objects"});
|
||||
|
||||
types.push_back(
|
||||
Storage::Type{importId3,
|
||||
@@ -1119,6 +1158,32 @@ TEST_F(ProjectStorageSlowTest, DontDeleteTypeIfSourceIdIsNotSynchronized)
|
||||
UnorderedElementsAre(IsExportedType("Item"))))));
|
||||
}
|
||||
|
||||
TEST_F(ProjectStorageSlowTest, UpdateExportedTypesIfTypeNameChanges)
|
||||
{
|
||||
Storage::Types types{createTypes()};
|
||||
storage.synchronizeTypes(types, {sourceId1, sourceId2});
|
||||
types[0].typeName = "QQuickItem2";
|
||||
|
||||
storage.synchronizeTypes({types[0]}, {sourceId1});
|
||||
|
||||
ASSERT_THAT(storage.fetchTypes(),
|
||||
UnorderedElementsAre(AllOf(IsStorageType(importId1,
|
||||
"QObject",
|
||||
Storage::NativeType{},
|
||||
TypeAccessSemantics::Reference,
|
||||
sourceId2),
|
||||
Field(&Storage::Type::exportedTypes,
|
||||
UnorderedElementsAre(IsExportedType("Object"),
|
||||
IsExportedType("Obj")))),
|
||||
AllOf(IsStorageType(importId2,
|
||||
"QQuickItem2",
|
||||
Storage::NativeType{"QObject"},
|
||||
TypeAccessSemantics::Reference,
|
||||
sourceId1),
|
||||
Field(&Storage::Type::exportedTypes,
|
||||
UnorderedElementsAre(IsExportedType("Item"))))));
|
||||
}
|
||||
|
||||
TEST_F(ProjectStorageSlowTest, BreakingPrototypeChainByDeletingBaseComponentThrows)
|
||||
{
|
||||
Storage::Types types{createTypes()};
|
||||
@@ -2676,7 +2741,7 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesRemoveTypeWithAliasTargetProperty
|
||||
types[2].aliasDeclarations[1].aliasTypeName = Storage::ExportedType{"Object2"};
|
||||
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
|
||||
|
||||
ASSERT_THROW(storage.synchronizeTypes({}, {sourceId4}), Sqlite::ConstraintPreventsModification);
|
||||
ASSERT_THROW(storage.synchronizeTypes({}, {sourceId4}), QmlDesigner::TypeNameDoesNotExists);
|
||||
}
|
||||
|
||||
TEST_F(ProjectStorageSlowTest, SynchronizeTypesRemoveTypeAndAliasPropertyDeclaration)
|
||||
@@ -2839,4 +2904,105 @@ TEST_F(ProjectStorageSlowTest, RelinkAliasProperty)
|
||||
Storage::PropertyDeclarationTraits::IsList))))));
|
||||
}
|
||||
|
||||
TEST_F(ProjectStorageSlowTest, RelinkAliasPropertyReactToTypeNameChange)
|
||||
{
|
||||
Storage::Types types{createTypesWithAliases2()};
|
||||
types[2].aliasDeclarations.push_back(
|
||||
Storage::AliasPropertyDeclaration{"items", Storage::ExportedType{"Item"}, "children"});
|
||||
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
|
||||
types[0].typeName = "QQuickItem2";
|
||||
|
||||
storage.synchronizeTypes({types[0]}, {sourceId1});
|
||||
|
||||
ASSERT_THAT(storage.fetchTypes(),
|
||||
Contains(AllOf(
|
||||
IsStorageType(importId2,
|
||||
"QAliasItem",
|
||||
Storage::NativeType{"QObject"},
|
||||
TypeAccessSemantics::Reference,
|
||||
sourceId3),
|
||||
Field(&Storage::Type::propertyDeclarations,
|
||||
UnorderedElementsAre(
|
||||
IsPropertyDeclaration("items",
|
||||
Storage::NativeType{"QQuickItem2"},
|
||||
Storage::PropertyDeclarationTraits::IsList
|
||||
| Storage::PropertyDeclarationTraits::IsReadOnly),
|
||||
IsPropertyDeclaration("objects",
|
||||
Storage::NativeType{"QObject"},
|
||||
Storage::PropertyDeclarationTraits::IsList),
|
||||
IsPropertyDeclaration("data",
|
||||
Storage::NativeType{"QObject"},
|
||||
Storage::PropertyDeclarationTraits::IsList))))));
|
||||
}
|
||||
|
||||
TEST_F(ProjectStorageSlowTest, DoNotRelinkAliasPropertyForDeletedType)
|
||||
{
|
||||
Storage::Types types{createTypesWithAliases()};
|
||||
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject2"};
|
||||
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
|
||||
types.erase(std::next(types.begin(), 2));
|
||||
types[2].importId = importId2;
|
||||
|
||||
storage.synchronizeTypes({types[2]}, {sourceId3, sourceId4});
|
||||
|
||||
ASSERT_THAT(storage.fetchTypes(),
|
||||
Not(Contains(AllOf(IsStorageType(importId2,
|
||||
"QAliasItem",
|
||||
Storage::NativeType{"QQuickItem"},
|
||||
TypeAccessSemantics::Reference,
|
||||
sourceId3)))));
|
||||
}
|
||||
|
||||
TEST_F(ProjectStorageSlowTest, DoNotRelinkAliasPropertyForDeletedTypeAndPropertyType)
|
||||
{
|
||||
Storage::Types types{createTypesWithAliases()};
|
||||
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject2"};
|
||||
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
|
||||
types.pop_back();
|
||||
types.pop_back();
|
||||
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject"};
|
||||
|
||||
storage.synchronizeTypes({types[1]}, {sourceId2, sourceId3, sourceId4});
|
||||
|
||||
ASSERT_THAT(storage.fetchTypes(), SizeIs(2));
|
||||
}
|
||||
|
||||
TEST_F(ProjectStorageSlowTest, DoNotRelinkAliasPropertyForDeletedTypeAndPropertyTypeNameChange)
|
||||
{
|
||||
Storage::Types types{createTypesWithAliases()};
|
||||
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject2"};
|
||||
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
|
||||
types.erase(std::next(types.begin(), 2));
|
||||
types[2].importId = importId2;
|
||||
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject"};
|
||||
|
||||
storage.synchronizeTypes({types[2]}, {sourceId3, sourceId4});
|
||||
|
||||
ASSERT_THAT(storage.fetchTypes(),
|
||||
Not(Contains(AllOf(IsStorageType(importId2,
|
||||
"QAliasItem",
|
||||
Storage::NativeType{"QQuickItem"},
|
||||
TypeAccessSemantics::Reference,
|
||||
sourceId3)))));
|
||||
}
|
||||
|
||||
TEST_F(ProjectStorageSlowTest, DoNotRelinkPropertyTypeDoesNotExists)
|
||||
{
|
||||
Storage::Types types{createTypesWithAliases()};
|
||||
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject2"};
|
||||
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
|
||||
types.pop_back();
|
||||
|
||||
ASSERT_THROW(storage.synchronizeTypes({}, {sourceId4}), QmlDesigner::TypeNameDoesNotExists);
|
||||
}
|
||||
|
||||
TEST_F(ProjectStorageSlowTest, DoNotRelinkAliasPropertyTypeDoesNotExists)
|
||||
{
|
||||
Storage::Types types{createTypesWithAliases2()};
|
||||
types[1].propertyDeclarations[0].typeName = Storage::NativeType{"QObject2"};
|
||||
storage.synchronizeTypes(types, {sourceId1, sourceId2, sourceId3, sourceId4});
|
||||
types.erase(types.begin());
|
||||
|
||||
ASSERT_THROW(storage.synchronizeTypes({}, {sourceId1}), QmlDesigner::TypeNameDoesNotExists);
|
||||
}
|
||||
} // namespace
|
||||
|
Reference in New Issue
Block a user