1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qqmltypescreator_p.h"
5#include "qqmltypesclassdescription_p.h"
6
7#include <QtCore/qset.h>
8#include <QtCore/qjsonarray.h>
9#include <QtCore/qsavefile.h>
10#include <QtCore/qfile.h>
11#include <QtCore/qjsondocument.h>
12#include <QtCore/qversionnumber.h>
13
14using namespace Qt::StringLiterals;
15
16QT_BEGIN_NAMESPACE
17
18static QString enquote(const QString &string)
19{
20 QString s = string;
21 return QString::fromLatin1(ba: "\"%1\"").arg(a: s.replace(c: QLatin1Char('\\'), after: QLatin1String("\\\\"))
22 .replace(c: QLatin1Char('"'),after: QLatin1String("\\\"")));
23}
24
25static QString convertPrivateClassToUsableForm(QString s)
26{
27 // typical privateClass entry in MOC looks like: ClassName::d_func(), where
28 // ClassName is a non-private class name. we don't need "::d_func()" piece
29 // so that could be removed, but we need "Private" so that ClassName becomes
30 // ClassNamePrivate (at present, simply consider this correct)
31 s.replace(before: u"::d_func()"_s, after: u"Private"_s);
32 return s;
33}
34
35void QmlTypesCreator::writeClassProperties(const QmlTypesClassDescription &collector)
36{
37 if (!collector.file.isEmpty())
38 m_qml.writeScriptBinding(name: QLatin1String("file"), rhs: enquote(string: collector.file));
39 m_qml.writeScriptBinding(name: QLatin1String("name"), rhs: enquote(string: collector.className));
40
41 if (!collector.accessSemantics.isEmpty())
42 m_qml.writeScriptBinding(name: QLatin1String("accessSemantics"), rhs: enquote(string: collector.accessSemantics));
43
44 if (!collector.defaultProp.isEmpty())
45 m_qml.writeScriptBinding(name: QLatin1String("defaultProperty"), rhs: enquote(string: collector.defaultProp));
46
47 if (!collector.parentProp.isEmpty())
48 m_qml.writeScriptBinding(name: QLatin1String("parentProperty"), rhs: enquote(string: collector.parentProp));
49
50 if (!collector.superClass.isEmpty())
51 m_qml.writeScriptBinding(name: QLatin1String("prototype"), rhs: enquote(string: collector.superClass));
52
53 if (!collector.sequenceValueType.isEmpty()) {
54 const QString name = collector.sequenceValueType.endsWith(c: '*'_L1)
55 ? collector.sequenceValueType.chopped(n: 1)
56 : collector.sequenceValueType;
57 m_qml.writeScriptBinding(name: QLatin1String("valueType"), rhs: enquote(string: name));
58 }
59
60 if (!collector.extensionType.isEmpty())
61 m_qml.writeScriptBinding(name: QLatin1String("extension"), rhs: enquote(string: collector.extensionType));
62
63 if (collector.extensionIsNamespace)
64 m_qml.writeScriptBinding(name: QLatin1String("extensionIsNamespace"), rhs: QLatin1String("true"));
65
66 if (!collector.implementsInterfaces.isEmpty()) {
67 QStringList interfaces;
68 for (const QString &interface : collector.implementsInterfaces)
69 interfaces << enquote(string: interface);
70
71 m_qml.writeArrayBinding(name: QLatin1String("interfaces"), elements: interfaces);
72 }
73
74 if (!collector.deferredNames.isEmpty()) {
75 QStringList deferredNames;
76 for (const QString &name : collector.deferredNames)
77 deferredNames << enquote(string: name);
78
79 m_qml.writeArrayBinding(name: QLatin1String("deferredNames"), elements: deferredNames);
80 }
81
82 if (!collector.immediateNames.isEmpty()) {
83 QStringList immediateNames;
84 for (const QString &name : collector.immediateNames)
85 immediateNames << enquote(string: name);
86
87 m_qml.writeArrayBinding(name: QLatin1String("immediateNames"), elements: immediateNames);
88 }
89
90 if (collector.elementName.isEmpty()) // e.g. if QML_ANONYMOUS
91 return;
92
93 if (!collector.sequenceValueType.isEmpty()) {
94 qWarning() << "Ignoring name of sequential container:" << collector.elementName;
95 qWarning() << "Sequential containers are anonymous. Use QML_ANONYMOUS to register them.";
96 return;
97 }
98
99 QStringList exports;
100 QStringList metaObjects;
101
102 for (auto it = collector.revisions.begin(), end = collector.revisions.end(); it != end; ++it) {
103 const QTypeRevision revision = *it;
104 if (revision < collector.addedInRevision)
105 continue;
106 if (collector.removedInRevision.isValid() && !(revision < collector.removedInRevision))
107 break;
108 if (revision.hasMajorVersion() && revision.majorVersion() > m_version.majorVersion())
109 break;
110
111 exports.append(t: enquote(string: QString::fromLatin1(ba: "%1/%2 %3.%4")
112 .arg(args&: m_module, args: collector.elementName)
113 .arg(a: revision.hasMajorVersion() ? revision.majorVersion()
114 : m_version.majorVersion())
115 .arg(a: revision.minorVersion())));
116 metaObjects.append(t: QString::number(revision.toEncodedVersion<quint16>()));
117 }
118
119 m_qml.writeArrayBinding(name: QLatin1String("exports"), elements: exports);
120
121 if (!collector.isCreatable || collector.isSingleton)
122 m_qml.writeScriptBinding(name: QLatin1String("isCreatable"), rhs: QLatin1String("false"));
123
124 if (collector.isSingleton)
125 m_qml.writeScriptBinding(name: QLatin1String("isSingleton"), rhs: QLatin1String("true"));
126
127 if (collector.hasCustomParser)
128 m_qml.writeScriptBinding(name: QLatin1String("hasCustomParser"), rhs: QLatin1String("true"));
129
130 m_qml.writeArrayBinding(name: QLatin1String("exportMetaObjectRevisions"), elements: metaObjects);
131
132 if (!collector.attachedType.isEmpty())
133 m_qml.writeScriptBinding(name: QLatin1String("attachedType"), rhs: enquote(string: collector.attachedType));
134}
135
136void QmlTypesCreator::writeType(const QJsonObject &property, const QString &key)
137{
138 auto it = property.find(key);
139 if (it == property.end())
140 return;
141
142 QString type = (*it).toString();
143 if (type.isEmpty() || type == QLatin1String("void"))
144 return;
145
146 const QLatin1String typeKey("type");
147
148 bool isList = false;
149 bool isPointer = false;
150 // This is a best effort approach (like isPointer) and will not return correct results in the
151 // presence of typedefs.
152 bool isConstant = false;
153
154 auto handleList = [&](QLatin1String list) {
155 if (!type.startsWith(s: list) || !type.endsWith(c: QLatin1Char('>')))
156 return false;
157
158 const int listSize = list.size();
159 const QString elementType = type.mid(position: listSize, n: type.size() - listSize - 1).trimmed();
160
161 // QQmlListProperty internally constructs the pointer. Passing an explicit '*' will
162 // produce double pointers. QList is only for value types. We can't handle QLists
163 // of pointers (unless specially registered, but then they're not isList).
164 if (elementType.endsWith(c: QLatin1Char('*')))
165 return false;
166
167 isList = true;
168 type = elementType;
169 return true;
170 };
171
172 if (!handleList(QLatin1String("QQmlListProperty<"))
173 && !handleList(QLatin1String("QList<"))) {
174 if (type.endsWith(c: QLatin1Char('*'))) {
175 isPointer = true;
176 type = type.left(n: type.size() - 1);
177 }
178 if (type.startsWith(s: u"const ")) {
179 isConstant = true;
180 type = type.sliced(pos: strlen(s: "const "));
181 }
182 }
183
184 if (type == QLatin1String("qreal")) {
185#ifdef QT_COORD_TYPE_STRING
186 type = QLatin1String(QT_COORD_TYPE_STRING);
187#else
188 type = QLatin1String("double");
189#endif
190 } else if (type == QLatin1String("qint16")) {
191 type = QLatin1String("short");
192 } else if (type == QLatin1String("quint16")) {
193 type = QLatin1String("ushort");
194 } else if (type == QLatin1String("qint32")) {
195 type = QLatin1String("int");
196 } else if (type == QLatin1String("quint32")) {
197 type = QLatin1String("uint");
198 } else if (type == QLatin1String("qint64")) {
199 type = QLatin1String("qlonglong");
200 } else if (type == QLatin1String("quint64")) {
201 type = QLatin1String("qulonglong");
202 } else if (type == QLatin1String("QList<QObject*>")) {
203 type = QLatin1String("QObjectList");
204 }
205
206 m_qml.writeScriptBinding(name: typeKey, rhs: enquote(string: type));
207 const QLatin1String trueString("true");
208 if (isList)
209 m_qml.writeScriptBinding(name: QLatin1String("isList"), rhs: trueString);
210 if (isPointer)
211 m_qml.writeScriptBinding(name: QLatin1String("isPointer"), rhs: trueString);
212 if (isConstant)
213 m_qml.writeScriptBinding(name: QLatin1String("isConstant"), rhs: trueString);
214}
215
216void QmlTypesCreator::writeProperties(const QJsonArray &properties)
217{
218 for (const QJsonValue property : properties) {
219 const QJsonObject obj = property.toObject();
220 const QString name = obj[QLatin1String("name")].toString();
221 m_qml.writeStartObject(component: QLatin1String("Property"));
222 m_qml.writeScriptBinding(name: QLatin1String("name"), rhs: enquote(string: name));
223 const auto it = obj.find(key: QLatin1String("revision"));
224 if (it != obj.end())
225 m_qml.writeScriptBinding(name: QLatin1String("revision"), rhs: QString::number(it.value().toInt()));
226
227 writeType(property: obj, key: QLatin1String("type"));
228
229 const auto bindable = obj.constFind(key: QLatin1String("bindable"));
230 if (bindable != obj.constEnd())
231 m_qml.writeScriptBinding(name: QLatin1String("bindable"), rhs: enquote(string: bindable->toString()));
232 const auto read = obj.constFind(key: QLatin1String("read"));
233 if (read != obj.constEnd())
234 m_qml.writeScriptBinding(name: QLatin1String("read"), rhs: enquote(string: read->toString()));
235 const auto write = obj.constFind(key: QLatin1String("write"));
236 if (write != obj.constEnd())
237 m_qml.writeScriptBinding(name: QLatin1String("write"), rhs: enquote(string: write->toString()));
238 const auto reset = obj.constFind(key: QLatin1String("reset"));
239 if (reset != obj.constEnd())
240 m_qml.writeScriptBinding(name: QLatin1String("reset"), rhs: enquote(string: reset->toString()));
241 const auto notify = obj.constFind(key: QLatin1String("notify"));
242 if (notify != obj.constEnd())
243 m_qml.writeScriptBinding(name: QLatin1String("notify"), rhs: enquote(string: notify->toString()));
244 const auto index = obj.constFind(key: QLatin1String("index"));
245 if (index != obj.constEnd()) {
246 m_qml.writeScriptBinding(name: QLatin1String("index"),
247 rhs: QString::number(index.value().toInt()));
248 }
249 const auto privateClass = obj.constFind(key: QLatin1String("privateClass"));
250 if (privateClass != obj.constEnd()) {
251 m_qml.writeScriptBinding(
252 name: QLatin1String("privateClass"),
253 rhs: enquote(string: convertPrivateClassToUsableForm(s: privateClass->toString())));
254 }
255
256 if (!obj.contains(key: QLatin1String("write")) && !obj.contains(key: QLatin1String("member")))
257 m_qml.writeScriptBinding(name: QLatin1String("isReadonly"), rhs: QLatin1String("true"));
258
259 const auto final = obj.constFind(key: QLatin1String("final"));
260 if (final != obj.constEnd() && final->toBool())
261 m_qml.writeScriptBinding(name: QLatin1String("isFinal"), rhs: QLatin1String("true"));
262
263 const auto constant = obj.constFind(key: QLatin1String("constant"));
264 if (constant != obj.constEnd() && constant->toBool())
265 m_qml.writeScriptBinding(name: QLatin1String("isConstant"), rhs: QLatin1String("true"));
266
267 const auto required = obj.constFind(key: QLatin1String("required"));
268 if (required != obj.constEnd() && required->toBool())
269 m_qml.writeScriptBinding(name: QLatin1String("isRequired"), rhs: QLatin1String("true"));
270
271 m_qml.writeEndObject();
272 }
273}
274
275void QmlTypesCreator::writeMethods(const QJsonArray &methods, const QString &type)
276{
277 const auto writeFlag = [this](const QLatin1String &name, const QJsonObject &obj) {
278 const auto flag = obj.find(key: name);
279 if (flag != obj.constEnd() && flag->toBool())
280 m_qml.writeBooleanBinding(name, value: true);
281 };
282
283 for (const QJsonValue method : methods) {
284 const QJsonObject obj = method.toObject();
285 const QString name = obj[QLatin1String("name")].toString();
286 if (name.isEmpty())
287 continue;
288 const QJsonArray arguments = method[QLatin1String("arguments")].toArray();
289 const auto revision = obj.find(key: QLatin1String("revision"));
290 m_qml.writeStartObject(component: type);
291 m_qml.writeScriptBinding(name: QLatin1String("name"), rhs: enquote(string: name));
292 if (revision != obj.end())
293 m_qml.writeScriptBinding(name: QLatin1String("revision"), rhs: QString::number(revision.value().toInt()));
294 writeType(property: obj, key: QLatin1String("returnType"));
295
296 writeFlag(QLatin1String("isCloned"), obj);
297 writeFlag(QLatin1String("isConstructor"), obj);
298 writeFlag(QLatin1String("isJavaScriptFunction"), obj);
299
300 for (qsizetype i = 0, end = arguments.size(); i != end; ++i) {
301 const QJsonObject obj = arguments[i].toObject();
302 if (i == 0 && end == 1 &&
303 obj[QLatin1String("type")].toString() == QLatin1String("QQmlV4Function*")) {
304 m_qml.writeScriptBinding(name: QLatin1String("isJavaScriptFunction"),
305 rhs: QLatin1String("true"));
306 break;
307 }
308 m_qml.writeStartObject(component: QLatin1String("Parameter"));
309 const QString name = obj[QLatin1String("name")].toString();
310 if (!name.isEmpty())
311 m_qml.writeScriptBinding(name: QLatin1String("name"), rhs: enquote(string: name));
312 writeType(property: obj, key: QLatin1String("type"));
313 m_qml.writeEndObject();
314 }
315 m_qml.writeEndObject();
316 }
317}
318
319void QmlTypesCreator::writeEnums(const QJsonArray &enums)
320{
321 for (const QJsonValue item : enums) {
322 const QJsonObject obj = item.toObject();
323 const QJsonArray values = obj.value(key: QLatin1String("values")).toArray();
324 QStringList valueList;
325
326 for (const QJsonValue value : values)
327 valueList.append(t: enquote(string: value.toString()));
328
329 m_qml.writeStartObject(component: QLatin1String("Enum"));
330 m_qml.writeScriptBinding(name: QLatin1String("name"),
331 rhs: enquote(string: obj.value(key: QLatin1String("name")).toString()));
332 auto alias = obj.find(key: QLatin1String("alias"));
333 if (alias != obj.end())
334 m_qml.writeScriptBinding(name: alias.key(), rhs: enquote(string: alias->toString()));
335 auto isFlag = obj.find(key: QLatin1String("isFlag"));
336 if (isFlag != obj.end() && isFlag->toBool())
337 m_qml.writeBooleanBinding(name: isFlag.key(), value: true);
338 writeType(property: obj, key: QLatin1String("type"));
339 m_qml.writeArrayBinding(name: QLatin1String("values"), elements: valueList);
340 m_qml.writeEndObject();
341 }
342}
343
344static bool isAllowedInMajorVersion(const QJsonValue &member, QTypeRevision maxMajorVersion)
345{
346 const auto memberObject = member.toObject();
347 const auto it = memberObject.find(key: QLatin1String("revision"));
348 if (it == memberObject.end())
349 return true;
350
351 const QTypeRevision memberRevision = QTypeRevision::fromEncodedVersion(value: it->toInt());
352 return !memberRevision.hasMajorVersion()
353 || memberRevision.majorVersion() <= maxMajorVersion.majorVersion();
354}
355
356template<typename Postprocess>
357QJsonArray members(
358 const QJsonObject *classDef, const QString &key, QTypeRevision maxMajorVersion,
359 Postprocess &&process)
360{
361 QJsonArray classDefMembers;
362
363 const QJsonArray candidates = classDef->value(key).toArray();
364 for (QJsonValue member : candidates) {
365 if (isAllowedInMajorVersion(member, maxMajorVersion))
366 classDefMembers.append(value: process(std::move(member)));
367 }
368
369 return classDefMembers;
370}
371
372static QJsonArray members(
373 const QJsonObject *classDef, const QString &key, QTypeRevision maxMajorVersion)
374{
375 return members(classDef, key, maxMajorVersion, process: [](QJsonValue &&member) { return member; });
376}
377
378static QJsonArray constructors(
379 const QJsonObject *classDef, const QString &key, QTypeRevision maxMajorVersion)
380{
381 return members(classDef, key, maxMajorVersion, process: [](QJsonValue &&member) {
382 QJsonObject ctor = member.toObject();
383 ctor[QLatin1String("isConstructor")] = true;
384 return ctor;
385 });
386}
387
388
389void QmlTypesCreator::writeComponents()
390{
391 const QLatin1String signalsKey("signals");
392 const QLatin1String enumsKey("enums");
393 const QLatin1String propertiesKey("properties");
394 const QLatin1String slotsKey("slots");
395 const QLatin1String methodsKey("methods");
396 const QLatin1String constructorsKey("constructors");
397
398 const QLatin1String signalElement("Signal");
399 const QLatin1String componentElement("Component");
400 const QLatin1String methodElement("Method");
401
402 for (const QJsonObject &component : m_ownTypes) {
403 QmlTypesClassDescription collector;
404 collector.collect(classDef: &component, types: m_ownTypes, foreign: m_foreignTypes,
405 mode: QmlTypesClassDescription::TopLevel, defaultRevision: m_version);
406
407 if (collector.omitFromQmlTypes)
408 continue;
409
410 m_qml.writeStartObject(component: componentElement);
411
412 writeClassProperties(collector);
413
414 if (const QJsonObject *classDef = collector.resolvedClass) {
415 writeEnums(enums: members(classDef, key: enumsKey, maxMajorVersion: m_version));
416
417 writeProperties(properties: members(classDef, key: propertiesKey, maxMajorVersion: m_version));
418
419 writeMethods(methods: members(classDef, key: signalsKey, maxMajorVersion: m_version), type: signalElement);
420 writeMethods(methods: members(classDef, key: slotsKey, maxMajorVersion: m_version), type: methodElement);
421 writeMethods(methods: members(classDef, key: methodsKey, maxMajorVersion: m_version), type: methodElement);
422 writeMethods(methods: constructors(classDef, key: constructorsKey, maxMajorVersion: m_version), type: methodElement);
423 }
424 m_qml.writeEndObject();
425
426 if (collector.resolvedClass != &component
427 && std::binary_search(
428 first: m_referencedTypes.begin(), last: m_referencedTypes.end(),
429 val: component.value(QStringLiteral("qualifiedClassName")).toString())) {
430
431 // This type is referenced from elsewhere and has a QML_FOREIGN of its own. We need to
432 // also generate a description of the local type then. All the QML_* macros are
433 // ignored, and the result is an anonymous type.
434
435 m_qml.writeStartObject(component: componentElement);
436
437 QmlTypesClassDescription collector;
438 collector.collectLocalAnonymous(classDef: &component, types: m_ownTypes, foreign: m_foreignTypes, defaultRevision: m_version);
439
440 writeClassProperties(collector);
441 writeEnums(enums: members(classDef: &component, key: enumsKey, maxMajorVersion: m_version));
442
443 writeProperties(properties: members(classDef: &component, key: propertiesKey, maxMajorVersion: m_version));
444
445 writeMethods(methods: members(classDef: &component, key: signalsKey, maxMajorVersion: m_version), type: signalElement);
446 writeMethods(methods: members(classDef: &component, key: slotsKey, maxMajorVersion: m_version), type: methodElement);
447 writeMethods(methods: members(classDef: &component, key: methodsKey, maxMajorVersion: m_version), type: methodElement);
448 writeMethods(methods: constructors(classDef: &component, key: constructorsKey, maxMajorVersion: m_version), type: methodElement);
449
450 m_qml.writeEndObject();
451 }
452 }
453}
454
455bool QmlTypesCreator::generate(const QString &outFileName)
456{
457 m_qml.writeStartDocument();
458 m_qml.writeLibraryImport(uri: QLatin1String("QtQuick.tooling"), majorVersion: 1, minorVersion: 2);
459 m_qml.write(data: QString::fromLatin1(
460 ba: "\n// This file describes the plugin-supplied types contained in the library."
461 "\n// It is used for QML tooling purposes only."
462 "\n//"
463 "\n// This file was auto-generated by qmltyperegistrar.\n\n"));
464 m_qml.writeStartObject(component: QLatin1String("Module"));
465
466 writeComponents();
467
468 m_qml.writeEndObject();
469
470 QSaveFile file(outFileName);
471 if (!file.open(flags: QIODevice::WriteOnly))
472 return false;
473
474 if (file.write(data: m_output) != m_output.size())
475 return false;
476
477 return file.commit();
478}
479
480QT_END_NAMESPACE
481
482

source code of qtdeclarative/src/qmltyperegistrar/qqmltypescreator.cpp