From 1b16eb209ce05aa6e36215a79d21931b164926d6 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Tue, 28 Sep 2021 17:05:23 +0200 Subject: [PATCH] QmlDesigner: Add QmlDocumentParser and QmlTypesParser Task-number: QDS-5174 Task-number: QDS-5228 Change-Id: I0889e8d63b0260aeb0efae1b3c8a373c18ea1f03 Reviewed-by: Thomas Hartmann Reviewed-by: Qt CI Bot --- .../designercore/include/projectstorageids.h | 2 +- .../projectstorage/projectstorage.h | 2 +- .../projectstorage/projectstorageexceptions.h | 6 + .../projectstorage/projectstoragetypes.h | 37 +- .../projectstorage/projectstorageupdater.cpp | 26 +- .../projectstorage/projectstorageupdater.h | 5 +- .../projectstorage/qmldocumentparser.cpp | 197 +++++++++ .../projectstorage/qmldocumentparser.h | 61 +++ .../projectstorage/qmltypesparser.cpp | 294 +++++++++++++ .../projectstorage/qmltypesparser.h | 57 ++- .../projectstorage/qmltypesparserinterface.h | 3 +- tests/unit/unittest/CMakeLists.txt | 18 +- .../unit/unittest/gtest-creator-printing.cpp | 29 +- tests/unit/unittest/gtest-creator-printing.h | 2 + .../unittest/projectstorageupdater-test.cpp | 15 +- .../unit/unittest/qmldocumentparser-test.cpp | 243 +++++++++++ tests/unit/unittest/qmltypesparser-test.cpp | 410 ++++++++++++++++++ tests/unit/unittest/qmltypesparsermock.h | 3 +- 18 files changed, 1352 insertions(+), 58 deletions(-) create mode 100644 src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.cpp create mode 100644 src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.h create mode 100644 src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp rename tests/unit/unittest/qmldom-test.cpp => src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.h (57%) create mode 100644 tests/unit/unittest/qmldocumentparser-test.cpp create mode 100644 tests/unit/unittest/qmltypesparser-test.cpp diff --git a/src/plugins/qmldesigner/designercore/include/projectstorageids.h b/src/plugins/qmldesigner/designercore/include/projectstorageids.h index 76f547eeb15..765f2c9c659 100644 --- a/src/plugins/qmldesigner/designercore/include/projectstorageids.h +++ b/src/plugins/qmldesigner/designercore/include/projectstorageids.h @@ -37,7 +37,7 @@ public: constexpr explicit BasicId() = default; - BasicId(const char *) = delete; + constexpr BasicId(const char *) = delete; constexpr explicit BasicId(InternalIntergerType id) : id{id} diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index 2a428c65332..9bd49ef467d 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -1096,7 +1096,7 @@ private: json.append(parameter.name); json.append("\",\"tn\":\""); json.append(parameter.typeName); - if (parameter.traits == Storage::PropertyDeclarationTraits::Non) { + if (parameter.traits == Storage::PropertyDeclarationTraits::None) { json.append("\"}"); } else { json.append("\",\"tr\":"); diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h index 0d23e116050..42790bded68 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h @@ -101,4 +101,10 @@ public: const char *what() const noexcept override { return "There is a prototype chain cycle!"; } }; +class CannotParseQmlTypesFile : std::exception +{ +public: + const char *what() const noexcept override { return "Cannot parse qml types file!"; } +}; + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h index 97958d7c309..592f510c8d2 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h @@ -35,10 +35,10 @@ namespace QmlDesigner::Storage { -enum class TypeAccessSemantics : int { Invalid, Reference, Value, Sequence, IsEnum = 1 << 8 }; +enum class TypeAccessSemantics : int { None, Reference, Value, Sequence, IsEnum = 1 << 8 }; enum class PropertyDeclarationTraits : unsigned int { - Non = 0, + None = 0, IsReadOnly = 1 << 0, IsPointer = 1 << 1, IsList = 1 << 2 @@ -165,15 +165,27 @@ inline int operator<(IsQualified first, IsQualified second) return static_cast(first) < static_cast(second); } +enum class ImportKind : char { Module, Directory, QmlTypesDependency }; + class Import { public: explicit Import() = default; - explicit Import(Utils::SmallStringView name, Version version, SourceId sourceId) + explicit Import(Utils::SmallStringView name, + Version version, + SourceId sourceId, + ImportKind kind = ImportKind::Module) : name{name} , version{version} , sourceId{sourceId} + , kind{kind} + {} + + explicit Import(Version version, ModuleId moduleId, SourceId sourceId) + : version{version} + , moduleId{moduleId} + , sourceId{sourceId} {} explicit Import(Utils::SmallStringView name, int majorVersion, int minorVersion, int sourceId) @@ -185,14 +197,16 @@ public: friend bool operator==(const Import &first, const Import &second) { return first.name == second.name && first.version == second.version - && first.sourceId == second.sourceId; + && first.sourceId == second.sourceId && first.moduleId.id == second.moduleId.id; } public: - Utils::PathString name; + Utils::SmallString name; Version version; ModuleId moduleId; SourceId sourceId; + Utils::SmallString aliasName; + ImportKind kind = ImportKind::Module; }; using Imports = std::vector; @@ -269,6 +283,12 @@ public: , version{version} {} + explicit ExportedType(ModuleId moduleId, Utils::SmallStringView name, Version version = Version{}) + : name{name} + , version{version} + , moduleId{moduleId} + {} + explicit ExportedType(Utils::SmallStringView name, Version version, TypeId typeId, ModuleId moduleId) : name{name} , version{version} @@ -299,6 +319,11 @@ class ExportedTypeView { public: explicit ExportedTypeView() = default; + explicit ExportedTypeView(ModuleId moduleId, Utils::SmallStringView name, Storage::Version version) + : name{name} + , version{version} + , moduleId{moduleId} + {} explicit ExportedTypeView(int moduleId, Utils::SmallStringView name, int majorVersion, @@ -702,7 +727,7 @@ public: FunctionDeclarations functionDeclarations; SignalDeclarations signalDeclarations; EnumerationDeclarations enumerationDeclarations; - TypeAccessSemantics accessSemantics = TypeAccessSemantics::Invalid; + TypeAccessSemantics accessSemantics = TypeAccessSemantics::None; SourceId sourceId; TypeId typeId; ModuleId moduleId; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp index b5a7945181f..a16859ae230 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.cpp @@ -73,7 +73,13 @@ void ProjectUpdater::update() SourceContextId directoryId = m_pathCache.sourceContextId(qmlDirSourceId); - parseTypeInfos(parser.typeInfos(), directoryId, imports, types, sourceIds, fileStatuses); + parseTypeInfos(parser.typeInfos(), + directoryId, + ModuleId{&qmlDirSourceId}, + imports, + types, + sourceIds, + fileStatuses); parseQmlComponents(createComponentReferences(parser.components()), directoryId, ModuleId{&qmlDirSourceId}, @@ -85,7 +91,12 @@ void ProjectUpdater::update() } case FileState::NotChanged: { SourceIds qmltypesSourceIds = m_projectStorage.fetchSourceDependencieIds(qmlDirSourceId); - parseTypeInfos(qmltypesSourceIds, imports, types, sourceIds, fileStatuses); + parseTypeInfos(ModuleId{&qmlDirSourceId}, + qmltypesSourceIds, + imports, + types, + sourceIds, + fileStatuses); break; } case FileState::NotExists: { @@ -106,6 +117,7 @@ void ProjectUpdater::pathsWithIdsChanged(const std::vector &idPaths) {} void ProjectUpdater::parseTypeInfos(const QStringList &typeInfos, SourceContextId directoryId, + ModuleId moduleId, Storage::Imports &imports, Storage::Types &types, SourceIds &sourceIds, @@ -117,11 +129,12 @@ void ProjectUpdater::parseTypeInfos(const QStringList &typeInfos, SourceId sourceId = m_pathCache.sourceId(directoryId, Utils::SmallString{typeInfo}); QString qmltypesPath = directory + "/" + typeInfo; - parseTypeInfo(sourceId, qmltypesPath, imports, types, sourceIds, fileStatuses); + parseTypeInfo(sourceId, moduleId, qmltypesPath, imports, types, sourceIds, fileStatuses); } } -void ProjectUpdater::parseTypeInfos(const SourceIds &qmltypesSourceIds, +void ProjectUpdater::parseTypeInfos(ModuleId moduleId, + const SourceIds &qmltypesSourceIds, Storage::Imports &imports, Storage::Types &types, SourceIds &sourceIds, @@ -130,11 +143,12 @@ void ProjectUpdater::parseTypeInfos(const SourceIds &qmltypesSourceIds, for (SourceId sourceId : qmltypesSourceIds) { QString qmltypesPath = m_pathCache.sourcePath(sourceId).toQString(); - parseTypeInfo(sourceId, qmltypesPath, imports, types, sourceIds, fileStatuses); + parseTypeInfo(sourceId, moduleId, qmltypesPath, imports, types, sourceIds, fileStatuses); } } void ProjectUpdater::parseTypeInfo(SourceId sourceId, + ModuleId moduleId, const QString &qmltypesPath, Storage::Imports &imports, Storage::Types &types, @@ -144,7 +158,7 @@ void ProjectUpdater::parseTypeInfo(SourceId sourceId, if (fileState(sourceId, fileStatuses) == FileState::Changed) { sourceIds.push_back(sourceId); const auto content = m_fileSystem.contentAsQString(qmltypesPath); - m_qmlTypesParser.parse(content, imports, types, sourceIds); + m_qmlTypesParser.parse(content, imports, types, sourceId, moduleId); } } diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h index dfe8ce05c44..ce611b71412 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageupdater.h @@ -88,16 +88,19 @@ private: void parseTypeInfos(const QStringList &typeInfos, SourceContextId directoryId, + ModuleId moduleId, Storage::Imports &imports, Storage::Types &types, SourceIds &sourceIds, FileStatuses &fileStatuses); - void parseTypeInfos(const SourceIds &qmltypesSourceIds, + void parseTypeInfos(ModuleId moduleId, + const SourceIds &qmltypesSourceIds, Storage::Imports &imports, Storage::Types &types, SourceIds &sourceIds, FileStatuses &fileStatuses); void parseTypeInfo(SourceId sourceId, + ModuleId moduleId, const QString &qmltypesPath, Storage::Imports &imports, Storage::Types &types, diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.cpp b/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.cpp new file mode 100644 index 00000000000..be0474e77f9 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "qmldocumentparser.h" + +#include "projectstorage.h" +#include "sourcepathcache.h" + +#include + +#include + +#include + +namespace QmlDesigner { + +namespace QmlDom = QQmlJS::Dom; + +namespace { + +int convertVersionNumber(qint32 versionNumber) +{ + return versionNumber < 0 ? -1 : versionNumber; +} + +Storage::Version convertVersion(QmlDom::Version version) +{ + return Storage::Version{convertVersionNumber(version.majorVersion), + convertVersionNumber(version.minorVersion)}; +} + +Utils::PathString convertUri(const QString &uri) +{ + Utils::PathString path{QStringView{uri.begin() + 7, uri.end()}}; + if (path.endsWith("/.")) + return path; + if (path.endsWith("/")) { + path += "."; + return path; + } + + path += "/."; + return path; +} + +void addImports(Storage::Imports &imports, + const QList &qmlImports, + SourceId sourceId, + SourceContextId sourceContextId, + QmlDocumentParser::PathCache &pathCache) +{ + for (const QmlDom::Import &qmlImport : qmlImports) { + if (qmlImport.uri == u"file://.") { + SourceId directorySourceId = pathCache.sourceId(sourceContextId, "."); + imports.emplace_back(Storage::Version{}, ModuleId{&directorySourceId}, sourceId); + } else if (qmlImport.uri.startsWith(u"file://")) { + SourceId uriSourceId = pathCache.sourceId(sourceContextId, convertUri(qmlImport.uri)); + imports.emplace_back(Storage::Version{}, ModuleId{&uriSourceId}, sourceId); + } else { + imports.emplace_back(Utils::SmallString{qmlImport.uri}, + convertVersion(qmlImport.version), + sourceId); + } + } +} + +void addPropertyDeclarations(Storage::Type &type, const QmlDom::QmlObject &rootObject) +{ + for (const QmlDom::PropertyDefinition &propertyDeclaration : rootObject.propertyDefs()) { + type.propertyDeclarations.emplace_back(Utils::SmallString{propertyDeclaration.name}, + Storage::ImportedType{ + Utils::SmallString{propertyDeclaration.typeName}}, + Storage::PropertyDeclarationTraits::None); + } +} + +void addParameterDeclaration(Storage::ParameterDeclarations ¶meterDeclarations, + const QList ¶meters) +{ + for (const QmlDom::MethodParameter ¶meter : parameters) { + parameterDeclarations.emplace_back(Utils::SmallString{parameter.name}, + Utils::SmallString{parameter.typeName}); + } +} + +void addFunctionAndSignalDeclarations(Storage::Type &type, const QmlDom::QmlObject &rootObject) +{ + for (const QmlDom::MethodInfo &methodInfo : rootObject.methods()) { + if (methodInfo.methodType == QmlDom::MethodInfo::Method) { + auto &functionDeclaration = type.functionDeclarations.emplace_back( + Utils::SmallString{methodInfo.name}, "", Storage::ParameterDeclarations{}); + addParameterDeclaration(functionDeclaration.parameters, methodInfo.parameters); + } else { + auto &signalDeclaration = type.signalDeclarations.emplace_back( + Utils::SmallString{methodInfo.name}); + addParameterDeclaration(signalDeclaration.parameters, methodInfo.parameters); + } + } +} + +Storage::EnumeratorDeclarations createEnumerators(const QmlDom::EnumDecl &enumeration) +{ + Storage::EnumeratorDeclarations enumeratorDeclarations; + for (const QmlDom::EnumItem &enumerator : enumeration.values()) { + enumeratorDeclarations.emplace_back(Utils::SmallString{enumerator.name()}, + static_cast(enumerator.value())); + } + return enumeratorDeclarations; +} + +void addEnumeraton(Storage::Type &type, const QmlDom::Component &component) +{ + for (const QmlDom::EnumDecl &enumeration : component.enumerations()) { + Storage::EnumeratorDeclarations enumeratorDeclarations = createEnumerators(enumeration); + type.enumerationDeclarations.emplace_back(Utils::SmallString{enumeration.name()}, + std::move(enumeratorDeclarations)); + } +} + +} // namespace + +Storage::Type QmlDocumentParser::parse(const QString &sourceContent, + Storage::Imports &imports, + SourceId sourceId, + SourceContextId sourceContextId) +{ + Storage::Type type; + + QmlDom::DomItem environment = QmlDom::DomEnvironment::create( + {}, + QmlDom::DomEnvironment::Option::SingleThreaded + | QmlDom::DomEnvironment::Option::NoDependencies); + + QmlDom::DomItem items; + + environment.loadFile( + {}, + {}, + sourceContent, + QDateTime{}, + [&](QmlDom::Path, const QmlDom::DomItem &, const QmlDom::DomItem &newItems) { + items = newItems; + }, + QmlDom::LoadOption::DefaultLoad, + QmlDom::DomType::QmlFile); + + environment.loadPendingDependencies(); + + QmlDom::DomItem file = items.field(QmlDom::Fields::currentItem); + const QmlDom::QmlFile *qmlFile = file.as(); + const auto &components = qmlFile->components(); + + if (components.empty()) + return type; + + const auto &component = components.first(); + const auto &objects = component.objects(); + + if (objects.empty()) + return type; + + const QmlDom::QmlObject &qmlObject = objects.front(); + + type.prototype = Storage::ImportedType{Utils::SmallString{qmlObject.name()}}; + + addImports(imports, qmlFile->imports(), sourceId, sourceContextId, m_pathCache); + + addPropertyDeclarations(type, qmlObject); + addFunctionAndSignalDeclarations(type, qmlObject); + addEnumeraton(type, component); + + return type; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.h b/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.h new file mode 100644 index 00000000000..cc272dd4227 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/projectstorage/qmldocumentparser.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "nonlockingmutex.h" +#include "qmldocumentparserinterface.h" + +namespace Sqlite { +class Database; +} + +namespace QmlDesigner { + +template +class ProjectStorage; + +template +class SourcePathCache; + +class QmlDocumentParser +{ +public: + using PathCache = QmlDesigner::SourcePathCache, + NonLockingMutex>; + + QmlDocumentParser(PathCache &pathCache) + : m_pathCache{pathCache} + {} + + virtual Storage::Type parse(const QString &sourceContent, + Storage::Imports &imports, + SourceId sourceId, + SourceContextId sourceContextId); + +private: + PathCache &m_pathCache; +}; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp new file mode 100644 index 00000000000..ed9352cd474 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.cpp @@ -0,0 +1,294 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "qmltypesparser.h" + +#include "projectstorage.h" +#include "sourcepathcache.h" + +#include + +#include +#include + +#include + +#include +#include + +namespace QmlDesigner { + +namespace QmlDom = QQmlJS::Dom; + +namespace { +void addType(const QQmlJSScope::Ptr &object, Storage::Types &types) {} + +Storage::Import createImport(const QString &dependency, SourceId sourceId) +{ + Storage::Import import; + import.kind = Storage::ImportKind::QmlTypesDependency; + import.sourceId = sourceId; + auto spaceFound = std::find_if(dependency.begin(), dependency.end(), [](QChar c) { + return c.isSpace(); + }); + import.name = Utils::SmallString{QStringView(dependency.begin(), spaceFound)}; + + auto majorVersionFound = std::find_if(spaceFound, dependency.end(), [](QChar c) { + return c.isDigit(); + }); + auto majorVersionEnd = std::find_if(majorVersionFound, dependency.end(), [](QChar c) { + return !c.isDigit(); + }); + QStringView majorVersionString(majorVersionFound, majorVersionEnd); + if (majorVersionString.isEmpty()) + return import; + import.version.major.value = majorVersionString.toInt(); + + auto minorVersionFound = std::find_if(majorVersionEnd, dependency.end(), [](QChar c) { + return c.isDigit(); + }); + auto minorVersionEnd = std::find_if(minorVersionFound, dependency.end(), [](QChar c) { + return !c.isDigit(); + }); + QStringView minorVersionString(minorVersionFound, minorVersionEnd); + if (minorVersionString.isEmpty()) + return import; + import.version.minor.value = QStringView(minorVersionFound, minorVersionEnd).toInt(); + + return import; +} + +void addImports(Storage::Imports &imports, SourceId sourceId, const QStringList &dependencies) +{ + for (const QString &dependency : dependencies) { + imports.push_back(createImport(dependency, sourceId)); + } + + imports.emplace_back("QML", Storage::Version{}, sourceId, Storage::ImportKind::QmlTypesDependency); + imports.emplace_back("QtQml", Storage::Version{}, sourceId, Storage::ImportKind::QmlTypesDependency); +} + +Storage::TypeAccessSemantics createTypeAccessSemantics(QQmlJSScope::AccessSemantics accessSematics) +{ + switch (accessSematics) { + case QQmlJSScope::AccessSemantics::Reference: + return Storage::TypeAccessSemantics::Reference; + case QQmlJSScope::AccessSemantics::Value: + return Storage::TypeAccessSemantics::Value; + case QQmlJSScope::AccessSemantics::None: + return Storage::TypeAccessSemantics::None; + case QQmlJSScope::AccessSemantics::Sequence: + return Storage::TypeAccessSemantics::Sequence; + } + + return Storage::TypeAccessSemantics::None; +} + +Storage::Version createVersion(QTypeRevision qmlVersion) +{ + return Storage::Version{qmlVersion.majorVersion(), qmlVersion.minorVersion()}; +} + +Storage::ExportedTypes createExports(ModuleId moduleId, const QList &qmlExports) +{ + Storage::ExportedTypes exportedTypes; + exportedTypes.reserve(Utils::usize(qmlExports)); + + for (const QQmlJSScope::Export &qmlExport : qmlExports) { + exportedTypes.emplace_back(moduleId, + Utils::SmallString{qmlExport.type()}, + createVersion(qmlExport.version())); + } + + return exportedTypes; +} + +Storage::PropertyDeclarationTraits createPropertyDeclarationTraits(const QQmlJSMetaProperty &qmlProperty) +{ + Storage::PropertyDeclarationTraits traits{}; + + if (qmlProperty.isList()) + traits = traits | Storage::PropertyDeclarationTraits::IsList; + + if (qmlProperty.isPointer()) + traits = traits | Storage::PropertyDeclarationTraits::IsPointer; + + if (!qmlProperty.isWritable()) + traits = traits | Storage::PropertyDeclarationTraits::IsReadOnly; + + return traits; +} + +Storage::PropertyDeclarations createProperties(const QHash &qmlProperties) +{ + Storage::PropertyDeclarations propertyDeclarations; + propertyDeclarations.reserve(Utils::usize(qmlProperties)); + + for (const QQmlJSMetaProperty &qmlProperty : qmlProperties) { + propertyDeclarations.emplace_back(Utils::SmallString{qmlProperty.propertyName()}, + Storage::NativeType{ + Utils::SmallString{qmlProperty.typeName()}}, + createPropertyDeclarationTraits(qmlProperty)); + } + + return propertyDeclarations; +} + +Storage::ParameterDeclarations createParameters(const QQmlJSMetaMethod &qmlMethod) +{ + Storage::ParameterDeclarations parameterDeclarations; + + const QStringList ¶meterNames = qmlMethod.parameterNames(); + const QStringList ¶meterTypeNames = qmlMethod.parameterTypeNames(); + auto currentName = parameterNames.begin(); + auto currentType = parameterTypeNames.begin(); + auto nameEnd = parameterNames.end(); + auto typeEnd = parameterTypeNames.end(); + + for (; currentName != nameEnd && currentType != typeEnd; ++currentName, ++currentType) { + parameterDeclarations.emplace_back(Utils::SmallString{*currentName}, + Utils::SmallString{*currentType}); + } + + return parameterDeclarations; +} + +std::tuple createFunctionAndSignals( + const QMultiHash &qmlMethods) +{ + std::tuple functionAndSignalDeclarations; + Storage::FunctionDeclarations &functionsDeclarations{std::get<0>(functionAndSignalDeclarations)}; + functionsDeclarations.reserve(Utils::usize(qmlMethods)); + Storage::SignalDeclarations &signalDeclarations{std::get<1>(functionAndSignalDeclarations)}; + signalDeclarations.reserve(Utils::usize(qmlMethods)); + + for (const QQmlJSMetaMethod &qmlMethod : qmlMethods) { + if (qmlMethod.methodType() != QQmlJSMetaMethod::Type::Signal) { + functionsDeclarations.emplace_back(Utils::SmallString{qmlMethod.methodName()}, + Utils::SmallString{qmlMethod.returnTypeName()}, + createParameters(qmlMethod)); + } else { + signalDeclarations.emplace_back(Utils::SmallString{qmlMethod.methodName()}, + createParameters(qmlMethod)); + } + } + + return functionAndSignalDeclarations; +} + +Storage::EnumeratorDeclarations createEnumeratorsWithValues(const QQmlJSMetaEnum &qmlEnumeration) +{ + Storage::EnumeratorDeclarations enumeratorDeclarations; + + const QStringList &keys = qmlEnumeration.keys(); + const QList &values = qmlEnumeration.values(); + auto currentKey = keys.begin(); + auto currentValue = values.begin(); + auto keyEnd = keys.end(); + auto valueEnd = values.end(); + + for (; currentKey != keyEnd && currentValue != valueEnd; ++currentKey, ++currentValue) + enumeratorDeclarations.emplace_back(Utils::SmallString{*currentKey}, *currentValue); + + return enumeratorDeclarations; +} + +Storage::EnumeratorDeclarations createEnumeratorsWithoutValues(const QQmlJSMetaEnum &qmlEnumeration) +{ + Storage::EnumeratorDeclarations enumeratorDeclarations; + + for (const QString &key : qmlEnumeration.keys()) + enumeratorDeclarations.emplace_back(Utils::SmallString{key}); + + return enumeratorDeclarations; +} + +Storage::EnumeratorDeclarations createEnumerators(const QQmlJSMetaEnum &qmlEnumeration) +{ + if (qmlEnumeration.hasValues()) + return createEnumeratorsWithValues(qmlEnumeration); + + return createEnumeratorsWithoutValues(qmlEnumeration); +} + +Storage::EnumerationDeclarations createEnumeration(const QHash &qmlEnumerations) +{ + Storage::EnumerationDeclarations enumerationDeclarations; + enumerationDeclarations.reserve(Utils::usize(qmlEnumerations)); + + for (const QQmlJSMetaEnum &qmlEnumeration : qmlEnumerations) { + enumerationDeclarations.emplace_back(Utils::SmallString{qmlEnumeration.name()}, + createEnumerators(qmlEnumeration)); + } + + return enumerationDeclarations; +} + +void addType(Storage::Types &types, SourceId sourceId, ModuleId moduleId, const QQmlJSScope &component) +{ + auto [functionsDeclarations, signalDeclarations] = createFunctionAndSignals(component.ownMethods()); + types.emplace_back(moduleId, + Utils::SmallString{component.internalName()}, + Storage::NativeType{Utils::SmallString{component.baseTypeName()}}, + createTypeAccessSemantics(component.accessSemantics()), + sourceId, + createExports(moduleId, component.exports()), + createProperties(component.ownProperties()), + std::move(functionsDeclarations), + std::move(signalDeclarations), + createEnumeration(component.ownEnumerations())); +} + +void addTypes(Storage::Types &types, + SourceId sourceId, + ModuleId moduleId, + const QHash &objects) +{ + types.reserve(Utils::usize(objects) + types.size()); + + for (const auto &object : objects) + addType(types, sourceId, moduleId, *object.get()); +} + +} // namespace + +void QmlTypesParser::parse(const QString &sourceContent, + Storage::Imports &imports, + Storage::Types &types, + SourceId sourceId, + ModuleId moduleId) +{ + QQmlJSTypeDescriptionReader reader({}, sourceContent); + QHash components; + QStringList dependencies; + bool isValid = reader(&components, &dependencies); + if (!isValid) + throw CannotParseQmlTypesFile{}; + + addImports(imports, sourceId, dependencies); + addTypes(types, sourceId, moduleId, components); +} + +} // namespace QmlDesigner diff --git a/tests/unit/unittest/qmldom-test.cpp b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.h similarity index 57% rename from tests/unit/unittest/qmldom-test.cpp rename to src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.h index b7e8557f10e..04733f3ecdc 100644 --- a/tests/unit/unittest/qmldom-test.cpp +++ b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparser.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -23,29 +23,40 @@ ** ****************************************************************************/ -#include "googletest.h" +#pragma once -// cast of the top level items (DomEnvironments,...) -#include +#include "nonlockingmutex.h" +#include "qmltypesparserinterface.h" -// everything is in the QQmlJS::Dom namespace -using namespace QQmlJS::Dom; - -namespace { - -class QmlDom : public ::testing::Test -{ -public: -// static void SetUpTestCase(); -// static void TearDownTestCase(); - -protected: -}; - -TEST_F(QmlDom, First) -{ - DomItem env = DomEnvironment::create({}, DomEnvironment::Option::SingleThreaded - | DomEnvironment::Option::NoDependencies); +namespace Sqlite { +class Database; } -} // anonymous +namespace QmlDesigner { + +template +class ProjectStorage; + +template +class SourcePathCache; + +class QmlTypesParser : public QmlTypesParserInterface +{ +public: + using PathCache = QmlDesigner::SourcePathCache, + NonLockingMutex>; + + QmlTypesParser(PathCache &pathCache) + : m_pathCache{pathCache} + {} + + void parse(const QString &sourceContent, + Storage::Imports &imports, + Storage::Types &types, + SourceId sourceId, + ModuleId moduleId) override; + +private: + PathCache &m_pathCache; +}; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparserinterface.h b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparserinterface.h index 40c98838353..d3b102954c0 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparserinterface.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/qmltypesparserinterface.h @@ -37,7 +37,8 @@ public: virtual void parse(const QString &sourceContent, Storage::Imports &imports, Storage::Types &types, - SourceIds &sourceIds) + SourceId sourceId, + ModuleId moduleId) = 0; protected: diff --git a/tests/unit/unittest/CMakeLists.txt b/tests/unit/unittest/CMakeLists.txt index 6252f4517d5..36aa86e6312 100644 --- a/tests/unit/unittest/CMakeLists.txt +++ b/tests/unit/unittest/CMakeLists.txt @@ -481,8 +481,7 @@ get_filename_component( ABSOLUTE ) - -if (EXISTS ../../../../qmldom_standalone/src/qmldom/standalone) +if(EXISTS ${QMLDOM_STANDALONE_CMAKELISTS} AND Qt6_FOUND) add_subdirectory( ../../../../qmldom_standalone/src/qmldom/standalone ${CMAKE_CURRENT_BINARY_DIR}/qmldom_standalone) @@ -491,9 +490,16 @@ if (EXISTS ../../../../qmldom_standalone/src/qmldom/standalone) RUNTIME_OUTPUT_DIRECTORY "$" LIBRARY_OUTPUT_DIRECTORY "$") - extend_qtc_test(unittest - DEPENDS qmldomlib - SOURCES - qmldom-test.cpp + extend_qtc_test(unittest + DEPENDS qmldomlib + SOURCES + qmldocumentparser-test.cpp + qmltypesparser-test.cpp + ) + extend_qtc_test(unittest + SOURCES_PREFIX "${QmlDesignerDir}/designercore" + SOURCES + projectstorage/qmldocumentparser.cpp projectstorage/qmldocumentparser.h + projectstorage/qmltypesparser.cpp projectstorage/qmltypesparser.h ) endif() diff --git a/tests/unit/unittest/gtest-creator-printing.cpp b/tests/unit/unittest/gtest-creator-printing.cpp index 89577c1f051..85ff406f953 100644 --- a/tests/unit/unittest/gtest-creator-printing.cpp +++ b/tests/unit/unittest/gtest-creator-printing.cpp @@ -934,7 +934,7 @@ std::ostream &operator<<(std::ostream &out, const Diagnostic &diag) { } // namespace ClangTools namespace QmlDesigner { - +namespace { const char *sourceTypeToText(SourceType sourceType) { switch (sourceType) { @@ -950,6 +950,7 @@ const char *sourceTypeToText(SourceType sourceType) return ""; } +} // namespace std::ostream &operator<<(std::ostream &out, const FileStatus &fileStatus) { @@ -1015,8 +1016,8 @@ TypeAccessSemantics cleanFlags(TypeAccessSemantics accessSemantics) const char *typeAccessSemanticsToString(TypeAccessSemantics accessSemantics) { switch (cleanFlags(accessSemantics)) { - case TypeAccessSemantics::Invalid: - return "Invalid"; + case TypeAccessSemantics::None: + return "None"; case TypeAccessSemantics::Reference: return "Reference"; case TypeAccessSemantics::Sequence: @@ -1055,6 +1056,20 @@ const char *isQualifiedToString(IsQualified isQualified) return ""; } +const char *importKindToText(ImportKind kind) +{ + switch (kind) { + case ImportKind::Module: + return "Module"; + case ImportKind::Directory: + return "Directory"; + case ImportKind::QmlTypesDependency: + return "QmlTypesDependency"; + } + + return ""; +} + } // namespace std::ostream &operator<<(std::ostream &out, TypeAccessSemantics accessSemantics) @@ -1176,9 +1191,15 @@ std::ostream &operator<<(std::ostream &out, const Module &module) return out << "(" << module.name << ", " << module.sourceId << ")"; } +std::ostream &operator<<(std::ostream &out, const ImportKind &importKind) +{ + return out << importKindToText(importKind); +} + std::ostream &operator<<(std::ostream &out, const Import &import) { - return out << "(" << import.name << ", " << import.version << ", " << import.sourceId << ")"; + return out << "(" << import.name << ", " << import.version << ", " << import.sourceId << ", " + << import.moduleId << ", " << import.kind << ")"; } } // namespace Storage diff --git a/tests/unit/unittest/gtest-creator-printing.h b/tests/unit/unittest/gtest-creator-printing.h index d2c20d92b5f..5b8cb51e8e3 100644 --- a/tests/unit/unittest/gtest-creator-printing.h +++ b/tests/unit/unittest/gtest-creator-printing.h @@ -262,6 +262,7 @@ class EnumerationDeclaration; class EnumeratorDeclaration; class Module; class ModuleDependency; +enum class ImportKind : char; class Import; enum class IsQualified : int; @@ -282,6 +283,7 @@ std::ostream &operator<<(std::ostream &out, const EnumerationDeclaration &enumer std::ostream &operator<<(std::ostream &out, const EnumeratorDeclaration &enumeratorDeclaration); std::ostream &operator<<(std::ostream &out, const Module &module); std::ostream &operator<<(std::ostream &out, const ModuleDependency &module); +std::ostream &operator<<(std::ostream &out, const ImportKind &importKind); std::ostream &operator<<(std::ostream &out, const Import &import); std::ostream &operator<<(std::ostream &out, IsQualified isQualified); diff --git a/tests/unit/unittest/projectstorageupdater-test.cpp b/tests/unit/unittest/projectstorageupdater-test.cpp index d198587821e..d6a26bb1094 100644 --- a/tests/unit/unittest/projectstorageupdater-test.cpp +++ b/tests/unit/unittest/projectstorageupdater-test.cpp @@ -305,8 +305,8 @@ TEST_F(ProjectStorageUpdater, ParseQmlTypes) ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example2.qmltypes")))) .WillByDefault(Return(qmltypes2)); - EXPECT_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _)); - EXPECT_CALL(qmlTypesParserMock, parse(qmltypes2, _, _, _)); + EXPECT_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _, _)); + EXPECT_CALL(qmlTypesParserMock, parse(qmltypes2, _, _, _, _)); updater.update(); } @@ -334,8 +334,8 @@ TEST_F(ProjectStorageUpdater, SynchronizeQmlTypes) QString qmltypes{"Module {\ndependencies: []}"}; ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))) .WillByDefault(Return(qmltypes)); - ON_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _)) - .WillByDefault([&](auto, auto &imports, auto &types, auto &sourceIds) { + ON_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _, _)) + .WillByDefault([&](auto, auto &imports, auto &types, auto, auto) { types.push_back(objectType); imports.push_back(import); }); @@ -356,10 +356,9 @@ TEST_F(ProjectStorageUpdater, SynchronizeQmlTypesAreEmptyIfFileDoesNotChanged) QString qmltypes{"Module {\ndependencies: []}"}; ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))) .WillByDefault(Return(qmltypes)); - ON_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _)) - .WillByDefault([&](auto, auto &imports, auto &types, auto &sourceIds) { - types.push_back(objectType); - }); + ON_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _, _)) + .WillByDefault( + [&](auto, auto &imports, auto &types, auto, auto) { types.push_back(objectType); }); ON_CALL(fileSystemMock, fileStatus(Eq(qmltypesPathSourceId))) .WillByDefault(Return(FileStatus{qmltypesPathSourceId, 2, 421})); ON_CALL(fileSystemMock, fileStatus(Eq(qmltypes2PathSourceId))) diff --git a/tests/unit/unittest/qmldocumentparser-test.cpp b/tests/unit/unittest/qmldocumentparser-test.cpp new file mode 100644 index 00000000000..4cadf8253d7 --- /dev/null +++ b/tests/unit/unittest/qmldocumentparser-test.cpp @@ -0,0 +1,243 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "googletest.h" + +#include + +#include +#include +#include + +namespace { + +namespace Storage = QmlDesigner::Storage; +using QmlDesigner::ModuleId; +using QmlDesigner::SourceContextId; +using QmlDesigner::SourceId; + +MATCHER_P(HasPrototype, prototype, std::string(negation ? "isn't " : "is ") + PrintToString(prototype)) +{ + const Storage::Type &type = arg; + + return Storage::ImportedTypeName{prototype} == type.prototype; +} + +MATCHER_P3(IsPropertyDeclaration, + name, + typeName, + traits, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::PropertyDeclaration{name, typeName, traits})) +{ + const Storage::PropertyDeclaration &propertyDeclaration = arg; + + return propertyDeclaration.name == name + && Storage::ImportedTypeName{typeName} == propertyDeclaration.typeName + && propertyDeclaration.traits == traits; +} + +MATCHER_P2(IsFunctionDeclaration, + name, + returnTypeName, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::FunctionDeclaration{name, returnTypeName})) +{ + const Storage::FunctionDeclaration &declaration = arg; + + return declaration.name == name && declaration.returnTypeName == returnTypeName; +} + +MATCHER_P(IsSignalDeclaration, + name, + std::string(negation ? "isn't " : "is ") + PrintToString(Storage::SignalDeclaration{name})) +{ + const Storage::SignalDeclaration &declaration = arg; + + return declaration.name == name; +} + +MATCHER_P2(IsParameter, + name, + typeName, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::ParameterDeclaration{name, typeName})) +{ + const Storage::ParameterDeclaration &declaration = arg; + + return declaration.name == name && declaration.typeName == typeName; +} + +MATCHER_P(IsEnumeration, + name, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::EnumerationDeclaration{name, {}})) +{ + const Storage::EnumerationDeclaration &declaration = arg; + + return declaration.name == name; +} + +MATCHER_P(IsEnumerator, + name, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::EnumeratorDeclaration{name})) +{ + const Storage::EnumeratorDeclaration &declaration = arg; + + return declaration.name == name && !declaration.hasValue; +} + +MATCHER_P2(IsEnumerator, + name, + value, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::EnumeratorDeclaration{name, value, true})) +{ + const Storage::EnumeratorDeclaration &declaration = arg; + + return declaration.name == name && declaration.value == value && declaration.hasValue; +} + +class QmlDocumentParser : public ::testing::Test +{ +public: +protected: + Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; + QmlDesigner::ProjectStorage storage{database, database.isInitialized()}; + QmlDesigner::SourcePathCache> sourcePathCache{ + storage}; + QmlDesigner::QmlDocumentParser parser{sourcePathCache}; + Storage::Imports imports; + SourceId qmlFileSourceId{sourcePathCache.sourceId("path/to/qmlfile.qml")}; + SourceContextId qmlFileSourceContextId{sourcePathCache.sourceContextId(qmlFileSourceId)}; + SourceId directorySourceId{sourcePathCache.sourceId("path/to/.")}; + ModuleId directoryModuleId{&directorySourceId}; +}; + +TEST_F(QmlDocumentParser, Prototype) +{ + auto type = parser.parse("Example{}", imports, qmlFileSourceId, qmlFileSourceContextId); + + ASSERT_THAT(type, HasPrototype(Storage::ImportedType("Example"))); +} + +TEST_F(QmlDocumentParser, DISABLED_QualifiedPrototype) +{ + auto type = parser.parse("import Example as Example\n Example.Item{}", + imports, + qmlFileSourceId, + qmlFileSourceContextId); + + ASSERT_THAT(type, + HasPrototype(Storage::QualifiedImportedType( + "Item", Storage::Import{"Example", Storage::Version{}, qmlFileSourceId}))); +} + +TEST_F(QmlDocumentParser, Properties) +{ + auto type = parser.parse("Example{\n property int foo\n}", + imports, + qmlFileSourceId, + qmlFileSourceContextId); + + ASSERT_THAT(type.propertyDeclarations, + UnorderedElementsAre(IsPropertyDeclaration("foo", + Storage::ImportedType{"int"}, + Storage::PropertyDeclarationTraits::None))); +} + +TEST_F(QmlDocumentParser, DISABLED_Imports) +{ + ModuleId fooDirectoryModuleId{&sourcePathCache.sourceId("path/to/foo/.")}; + auto type = parser.parse("import QtQuick\n import \"../foo\"\nExample{}", + imports, + qmlFileSourceId, + qmlFileSourceContextId); + + ASSERT_THAT(imports, + UnorderedElementsAre( + Storage::Import{Storage::Version{}, directoryModuleId, qmlFileSourceId}, + Storage::Import{Storage::Version{}, fooDirectoryModuleId, qmlFileSourceId}, + Storage::Import{"QML", Storage::Version{1, 0}, qmlFileSourceId}, + Storage::Import{"QtQml", Storage::Version{6, 0}, qmlFileSourceId}, + Storage::Import{"QtQuick", Storage::Version{}, qmlFileSourceId})); +} + +TEST_F(QmlDocumentParser, Functions) +{ + auto type = parser.parse( + "Example{\n function someScript(x, y) {}\n function otherFunction() {}\n}", + imports, + qmlFileSourceId, + qmlFileSourceContextId); + + ASSERT_THAT(type.functionDeclarations, + UnorderedElementsAre(AllOf(IsFunctionDeclaration("otherFunction", ""), + Field(&Storage::FunctionDeclaration::parameters, IsEmpty())), + AllOf(IsFunctionDeclaration("someScript", ""), + Field(&Storage::FunctionDeclaration::parameters, + ElementsAre(IsParameter("x", ""), + IsParameter("y", "")))))); +} + +TEST_F(QmlDocumentParser, Signals) +{ + auto type = parser.parse("Example{\n signal someSignal(int x, real y)\n signal signal2()\n}", + imports, + qmlFileSourceId, + qmlFileSourceContextId); + + ASSERT_THAT(type.signalDeclarations, + UnorderedElementsAre(AllOf(IsSignalDeclaration("someSignal"), + Field(&Storage::SignalDeclaration::parameters, + ElementsAre(IsParameter("x", "int"), + IsParameter("y", "real")))), + AllOf(IsSignalDeclaration("signal2"), + Field(&Storage::SignalDeclaration::parameters, IsEmpty())))); +} + +TEST_F(QmlDocumentParser, Enumeration) +{ + auto type = parser.parse("Example{\n enum Color{red, green, blue=10, white}\n enum " + "State{On,Off}\n}", + imports, + qmlFileSourceId, + qmlFileSourceContextId); + + ASSERT_THAT(type.enumerationDeclarations, + UnorderedElementsAre( + AllOf(IsEnumeration("Color"), + Field(&Storage::EnumerationDeclaration::enumeratorDeclarations, + ElementsAre(IsEnumerator("red", 0), + IsEnumerator("green", 1), + IsEnumerator("blue", 10), + IsEnumerator("white", 11)))), + AllOf(IsEnumeration("State"), + Field(&Storage::EnumerationDeclaration::enumeratorDeclarations, + ElementsAre(IsEnumerator("On", 0), IsEnumerator("Off", 1)))))); +} + +} // namespace diff --git a/tests/unit/unittest/qmltypesparser-test.cpp b/tests/unit/unittest/qmltypesparser-test.cpp new file mode 100644 index 00000000000..fcd182b9206 --- /dev/null +++ b/tests/unit/unittest/qmltypesparser-test.cpp @@ -0,0 +1,410 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "googletest.h" + +#include + +#include +#include +#include +#include + +namespace { + +namespace Storage = QmlDesigner::Storage; +using QmlDesigner::ModuleId; +using QmlDesigner::SourceContextId; +using QmlDesigner::SourceId; + +MATCHER_P4(IsImport, + name, + version, + sourceId, + kind, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::Import{name, version, sourceId, kind})) +{ + const Storage::Import &import = arg; + + return import.name == name && import.version == version && import.sourceId == sourceId + && import.kind == kind; +} + +MATCHER_P(HasPrototype, prototype, std::string(negation ? "isn't " : "is ") + PrintToString(prototype)) +{ + const Storage::Type &type = arg; + + return Storage::ImportedTypeName{prototype} == type.prototype; +} + +MATCHER_P5(IsType, + moduleId, + typeName, + prototype, + accessSemantics, + sourceId, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::Type{moduleId, typeName, prototype, accessSemantics, sourceId})) +{ + const Storage::Type &type = arg; + + return type.moduleId == moduleId && type.typeName == typeName + && type.prototype == Storage::ImportedTypeName{prototype} + && type.accessSemantics == accessSemantics && type.sourceId == sourceId; +} + +MATCHER_P3(IsPropertyDeclaration, + name, + typeName, + traits, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::PropertyDeclaration{name, typeName, traits})) +{ + const Storage::PropertyDeclaration &propertyDeclaration = arg; + + return propertyDeclaration.name == name + && Storage::ImportedTypeName{typeName} == propertyDeclaration.typeName + && propertyDeclaration.traits == traits + && propertyDeclaration.kind == Storage::PropertyKind::Property; +} + +MATCHER_P2(IsFunctionDeclaration, + name, + returnTypeName, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::FunctionDeclaration{name, returnTypeName})) +{ + const Storage::FunctionDeclaration &declaration = arg; + + return declaration.name == name && declaration.returnTypeName == returnTypeName; +} + +MATCHER_P(IsSignalDeclaration, + name, + std::string(negation ? "isn't " : "is ") + PrintToString(Storage::SignalDeclaration{name})) +{ + const Storage::SignalDeclaration &declaration = arg; + + return declaration.name == name; +} + +MATCHER_P2(IsParameter, + name, + typeName, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::ParameterDeclaration{name, typeName})) +{ + const Storage::ParameterDeclaration &declaration = arg; + + return declaration.name == name && declaration.typeName == typeName; +} + +MATCHER_P(IsEnumeration, + name, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::EnumerationDeclaration{name, {}})) +{ + const Storage::EnumerationDeclaration &declaration = arg; + + return declaration.name == name; +} + +MATCHER_P(IsEnumerator, + name, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::EnumeratorDeclaration{name})) +{ + const Storage::EnumeratorDeclaration &declaration = arg; + + return declaration.name == name && !declaration.hasValue; +} + +MATCHER_P2(IsEnumerator, + name, + value, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::EnumeratorDeclaration{name, value, true})) +{ + const Storage::EnumeratorDeclaration &declaration = arg; + + return declaration.name == name && declaration.value == value && declaration.hasValue; +} + +MATCHER_P3(IsExportedType, + moduleId, + name, + version, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::ExportedType{moduleId, name, version})) +{ + const Storage::ExportedType &type = arg; + + return type.name == name && type.moduleId == moduleId && type.version == version; +} + +class QmlTypesParser : public ::testing::Test +{ +public: +protected: + Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; + QmlDesigner::ProjectStorage storage{database, database.isInitialized()}; + QmlDesigner::SourcePathCache> sourcePathCache{ + storage}; + QmlDesigner::QmlTypesParser parser{sourcePathCache}; + Storage::Imports imports; + Storage::Types types; + SourceId qmltypesFileSourceId{sourcePathCache.sourceId("path/to/types.qmltypes")}; + SourceContextId qmltypesFileSourceContextId{sourcePathCache.sourceContextId(qmltypesFileSourceId)}; + SourceId directorySourceId{sourcePathCache.sourceId("path/to/.")}; + ModuleId directoryModuleId{&directorySourceId}; +}; + +TEST_F(QmlTypesParser, Imports) +{ + QString source{R"(import QtQuick.tooling 1.2 + Module{ + dependencies: + ["QtQuick 2.15", "QtQuick.Window 2.1", "QtFoo 6"]})"}; + + parser.parse(source, imports, types, qmltypesFileSourceId, directoryModuleId); + + ASSERT_THAT(imports, + UnorderedElementsAre(IsImport("QtQuick", + Storage::Version{2, 15}, + qmltypesFileSourceId, + Storage::ImportKind::QmlTypesDependency), + IsImport("QtQuick.Window", + Storage::Version{2, 1}, + qmltypesFileSourceId, + Storage::ImportKind::QmlTypesDependency), + IsImport("QML", + Storage::Version{}, + qmltypesFileSourceId, + Storage::ImportKind::QmlTypesDependency), + IsImport("QtQml", + Storage::Version{}, + qmltypesFileSourceId, + Storage::ImportKind::QmlTypesDependency), + IsImport("QtFoo", + Storage::Version{6}, + qmltypesFileSourceId, + Storage::ImportKind::QmlTypesDependency))); +} + +TEST_F(QmlTypesParser, Types) +{ + QString source{R"(import QtQuick.tooling 1.2 + Module{ + Component { name: "QObject"} + Component { name: "QQmlComponent" + prototype: "QObject"}})"}; + + parser.parse(source, imports, types, qmltypesFileSourceId, directoryModuleId); + + ASSERT_THAT(types, + UnorderedElementsAre(IsType(directoryModuleId, + "QObject", + Storage::NativeType{}, + Storage::TypeAccessSemantics::Reference, + qmltypesFileSourceId), + IsType(directoryModuleId, + "QQmlComponent", + Storage::NativeType{"QObject"}, + Storage::TypeAccessSemantics::Reference, + qmltypesFileSourceId))); +} + +TEST_F(QmlTypesParser, ExportedTypes) +{ + QString source{R"(import QtQuick.tooling 1.2 + Module{ + Component { name: "QObject" + exports: ["QtQml/QtObject 1.0", "QtQml/QtObject 2.1"] + }})"}; + + parser.parse(source, imports, types, qmltypesFileSourceId, directoryModuleId); + + ASSERT_THAT( + types, + ElementsAre( + Field(&Storage::Type::exportedTypes, + ElementsAre(IsExportedType(directoryModuleId, "QtObject", Storage::Version{1, 0}), + IsExportedType(directoryModuleId, "QtObject", Storage::Version{2, 1}))))); +} + +TEST_F(QmlTypesParser, Properties) +{ + QString source{R"(import QtQuick.tooling 1.2 + Module{ + Component { name: "QObject" + Property { name: "objectName"; type: "string" } + Property { name: "target"; type: "QObject"; isPointer: true } + Property { name: "progress"; type: "double"; isReadonly: true } + Property { name: "targets"; type: "QQuickItem"; isList: true; isReadonly: true; isPointer: true } + }})"}; + + parser.parse(source, imports, types, qmltypesFileSourceId, directoryModuleId); + + ASSERT_THAT(types, + ElementsAre(Field( + &Storage::Type::propertyDeclarations, + UnorderedElementsAre( + IsPropertyDeclaration("objectName", + Storage::NativeType{"string"}, + Storage::PropertyDeclarationTraits::None), + IsPropertyDeclaration("target", + Storage::NativeType{"QObject"}, + Storage::PropertyDeclarationTraits::IsPointer), + IsPropertyDeclaration("progress", + Storage::NativeType{"double"}, + Storage::PropertyDeclarationTraits::IsReadOnly), + IsPropertyDeclaration("targets", + Storage::NativeType{"QQuickItem"}, + Storage::PropertyDeclarationTraits::IsReadOnly + | Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsPointer))))); +} + +TEST_F(QmlTypesParser, Functions) +{ + QString source{R"(import QtQuick.tooling 1.2 + Module{ + Component { name: "QObject" + Method { name: "movieUpdate" } + Method { + name: "advance" + Parameter { name: "frames"; type: "int" } + Parameter { name: "fps"; type: "double" } + } + Method { + name: "isImageLoading" + type: "bool" + Parameter { name: "url"; type: "QUrl" } + } + Method { + name: "getContext" + Parameter { name: "args"; type: "QQmlV4Function"; isPointer: true } + } + }})"}; + + parser.parse(source, imports, types, qmltypesFileSourceId, directoryModuleId); + + ASSERT_THAT(types, + ElementsAre( + Field(&Storage::Type::functionDeclarations, + UnorderedElementsAre( + AllOf(IsFunctionDeclaration("advance", ""), + Field(&Storage::FunctionDeclaration::parameters, + UnorderedElementsAre(IsParameter("frames", "int"), + IsParameter("fps", "double")))), + AllOf(IsFunctionDeclaration("isImageLoading", "bool"), + Field(&Storage::FunctionDeclaration::parameters, + UnorderedElementsAre(IsParameter("url", "QUrl")))), + AllOf(IsFunctionDeclaration("getContext", ""), + Field(&Storage::FunctionDeclaration::parameters, + UnorderedElementsAre(IsParameter("args", "QQmlV4Function")))), + AllOf(IsFunctionDeclaration("movieUpdate", ""), + Field(&Storage::FunctionDeclaration::parameters, IsEmpty())))))); +} + +TEST_F(QmlTypesParser, Signals) +{ + QString source{R"(import QtQuick.tooling 1.2 + Module{ + Component { name: "QObject" + Method { name: "movieUpdate" } + Signal { + name: "advance" + Parameter { name: "frames"; type: "int" } + Parameter { name: "fps"; type: "double" } + } + Signal { + name: "isImageLoading" + Parameter { name: "url"; type: "QUrl" } + } + Signal { + name: "getContext" + Parameter { name: "args"; type: "QQmlV4Function"; isPointer: true } + } + }})"}; + + parser.parse(source, imports, types, qmltypesFileSourceId, directoryModuleId); + + ASSERT_THAT(types, + ElementsAre(Field(&Storage::Type::signalDeclarations, + UnorderedElementsAre( + AllOf(IsSignalDeclaration("advance"), + Field(&Storage::SignalDeclaration::parameters, + UnorderedElementsAre(IsParameter("frames", "int"), + IsParameter("fps", "double")))), + AllOf(IsSignalDeclaration("isImageLoading"), + Field(&Storage::SignalDeclaration::parameters, + UnorderedElementsAre(IsParameter("url", "QUrl")))), + AllOf(IsSignalDeclaration("getContext"), + Field(&Storage::SignalDeclaration::parameters, + UnorderedElementsAre( + IsParameter("args", "QQmlV4Function")))))))); +} + +TEST_F(QmlTypesParser, Enumerations) +{ + QString source{R"(import QtQuick.tooling 1.2 + Module{ + Component { name: "QObject" + Enum { + name: "NamedColorSpace" + values: [ + "Unknown", + "SRgb", + "AdobeRgb", + "DisplayP3", + ] + } + Enum { + name: "VerticalLayoutDirection" + values: ["TopToBottom", "BottomToTop"] + } + }})"}; + + parser.parse(source, imports, types, qmltypesFileSourceId, directoryModuleId); + + ASSERT_THAT(types, + ElementsAre( + Field(&Storage::Type::enumerationDeclarations, + UnorderedElementsAre( + AllOf(IsEnumeration("NamedColorSpace"), + Field(&Storage::EnumerationDeclaration::enumeratorDeclarations, + UnorderedElementsAre(IsEnumerator("Unknown"), + IsEnumerator("SRgb"), + IsEnumerator("AdobeRgb"), + IsEnumerator("DisplayP3")))), + AllOf(IsEnumeration("VerticalLayoutDirection"), + Field(&Storage::EnumerationDeclaration::enumeratorDeclarations, + UnorderedElementsAre(IsEnumerator("TopToBottom"), + IsEnumerator("BottomToTop")))))))); +} + +} // namespace diff --git a/tests/unit/unittest/qmltypesparsermock.h b/tests/unit/unittest/qmltypesparsermock.h index 337c0f05ff8..cfbb8729841 100644 --- a/tests/unit/unittest/qmltypesparsermock.h +++ b/tests/unit/unittest/qmltypesparsermock.h @@ -37,6 +37,7 @@ public: (const QString &sourceContent, QmlDesigner::Storage::Imports &imports, QmlDesigner::Storage::Types &types, - QmlDesigner::SourceIds &sourceIds), + QmlDesigner::SourceId sourceId, + QmlDesigner::ModuleId moduleId), (override)); };