Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmltypescreator.cpp
Go to the documentation of this file.
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// Qt-Security score:significant
4
10
11#include <QtCore/qset.h>
12#include <QtCore/qcborarray.h>
13#include <QtCore/qcbormap.h>
14#include <QtCore/qsavefile.h>
15#include <QtCore/qfile.h>
16
17#include <QtCore/private/qstringalgorithms_p.h>
18
20
21using namespace Qt::StringLiterals;
22using namespace Constants;
23using namespace Constants::DotQmltypes;
24using namespace QAnyStringViewUtils;
25
27{
28 // typical privateClass entry in MOC looks like: ClassName::d_func(), where
29 // ClassName is a non-private class name. we don't need "::d_func()" piece
30 // so that could be removed, but we need "Private" so that ClassName becomes
31 // ClassNamePrivate (at present, simply consider this correct)
32 return s.toString().replace("::d_func()"_L1, "Private"_L1);
33}
34
35void QmlTypesCreator::writeClassProperties(const QmlTypesClassDescription &collector)
36{
37 if (!collector.file.isEmpty()) {
38 m_qml.writeStringBinding(S_FILE, collector.file);
39 m_qml.writeNumberBinding(S_LINE_NUMBER, collector.lineNumber);
40 }
41 if (!collector.resolvedFile.isEmpty()) {
42 m_qml.writeStringBinding(S_RESOLVED_FILE, collector.resolvedFile);
43 m_qml.writeNumberBinding(S_LINE_NUMBER_IN_RESOLVED_FILE,
44 collector.lineNumberInResolvedFile);
45 }
46 m_qml.writeStringBinding(S_NAME, collector.className);
47
48 if (!collector.primitiveAliases.isEmpty())
49 m_qml.writeStringListBinding(S_ALIASES, collector.primitiveAliases);
50
51 if (!collector.accessSemantics.isEmpty())
52 m_qml.writeStringBinding(S_ACCESS_SEMANTICS, collector.accessSemantics);
53
54 if (!collector.defaultProp.isEmpty())
55 m_qml.writeStringBinding(S_DEFAULT_PROPERTY, collector.defaultProp);
56
57 if (!collector.parentProp.isEmpty())
58 m_qml.writeStringBinding(S_PARENT_PROPERTY, collector.parentProp);
59
60 if (!collector.superClass.isEmpty())
61 m_qml.writeStringBinding(S_PROTOTYPE, collector.superClass);
62
63 if (!collector.sequenceValueType.isEmpty()) {
64 QAnyStringView name = collector.sequenceValueType.back() == '*'_L1
65 ? collector.sequenceValueType.chopped(1)
66 : collector.sequenceValueType;
67 while (name.back().isSpace())
68 name.chop(1);
69 m_qml.writeStringBinding(S_VALUE_TYPE, name);
70 }
71
72 if (m_generatingJSRoot)
73 m_qml.writeBooleanBinding(S_IS_JAVASCRIPT_BUILTIN, true);
74
75 if (collector.extensionIsJavaScript) {
76 if (!collector.javaScriptExtensionType.isEmpty()) {
77 m_qml.writeStringBinding(S_EXTENSION, collector.javaScriptExtensionType);
78 m_qml.writeBooleanBinding(S_EXTENSION_IS_JAVA_SCRIPT, true);
79 } else {
80 warning(collector.file)
81 << "JavaScript extension type for" << collector.className
82 << "does not exist";
83 }
84
85 if (collector.extensionIsNamespace) {
86 warning(collector.file)
87 << "Extension type for" << collector.className
88 << "cannot be both a JavaScript type and a namespace";
89 if (!collector.nativeExtensionType.isEmpty()) {
90 m_qml.writeStringBinding(S_EXTENSION, collector.nativeExtensionType);
91 m_qml.writeBooleanBinding(S_EXTENSION_IS_NAMESPACE, true);
92 }
93 }
94 } else if (!collector.nativeExtensionType.isEmpty()) {
95 m_qml.writeStringBinding(S_EXTENSION, collector.nativeExtensionType);
96 if (collector.extensionIsNamespace)
97 m_qml.writeBooleanBinding(S_EXTENSION_IS_NAMESPACE, true);
98 } else if (collector.extensionIsNamespace) {
99 warning(collector.file)
100 << "Extension namespace for" << collector.className << "does not exist";
101 m_qml.writeBooleanBinding(S_EXTENSION_IS_NAMESPACE, true);
102 }
103 if (collector.isSelfExtension)
104 m_qml.writeBooleanBinding(S_IS_SELF_EXTENSION, true);
105
106 if (!collector.implementsInterfaces.isEmpty())
107 m_qml.writeStringListBinding(S_INTERFACES, collector.implementsInterfaces);
108
109 if (!collector.deferredNames.isEmpty())
110 m_qml.writeStringListBinding(S_DEFERRED_NAMES, collector.deferredNames);
111
112 if (!collector.immediateNames.isEmpty())
113 m_qml.writeStringListBinding(S_IMMEDIATE_NAMES, collector.immediateNames);
114
115 if (collector.elementNames.isEmpty()) // e.g. if QML_ANONYMOUS
116 return;
117
118 if (!collector.sequenceValueType.isEmpty()) {
119 warning(collector.file) << "Ignoring names of sequential container:";
120 for (const QAnyStringView &name : std::as_const(collector.elementNames))
121 warning(collector.file) << " - " << name.toString();
122 warning(collector.file)
123 << "Sequential containers are anonymous. Use QML_ANONYMOUS to register them.";
124 return;
125 }
126
127 QByteArrayList exports;
128 QByteArrayList metaObjects;
129
130 for (auto it = collector.revisions.begin(), end = collector.revisions.end(); it != end; ++it) {
131 const QTypeRevision revision = *it;
132 if (revision < collector.addedInRevision)
133 continue;
134 if (collector.removedInRevision.isValid() && !(revision < collector.removedInRevision))
135 break;
136 if (revision.hasMajorVersion() && revision.majorVersion() > m_version.majorVersion())
137 break;
138
139 for (const QAnyStringView &elementName : std::as_const(collector.elementNames)) {
140 QByteArray exportEntry = m_module + '/';
141
142 elementName.visit([&](auto view) {
143 processAsUtf8(view, [&](QByteArrayView view) { exportEntry.append(view); });
144 });
145 exportEntry += ' ' + QByteArray::number(revision.hasMajorVersion()
146 ? revision.majorVersion()
147 : m_version.majorVersion());
148 exportEntry += '.' + QByteArray::number(revision.minorVersion());
149
150 exports.append(exportEntry);
151 }
152 metaObjects.append(QByteArray::number(revision.toEncodedVersion<quint16>()));
153 }
154
155 QList<QAnyStringView> exportStrings;
156 exportStrings.reserve(exports.length());
157 for (const QByteArray &entry: exports)
158 exportStrings.append(QUtf8StringView(entry));
159
160 m_qml.writeStringListBinding(S_EXPORTS, exportStrings);
161
162 if (!collector.isCreatable || collector.isSingleton)
163 m_qml.writeBooleanBinding(S_IS_CREATABLE, false);
164
165 if (collector.isStructured)
166 m_qml.writeBooleanBinding(S_IS_STRUCTURED, true);
167
168 if (collector.isSingleton)
169 m_qml.writeBooleanBinding(S_IS_SINGLETON, true);
170
171 if (collector.hasCustomParser)
172 m_qml.writeBooleanBinding(S_HAS_CUSTOM_PARSER, true);
173
174 if (collector.enforcesScopedEnums)
175 m_qml.writeBooleanBinding(S_ENFORCES_SCOPED_ENUMS, true);
176
177 m_qml.writeArrayBinding(S_EXPORT_META_OBJECT_REVISIONS, metaObjects);
178
179 if (!collector.attachedType.isEmpty())
180 m_qml.writeStringBinding(S_ATTACHED_TYPE, collector.attachedType);
181}
182
183void QmlTypesCreator::writeType(QAnyStringView type)
184{
185 ResolvedTypeAlias resolved(type, m_usingDeclarations);
186 if (resolved.type.isEmpty())
187 return;
188
189 m_qml.writeStringBinding(S_TYPE, resolved.type);
190 if (resolved.isList)
191 m_qml.writeBooleanBinding(S_IS_LIST, true);
192 if (resolved.isPointer)
193 m_qml.writeBooleanBinding(S_IS_POINTER, true);
194 if (resolved.isConstant)
195 m_qml.writeBooleanBinding(S_IS_TYPE_CONSTANT, true);
196}
197
198void QmlTypesCreator::writeProperties(const Property::Container &properties)
199{
200 for (const Property &obj : properties) {
201 const QAnyStringView name = obj.name;
202 m_qml.writeStartObject(S_PROPERTY);
203 m_qml.writeStringBinding(S_NAME, name);
204 if (obj.revision.isValid())
205 m_qml.writeNumberBinding(S_REVISION, obj.revision.toEncodedVersion<int>());
206
207 writeType(obj.type);
208
209 const auto bindable = obj.bindable;
210 if (!bindable.isEmpty())
211 m_qml.writeStringBinding(S_BINDABLE, bindable);
212 const auto read = obj.read;
213 if (!read.isEmpty())
214 m_qml.writeStringBinding(S_READ, read);
215 const auto write = obj.write;
216 if (!write.isEmpty())
217 m_qml.writeStringBinding(S_WRITE, write);
218 const auto reset = obj.reset;
219 if (!reset.isEmpty())
220 m_qml.writeStringBinding(S_RESET, reset);
221 const auto notify = obj.notify;
222 if (!notify.isEmpty())
223 m_qml.writeStringBinding(S_NOTIFY, notify);
224 const auto index = obj.index;
225 if (index != -1) {
226 m_qml.writeNumberBinding(S_INDEX, index);
227 }
228 const auto lineNumber = obj.lineNumber;
229 if (lineNumber != 0)
230 m_qml.writeNumberBinding(S_LINE_NUMBER, obj.lineNumber);
231
232 const auto privateClass = obj.privateClass;
233 if (!privateClass.isEmpty()) {
234 m_qml.writeStringBinding(
235 S_PRIVATE_CLASS, convertPrivateClassToUsableForm(privateClass));
236 }
237
238 if (obj.write.isEmpty() && obj.member.isEmpty())
239 m_qml.writeBooleanBinding(S_IS_READONLY, true);
240
241 if (obj.isFinal)
242 m_qml.writeBooleanBinding(S_IS_FINAL, true);
243 if (obj.isVirtual)
244 m_qml.writeBooleanBinding(S_IS_VIRTUAL, true);
245 if (obj.isOverride)
246 m_qml.writeBooleanBinding(S_IS_OVERRIDE, true);
247
248 if (obj.isConstant)
249 m_qml.writeBooleanBinding(S_IS_PROPERTY_CONSTANT, true);
250
251 if (obj.isRequired)
252 m_qml.writeBooleanBinding(S_IS_REQUIRED, true);
253
254 m_qml.writeEndObject();
255 }
256}
257
258void QmlTypesCreator::writeMethods(const Method::Container &methods, QLatin1StringView type)
259{
260 for (const Method &obj : methods) {
261 const QAnyStringView name = obj.name;
262 if (name.isEmpty())
263 continue;
264
265 const auto revision = obj.revision;
266 m_qml.writeStartObject(type);
267 m_qml.writeStringBinding(S_NAME, name);
268 if (revision.isValid())
269 m_qml.writeNumberBinding(S_REVISION, revision.toEncodedVersion<int>());
270 writeType(obj.returnType);
271
272 if (obj.isCloned)
273 m_qml.writeBooleanBinding(S_IS_CLONED, true);
274 if (obj.isConstructor)
275 m_qml.writeBooleanBinding(S_IS_CONSTRUCTOR, true);
276 if (obj.isJavaScriptFunction)
277 m_qml.writeBooleanBinding(S_IS_JAVASCRIPT_FUNCTION, true);
278 if (obj.isConst)
279 m_qml.writeBooleanBinding(S_IS_METHOD_CONSTANT, true);
280 const auto lineNumber = obj.lineNumber;
281 if (lineNumber != 0)
282 m_qml.writeNumberBinding(S_LINE_NUMBER, obj.lineNumber);
283
284 const Argument::Container &arguments = obj.arguments;
285 for (qsizetype i = 0, end = arguments.size(); i != end; ++i) {
286 const Argument &obj = arguments[i];
287 m_qml.writeStartObject(S_PARAMETER);
288 const QAnyStringView name = obj.name;
289 if (!name.isEmpty())
290 m_qml.writeStringBinding(S_NAME, name);
291 writeType(obj.type);
292 m_qml.writeEndObject();
293 }
294 m_qml.writeEndObject();
295 }
296}
297
298void QmlTypesCreator::writeEnums(const Enum::Container &enums)
299{
300 for (const Enum &obj : enums) {
301 m_qml.writeStartObject(S_ENUM);
302 m_qml.writeStringBinding(S_NAME, obj.name);
303 if (!obj.alias.isEmpty())
304 m_qml.writeStringBinding(S_ALIAS, obj.alias);
305 if (obj.isFlag)
306 m_qml.writeBooleanBinding(S_IS_FLAG, true);
307 if (obj.isClass)
308 m_qml.writeBooleanBinding(S_IS_SCOPED, true);
309 writeType(obj.type);
310 const auto lineNumber = obj.lineNumber;
311 if (lineNumber != 0)
312 m_qml.writeNumberBinding(S_LINE_NUMBER, obj.lineNumber);
313 m_qml.writeStringListBinding(S_VALUES, obj.values);
314 m_qml.writeEndObject();
315 }
316}
317
318void QmlTypesCreator::writeRootMethods(const MetaType &classDef)
319{
320 // Hide destroyed() signals
321 Method::Container componentSignals = classDef.sigs();
322 for (auto it = componentSignals.begin(); it != componentSignals.end();) {
323 if (it->name == "destroyed"_L1)
324 it = componentSignals.erase(it);
325 else
326 ++it;
327 }
328 writeMethods(componentSignals, S_SIGNAL);
329
330 // Hide deleteLater() methods
331 Method::Container componentMethods = classDef.methods();
332 for (auto it = componentMethods.begin(); it != componentMethods.end();) {
333 if (it->name == "deleteLater"_L1)
334 it = componentMethods.erase(it);
335 else
336 ++it;
337 }
338
339 // Add toString()
340 Method toStringMethod;
341 toStringMethod.index = -2; // See QV4::QObjectMethod
342 toStringMethod.name = "toString"_L1;
343 toStringMethod.access = Access::Public;
344 toStringMethod.returnType = "QString"_L1;
345 toStringMethod.isConst = true;
346 componentMethods.push_back(std::move(toStringMethod));
347
348 // Add destroy(int)
349 Method destroyMethodWithArgument;
350 destroyMethodWithArgument.index = -1; // See QV4::QObjectMethod
351 destroyMethodWithArgument.name = "destroy"_L1;
352 destroyMethodWithArgument.access = Access::Public;
353 Argument delayArgument;
354 delayArgument.name = "delay"_L1;
355 delayArgument.type = "int"_L1;
356 destroyMethodWithArgument.arguments.push_back(std::move(delayArgument));
357 componentMethods.push_back(std::move(destroyMethodWithArgument));
358
359 // Add destroy()
360 Method destroyMethod;
361 destroyMethod.index = -1; // See QV4::QObjectMethod
362 destroyMethod.name = "destroy"_L1;
363 destroyMethod.access = Access::Public;
364 destroyMethod.isCloned = true;
365 componentMethods.push_back(std::move(destroyMethod));
366
367 writeMethods(componentMethods, S_METHOD);
368};
369
370void QmlTypesCreator::writeComponent(const QmlTypesClassDescription &collector)
371{
372 m_qml.writeStartObject(S_COMPONENT);
373
374 writeClassProperties(collector);
375
376 if (const MetaType &classDef = collector.resolvedClass; !classDef.isEmpty()) {
377 writeEnums(classDef.enums());
378 writeProperties(classDef.properties());
379
380 if (collector.isRootClass) {
381 writeRootMethods(classDef);
382 } else {
383 writeMethods(classDef.sigs(), S_SIGNAL);
384 writeMethods(classDef.methods(), S_METHOD);
385 }
386
387 writeMethods(classDef.constructors(), S_METHOD);
388 }
389 m_qml.writeEndObject();
390}
391
392void QmlTypesCreator::writeOpaqueComponent(const QmlTypesClassDescription &collector)
393{
394 m_qml.writeStartObject(S_COMPONENT);
395
396 // we always mark the type as opaque, even if we have some data
397 m_qml.writeBooleanBinding(S_IS_TYPE_OPAQUE, true);
398 // compare writeClassProperties
399 if (!collector.file.isEmpty()) {
400 m_qml.writeStringBinding(S_FILE, collector.file);
401 m_qml.writeNumberBinding(S_LINE_NUMBER, collector.lineNumber);
402 }
403 m_qml.writeStringBinding(S_NAME, collector.className);
404 m_qml.writeStringBinding(S_ACCESS_SEMANTICS, collector.accessSemantics); // restrict instead?
405 // there's no sensible use case for defaultProp and parentProp
406 // as the type is not creatable
407 if (!collector.superClass.isEmpty())
408 m_qml.writeStringBinding(S_PROTOTYPE, collector.superClass);
409 // can't be JSRoot or extension, basically an anonymous type
410
411 if (const MetaType &classDef = collector.resolvedClass; !classDef.isEmpty()) {
412 writeEnums(classDef.enums());
413 writeProperties(classDef.properties());
414 writeMethods(classDef.sigs(), S_SIGNAL);
415 writeMethods(classDef.methods(), S_METHOD);
416 writeMethods(classDef.constructors(), S_METHOD);
417 }
418
419 m_qml.writeEndObject();
420}
421
422void QmlTypesCreator::writeComponents()
423{
424 for (const MetaType &component : std::as_const(m_ownTypes)) {
425 QmlTypesClassDescription collector;
426 collector.collect(component, m_ownTypes, m_foreignTypes,
427 QmlTypesClassDescription::TopLevel, m_version);
428
429 writeComponent(collector);
430
431 if (collector.resolvedClass != component
432 && std::binary_search(
433 m_referencedTypes.begin(), m_referencedTypes.end(),
434 component.qualifiedClassName())) {
435
436 // This type is referenced from elsewhere and has a QML_FOREIGN of its own. We need to
437 // also generate a description of the local type then. All the QML_* macros are
438 // ignored, and the result is an anonymous type.
439
440 QmlTypesClassDescription collector;
441 collector.collectLocalAnonymous(component, m_ownTypes, m_foreignTypes, m_version);
442 Q_ASSERT(!collector.isRootClass);
443
444 writeComponent(collector);
445 }
446 }
447 for (const MetaType &component : std::as_const(m_opaqueTypes)) {
448 QmlTypesClassDescription collector;
449 collector.collect(component, m_ownTypes, m_foreignTypes,
450 QmlTypesClassDescription::TopLevel, m_version);
451
452 writeOpaqueComponent(collector);
453 }
454}
455
456bool QmlTypesCreator::generate(const QString &outFileName)
457{
458 m_qml.writeStartDocument();
459 m_qml.writeLibraryImport("QtQuick.tooling", 1, 2);
460 m_qml.write(
461 "\n// This file describes the plugin-supplied types contained in the library."
462 "\n// It is used for QML tooling purposes only."
463 "\n//"
464 "\n// This file was auto-generated by qmltyperegistrar.\n\n");
465 m_qml.writeStartObject(S_MODULE);
466
467 writeComponents();
468
469 m_qml.writeEndObject();
470
471 QSaveFile file(outFileName);
472 if (!file.open(QIODevice::WriteOnly)) {
473 qCritical().nospace() << "Error: Failed to open " << file.fileName()
474 << " for writing: " << file.errorString();
475 return false;
476 }
477
478 if (file.write(m_output) != m_output.size())
479 return false;
480
481 return file.commit();
482}
483
484QT_END_NAMESPACE
bool generate(const QString &outFileName)
Combined button and popup list for selecting options.
static QString convertPrivateClassToUsableForm(QAnyStringView s)