From f296f9d77cad461118ef5cd594e3fd9c173bedfb Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Mon, 6 May 2024 17:02:32 +0200 Subject: [PATCH] QmlDesigner: Parse components recursively Change-Id: I8fa892cce8e34b5e58cbdde04c57e30b9fc74866 Reviewed-by: Reviewed-by: Tim Jenssen Reviewed-by: Qt CI Patch Build Bot --- .../qmldesigner/projectstorage/fake.qmltypes | 9 + .../designercore/include/projectstorageids.h | 2 + .../projectstorage/filesystem.cpp | 13 + .../designercore/projectstorage/filesystem.h | 1 + .../projectstorage/filesysteminterface.h | 1 + .../projectstorage/projectstorage.cpp | 78 +++-- .../projectstorage/projectstorage.h | 6 +- .../projectstorage/projectstorageinfotypes.h | 5 + .../projectstorage/projectstorageinterface.h | 4 + .../projectstorage/projectstoragetypes.h | 11 +- .../projectstorage/projectstorageupdater.cpp | 135 ++++++++- .../projectstorage/projectstorageupdater.h | 8 + .../designercore/projectstorage/sourcepath.h | 5 +- .../qmldesigner/qmldesignerprojectmanager.cpp | 69 +---- tests/unit/tests/mocks/filesystemmock.h | 1 + tests/unit/tests/mocks/projectstoragemock.h | 10 + .../tests/printers/gtest-creator-printing.cpp | 4 +- .../projectstorage/projectstorage-test.cpp | 43 +++ .../projectstorageupdater-test.cpp | 286 ++++++++++++++++-- 19 files changed, 566 insertions(+), 125 deletions(-) diff --git a/share/qtcreator/qmldesigner/projectstorage/fake.qmltypes b/share/qtcreator/qmldesigner/projectstorage/fake.qmltypes index f6f21596899..60096cd7900 100644 --- a/share/qtcreator/qmldesigner/projectstorage/fake.qmltypes +++ b/share/qtcreator/qmldesigner/projectstorage/fake.qmltypes @@ -318,4 +318,13 @@ Component { Component { name: "QScxmlError" } + +Component { + name: "QList" +} + +Component { + name: "VertexColorMaskFlags" +} + } diff --git a/src/plugins/qmldesigner/designercore/include/projectstorageids.h b/src/plugins/qmldesigner/designercore/include/projectstorageids.h index 0b157e55e77..7c2e8c4b252 100644 --- a/src/plugins/qmldesigner/designercore/include/projectstorageids.h +++ b/src/plugins/qmldesigner/designercore/include/projectstorageids.h @@ -48,6 +48,8 @@ using EnumerationDeclarationIds = std::vector; using SourceContextId = Sqlite::BasicId; using SourceContextIds = std::vector; +template +using SmallSourceContextIds = QVarLengthArray; using SourceId = Sqlite::BasicId; using SourceIds = std::vector; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/filesystem.cpp b/src/plugins/qmldesigner/designercore/projectstorage/filesystem.cpp index 1376b2c3d9d..d11190fdc74 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/filesystem.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/filesystem.cpp @@ -11,6 +11,7 @@ #include #include +#include #include namespace QmlDesigner { @@ -69,6 +70,18 @@ QString FileSystem::contentAsQString(const QString &filePath) const return {}; } +QStringList FileSystem::subdirectories(const QString &directoryPath) const +{ + QStringList directoryPaths; + directoryPaths.reserve(100); + QDirIterator directoryIterator{directoryPath, QDir::Dirs | QDir::NoDotAndDotDot}; + + while (directoryIterator.hasNext()) + directoryPaths.push_back(directoryIterator.next()); + + return directoryPaths; +} + void FileSystem::remove(const SourceIds &sourceIds) { for (SourceId sourceId : sourceIds) diff --git a/src/plugins/qmldesigner/designercore/projectstorage/filesystem.h b/src/plugins/qmldesigner/designercore/projectstorage/filesystem.h index 28754a8560b..1c881741c6a 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/filesystem.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/filesystem.h @@ -31,6 +31,7 @@ public: long long lastModified(SourceId sourceId) const override; FileStatus fileStatus(SourceId sourceId) const override; QString contentAsQString(const QString &filePath) const override; + QStringList subdirectories(const QString &directoryPath) const override; void remove(const SourceIds &sourceIds) override; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/filesysteminterface.h b/src/plugins/qmldesigner/designercore/projectstorage/filesysteminterface.h index 6a7c964fa61..ff7608c9a3f 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/filesysteminterface.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/filesysteminterface.h @@ -20,6 +20,7 @@ public: virtual FileStatus fileStatus(SourceId sourceId) const = 0; virtual void remove(const SourceIds &sourceIds) = 0; virtual QString contentAsQString(const QString &filePath) const = 0; + virtual QStringList subdirectories(const QString &directoryPath) const = 0; protected: ~FileSystemInterface() = default; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp index 98ee3253df7..3c6636f1f5f 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.cpp @@ -519,6 +519,12 @@ struct ProjectStorage::Statements "SELECT directorySourceId, sourceId, moduleId, fileType FROM directoryInfos WHERE " "directorySourceId=?1", database}; + mutable Sqlite::ReadStatement<4, 2> selectDirectoryInfosForSourceIdAndFileTypeStatement{ + "SELECT directorySourceId, sourceId, moduleId, fileType FROM directoryInfos WHERE " + "directorySourceId=?1 AND fileType=?2", + database}; + mutable Sqlite::ReadStatement<1, 2> selectDirectoryInfosSourceIdsForSourceIdAndFileTypeStatement{ + "SELECT sourceId FROM directoryInfos WHERE directorySourceId=?1 AND fileType=?2", database}; mutable Sqlite::ReadStatement<4, 1> selectDirectoryInfoForSourceIdStatement{ "SELECT directorySourceId, sourceId, moduleId, fileType FROM directoryInfos WHERE " "sourceId=?1 LIMIT 1", @@ -1073,10 +1079,11 @@ public: Sqlite::StrictColumnType::Integer); auto &sourceIdColumn = table.addColumn("sourceId", Sqlite::StrictColumnType::Integer); table.addColumn("moduleId", Sqlite::StrictColumnType::Integer); - table.addColumn("fileType", Sqlite::StrictColumnType::Integer); + auto &fileTypeColumn = table.addColumn("fileType", Sqlite::StrictColumnType::Integer); table.addPrimaryKeyContraint({directorySourceIdColumn, sourceIdColumn}); table.addUniqueIndex({sourceIdColumn}); + table.addIndex({directorySourceIdColumn, fileTypeColumn}); table.initialize(database); } @@ -1196,7 +1203,7 @@ void ProjectStorage::synchronize(Storage::Synchronization::SynchronizationPackag linkAliases(insertedAliasPropertyDeclarations, updatedAliasPropertyDeclarations); - synchronizeDirectoryInfos(package.directoryInfos, package.updatedProjectSourceIds); + synchronizeDirectoryInfos(package.directoryInfos, package.updatedDirectoryInfoSourceIds); commonTypeCache_.resetTypeIds(); }); @@ -2116,7 +2123,7 @@ FileStatus ProjectStorage::fetchFileStatus(SourceId sourceId) const std::optional ProjectStorage::fetchDirectoryInfo(SourceId sourceId) const { using NanotraceHR::keyValue; - NanotraceHR::Tracer tracer{"fetch project data"_t, + NanotraceHR::Tracer tracer{"fetch directory info"_t, projectStorageCategory(), keyValue("source id", sourceId)}; @@ -2124,7 +2131,7 @@ std::optional ProjectStorage::fetchDire .optionalValueWithTransaction( sourceId); - tracer.end(keyValue("project data", directoryInfo)); + tracer.end(keyValue("directory info", directoryInfo)); return directoryInfo; } @@ -2132,7 +2139,7 @@ std::optional ProjectStorage::fetchDire Storage::Synchronization::DirectoryInfos ProjectStorage::fetchDirectoryInfos(SourceId directorySourceId) const { using NanotraceHR::keyValue; - NanotraceHR::Tracer tracer{"fetch project datas by source id"_t, + NanotraceHR::Tracer tracer{"fetch directory infos by source id"_t, projectStorageCategory(), keyValue("source id", directorySourceId)}; @@ -2140,7 +2147,25 @@ Storage::Synchronization::DirectoryInfos ProjectStorage::fetchDirectoryInfos(Sou .valuesWithTransaction( directorySourceId); - tracer.end(keyValue("project datas", directoryInfos)); + tracer.end(keyValue("directory infos", directoryInfos)); + + return directoryInfos; +} + +Storage::Synchronization::DirectoryInfos ProjectStorage::fetchDirectoryInfos( + SourceId directorySourceId, Storage::Synchronization::FileType fileType) const +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"fetch directory infos by source id and file type"_t, + projectStorageCategory(), + keyValue("source id", directorySourceId), + keyValue("file type", fileType)}; + + auto directoryInfos = s->selectDirectoryInfosForSourceIdAndFileTypeStatement + .valuesWithTransaction( + directorySourceId, fileType); + + tracer.end(keyValue("directory infos", directoryInfos)); return directoryInfos; } @@ -2149,7 +2174,7 @@ Storage::Synchronization::DirectoryInfos ProjectStorage::fetchDirectoryInfos( const SourceIds &directorySourceIds) const { using NanotraceHR::keyValue; - NanotraceHR::Tracer tracer{"fetch project datas by source ids"_t, + NanotraceHR::Tracer tracer{"fetch directory infos by source ids"_t, projectStorageCategory(), keyValue("source ids", directorySourceIds)}; @@ -2157,11 +2182,27 @@ Storage::Synchronization::DirectoryInfos ProjectStorage::fetchDirectoryInfos( .valuesWithTransaction( toIntegers(directorySourceIds)); - tracer.end(keyValue("project datas", directoryInfos)); + tracer.end(keyValue("directory infos", directoryInfos)); return directoryInfos; } +SmallSourceIds<32> ProjectStorage::fetchSubdirectorySourceIds(SourceId directorySourceId) const +{ + using NanotraceHR::keyValue; + NanotraceHR::Tracer tracer{"fetch subdirectory source ids"_t, + projectStorageCategory(), + keyValue("source id", directorySourceId)}; + + auto sourceIds = s->selectDirectoryInfosSourceIdsForSourceIdAndFileTypeStatement + .valuesWithTransaction>( + directorySourceId, Storage::Synchronization::FileType::Directory); + + tracer.end(keyValue("source ids", sourceIds)); + + return sourceIds; +} + void ProjectStorage::setPropertyEditorPathId(TypeId typeId, SourceId pathId) { Sqlite::ImmediateSessionTransaction transaction{database}; @@ -2466,9 +2507,9 @@ void ProjectStorage::synchronizeTypes(Storage::Synchronization::Types &types, } void ProjectStorage::synchronizeDirectoryInfos(Storage::Synchronization::DirectoryInfos &directoryInfos, - const SourceIds &updatedProjectSourceIds) + const SourceIds &updatedDirectoryInfoSourceIds) { - NanotraceHR::Tracer tracer{"synchronize project datas"_t, projectStorageCategory()}; + NanotraceHR::Tracer tracer{"synchronize directory infos"_t, projectStorageCategory()}; auto compareKey = [](auto &&first, auto &&second) { auto directorySourceIdDifference = first.directorySourceId - second.directorySourceId; @@ -2484,13 +2525,13 @@ void ProjectStorage::synchronizeDirectoryInfos(Storage::Synchronization::Directo }); auto range = s->selectDirectoryInfosForSourceIdsStatement.range( - toIntegers(updatedProjectSourceIds)); + toIntegers(updatedDirectoryInfoSourceIds)); auto insert = [&](const Storage::Synchronization::DirectoryInfo &directoryInfo) { using NanotraceHR::keyValue; - NanotraceHR::Tracer tracer{"insert project data"_t, + NanotraceHR::Tracer tracer{"insert directory info"_t, projectStorageCategory(), - keyValue("project data", directoryInfo)}; + keyValue("directory info", directoryInfo)}; if (!directoryInfo.directorySourceId) throw DirectoryInfoHasInvalidProjectSourceId{}; @@ -2508,10 +2549,11 @@ void ProjectStorage::synchronizeDirectoryInfos(Storage::Synchronization::Directo if (directoryInfoFromDatabase.fileType != directoryInfo.fileType || !compareInvalidAreTrue(directoryInfoFromDatabase.moduleId, directoryInfo.moduleId)) { using NanotraceHR::keyValue; - NanotraceHR::Tracer tracer{"update project data"_t, + NanotraceHR::Tracer tracer{"update directory info"_t, projectStorageCategory(), - keyValue("project data", directoryInfo), - keyValue("project data from database", directoryInfoFromDatabase)}; + keyValue("directory info", directoryInfo), + keyValue("directory info from database", + directoryInfoFromDatabase)}; s->updateDirectoryInfoStatement.write(directoryInfo.directorySourceId, directoryInfo.sourceId, @@ -2525,9 +2567,9 @@ void ProjectStorage::synchronizeDirectoryInfos(Storage::Synchronization::Directo auto remove = [&](const Storage::Synchronization::DirectoryInfo &directoryInfo) { using NanotraceHR::keyValue; - NanotraceHR::Tracer tracer{"remove project data"_t, + NanotraceHR::Tracer tracer{"remove directory info"_t, projectStorageCategory(), - keyValue("project data", directoryInfo)}; + keyValue("directory info", directoryInfo)}; s->deleteDirectoryInfoStatement.write(directoryInfo.directorySourceId, directoryInfo.sourceId); }; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index 0ae508f58f1..8f69049f706 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -232,8 +232,10 @@ public: std::optional fetchDirectoryInfo(SourceId sourceId) const override; Storage::Synchronization::DirectoryInfos fetchDirectoryInfos(SourceId directorySourceId) const override; - + Storage::Synchronization::DirectoryInfos fetchDirectoryInfos( + SourceId directorySourceId, Storage::Synchronization::FileType fileType) const override; Storage::Synchronization::DirectoryInfos fetchDirectoryInfos(const SourceIds &directorySourceIds) const; + SmallSourceIds<32> fetchSubdirectorySourceIds(SourceId directorySourceId) const override; void setPropertyEditorPathId(TypeId typeId, SourceId pathId); @@ -561,7 +563,7 @@ private: const SourceIds &updatedSourceIds); void synchronizeDirectoryInfos(Storage::Synchronization::DirectoryInfos &directoryInfos, - const SourceIds &updatedProjectSourceIds); + const SourceIds &updatedDirectoryInfoSourceIds); void synchronizeFileStatuses(FileStatuses &fileStatuses, const SourceIds &updatedSourceIds); diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h index 98685d22e2d..75ab0d1f5a5 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinfotypes.h @@ -8,6 +8,8 @@ #include #include +#include + #include #include #include @@ -15,6 +17,9 @@ namespace QmlDesigner { +template +using SmallPathStrings = QVarLengthArray; + template constexpr std::underlying_type_t to_underlying(Enumeration enumeration) noexcept { diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h index fcaccd932ff..4d840d2a5c5 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h @@ -81,7 +81,11 @@ public: virtual FileStatus fetchFileStatus(SourceId sourceId) const = 0; virtual Storage::Synchronization::DirectoryInfos fetchDirectoryInfos(SourceId sourceId) const = 0; + virtual Storage::Synchronization::DirectoryInfos fetchDirectoryInfos( + SourceId directorySourceId, Storage::Synchronization::FileType) const + = 0; virtual std::optional fetchDirectoryInfo(SourceId sourceId) const = 0; + virtual SmallSourceIds<32> fetchSubdirectorySourceIds(SourceId directorySourceId) const = 0; virtual SourceId propertyEditorPathId(TypeId typeId) const = 0; virtual const Storage::Info::CommonTypeCache &commonTypeCache() const = 0; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h index 7eb3767219b..5f51a9acf61 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h @@ -82,7 +82,7 @@ void convertToString(String &string, const TypeNameKind &kind) } } -enum class FileType : char { QmlTypes, QmlDocument }; +enum class FileType : char { QmlTypes, QmlDocument, Directory }; template void convertToString(String &string, const FileType &type) @@ -94,6 +94,9 @@ void convertToString(String &string, const FileType &type) case FileType::QmlDocument: convertToString(string, "QmlDocument"); break; + case FileType::Directory: + convertToString(string, "Directory"); + break; } } @@ -1291,9 +1294,9 @@ public: , fileStatuses(std::move(fileStatuses)) {} - SynchronizationPackage(SourceIds updatedProjectSourceIds, DirectoryInfos directoryInfos) + SynchronizationPackage(SourceIds updatedDirectoryInfoSourceIds, DirectoryInfos directoryInfos) : directoryInfos(std::move(directoryInfos)) - , updatedProjectSourceIds(std::move(updatedProjectSourceIds)) + , updatedDirectoryInfoSourceIds(std::move(updatedDirectoryInfoSourceIds)) {} public: @@ -1303,7 +1306,7 @@ public: SourceIds updatedFileStatusSourceIds; FileStatuses fileStatuses; DirectoryInfos directoryInfos; - SourceIds updatedProjectSourceIds; + SourceIds updatedDirectoryInfoSourceIds; Imports moduleDependencies; SourceIds updatedModuleDependencySourceIds; ModuleExportedImports moduleExportedImports; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp index e6f347ff12a..a0e7bba3c52 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp @@ -13,9 +13,9 @@ #include "sourcepathcache.h" #include "typeannotationreader.h" -#include - #include +#include +#include #include #include @@ -102,6 +102,8 @@ ProjectStorageUpdater::Components createComponents( } for (const QmlDirParser::Component &qmlDirParserComponent : qmlDirParserComponents) { + if (qmlDirParserComponent.fileName.contains('/')) + continue; components.push_back(ProjectStorageUpdater::Component{qmlDirParserComponent.fileName, qmlDirParserComponent.typeName, moduleId, @@ -285,6 +287,8 @@ void ProjectStorageUpdater::update(QStringList directories, try { m_projectStorage.synchronize(std::move(package)); + } catch (const TypeNameDoesNotExists &exception) { + qDebug() << "missing type: " << exception.what(); } catch (...) { qWarning() << "Project storage could not been updated!"; } @@ -323,7 +327,7 @@ void ProjectStorageUpdater::updateQmlTypes(const QStringList &qmlTypesPaths, tracer.tick("append project data"_t, keyValue("project data", directoryInfo)); package.directoryInfos.push_back(std::move(directoryInfo)); tracer.tick("append updated project source ids"_t, keyValue("source id", sourceId)); - package.updatedProjectSourceIds.push_back(sourceId); + package.updatedDirectoryInfoSourceIds.push_back(sourceId); } } } @@ -409,7 +413,7 @@ void ProjectStorageUpdater::updateDirectoryChanged(std::string_view directoryPat watchedSourceIdsIds, qmldirState); tracer.tick("append updated project source id"_t, keyValue("module id", moduleId)); - package.updatedProjectSourceIds.push_back(directorySourceId); + package.updatedDirectoryInfoSourceIds.push_back(directorySourceId); } void ProjectStorageUpdater::updateDirectories(const QStringList &directories, @@ -420,10 +424,111 @@ void ProjectStorageUpdater::updateDirectories(const QStringList &directories, NanotraceHR::Tracer tracer{"update directories"_t, category()}; for (const QString &directory : directories) - updateDirectory({directory}, package, notUpdatedSourceIds, watchedSourceIdsIds); + updateDirectory({directory}, {}, package, notUpdatedSourceIds, watchedSourceIdsIds); +} + +void ProjectStorageUpdater::updateSubdirectories(const Utils::PathString &directoryPath, + SourceId directorySourceId, + FileState directoryState, + const SourceContextIds &subdirectoriesToIgnore, + Storage::Synchronization::SynchronizationPackage &package, + NotUpdatedSourceIds ¬UpdatedSourceIds, + WatchedSourceIdsIds &watchedSourceIdsIds) +{ + struct Directory + { + Directory(Utils::SmallStringView path, SourceContextId sourceContextId, SourceId sourceId) + : path{path} + , sourceContextId{sourceContextId} + , sourceId{sourceId} + {} + + bool operator<(const Directory &other) const + { + return sourceContextId < other.sourceContextId; + } + + bool operator==(const Directory &other) const + { + return sourceContextId == other.sourceContextId; + } + + Utils::PathString path; + SourceContextId sourceContextId; + SourceId sourceId; + }; + + struct Compare + { + bool operator()(const Directory &first, const Directory &second) const + { + return first.sourceContextId < second.sourceContextId; + } + + bool operator()(const Directory &first, SourceContextId second) const + { + return first.sourceContextId < second; + } + + bool operator()(SourceContextId first, const Directory &second) const + { + return first < second.sourceContextId; + } + }; + + using Directories = QVarLengthArray; + + auto subdirectorySourceIds = m_projectStorage.fetchSubdirectorySourceIds(directorySourceId); + auto subdirectories = Utils::transform( + subdirectorySourceIds, [&](SourceId sourceId) -> Directory { + auto sourceContextId = m_pathCache.sourceContextId(sourceId); + auto subdirectoryPath = m_pathCache.sourceContextPath(sourceContextId); + return {subdirectoryPath, sourceContextId, sourceId}; + }); + + auto exisitingSubdirectoryPaths = m_fileSystem.subdirectories(directoryPath.toQString()); + Directories existingSubdirecories; + for (const QString &subdirectory : exisitingSubdirectoryPaths) { + if (subdirectory.endsWith("/designer") || subdirectory.endsWith("/QtQuick/Scene2D") + || subdirectory.endsWith("/QtQuick/Scene3D")) + continue; + Utils::PathString subdirectoryPath = subdirectory; + auto [sourceContextId, sourceId] = m_pathCache.sourceContextAndSourceId( + SourcePath{subdirectoryPath + "/."}); + subdirectories.emplace_back(subdirectoryPath, sourceContextId, sourceId); + existingSubdirecories.emplace_back(subdirectoryPath, sourceContextId, sourceId); + } + + std::sort(subdirectories.begin(), subdirectories.end()); + subdirectories.erase(std::unique(subdirectories.begin(), subdirectories.end()), + subdirectories.end()); + + std::set_difference(subdirectories.begin(), + subdirectories.end(), + subdirectoriesToIgnore.begin(), + subdirectoriesToIgnore.end(), + Utils::make_iterator([&](const Directory &subdirectory) { + updateDirectory(subdirectory.path, + subdirectoriesToIgnore, + package, + notUpdatedSourceIds, + watchedSourceIdsIds); + }), + Compare{}); + + if (directoryState == FileState::Changed) { + for (const auto &[subdirectoryPath, sourceContextId, subdirectorySourceId] : + existingSubdirecories) { + package.directoryInfos.emplace_back(directorySourceId, + subdirectorySourceId, + ModuleId{}, + Storage::Synchronization::FileType::Directory); + } + } } void ProjectStorageUpdater::updateDirectory(const Utils::PathString &directoryPath, + const SourceContextIds &subdirectoriesToIgnore, Storage::Synchronization::SynchronizationPackage &package, NotUpdatedSourceIds ¬UpdatedSourceIds, WatchedSourceIdsIds &watchedSourceIdsIds) @@ -472,7 +577,7 @@ void ProjectStorageUpdater::updateDirectory(const Utils::PathString &directoryPa package.updatedFileStatusSourceIds.push_back(directorySourceId); package.updatedFileStatusSourceIds.push_back(qmldirSourceId); - package.updatedProjectSourceIds.push_back(directorySourceId); + package.updatedDirectoryInfoSourceIds.push_back(directorySourceId); package.updatedSourceIds.push_back(qmldirSourceId); auto qmlDirectoryInfos = m_projectStorage.fetchDirectoryInfos(directorySourceId); for (const Storage::Synchronization::DirectoryInfo &directoryInfo : qmlDirectoryInfos) { @@ -487,6 +592,14 @@ void ProjectStorageUpdater::updateDirectory(const Utils::PathString &directoryPa } } + updateSubdirectories(directoryPath, + directorySourceId, + directoryState, + subdirectoriesToIgnore, + package, + notUpdatedSourceIds, + watchedSourceIdsIds); + tracer.end(keyValue("qmldir source path", qmldirSourcePath), keyValue("directory source path", directorySourcePath), keyValue("directory id", directoryId), @@ -788,7 +901,11 @@ void ProjectStorageUpdater::pathsWithIdsChanged(const std::vector &chan for (auto sourceContextId : directorySourceContextIds) { Utils::PathString directory = m_pathCache.sourceContextPath(sourceContextId); - updateDirectory(directory, package, notUpdatedSourceIds, watchedSourceIds); + updateDirectory(directory, + directorySourceContextIds, + package, + notUpdatedSourceIds, + watchedSourceIds); } for (SourceId sourceId : filterUniqueSourceIds(qmlDocumentSourceIds)) { @@ -867,7 +984,7 @@ void ProjectStorageUpdater::parseTypeInfos(const QStringList &typeInfos, tracer.tick("append module dependenct source source id"_t, keyValue("source id", sourceId)); package.updatedModuleDependencySourceIds.push_back(sourceId); - auto directoryInfo = package.directoryInfos.emplace_back( + const auto &directoryInfo = package.directoryInfos.emplace_back( directorySourceId, sourceId, moduleId, Storage::Synchronization::FileType::QmlTypes); tracer.tick("append project data"_t, keyValue("source id", sourceId)); @@ -898,6 +1015,8 @@ void ProjectStorageUpdater::parseDirectoryInfos( parseQmlComponent(directoryInfo.sourceId, package, notUpdatedSourceIds); break; } + case Storage::Synchronization::FileType::Directory: + break; } } } diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h index 3cb7d24c3f6..baecbd6b11b 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h @@ -143,9 +143,17 @@ private: WatchedSourceIdsIds &watchedSourceIdsIds); void updateDirectory(const Utils::PathString &directory, + const SourceContextIds &subdirecoriesToIgnore, Storage::Synchronization::SynchronizationPackage &package, NotUpdatedSourceIds ¬UpdatedSourceIds, WatchedSourceIdsIds &watchedSourceIdsIds); + void updateSubdirectories(const Utils::PathString &directory, + SourceId directorySourceId, + FileState directoryFileState, + const SourceContextIds &subdirecoriesToIgnore, + Storage::Synchronization::SynchronizationPackage &package, + NotUpdatedSourceIds ¬UpdatedSourceIds, + WatchedSourceIdsIds &watchedSourceIdsIds); void updateDirectoryChanged(std::string_view directoryPath, FileState qmldirState, SourcePath qmldirSourcePath, diff --git a/src/plugins/qmldesigner/designercore/projectstorage/sourcepath.h b/src/plugins/qmldesigner/designercore/projectstorage/sourcepath.h index b655c5cc345..fa550a4d52e 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/sourcepath.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/sourcepath.h @@ -7,6 +7,8 @@ #include +#include + namespace QmlDesigner { class SourcePath : public Utils::PathString @@ -128,5 +130,6 @@ private: }; using SourcePaths = std::vector; - +template +using SmallSourcePaths = QVarLengthArray; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp index 30a827b80bb..6af4d77974a 100644 --- a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp +++ b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp @@ -341,81 +341,32 @@ Utils::FilePath qmlPath(::ProjectExplorer::Target *target) return {}; } -template -bool skipDirectoriesWith(const QStringView directoryPath, const Path &...paths) -{ - return (directoryPath.contains(paths) || ...); -} - -template -bool skipDirectoriesEndsWith(const QStringView directoryPath, const Path &...paths) -{ - return (directoryPath.endsWith(paths) || ...); -} - -bool skipPath(const QString &directoryPath) -{ - return skipDirectoriesWith(directoryPath, - u"QtApplicationManager", - u"QtInterfaceFramework", - u"QtOpcUa", - u"Qt3D", - u"Scene2D", - u"Scene3D", - u"QtWayland", - u"Qt5Compat", - u"QtCharts", - u"QtLocation", - u"QtPositioning", - u"MaterialEditor", - u"QtTextToSpeech", - u"QtWebEngine", - u"Qt/labs", - u"QtDataVisualization") - || skipDirectoriesEndsWith(directoryPath, u"designer"); -} - -void collectQmldirPaths(const QString &path, QStringList &qmldirPaths) -{ - QDirIterator dirIterator{path, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories}; - - QString rootQmldirPath = path + "/qmldir"; - if (!skipPath(path) && QFileInfo::exists(rootQmldirPath)) - qmldirPaths.push_back(path); - - while (dirIterator.hasNext()) { - auto directoryPath = dirIterator.next(); - - QString qmldirPath = directoryPath + "/qmldir"; - if (!skipPath(directoryPath) && QFileInfo::exists(qmldirPath)) - qmldirPaths.push_back(directoryPath); - } -} - [[maybe_unused]] void projectQmldirPaths(::ProjectExplorer::Target *target, QStringList &qmldirPaths) { ::QmlProjectManager::QmlBuildSystem *buildSystem = getQmlBuildSystem(target); const Utils::FilePath projectDirectoryPath = buildSystem->canonicalProjectDir(); - const QStringList importPaths = buildSystem->importPaths(); - const QDir projectDirectory(projectDirectoryPath.toString()); - for (const QString &importPath : importPaths) - collectQmldirPaths(importPath, qmldirPaths); + qmldirPaths.push_back(projectDirectoryPath.path()); } [[maybe_unused]] void qtQmldirPaths(::ProjectExplorer::Target *target, QStringList &qmldirPaths) { - if constexpr (useProjectStorage()) - collectQmldirPaths(qmlPath(target).toString(), qmldirPaths); + if constexpr (useProjectStorage()) { + auto qmlRootPath = qmlPath(target).toString(); + qmldirPaths.push_back(qmlRootPath + "/QtQml"); + qmldirPaths.push_back(qmlRootPath + "/QtQuick"); + qmldirPaths.push_back(qmlRootPath + "/QtQuick3D"); + qmldirPaths.push_back(qmlRootPath + "/Qt5Compat"); + } } [[maybe_unused]] void qtQmldirPathsForLiteDesigner(QStringList &qmldirPaths) { if constexpr (useProjectStorage()) { auto qmlRootPath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath); - collectQmldirPaths(qmlRootPath + "/QtQml", qmldirPaths); - collectQmldirPaths(qmlRootPath + "/QtQuick", qmldirPaths); + qmldirPaths.push_back(qmlRootPath + "/QtQml"); + qmldirPaths.push_back(qmlRootPath + "/QtQuick"); } } diff --git a/tests/unit/tests/mocks/filesystemmock.h b/tests/unit/tests/mocks/filesystemmock.h index cb1d4df4bc4..f8544e509f6 100644 --- a/tests/unit/tests/mocks/filesystemmock.h +++ b/tests/unit/tests/mocks/filesystemmock.h @@ -20,4 +20,5 @@ public: MOCK_METHOD(QmlDesigner::FileStatus, fileStatus, (QmlDesigner::SourceId sourceId), (const, override)); MOCK_METHOD(void, remove, (const QmlDesigner::SourceIds &sourceIds), (override)); MOCK_METHOD(QString, contentAsQString, (const QString &filePath), (const, override)); + MOCK_METHOD(QStringList, subdirectories, (const QString &directoryPath), (const, override)); }; diff --git a/tests/unit/tests/mocks/projectstoragemock.h b/tests/unit/tests/mocks/projectstoragemock.h index a0e880bc09e..8d9c3381b2e 100644 --- a/tests/unit/tests/mocks/projectstoragemock.h +++ b/tests/unit/tests/mocks/projectstoragemock.h @@ -305,6 +305,16 @@ public: (QmlDesigner::SourceId sourceId), (const, override)); + MOCK_METHOD(QmlDesigner::Storage::Synchronization::DirectoryInfos, + fetchDirectoryInfos, + (QmlDesigner::SourceId sourceId, QmlDesigner::Storage::Synchronization::FileType), + (const, override)); + + MOCK_METHOD(QmlDesigner::SmallSourceIds<32>, + fetchSubdirectorySourceIds, + (QmlDesigner::SourceId sourceId), + (const, override)); + MOCK_METHOD(std::optional, fetchDirectoryInfo, (QmlDesigner::SourceId sourceId), diff --git a/tests/unit/tests/printers/gtest-creator-printing.cpp b/tests/unit/tests/printers/gtest-creator-printing.cpp index 165f54ebfbd..0cbef7b1b7f 100644 --- a/tests/unit/tests/printers/gtest-creator-printing.cpp +++ b/tests/unit/tests/printers/gtest-creator-printing.cpp @@ -754,6 +754,8 @@ const char *fileTypeToText(FileType fileType) return "QmlDocument"; case FileType::QmlTypes: return "QmlTypes"; + case FileType::Directory: + return "Directory"; } return ""; @@ -791,7 +793,7 @@ std::ostream &operator<<(std::ostream &out, const SynchronizationPackage &packag << ", updatedSourceIds: " << package.updatedSourceIds << ", fileStatuses: " << package.fileStatuses << ", updatedFileStatusSourceIds: " << package.updatedFileStatusSourceIds - << ", updatedProjectSourceIds: " << package.updatedProjectSourceIds + << ", updatedDirectoryInfoSourceIds: " << package.updatedDirectoryInfoSourceIds << ", directoryInfos: " << package.directoryInfos << ", propertyEditorQmlPaths: " << package.propertyEditorQmlPaths << ", updatedPropertyEditorQmlPathSourceIds: " diff --git a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp index b6a56cdda08..230a6cbb21d 100644 --- a/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/projectstorage-test.cpp @@ -5317,6 +5317,49 @@ TEST_F(ProjectStorage, fetch_directory_infos_by_directory_source_id) ASSERT_THAT(directoryInfo, UnorderedElementsAre(directoryInfo1, directoryInfo2)); } +TEST_F(ProjectStorage, fetch_directory_infos_by_directory_source_id_and_file_type) +{ + Storage::Synchronization::DirectoryInfo directoryInfo1{ + qmlProjectSourceId, sourceId1, qmlModuleId, Storage::Synchronization::FileType::QmlDocument}; + Storage::Synchronization::DirectoryInfo directoryInfo2{ + qmlProjectSourceId, sourceId2, ModuleId{}, Storage::Synchronization::FileType::Directory}; + Storage::Synchronization::DirectoryInfo directoryInfo3{qtQuickProjectSourceId, + sourceId3, + qtQuickModuleId, + Storage::Synchronization::FileType::QmlTypes}; + Storage::Synchronization::DirectoryInfo directoryInfo4{ + qmlProjectSourceId, sourceId4, ModuleId{}, Storage::Synchronization::FileType::Directory}; + storage.synchronize( + SynchronizationPackage{{qmlProjectSourceId, qtQuickProjectSourceId}, + {directoryInfo1, directoryInfo2, directoryInfo3, directoryInfo4}}); + + auto directoryInfo = storage.fetchDirectoryInfos(qmlProjectSourceId, + Storage::Synchronization::FileType::Directory); + + ASSERT_THAT(directoryInfo, UnorderedElementsAre(directoryInfo2, directoryInfo4)); +} + +TEST_F(ProjectStorage, fetch_subdirectory_source_ids) +{ + Storage::Synchronization::DirectoryInfo directoryInfo1{ + qmlProjectSourceId, sourceId1, qmlModuleId, Storage::Synchronization::FileType::QmlDocument}; + Storage::Synchronization::DirectoryInfo directoryInfo2{ + qmlProjectSourceId, sourceId2, ModuleId{}, Storage::Synchronization::FileType::Directory}; + Storage::Synchronization::DirectoryInfo directoryInfo3{qtQuickProjectSourceId, + sourceId3, + qtQuickModuleId, + Storage::Synchronization::FileType::QmlTypes}; + Storage::Synchronization::DirectoryInfo directoryInfo4{ + qmlProjectSourceId, sourceId4, ModuleId{}, Storage::Synchronization::FileType::Directory}; + storage.synchronize( + SynchronizationPackage{{qmlProjectSourceId, qtQuickProjectSourceId}, + {directoryInfo1, directoryInfo2, directoryInfo3, directoryInfo4}}); + + auto directoryInfo = storage.fetchSubdirectorySourceIds(qmlProjectSourceId); + + ASSERT_THAT(directoryInfo, UnorderedElementsAre(sourceId2, sourceId4)); +} + TEST_F(ProjectStorage, fetch_directory_info_by_source_ids) { Storage::Synchronization::DirectoryInfo directoryInfo1{qmlProjectSourceId, diff --git a/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp b/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp index ae97c8b5abe..7ad09044c6b 100644 --- a/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp +++ b/tests/unit/tests/unittests/projectstorage/projectstorageupdater-test.cpp @@ -119,8 +119,9 @@ MATCHER(PackageIsEmpty, std::string(negation ? "isn't empty" : "is empty")) return package.imports.empty() && package.types.empty() && package.fileStatuses.empty() && package.updatedSourceIds.empty() && package.directoryInfos.empty() - && package.updatedFileStatusSourceIds.empty() && package.updatedProjectSourceIds.empty() - && package.moduleDependencies.empty() && package.updatedModuleDependencySourceIds.empty() + && package.updatedFileStatusSourceIds.empty() + && package.updatedDirectoryInfoSourceIds.empty() && package.moduleDependencies.empty() + && package.updatedModuleDependencySourceIds.empty() && package.moduleExportedImports.empty() && package.updatedModuleIds.empty() && package.propertyEditorQmlPaths.empty() && package.updatedPropertyEditorQmlPathSourceIds.empty() @@ -303,6 +304,18 @@ public: EXPECT_CALL(fileSystemMock, contentAsQString(Eq(path))).WillRepeatedly(Return(content)); } + void setSubdirectoryPaths(QStringView directoryPath, const QStringList &subdirectoryPaths) + { + ON_CALL(fileSystemMock, subdirectories(Eq(directoryPath))).WillByDefault(Return(subdirectoryPaths)); + } + + void setSubdirectorySourceIds(SourceId directorySourceId, + const QmlDesigner::SmallSourceIds<32> &subdirectorySourceId) + { + ON_CALL(projectStorageMock, fetchSubdirectorySourceIds(Eq(directorySourceId))) + .WillByDefault(Return(subdirectorySourceId)); + } + auto moduleId(Utils::SmallStringView name, ModuleKind kind) const { return storage.moduleId(name, kind); @@ -430,6 +443,24 @@ TEST_F(ProjectStorageUpdater, get_content_for_qml_dir_paths_if_file_status_is_di updater.update(directories, {}, {}, {}); } +TEST_F(ProjectStorageUpdater, + get_content_for_qml_dir_paths_if_file_status_is_different_for_subdirectories) +{ + SourceId qmlDir1PathSourceId = sourcePathCache.sourceId("/path/one/qmldir"); + SourceId qmlDir2PathSourceId = sourcePathCache.sourceId("/path/two/qmldir"); + SourceId qmlDir3PathSourceId = sourcePathCache.sourceId("/path/three/qmldir"); + SourceId path3SourceId = sourcePathCache.sourceId("/path/three/."); + QStringList directories = {"/path/one"}; + setSubdirectoryPaths(u"/path/one", {"/path/two", "/path/three"}); + setFilesChanged({qmlDir1PathSourceId, qmlDir2PathSourceId}); + setFilesDontChanged({qmlDir3PathSourceId, path3SourceId}); + + EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/one/qmldir")))); + EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/two/qmldir")))); + + updater.update(directories, {}, {}, {}); +} + TEST_F(ProjectStorageUpdater, request_file_status_from_file_system) { EXPECT_CALL(fileSystemMock, fileStatus(Ne(directoryPathSourceId))).Times(AnyNumber()); @@ -439,6 +470,20 @@ TEST_F(ProjectStorageUpdater, request_file_status_from_file_system) updater.update(directories, {}, {}, {}); } +TEST_F(ProjectStorageUpdater, request_file_status_from_file_system_for_subdirectories) +{ + EXPECT_CALL(fileSystemMock, + fileStatus(AllOf(Ne(directoryPathSourceId), Ne(path1SourceId), Ne(path2SourceId)))) + .Times(AnyNumber()); + setSubdirectoryPaths(u"/path", {"/path/one", "/path/two"}); + + EXPECT_CALL(fileSystemMock, fileStatus(Eq(path1SourceId))); + EXPECT_CALL(fileSystemMock, fileStatus(Eq(path2SourceId))); + EXPECT_CALL(fileSystemMock, fileStatus(Eq(directoryPathSourceId))); + + updater.update(directories, {}, {}, {}); +} + TEST_F(ProjectStorageUpdater, get_content_for_qml_types) { QString qmldir{R"(module Example @@ -483,6 +528,27 @@ TEST_F(ProjectStorageUpdater, parse_qml_types) updater.update(directories, {}, {}, {}); } +TEST_F(ProjectStorageUpdater, parse_qml_types_in_subdirectories) +{ + QString qmldir{R"(module Example + typeinfo example.qmltypes + typeinfo example2.qmltypes)"}; + setContent(u"/path/qmldir", qmldir); + QString qmltypes{"Module {\ndependencies: []}"}; + QString qmltypes2{"Module {\ndependencies: [foo]}"}; + setContent(u"/path/example.qmltypes", qmltypes); + setContent(u"/path/example2.qmltypes", qmltypes2); + QStringList directories = {"/root"}; + setSubdirectoryPaths(u"/root", {"/path"}); + + EXPECT_CALL(qmlTypesParserMock, + parse(qmltypes, _, _, Field(&DirectoryInfo::moduleId, exampleCppNativeModuleId))); + EXPECT_CALL(qmlTypesParserMock, + parse(qmltypes2, _, _, Field(&DirectoryInfo::moduleId, exampleCppNativeModuleId))); + + updater.update(directories, {}, {}, {}); +} + TEST_F(ProjectStorageUpdater, synchronize_is_empty_for_no_change) { setFilesDontChanged({qmltypesPathSourceId, qmltypes2PathSourceId, qmlDirPathSourceId}); @@ -492,6 +558,23 @@ TEST_F(ProjectStorageUpdater, synchronize_is_empty_for_no_change) updater.update(directories, {}, {}, {}); } +TEST_F(ProjectStorageUpdater, synchronize_is_empty_for_no_change_in_subdirectory) +{ + SourceId qmlDirRootPathSourceId = sourcePathCache.sourceId("/root/qmldir"); + SourceId rootPathSourceId = sourcePathCache.sourceId("/root/."); + setFilesDontChanged({qmltypesPathSourceId, + qmltypes2PathSourceId, + qmlDirPathSourceId, + qmlDirRootPathSourceId, + rootPathSourceId}); + QStringList directories = {"/root"}; + setSubdirectoryPaths(u"/root", {"/path"}); + + EXPECT_CALL(projectStorageMock, synchronize(PackageIsEmpty())); + + updater.update(directories, {}, {}, {}); +} + TEST_F(ProjectStorageUpdater, synchronize_qml_types) { Storage::Import import{qmlModuleId, Storage::Version{2, 3}, qmltypesPathSourceId}; @@ -521,12 +604,83 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_types) qmltypesPathSourceId, exampleCppNativeModuleId, FileType::QmlTypes))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId))))); updater.update(directories, {}, {}, {}); } +TEST_F(ProjectStorageUpdater, synchronize_subdircectories) +{ + QStringList directories = {"/root"}; + setSubdirectoryPaths(u"/root", {"/path/one", "/path/two"}); + setSubdirectoryPaths(u"/path/one", {"/path/three"}); + SourceId rootDirectoryPathSourceId = sourcePathCache.sourceId("/root/."); + setFilesChanged({rootDirectoryPathSourceId, path1SourceId, path2SourceId, path3SourceId}); + + EXPECT_CALL( + projectStorageMock, + synchronize(AllOf( + Field(&SynchronizationPackage::directoryInfos, + UnorderedElementsAre( + IsDirectoryInfo(rootDirectoryPathSourceId, path1SourceId, ModuleId{}, FileType::Directory), + IsDirectoryInfo(rootDirectoryPathSourceId, path2SourceId, ModuleId{}, FileType::Directory), + IsDirectoryInfo(path1SourceId, path3SourceId, ModuleId{}, FileType::Directory))), + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, + UnorderedElementsAre( + rootDirectoryPathSourceId, path1SourceId, path2SourceId, path3SourceId))))); + + updater.update(directories, {}, {}, {}); +} + +TEST_F(ProjectStorageUpdater, synchronize_subdircectories_even_for_no_changes) +{ + QStringList directories = {"/root"}; + setSubdirectoryPaths(u"/root", {"/path/one", "/path/two"}); + setSubdirectoryPaths(u"/path/one", {"/path/three"}); + SourceId rootDirectoryPathSourceId = sourcePathCache.sourceId("/root/."); + setFilesChanged({path1SourceId, path2SourceId, path3SourceId}); + setFilesDontChanged({rootDirectoryPathSourceId}); + + EXPECT_CALL(projectStorageMock, + synchronize( + AllOf(Field(&SynchronizationPackage::directoryInfos, + UnorderedElementsAre(IsDirectoryInfo( + path1SourceId, path3SourceId, ModuleId{}, FileType::Directory))), + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, + UnorderedElementsAre(path1SourceId, path2SourceId, path3SourceId))))); + + updater.update(directories, {}, {}, {}); +} + +TEST_F(ProjectStorageUpdater, synchronize_subdircectories_for_deleted_subdirecties) +{ + QStringList directories = {"/root"}; + setSubdirectoryPaths(u"/root", {"/path/two"}); + SourceId rootDirectoryPathSourceId = sourcePathCache.sourceId("/root/."); + setFilesChanged({rootDirectoryPathSourceId}); + setFilesDontExists({ + path1SourceId, + path3SourceId, + }); + setSubdirectorySourceIds(rootDirectoryPathSourceId, {path1SourceId, path2SourceId}); + setSubdirectorySourceIds(path1SourceId, {path3SourceId}); + + EXPECT_CALL(projectStorageMock, + synchronize(AllOf(Field(&SynchronizationPackage::directoryInfos, + UnorderedElementsAre(IsDirectoryInfo(rootDirectoryPathSourceId, + path2SourceId, + ModuleId{}, + FileType::Directory))), + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, + UnorderedElementsAre(rootDirectoryPathSourceId, + path1SourceId, + path2SourceId, + path3SourceId))))); + + updater.update(directories, {}, {}, {}); +} + TEST_F(ProjectStorageUpdater, synchronize_qml_types_throws_if_qmltpes_does_not_exists) { Storage::Import import{qmlModuleId, Storage::Version{2, 3}, qmltypesPathSourceId}; @@ -653,7 +807,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents) IsFileStatus(qmlDocumentSourceId1, 1, 21), IsFileStatus(qmlDocumentSourceId2, 1, 21), IsFileStatus(qmlDocumentSourceId3, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -713,7 +867,7 @@ TEST_F(ProjectStorageUpdater, synchronize_add_only_qml_document_in_directory) Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(qmlDocumentSourceId2, 1, 21), IsFileStatus(directoryPathSourceId, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -777,7 +931,7 @@ TEST_F(ProjectStorageUpdater, synchronize_removes_qml_document) UnorderedElementsAre(qmlDirPathSourceId, qmlDocumentSourceId3)), Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -832,7 +986,7 @@ TEST_F(ProjectStorageUpdater, synchronize_removes_qml_document_in_qmldir_only) UnorderedElementsAre(qmlDirPathSourceId)), Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -890,7 +1044,7 @@ TEST_F(ProjectStorageUpdater, synchronize_add_qml_document_to_qmldir) UnorderedElementsAre(qmlDirPathSourceId)), Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -945,7 +1099,7 @@ TEST_F(ProjectStorageUpdater, synchronize_remove_qml_document_from_qmldir) UnorderedElementsAre(qmlDirPathSourceId)), Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -1011,7 +1165,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_dont_update_if_up_to_dat IsFileStatus(qmlDocumentSourceId2, 1, 21))), Field(&SynchronizationPackage::updatedFileStatusSourceIds, UnorderedElementsAre(qmlDirPathSourceId, qmlDocumentSourceId1, qmlDocumentSourceId2)), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -1193,7 +1347,7 @@ TEST_F(ProjectStorageUpdater, update_qml_types_files_is_empty) Field(&SynchronizationPackage::fileStatuses, IsEmpty()), Field(&SynchronizationPackage::updatedFileStatusSourceIds, IsEmpty()), Field(&SynchronizationPackage::directoryInfos, IsEmpty()), - Field(&SynchronizationPackage::updatedProjectSourceIds, IsEmpty())))); + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, IsEmpty())))); updater.update({}, {}, {}, {}); } @@ -1220,7 +1374,7 @@ TEST_F(ProjectStorageUpdater, update_qml_types_files) qmltypes2PathSourceId, builtinCppNativeModuleId, FileType::QmlTypes))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(qmltypesPathSourceId, qmltypes2PathSourceId))))); updater.update({}, {"/path/example.qmltypes", "/path/example2.qmltypes"}, {}, {}); @@ -1245,7 +1399,7 @@ TEST_F(ProjectStorageUpdater, dont_update_qml_types_files_if_unchanged) qmltypesPathSourceId, builtinCppNativeModuleId, FileType::QmlTypes))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(qmltypesPathSourceId))))); updater.update({}, {"/path/example.qmltypes", "/path/example2.qmltypes"}, {}, {}); @@ -1283,7 +1437,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_with_different_version_b Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 1, 21), IsFileStatus(qmlDocumentSourceId1, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -1324,7 +1478,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_with_different_type_name Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 1, 21), IsFileStatus(qmlDocumentSourceId1, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -1926,7 +2080,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_without_qmldir) IsFileStatus(qmlDocumentSourceId1, 1, 21), IsFileStatus(qmlDocumentSourceId2, 1, 21), IsFileStatus(qmlDocumentSourceId3, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -1977,7 +2131,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_without_qmldir_throws_if qmlDocumentSourceId2, qmlDocumentSourceId3)), Field(&SynchronizationPackage::fileStatuses, IsEmpty()), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, IsEmpty())))); @@ -2014,7 +2168,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_without_qmldir_add_qml_d Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(directoryPathSourceId, 1, 21), IsFileStatus(qmlDocumentSourceId3, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -2058,7 +2212,7 @@ TEST_F(ProjectStorageUpdater, synchronize_qml_documents_without_qmldir_removes_q qmlDocumentSourceId3)), Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(directoryPathSourceId, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -2126,7 +2280,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories) IsFileStatus(qmlDocumentSourceId1, 1, 21), IsFileStatus(qmlDocumentSourceId2, 1, 21), IsFileStatus(qmlDocumentSourceId3, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -2145,6 +2299,74 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories) updater.pathsWithIdsChanged({{directoryProjectChunkId, {directoryPathSourceId}}}); } +TEST_F(ProjectStorageUpdater, watcher_updates_subdirectories) +{ + QString qmldir{R"(module Example + FirstType 1.0 First.qml + FirstType 2.2 First2.qml + SecondType 2.2 Second.qml)"}; + setContent(u"/path/qmldir", qmldir); + SourceId rootPathSourceId = sourcePathCache.sourceId("/root/."); + SourceId rootQmldirPathSourceId = sourcePathCache.sourceId("/root/qmldir"); + setFilesChanged({directoryPathSourceId, rootPathSourceId}); + setFilesDontChanged({qmlDirPathSourceId, rootQmldirPathSourceId}); + setSubdirectoryPaths(u"/root", {"/path"}); + setSubdirectorySourceIds(rootPathSourceId, {directoryPathSourceId}); + + EXPECT_CALL( + projectStorageMock, + synchronize(AllOf( + Field(&SynchronizationPackage::imports, UnorderedElementsAre(import1, import2, import3)), + Field( + &SynchronizationPackage::types, + UnorderedElementsAre( + AllOf(IsStorageType("First.qml", + Storage::Synchronization::ImportedType{"Object"}, + TypeTraitsKind::Reference, + qmlDocumentSourceId1, + Storage::Synchronization::ChangeLevel::Full), + Field(&Storage::Synchronization::Type::exportedTypes, + UnorderedElementsAre(IsExportedType(exampleModuleId, "FirstType", 1, 0), + IsExportedType(pathModuleId, "First", -1, -1)))), + AllOf(IsStorageType("First2.qml", + Storage::Synchronization::ImportedType{"Object2"}, + TypeTraitsKind::Reference, + qmlDocumentSourceId2, + Storage::Synchronization::ChangeLevel::Full), + Field(&Storage::Synchronization::Type::exportedTypes, + UnorderedElementsAre(IsExportedType(exampleModuleId, "FirstType", 2, 2), + IsExportedType(pathModuleId, "First2", -1, -1)))), + AllOf(IsStorageType("Second.qml", + Storage::Synchronization::ImportedType{"Object3"}, + TypeTraitsKind::Reference, + qmlDocumentSourceId3, + Storage::Synchronization::ChangeLevel::Full), + Field(&Storage::Synchronization::Type::exportedTypes, + UnorderedElementsAre(IsExportedType(exampleModuleId, "SecondType", 2, 2), + IsExportedType(pathModuleId, "Second", -1, -1)))))), + Field(&SynchronizationPackage::updatedSourceIds, + UnorderedElementsAre(qmlDocumentSourceId1, qmlDocumentSourceId2, qmlDocumentSourceId3)), + Field(&SynchronizationPackage::directoryInfos, + UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, + qmlDocumentSourceId1, + ModuleId{}, + FileType::QmlDocument), + IsDirectoryInfo(directoryPathSourceId, + qmlDocumentSourceId2, + ModuleId{}, + FileType::QmlDocument), + IsDirectoryInfo(directoryPathSourceId, + qmlDocumentSourceId3, + ModuleId{}, + FileType::QmlDocument), + IsDirectoryInfo(rootPathSourceId, + directoryPathSourceId, + ModuleId{}, + FileType::Directory)))))); + + updater.pathsWithIdsChanged({{directoryProjectChunkId, {rootPathSourceId, directoryPathSourceId}}}); +} + TEST_F(ProjectStorageUpdater, watcher_updates_removed_directory) { setFilesRemoved({directoryPathSourceId, @@ -2173,7 +2395,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_removed_directory) qmlDocumentSourceId2, qmlDocumentSourceId3)), Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre()), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, IsEmpty())))); @@ -2275,7 +2497,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_and_qmldir) IsFileStatus(qmlDocumentSourceId1, 1, 21), IsFileStatus(qmlDocumentSourceId2, 1, 21), IsFileStatus(qmlDocumentSourceId3, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -2372,7 +2594,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_add_only_qml_document_in_directory Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(qmlDocumentSourceId2, 1, 21), IsFileStatus(directoryPathSourceId, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -2436,7 +2658,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_removes_qml_document) UnorderedElementsAre(qmlDirPathSourceId, qmlDocumentSourceId3)), Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -2491,7 +2713,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_removes_qml_document_in_qmldir_onl UnorderedElementsAre(qmlDirPathSourceId)), Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -2549,7 +2771,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_add_qml_document_to_qm UnorderedElementsAre(qmlDirPathSourceId)), Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -2604,7 +2826,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_remove_qml_document_fr UnorderedElementsAre(qmlDirPathSourceId)), Field(&SynchronizationPackage::fileStatuses, UnorderedElementsAre(IsFileStatus(qmlDirPathSourceId, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -2670,7 +2892,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_dont_update_qml_docume IsFileStatus(qmlDocumentSourceId2, 1, 21))), Field(&SynchronizationPackage::updatedFileStatusSourceIds, UnorderedElementsAre(qmlDirPathSourceId, qmlDocumentSourceId1, qmlDocumentSourceId2)), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -2740,7 +2962,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_qmldirs_dont_update_qml_documents_ IsFileStatus(qmlDocumentSourceId2, 1, 21))), Field(&SynchronizationPackage::updatedFileStatusSourceIds, UnorderedElementsAre(qmlDirPathSourceId, qmlDocumentSourceId1, qmlDocumentSourceId2)), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -3053,7 +3275,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_directories_and_but_not_included_q IsFileStatus(qmlDocumentSourceId1, 1, 21), IsFileStatus(qmlDocumentSourceId2, 1, 21), IsFileStatus(qmlDocumentSourceId3, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -3130,7 +3352,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_qmldir_and_but_not_included_qml_do IsFileStatus(qmlDocumentSourceId1, 1, 21), IsFileStatus(qmlDocumentSourceId2, 1, 21), IsFileStatus(qmlDocumentSourceId3, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId, @@ -3229,7 +3451,7 @@ TEST_F(ProjectStorageUpdater, watcher_updates_qmldir_and_but_not_included_qmltyp IsFileStatus(qmlDocumentSourceId1, 1, 21), IsFileStatus(qmlDocumentSourceId2, 1, 21), IsFileStatus(qmlDocumentSourceId3, 1, 21))), - Field(&SynchronizationPackage::updatedProjectSourceIds, + Field(&SynchronizationPackage::updatedDirectoryInfoSourceIds, UnorderedElementsAre(directoryPathSourceId)), Field(&SynchronizationPackage::directoryInfos, UnorderedElementsAre(IsDirectoryInfo(directoryPathSourceId,