aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmltyperegistrar/qqmltypescreator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmltyperegistrar/qqmltypescreator.cpp')
-rw-r--r--src/qmltyperegistrar/qqmltypescreator.cpp453
1 files changed, 453 insertions, 0 deletions
diff --git a/src/qmltyperegistrar/qqmltypescreator.cpp b/src/qmltyperegistrar/qqmltypescreator.cpp
new file mode 100644
index 0000000000..297a23679a
--- /dev/null
+++ b/src/qmltyperegistrar/qqmltypescreator.cpp
@@ -0,0 +1,453 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qanystringviewutils_p.h"
+#include "qqmltyperegistrarconstants_p.h"
+#include "qqmltyperegistrarutils_p.h"
+#include "qqmltypesclassdescription_p.h"
+#include "qqmltypescreator_p.h"
+
+#include <QtCore/qset.h>
+#include <QtCore/qcborarray.h>
+#include <QtCore/qcbormap.h>
+#include <QtCore/qsavefile.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qversionnumber.h>
+
+#include <QtCore/private/qstringalgorithms_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+using namespace Constants;
+using namespace Constants::DotQmltypes;
+using namespace QAnyStringViewUtils;
+
+static QString convertPrivateClassToUsableForm(QAnyStringView s)
+{
+ // typical privateClass entry in MOC looks like: ClassName::d_func(), where
+ // ClassName is a non-private class name. we don't need "::d_func()" piece
+ // so that could be removed, but we need "Private" so that ClassName becomes
+ // ClassNamePrivate (at present, simply consider this correct)
+ return s.toString().replace("::d_func()"_L1, "Private"_L1);
+}
+
+void QmlTypesCreator::writeClassProperties(const QmlTypesClassDescription &collector)
+{
+ if (!collector.file.isEmpty())
+ m_qml.writeStringBinding(S_FILE, collector.file);
+ m_qml.writeStringBinding(S_NAME, collector.className);
+
+ if (!collector.primitiveAliases.isEmpty())
+ m_qml.writeStringListBinding(S_ALIASES, collector.primitiveAliases);
+
+ if (!collector.accessSemantics.isEmpty())
+ m_qml.writeStringBinding(S_ACCESS_SEMANTICS, collector.accessSemantics);
+
+ if (!collector.defaultProp.isEmpty())
+ m_qml.writeStringBinding(S_DEFAULT_PROPERTY, collector.defaultProp);
+
+ if (!collector.parentProp.isEmpty())
+ m_qml.writeStringBinding(S_PARENT_PROPERTY, collector.parentProp);
+
+ if (!collector.superClass.isEmpty())
+ m_qml.writeStringBinding(S_PROTOTYPE, collector.superClass);
+
+ if (!collector.sequenceValueType.isEmpty()) {
+ const QAnyStringView name = collector.sequenceValueType.back() == '*'_L1
+ ? collector.sequenceValueType.chopped(1)
+ : collector.sequenceValueType;
+ m_qml.writeStringBinding(S_VALUE_TYPE, name);
+ }
+
+ if (collector.extensionIsJavaScript) {
+ if (!collector.javaScriptExtensionType.isEmpty()) {
+ m_qml.writeStringBinding(S_EXTENSION, collector.javaScriptExtensionType);
+ m_qml.writeBooleanBinding(S_EXTENSION_IS_JAVA_SCRIPT, true);
+ } else {
+ warning(collector.file)
+ << "JavaScript extension type for" << collector.className
+ << "does not exist";
+ }
+
+ if (collector.extensionIsNamespace) {
+ warning(collector.file)
+ << "Extension type for" << collector.className
+ << "cannot be both a JavaScript type and a namespace";
+ if (!collector.nativeExtensionType.isEmpty()) {
+ m_qml.writeStringBinding(S_EXTENSION, collector.nativeExtensionType);
+ m_qml.writeBooleanBinding(S_EXTENSION_IS_NAMESPACE, true);
+ }
+ }
+ } else if (!collector.nativeExtensionType.isEmpty()) {
+ m_qml.writeStringBinding(S_EXTENSION, collector.nativeExtensionType);
+ if (collector.extensionIsNamespace)
+ m_qml.writeBooleanBinding(S_EXTENSION_IS_NAMESPACE, true);
+ } else if (collector.extensionIsNamespace) {
+ warning(collector.file)
+ << "Extension namespace for" << collector.className << "does not exist";
+ m_qml.writeBooleanBinding(S_EXTENSION_IS_NAMESPACE, true);
+ }
+
+ if (!collector.implementsInterfaces.isEmpty())
+ m_qml.writeStringListBinding(S_INTERFACES, collector.implementsInterfaces);
+
+ if (!collector.deferredNames.isEmpty())
+ m_qml.writeStringListBinding(S_DEFERRED_NAMES, collector.deferredNames);
+
+ if (!collector.immediateNames.isEmpty())
+ m_qml.writeStringListBinding(S_IMMEDIATE_NAMES, collector.immediateNames);
+
+ if (collector.elementNames.isEmpty()) // e.g. if QML_ANONYMOUS
+ return;
+
+ if (!collector.sequenceValueType.isEmpty()) {
+ warning(collector.file) << "Ignoring names of sequential container:";
+ for (const QAnyStringView &name : std::as_const(collector.elementNames))
+ warning(collector.file) << " - " << name.toString();
+ warning(collector.file)
+ << "Sequential containers are anonymous. Use QML_ANONYMOUS to register them.";
+ return;
+ }
+
+ QByteArrayList exports;
+ QByteArrayList metaObjects;
+
+ for (auto it = collector.revisions.begin(), end = collector.revisions.end(); it != end; ++it) {
+ const QTypeRevision revision = *it;
+ if (revision < collector.addedInRevision)
+ continue;
+ if (collector.removedInRevision.isValid() && !(revision < collector.removedInRevision))
+ break;
+ if (revision.hasMajorVersion() && revision.majorVersion() > m_version.majorVersion())
+ break;
+
+ for (const QAnyStringView &elementName : std::as_const(collector.elementNames)) {
+ QByteArray exportEntry = m_module + '/';
+
+ elementName.visit([&](auto view) {
+ processAsUtf8(view, [&](QByteArrayView view) { exportEntry.append(view); });
+ });
+ exportEntry += ' ' + QByteArray::number(revision.hasMajorVersion()
+ ? revision.majorVersion()
+ : m_version.majorVersion());
+ exportEntry += '.' + QByteArray::number(revision.minorVersion());
+
+ exports.append(exportEntry);
+ }
+ metaObjects.append(QByteArray::number(revision.toEncodedVersion<quint16>()));
+ }
+
+ QList<QAnyStringView> exportStrings;
+ exportStrings.reserve(exports.length());
+ for (const QByteArray &entry: exports)
+ exportStrings.append(QUtf8StringView(entry));
+
+ m_qml.writeStringListBinding(S_EXPORTS, exportStrings);
+ m_qml.writeBooleanBinding(S_IS_CREATABLE, collector.isCreatable && !collector.isSingleton);
+
+ if (collector.isStructured)
+ m_qml.writeBooleanBinding(S_IS_STRUCTURED, true);
+
+ if (collector.isSingleton)
+ m_qml.writeBooleanBinding(S_IS_SINGLETON, true);
+
+ if (collector.hasCustomParser)
+ m_qml.writeBooleanBinding(S_HAS_CUSTOM_PARSER, true);
+
+ m_qml.writeArrayBinding(S_EXPORT_META_OBJECT_REVISIONS, metaObjects);
+
+ if (!collector.attachedType.isEmpty())
+ m_qml.writeStringBinding(S_ATTACHED_TYPE, collector.attachedType);
+}
+
+void QmlTypesCreator::writeType(QAnyStringView type)
+{
+ ResolvedTypeAlias resolved(type);
+ if (resolved.type.isEmpty())
+ return;
+
+ m_qml.writeStringBinding(S_TYPE, resolved.type);
+ if (resolved.isList)
+ m_qml.writeBooleanBinding(S_IS_LIST, true);
+ if (resolved.isPointer)
+ m_qml.writeBooleanBinding(S_IS_POINTER, true);
+ if (resolved.isConstant)
+ m_qml.writeBooleanBinding(S_IS_CONSTANT, true);
+}
+
+void QmlTypesCreator::writeProperties(const Property::Container &properties)
+{
+ for (const Property &obj : properties) {
+ const QAnyStringView name = obj.name;
+ m_qml.writeStartObject(S_PROPERTY);
+ m_qml.writeStringBinding(S_NAME, name);
+ if (obj.revision.isValid())
+ m_qml.writeNumberBinding(S_REVISION, obj.revision.toEncodedVersion<int>());
+
+ writeType(obj.type);
+
+ const auto bindable = obj.bindable;
+ if (!bindable.isEmpty())
+ m_qml.writeStringBinding(S_BINDABLE, bindable);
+ const auto read = obj.read;
+ if (!read.isEmpty())
+ m_qml.writeStringBinding(S_READ, read);
+ const auto write = obj.write;
+ if (!write.isEmpty())
+ m_qml.writeStringBinding(S_WRITE, write);
+ const auto reset = obj.reset;
+ if (!reset.isEmpty())
+ m_qml.writeStringBinding(S_RESET, reset);
+ const auto notify = obj.notify;
+ if (!notify.isEmpty())
+ m_qml.writeStringBinding(S_NOTIFY, notify);
+ const auto index = obj.index;
+ if (index != -1) {
+ m_qml.writeNumberBinding(S_INDEX, index);
+ }
+ const auto privateClass = obj.privateClass;
+ if (!privateClass.isEmpty()) {
+ m_qml.writeStringBinding(
+ S_PRIVATE_CLASS, convertPrivateClassToUsableForm(privateClass));
+ }
+
+ if (obj.write.isEmpty() && obj.member.isEmpty())
+ m_qml.writeBooleanBinding(S_IS_READONLY, true);
+
+ if (obj.isFinal)
+ m_qml.writeBooleanBinding(S_IS_FINAL, true);
+
+ if (obj.isConstant)
+ m_qml.writeBooleanBinding(S_IS_CONSTANT, true);
+
+ if (obj.isRequired)
+ m_qml.writeBooleanBinding(S_IS_REQUIRED, true);
+
+ m_qml.writeEndObject();
+ }
+}
+
+void QmlTypesCreator::writeMethods(const Method::Container &methods, QLatin1StringView type)
+{
+ for (const Method &obj : methods) {
+ const QAnyStringView name = obj.name;
+ if (name.isEmpty())
+ continue;
+
+ const auto revision = obj.revision;
+ m_qml.writeStartObject(type);
+ m_qml.writeStringBinding(S_NAME, name);
+ if (revision.isValid())
+ m_qml.writeNumberBinding(S_REVISION, revision.toEncodedVersion<int>());
+ writeType(obj.returnType);
+
+ if (obj.isCloned)
+ m_qml.writeBooleanBinding(S_IS_CLONED, true);
+ if (obj.isConstructor)
+ m_qml.writeBooleanBinding(S_IS_CONSTRUCTOR, true);
+ if (obj.isJavaScriptFunction)
+ m_qml.writeBooleanBinding(S_IS_JAVASCRIPT_FUNCTION, true);
+
+ const Argument::Container &arguments = obj.arguments;
+ for (qsizetype i = 0, end = arguments.size(); i != end; ++i) {
+ const Argument &obj = arguments[i];
+ m_qml.writeStartObject(S_PARAMETER);
+ const QAnyStringView name = obj.name;
+ if (!name.isEmpty())
+ m_qml.writeStringBinding(S_NAME, name);
+ writeType(obj.type);
+ m_qml.writeEndObject();
+ }
+ m_qml.writeEndObject();
+ }
+}
+
+void QmlTypesCreator::writeEnums(
+ const Enum::Container &enums, QmlTypesCreator::EnumClassesMode enumClassesMode)
+{
+ for (const Enum &obj : enums) {
+ m_qml.writeStartObject(S_ENUM);
+ m_qml.writeStringBinding(S_NAME, obj.name);
+ if (!obj.alias.isEmpty())
+ m_qml.writeStringBinding(S_ALIAS, obj.alias);
+ if (obj.isFlag)
+ m_qml.writeBooleanBinding(S_IS_FLAG, true);
+
+ if (enumClassesMode == EnumClassesMode::Scoped) {
+ if (obj.isClass)
+ m_qml.writeBooleanBinding(S_IS_SCOPED, true);
+ }
+
+ writeType(obj.type);
+ m_qml.writeStringListBinding(S_VALUES, obj.values);
+ m_qml.writeEndObject();
+ }
+}
+
+template<typename Member>
+bool isAllowedInMajorVersion(const Member &memberObject, QTypeRevision maxMajorVersion)
+{
+ const QTypeRevision memberRevision = memberObject.revision;
+ return !memberRevision.hasMajorVersion()
+ || memberRevision.majorVersion() <= maxMajorVersion.majorVersion();
+}
+
+template<typename Members, typename Postprocess>
+Members members(const Members &candidates, QTypeRevision maxMajorVersion, Postprocess &&process)
+{
+ Members classDefMembers;
+
+ for (const auto &member : candidates) {
+ if (isAllowedInMajorVersion(member, maxMajorVersion))
+ classDefMembers.push_back(process(member));
+ }
+
+ return classDefMembers;
+}
+
+template<typename Members>
+Members members(const Members &candidates, QTypeRevision maxMajorVersion)
+{
+ return members(candidates, maxMajorVersion, [](const auto &member) { return member; });
+}
+
+template<typename Members>
+Members constructors(const Members &candidates, QTypeRevision maxMajorVersion)
+{
+ return members(candidates, maxMajorVersion, [](const auto &member) {
+ auto ctor = member;
+ ctor.isConstructor = true;
+ return ctor;
+ });
+}
+
+void QmlTypesCreator::writeRootMethods(const MetaType &classDef)
+{
+ // Hide destroyed() signals
+ Method::Container componentSignals = members(classDef.sigs(), m_version);
+ for (auto it = componentSignals.begin(); it != componentSignals.end();) {
+ if (it->name == "destroyed"_L1)
+ it = componentSignals.erase(it);
+ else
+ ++it;
+ }
+ writeMethods(componentSignals, S_SIGNAL);
+
+ // Hide deleteLater() methods
+ Method::Container componentMethods = members(classDef.methods(), m_version);
+ for (auto it = componentMethods.begin(); it != componentMethods.end();) {
+ if (it->name == "deleteLater"_L1)
+ it = componentMethods.erase(it);
+ else
+ ++it;
+ }
+
+ // Add toString()
+ Method toStringMethod;
+ toStringMethod.name = "toString"_L1;
+ toStringMethod.access = Access::Public;
+ toStringMethod.returnType = "QString"_L1;
+ componentMethods.push_back(std::move(toStringMethod));
+
+ // Add destroy(int)
+ Method destroyMethodWithArgument;
+ destroyMethodWithArgument.name = "destroy"_L1;
+ destroyMethodWithArgument.access = Access::Public;
+ Argument delayArgument;
+ delayArgument.name = "delay"_L1;
+ delayArgument.type = "int"_L1;
+ destroyMethodWithArgument.arguments.push_back(std::move(delayArgument));
+ componentMethods.push_back(std::move(destroyMethodWithArgument));
+
+ // Add destroy()
+ Method destroyMethod;
+ destroyMethod.name = "destroy"_L1;
+ destroyMethod.access = Access::Public;
+ destroyMethod.isCloned = true;
+ componentMethods.push_back(std::move(destroyMethod));
+
+ writeMethods(componentMethods, S_METHOD);
+};
+
+void QmlTypesCreator::writeComponent(const QmlTypesClassDescription &collector)
+{
+ m_qml.writeStartObject(S_COMPONENT);
+
+ writeClassProperties(collector);
+
+ if (const MetaType &classDef = collector.resolvedClass; !classDef.isEmpty()) {
+ writeEnums(
+ classDef.enums(),
+ collector.registerEnumClassesScoped
+ ? EnumClassesMode::Scoped
+ : EnumClassesMode::Unscoped);
+
+ writeProperties(members(classDef.properties(), m_version));
+
+ if (collector.isRootClass) {
+ writeRootMethods(classDef);
+ } else {
+ writeMethods(members(classDef.sigs(), m_version), S_SIGNAL);
+ writeMethods(members(classDef.methods(), m_version), S_METHOD);
+ }
+
+ writeMethods(constructors(classDef.constructors(), m_version), S_METHOD);
+ }
+ m_qml.writeEndObject();
+}
+
+void QmlTypesCreator::writeComponents()
+{
+ for (const MetaType &component : std::as_const(m_ownTypes)) {
+ QmlTypesClassDescription collector;
+ collector.collect(component, m_ownTypes, m_foreignTypes,
+ QmlTypesClassDescription::TopLevel, m_version);
+
+ writeComponent(collector);
+
+ if (collector.resolvedClass != component
+ && std::binary_search(
+ m_referencedTypes.begin(), m_referencedTypes.end(),
+ component.qualifiedClassName())) {
+
+ // This type is referenced from elsewhere and has a QML_FOREIGN of its own. We need to
+ // also generate a description of the local type then. All the QML_* macros are
+ // ignored, and the result is an anonymous type.
+
+ QmlTypesClassDescription collector;
+ collector.collectLocalAnonymous(component, m_ownTypes, m_foreignTypes, m_version);
+ Q_ASSERT(!collector.isRootClass);
+
+ writeComponent(collector);
+ }
+ }
+}
+
+bool QmlTypesCreator::generate(const QString &outFileName)
+{
+ m_qml.writeStartDocument();
+ m_qml.writeLibraryImport("QtQuick.tooling", 1, 2);
+ m_qml.write(
+ "\n// This file describes the plugin-supplied types contained in the library."
+ "\n// It is used for QML tooling purposes only."
+ "\n//"
+ "\n// This file was auto-generated by qmltyperegistrar.\n\n");
+ m_qml.writeStartObject(S_MODULE);
+
+ writeComponents();
+
+ m_qml.writeEndObject();
+
+ QSaveFile file(outFileName);
+ if (!file.open(QIODevice::WriteOnly))
+ return false;
+
+ if (file.write(m_output) != m_output.size())
+ return false;
+
+ return file.commit();
+}
+
+QT_END_NAMESPACE
+