diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index 97cd2faf695..92d7aefc620 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -550,6 +550,16 @@ private: auto [typeId, aliasTypeNameId] = fetchTypeIdByNameUngarded(alias.aliasTypeName, alias.sourceId); + if (!typeId) { + auto hasPropertyDeclaration = selectPropertyDeclarationIdStatement + .template optionalValue( + &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,9 +1493,10 @@ 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", - database}; + 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 " " typeSelection(typeId, level) AS (" @@ -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 " diff --git a/tests/unit/unittest/projectstorage-test.cpp b/tests/unit/unittest/projectstorage-test.cpp index ffc4f28164f..daeb2688b71 100644 --- a/tests/unit/unittest/projectstorage-test.cpp +++ b/tests/unit/unittest/projectstorage-test.cpp @@ -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