1// Copyright (C) 2021 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 <QtQml/private/qjsvalue_p.h>
5#include <QtQml/private/qv4propertykey_p.h>
6#include <QtQml/private/qv4global_p.h>
7#include <QtQml/private/qv4functionobject_p.h>
8#include <QtQml/qjsengine.h>
9#include <QtQml/qjsmanagedvalue.h>
10
11#include <QtCore/qcoreapplication.h>
12#include <QtCore/qfile.h>
13#include <QtCore/qcommandlineparser.h>
14
15#include <QtCore/qjsondocument.h>
16#include <QtCore/qjsonarray.h>
17#include <QtCore/qjsonobject.h>
18
19struct PropertyInfo
20{
21 QString name;
22 bool writable;
23};
24
25static QV4::ReturnedValue asManaged(const QJSManagedValue &value)
26{
27 const QJSValue jsVal = value.toJSValue();
28 const QV4::Managed *managed = QJSValuePrivate::asManagedType<QV4::Managed>(jsval: &jsVal);
29 return managed ? managed->asReturnedValue() : QV4::Encode::undefined();
30}
31
32static QJSManagedValue checkedProperty(const QJSManagedValue &value, const QString &name)
33{
34 return value.hasProperty(name) ? QJSManagedValue(value.property(name), value.engine())
35 : QJSManagedValue(QJSPrimitiveUndefined(), value.engine());
36}
37
38QList<PropertyInfo> getPropertyInfos(const QJSManagedValue &value)
39{
40 QV4::Scope scope(value.engine()->handle());
41 QV4::ScopedObject scoped(scope, asManaged(value));
42 if (!scoped)
43 return {};
44
45 QList<PropertyInfo> infos;
46
47 QScopedPointer<QV4::OwnPropertyKeyIterator> iterator(scoped->ownPropertyKeys(target: scoped));
48 QV4::Scoped<QV4::InternalClass> internalClass(scope, scoped->internalClass());
49
50 for (auto key = iterator->next(o: scoped); key.isValid(); key = iterator->next(o: scoped)) {
51 if (key.isSymbol())
52 continue;
53
54 const auto *entry = internalClass->d()->propertyTable.lookup(identifier: key);
55 infos.append(t: {
56 .name: key.toQString(),
57 .writable: !entry || internalClass->d()->propertyData.at(i: entry->index).isWritable()
58 });
59 };
60
61 return infos;
62}
63
64struct State {
65 QMap<QString, QJSValue> constructors;
66 QMap<QString, QJSValue> prototypes;
67 QSet<QString> primitives;
68};
69
70static QString buildConstructor(const QJSManagedValue &constructor, QJsonArray *classes,
71 State *seen, const QString &name, QJSManagedValue *constructed);
72
73static QString findClassName(const QJSManagedValue &value)
74{
75 if (value.isUndefined())
76 return QStringLiteral("undefined");
77 if (value.isBoolean())
78 return QStringLiteral("boolean");
79 if (value.isNumber())
80 return QStringLiteral("number");
81 if (value.isString())
82 return QStringLiteral("string");
83 if (value.isSymbol())
84 return QStringLiteral("symbol");
85
86 QV4::Scope scope(value.engine()->handle());
87 if (QV4::ScopedValue scoped(scope, asManaged(value)); scoped->isManaged())
88 return scoped->managed()->vtable()->className;
89
90 Q_UNREACHABLE_RETURN(QString());
91}
92
93static QString buildClass(const QJSManagedValue &value, QJsonArray *classes,
94 State *seen, const QString &name)
95{
96 if (value.isNull())
97 return QString();
98
99 if (seen->primitives.contains(value: name))
100 return name;
101 else if (name.at(i: 0).isLower())
102 seen->primitives.insert(value: name);
103
104 QJsonObject classObject;
105 QV4::Scope scope(value.engine()->handle());
106
107 classObject[QStringLiteral("className")] = name;
108 classObject[QStringLiteral("qualifiedClassName")] = name;
109
110 classObject[QStringLiteral("classInfos")] = QJsonArray({
111 QJsonObject({
112 { QStringLiteral("name"), QStringLiteral("QML.Element") },
113 { QStringLiteral("value"), QStringLiteral("anonymous") }
114 })
115 });
116
117 if (value.isObject() || value.isFunction())
118 classObject[QStringLiteral("object")] = true;
119 else
120 classObject[QStringLiteral("gadget")] = true;
121
122 const QJSManagedValue prototype = value.prototype();
123
124 if (!prototype.isNull()) {
125 QString protoName;
126 for (auto it = seen->prototypes.begin(), end = seen->prototypes.end(); it != end; ++it) {
127 if (prototype.strictlyEquals(other: QJSManagedValue(*it, value.engine()))) {
128 protoName = it.key();
129 break;
130 }
131 }
132
133 if (protoName.isEmpty()) {
134 if (name.endsWith(QStringLiteral("ErrorPrototype"))
135 && name != QStringLiteral("ErrorPrototype")) {
136 protoName = QStringLiteral("ErrorPrototype");
137 } else if (name.endsWith(QStringLiteral("Prototype"))) {
138 protoName = findClassName(value: prototype);
139 if (!protoName.endsWith(QStringLiteral("Prototype")))
140 protoName += QStringLiteral("Prototype");
141 } else {
142 protoName = name.at(i: 0).toUpper() + name.mid(position: 1) + QStringLiteral("Prototype");
143 }
144
145 auto it = seen->prototypes.constFind(key: protoName);
146 if (it == seen->prototypes.cend()) {
147 seen->prototypes.insert(key: protoName, value: prototype.toJSValue());
148 buildClass(value: prototype, classes, seen, name: protoName);
149 } else if (!it->strictlyEquals(other: prototype.toJSValue())) {
150 qWarning() << "Cannot find a distinct name for the prototype of" << name;
151 qWarning() << protoName << "is already in use.";
152 }
153 }
154
155 classObject[QStringLiteral("superClasses")] = QJsonArray {
156 QJsonObject ({
157 { QStringLiteral("access"), QStringLiteral("public") },
158 { QStringLiteral("name"), protoName }
159 })};
160 }
161
162 QJsonArray properties, methods;
163
164 auto defineProperty = [&](const QJSManagedValue &prop, const PropertyInfo &info) {
165 QJsonObject propertyObject;
166 propertyObject.insert(QStringLiteral("name"), value: info.name);
167
168 // Insert faux member entry if we're allowed to write to this
169 if (info.writable)
170 propertyObject.insert(QStringLiteral("member"), QStringLiteral("fakeMember"));
171
172 if (!prop.isUndefined() && !prop.isNull()) {
173 QString propClassName = findClassName(value: prop);
174 if (!propClassName.at(i: 0).isLower() && info.name != QStringLiteral("prototype")) {
175 propClassName = (name == QStringLiteral("GlobalObject"))
176 ? QString()
177 : name.at(i: 0).toUpper() + name.mid(position: 1);
178
179 propClassName += info.name.at(i: 0).toUpper() + info.name.mid(position: 1);
180 propertyObject.insert(QStringLiteral("type"),
181 value: buildClass(value: prop, classes, seen, name: propClassName));
182 } else {
183 // If it's the "prototype" property we just refer to generic "Object",
184 // and if it's a value type, we handle it separately.
185 propertyObject.insert(QStringLiteral("type"), value: propClassName);
186 }
187 }
188 return propertyObject;
189 };
190
191 QList<PropertyInfo> unRetrievedProperties;
192 QJSManagedValue constructed;
193 for (const PropertyInfo &info : getPropertyInfos(value)) {
194 QJSManagedValue prop = checkedProperty(value, name: info.name);
195 if (prop.engine()->hasError()) {
196 unRetrievedProperties.append(t: info);
197 prop.engine()->catchError();
198 continue;
199 }
200
201 // Method or constructor
202 if (prop.isFunction()) {
203 QV4::Scoped<QV4::FunctionObject> propFunction(scope, asManaged(value: prop));
204
205 QJsonObject methodObject;
206
207 methodObject.insert(QStringLiteral("access"), QStringLiteral("public"));
208 methodObject.insert(QStringLiteral("name"), value: info.name);
209 methodObject.insert(QStringLiteral("isJavaScriptFunction"), value: true);
210
211 const int formalParams = propFunction->getLength();
212 if (propFunction->isConstructor()) {
213 methodObject.insert(QStringLiteral("isConstructor"), value: true);
214
215 QString ctorName;
216 if (info.name.at(i: 0).isUpper()) {
217 ctorName = info.name;
218 } else if (info.name == QStringLiteral("constructor")) {
219 if (name.endsWith(QStringLiteral("Prototype")))
220 ctorName = name.chopped(n: strlen(s: "Prototype"));
221 else if (name.endsWith(QStringLiteral("PrototypeMember")))
222 ctorName = name.chopped(n: strlen(s: "PrototypeMember"));
223 else
224 ctorName = name;
225
226 if (!ctorName.endsWith(QStringLiteral("Constructor")))
227 ctorName += QStringLiteral("Constructor");
228 }
229
230 methodObject.insert(
231 QStringLiteral("returnType"),
232 value: buildConstructor(constructor: prop, classes, seen, name: ctorName, constructed: &constructed));
233 }
234
235 QJsonArray arguments;
236 for (int i = 0; i < formalParams; i++)
237 arguments.append(value: QJsonObject {});
238
239 methodObject.insert(QStringLiteral("arguments"), value: arguments);
240
241 methods.append(value: methodObject);
242
243 continue;
244 }
245
246 // ...else it's just a property
247 properties.append(value: defineProperty(prop, info));
248 }
249
250 for (const PropertyInfo &info : unRetrievedProperties) {
251 QJSManagedValue prop = checkedProperty(
252 value: constructed.isUndefined() ? value : constructed, name: info.name);
253 if (prop.engine()->hasError()) {
254 qWarning() << "Cannot retrieve property " << info.name << "of" << name << constructed.toString();
255 qWarning().noquote() << " " << prop.engine()->catchError().toString();
256 }
257
258 properties.append(value: defineProperty(prop, info));
259 }
260
261 classObject[QStringLiteral("properties")] = properties;
262 classObject[QStringLiteral("methods")] = methods;
263
264 classes->append(value: classObject);
265
266 return name;
267}
268
269static QString buildConstructor(const QJSManagedValue &constructor, QJsonArray *classes,
270 State *seen, const QString &name, QJSManagedValue *constructed)
271{
272 QJSEngine *engine = constructor.engine();
273
274 // If the constructor appears in the global object, use the name from there.
275 const QJSManagedValue globalObject(engine->globalObject(), engine);
276 const auto infos = getPropertyInfos(value: globalObject);
277 for (const auto &info : infos) {
278 const QJSManagedValue member(globalObject.property(name: info.name), engine);
279 if (member.strictlyEquals(other: constructor) && info.name != name)
280 return buildConstructor(constructor, classes, seen, name: info.name, constructed);
281 }
282
283 if (name == QStringLiteral("Symbol"))
284 return QStringLiteral("undefined"); // Cannot construct symbols with "new";
285
286 if (name == QStringLiteral("URL")) {
287 *constructed = QJSManagedValue(
288 constructor.callAsConstructor(arguments: { QJSValue(QStringLiteral("http://a.bc")) }),
289 engine);
290 } else if (name == QStringLiteral("Promise")) {
291 *constructed = QJSManagedValue(
292 constructor.callAsConstructor(
293 arguments: { engine->evaluate(QStringLiteral("(function() {})")) }),
294 engine);
295 } else if (name == QStringLiteral("DataView")) {
296 *constructed = QJSManagedValue(
297 constructor.callAsConstructor(
298 arguments: { engine->evaluate(QStringLiteral("new ArrayBuffer()")) }),
299 engine);
300 } else if (name == QStringLiteral("Proxy")) {
301 *constructed = QJSManagedValue(constructor.callAsConstructor(
302 arguments: { engine->newObject(), engine->newObject() }), engine);
303 } else {
304 *constructed = QJSManagedValue(constructor.callAsConstructor(), engine);
305 }
306
307 if (engine->hasError()) {
308 qWarning() << "Calling constructor" << name << "failed";
309 qWarning().noquote() << " " << engine->catchError().toString();
310 return QString();
311 } else if (name.isEmpty()) {
312 Q_UNREACHABLE();
313 }
314
315 auto it = seen->constructors.constFind(key: name);
316 if (it == seen->constructors.cend()) {
317 seen->constructors.insert(key: name, value: constructor.toJSValue());
318 return buildClass(value: *constructed, classes, seen, name);
319 } else if (!constructor.strictlyEquals(other: QJSManagedValue(*it, constructor.engine()))) {
320 qWarning() << "Two constructors of the same name seen:" << name;
321 }
322 return name;
323}
324
325int main(int argc, char *argv[])
326{
327 QCoreApplication app(argc, argv);
328 QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
329
330 QCommandLineParser parser;
331 parser.addHelpOption();
332 parser.setApplicationDescription("Internal development tool.");
333 parser.addPositionalArgument(name: "path", description: "Output json path.", syntax: "path");
334
335 parser.process(app);
336
337 const QStringList args = parser.positionalArguments();
338 if (auto size = args.size(); size == 0) {
339 qWarning().noquote().nospace() << app.applicationName() << ": Output path missing.";
340 return EXIT_FAILURE;
341 } else if (size >= 2) {
342 qWarning().noquote().nospace() << app.applicationName() << ": Too many output paths given. Only one allowed.";
343 }
344
345 const QString fileName = args.at(i: 0);
346
347 QJSEngine engine;
348 engine.installExtensions(extensions: QJSEngine::AllExtensions);
349
350 QJsonArray classesArray;
351 State seen;
352
353 // object. Do this first to claim the "Object" name for the prototype.
354 buildClass(value: QJSManagedValue(engine.newObject(), &engine), classes: &classesArray, seen: &seen,
355 QStringLiteral("object"));
356
357
358 buildClass(value: QJSManagedValue(engine.globalObject(), &engine), classes: &classesArray, seen: &seen,
359 QStringLiteral("GlobalObject"));
360
361 // Add JS types, in case they aren't used anywhere.
362
363
364 // function
365 buildClass(value: QJSManagedValue(engine.evaluate(QStringLiteral("(function() {})")), &engine),
366 classes: &classesArray, seen: &seen, QStringLiteral("function"));
367
368 // string
369 buildClass(value: QJSManagedValue(QStringLiteral("s"), &engine), classes: &classesArray, seen: &seen,
370 QStringLiteral("string"));
371
372 // undefined
373 buildClass(value: QJSManagedValue(QJSPrimitiveUndefined(), &engine), classes: &classesArray, seen: &seen,
374 QStringLiteral("undefined"));
375
376 // number
377 buildClass(value: QJSManagedValue(QJSPrimitiveValue(1.1), &engine), classes: &classesArray, seen: &seen,
378 QStringLiteral("number"));
379
380 // boolean
381 buildClass(value: QJSManagedValue(QJSPrimitiveValue(true), &engine), classes: &classesArray, seen: &seen,
382 QStringLiteral("boolean"));
383
384 // symbol
385 buildClass(value: QJSManagedValue(engine.newSymbol(QStringLiteral("s")), &engine),
386 classes: &classesArray, seen: &seen, QStringLiteral("symbol"));
387
388 // Generate the fake metatypes json structure
389 QJsonDocument metatypesJson = QJsonDocument(
390 QJsonArray({
391 QJsonObject({
392 {QStringLiteral("classes"), classesArray}
393 })
394 })
395 );
396
397 QFile file(fileName);
398 if (!file.open(flags: QFile::WriteOnly)) {
399 qWarning() << "Failed to write metatypes json to" << fileName;
400 return 1;
401 }
402
403 file.write(data: metatypesJson.toJson());
404 file.close();
405
406 return 0;
407}
408

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtdeclarative/tools/qmljsrootgen/main.cpp