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 "qqmljstypedescriptionreader_p.h"
5
6#include <QtQml/private/qqmljsparser_p.h>
7#include <QtQml/private/qqmljslexer_p.h>
8#include <QtQml/private/qqmljsengine_p.h>
9
10#include <QtCore/qdir.h>
11#include <QtCore/qstring.h>
12
13QT_BEGIN_NAMESPACE
14
15using namespace QQmlJS;
16using namespace QQmlJS::AST;
17using namespace Qt::StringLiterals;
18
19QString toString(const UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.'))
20{
21 QString result;
22
23 for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
24 if (iter != qualifiedId)
25 result += delimiter;
26
27 result += iter->name;
28 }
29
30 return result;
31}
32
33bool QQmlJSTypeDescriptionReader::operator()(
34 QList<QQmlJSExportedScope> *objects, QStringList *dependencies)
35{
36 Engine engine;
37
38 Lexer lexer(&engine);
39 Parser parser(&engine);
40
41 lexer.setCode(code: m_source, /*lineno = */ 1, /*qmlMode = */true);
42
43 if (!parser.parse()) {
44 m_errorMessage = QString::fromLatin1(ba: "%1:%2: %3").arg(
45 QString::number(parser.errorLineNumber()),
46 QString::number(parser.errorColumnNumber()),
47 parser.errorMessage());
48 return false;
49 }
50
51 m_objects = objects;
52 m_dependencies = dependencies;
53 readDocument(ast: parser.ast());
54
55 return m_errorMessage.isEmpty();
56}
57
58void QQmlJSTypeDescriptionReader::readDocument(UiProgram *ast)
59{
60 if (!ast) {
61 addError(loc: SourceLocation(), message: tr(sourceText: "Could not parse document."));
62 return;
63 }
64
65 if (!ast->headers || ast->headers->next || !cast<UiImport *>(ast->headers->headerItem)) {
66 addError(loc: SourceLocation(), message: tr(sourceText: "Expected a single import."));
67 return;
68 }
69
70 auto *import = cast<UiImport *>(ast->headers->headerItem);
71 if (toString(import->importUri) != QLatin1String("QtQuick.tooling")) {
72 addError(loc: import->importToken, message: tr(sourceText: "Expected import of QtQuick.tooling."));
73 return;
74 }
75
76 if (!import->version) {
77 addError(loc: import->firstSourceLocation(), message: tr(sourceText: "Import statement without version."));
78 return;
79 }
80
81 if (import->version->version.majorVersion() != 1) {
82 addError(loc: import->version->firstSourceLocation(),
83 message: tr(sourceText: "Major version different from 1 not supported."));
84 return;
85 }
86
87 if (!ast->members || !ast->members->member || ast->members->next) {
88 addError(loc: SourceLocation(), message: tr(sourceText: "Expected document to contain a single object definition."));
89 return;
90 }
91
92 auto *module = cast<UiObjectDefinition *>(ast->members->member);
93 if (!module) {
94 addError(loc: SourceLocation(), message: tr(sourceText: "Expected document to contain a single object definition."));
95 return;
96 }
97
98 if (toString(module->qualifiedTypeNameId) != QLatin1String("Module")) {
99 addError(loc: SourceLocation(), message: tr(sourceText: "Expected document to contain a Module {} member."));
100 return;
101 }
102
103 readModule(ast: module);
104}
105
106void QQmlJSTypeDescriptionReader::readModule(UiObjectDefinition *ast)
107{
108 for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
109 UiObjectMember *member = it->member;
110 auto *component = cast<UiObjectDefinition *>(member);
111
112 auto *script = cast<UiScriptBinding *>(member);
113 if (script && (toString(script->qualifiedId) == QStringLiteral("dependencies"))) {
114 readDependencies(ast: script);
115 continue;
116 }
117
118 QString typeName;
119 if (component)
120 typeName = toString(component->qualifiedTypeNameId);
121
122 if (!component || typeName != QLatin1String("Component")) {
123 continue;
124 }
125
126 if (typeName == QLatin1String("Component"))
127 readComponent(ast: component);
128 }
129}
130
131void QQmlJSTypeDescriptionReader::addError(const SourceLocation &loc, const QString &message)
132{
133 m_errorMessage += QString::fromLatin1(ba: "%1:%2:%3: %4\n").arg(
134 args: QDir::toNativeSeparators(pathName: m_fileName),
135 args: QString::number(loc.startLine),
136 args: QString::number(loc.startColumn),
137 args: message);
138}
139
140void QQmlJSTypeDescriptionReader::addWarning(const SourceLocation &loc, const QString &message)
141{
142 m_warningMessage += QString::fromLatin1(ba: "%1:%2:%3: %4\n").arg(
143 args: QDir::toNativeSeparators(pathName: m_fileName),
144 args: QString::number(loc.startLine),
145 args: QString::number(loc.startColumn),
146 args: message);
147}
148
149void QQmlJSTypeDescriptionReader::readDependencies(UiScriptBinding *ast)
150{
151 auto *stmt = cast<ExpressionStatement*>(ast->statement);
152 if (!stmt) {
153 addError(loc: ast->statement->firstSourceLocation(), message: tr(sourceText: "Expected dependency definitions"));
154 return;
155 }
156 auto *exp = cast<ArrayPattern *>(stmt->expression);
157 if (!exp) {
158 addError(loc: stmt->expression->firstSourceLocation(), message: tr(sourceText: "Expected dependency definitions"));
159 return;
160 }
161 for (PatternElementList *l = exp->elements; l; l = l->next) {
162 auto *str = cast<StringLiteral *>(l->element->initializer);
163 *m_dependencies << str->value.toString();
164 }
165}
166
167void QQmlJSTypeDescriptionReader::readComponent(UiObjectDefinition *ast)
168{
169 m_currentCtorIndex = 0;
170 m_currentMethodIndex = 0;
171 QQmlJSScope::Ptr scope = QQmlJSScope::create();
172 QList<QQmlJSScope::Export> exports;
173
174 UiScriptBinding *metaObjectRevisions = nullptr;
175 for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
176 UiObjectMember *member = it->member;
177 auto *component = cast<UiObjectDefinition *>(member);
178 auto *script = cast<UiScriptBinding *>(member);
179 if (component) {
180 QString name = toString(component->qualifiedTypeNameId);
181 if (name == QLatin1String("Property"))
182 readProperty(ast: component, scope);
183 else if (name == QLatin1String("Method") || name == QLatin1String("Signal"))
184 readSignalOrMethod(ast: component, isMethod: name == QLatin1String("Method"), scope);
185 else if (name == QLatin1String("Enum"))
186 readEnum(ast: component, scope);
187 else
188 addWarning(loc: component->firstSourceLocation(),
189 message: tr(sourceText: "Expected only Property, Method, Signal and Enum object definitions, "
190 "not \"%1\".").arg(a: name));
191 } else if (script) {
192 QString name = toString(script->qualifiedId);
193 if (name == QLatin1String("file")) {
194 scope->setFilePath(readStringBinding(ast: script));
195 } else if (name == QLatin1String("lineNumber")) {
196 // only used in 6.11
197 } else if (name == QLatin1String("name")) {
198 scope->setInternalName(readStringBinding(ast: script));
199 } else if (name == QLatin1String("prototype")) {
200 scope->setBaseTypeName(readStringBinding(ast: script));
201 } else if (name == QLatin1String("defaultProperty")) {
202 scope->setOwnDefaultPropertyName(readStringBinding(ast: script));
203 } else if (name == QLatin1String("parentProperty")) {
204 scope->setOwnParentPropertyName(readStringBinding(ast: script));
205 } else if (name == QLatin1String("exports")) {
206 exports = readExports(ast: script);
207 } else if (name == QLatin1String("aliases")) {
208 readAliases(ast: script, scope);
209 } else if (name == QLatin1String("interfaces")) {
210 readInterfaces(ast: script, scope);
211 } else if (name == QLatin1String("exportMetaObjectRevisions")) {
212 metaObjectRevisions = script;
213 } else if (name == QLatin1String("attachedType")) {
214 scope->setOwnAttachedTypeName(readStringBinding(ast: script));
215 } else if (name == QLatin1String("valueType")) {
216 scope->setValueTypeName(readStringBinding(ast: script));
217 } else if (name == QLatin1String("isSingleton")) {
218 scope->setIsSingleton(readBoolBinding(ast: script));
219 } else if (name == QLatin1String("isCreatable")) {
220 scope->setCreatableFlag(readBoolBinding(ast: script));
221 } else if (name == QLatin1String("isStructured")) {
222 scope->setStructuredFlag(readBoolBinding(ast: script));
223 } else if (name == QLatin1String("isComposite")) {
224 scope->setIsComposite(readBoolBinding(ast: script));
225 } else if (name == QLatin1String("hasCustomParser")) {
226 scope->setHasCustomParser(readBoolBinding(ast: script));
227 } else if (name == QLatin1String("enforcesScopedEnums")) {
228 scope->setEnforcesScopedEnumsFlag(readBoolBinding(ast: script));
229 } else if (name == QLatin1String("accessSemantics")) {
230 const QString semantics = readStringBinding(ast: script);
231 if (semantics == QLatin1String("reference")) {
232 scope->setAccessSemantics(QQmlJSScope::AccessSemantics::Reference);
233 } else if (semantics == QLatin1String("value")) {
234 scope->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
235 } else if (semantics == QLatin1String("none")) {
236 scope->setAccessSemantics(QQmlJSScope::AccessSemantics::None);
237 } else if (semantics == QLatin1String("sequence")) {
238 scope->setAccessSemantics(QQmlJSScope::AccessSemantics::Sequence);
239 } else {
240 addWarning(loc: script->firstSourceLocation(),
241 message: tr(sourceText: "Unknown access semantics \"%1\".").arg(a: semantics));
242 }
243 } else if (name == QLatin1String("extension")) {
244 scope->setExtensionTypeName(readStringBinding(ast: script));
245 } else if (name == QLatin1String("extensionIsJavaScript")) {
246 scope->setExtensionIsJavaScript(readBoolBinding(ast: script));
247 } else if (name == QLatin1String("extensionIsNamespace")) {
248 scope->setExtensionIsNamespace(readBoolBinding(ast: script));
249 } else if (name == QLatin1String("deferredNames")) {
250 readDeferredNames(ast: script, scope);
251 } else if (name == QLatin1String("immediateNames")) {
252 readImmediateNames(ast: script, scope);
253 } else if (name == QLatin1String("isJavaScriptBuiltin")) {
254 scope->setIsJavaScriptBuiltin(true);
255 } else {
256 addWarning(loc: script->firstSourceLocation(),
257 message: tr(sourceText: "Expected only name, prototype, defaultProperty, attachedType, "
258 "valueType, exports, interfaces, isSingleton, isCreatable, "
259 "isStructured, isComposite, hasCustomParser, enforcesScopedEnums, "
260 "aliases, exportMetaObjectRevisions, deferredNames, and "
261 "immediateNames in script bindings, not \"%1\".")
262 .arg(a: name));
263 }
264 } else {
265 addWarning(loc: member->firstSourceLocation(),
266 message: tr(sourceText: "Expected only script bindings and object definitions."));
267 }
268 }
269
270 if (scope->internalName().isEmpty()) {
271 addError(loc: ast->firstSourceLocation(), message: tr(sourceText: "Component definition is missing a name binding."));
272 return;
273 }
274
275 if (metaObjectRevisions)
276 checkMetaObjectRevisions(ast: metaObjectRevisions, exports: &exports);
277 m_objects->append(t: {.scope: scope, .exports: exports});
278}
279
280void QQmlJSTypeDescriptionReader::readSignalOrMethod(
281 UiObjectDefinition *ast, bool isMethod, const QQmlJSScope::Ptr &scope)
282{
283 QQmlJSMetaMethod metaMethod;
284 // ### confusion between Method and Slot. Method should be removed.
285 if (isMethod)
286 metaMethod.setMethodType(QQmlJSMetaMethodType::Slot);
287 else
288 metaMethod.setMethodType(QQmlJSMetaMethodType::Signal);
289
290 for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
291 UiObjectMember *member = it->member;
292 auto *component = cast<UiObjectDefinition *>(member);
293 auto *script = cast<UiScriptBinding *>(member);
294 if (component) {
295 QString name = toString(component->qualifiedTypeNameId);
296 if (name == QLatin1String("Parameter")) {
297 readParameter(ast: component, metaMethod: &metaMethod);
298 } else {
299 addWarning(loc: component->firstSourceLocation(),
300 message: tr(sourceText: "Expected only Parameter in object definitions."));
301 }
302 } else if (script) {
303 QString name = toString(script->qualifiedId);
304 if (name == QLatin1String("name")) {
305 metaMethod.setMethodName(readStringBinding(ast: script));
306 } else if (name == QLatin1String("type")) {
307 metaMethod.setReturnTypeName(readStringBinding(ast: script));
308 } else if (name == QLatin1String("revision")) {
309 metaMethod.setRevision(readIntBinding(ast: script));
310 } else if (name == QLatin1String("isCloned")) {
311 metaMethod.setIsCloned(readBoolBinding(ast: script));
312 } else if (name == QLatin1String("isConstructor")) {
313 // The constructors in the moc json output are ordered the same
314 // way as the ones in the metaobject. qmltyperegistrar moves them into
315 // the same list as the other members, but maintains their order.
316 if (readBoolBinding(ast: script)) {
317 metaMethod.setIsConstructor(true);
318 metaMethod.setConstructorIndex(
319 QQmlJSMetaMethod::RelativeFunctionIndex(m_currentCtorIndex++));
320 }
321 } else if (name == QLatin1String("isJavaScriptFunction")) {
322 metaMethod.setIsJavaScriptFunction(readBoolBinding(ast: script));
323 } else if (name == QLatin1String("isList")) {
324 auto metaReturnType = metaMethod.returnValue();
325 metaReturnType.setIsList(readBoolBinding(ast: script));
326 metaMethod.setReturnValue(metaReturnType);
327 } else if (name == QLatin1String("isPointer")) {
328 // TODO: We don't need this information. We can probably drop all isPointer members
329 // once we make sure that the type information is always complete. The
330 // description of the type being referenced has access semantics after all.
331 auto metaReturnType = metaMethod.returnValue();
332 metaReturnType.setIsPointer(readBoolBinding(ast: script));
333 metaMethod.setReturnValue(metaReturnType);
334 } else if (name == QLatin1String("isTypeConstant")
335 || name == QLatin1String("isConstant")) {
336 // note: isConstant is only read for backwards compatibility
337 auto metaReturnType = metaMethod.returnValue();
338 metaReturnType.setTypeQualifier(readBoolBinding(ast: script)
339 ? QQmlJSMetaParameter::Const
340 : QQmlJSMetaParameter::NonConst);
341 metaMethod.setReturnValue(metaReturnType);
342 } else if (name == QLatin1String("isMethodConstant")) {
343 metaMethod.setIsConst(readBoolBinding(ast: script));
344 } else {
345 addWarning(loc: script->firstSourceLocation(),
346 message: tr(sourceText: "Expected only name, type, revision, isPointer, isTypeConstant, "
347 "isList, isCloned, isConstructor, isMethodConstant, and "
348 "isJavaScriptFunction in script bindings."));
349 }
350 } else {
351 addWarning(loc: member->firstSourceLocation(),
352 message: tr(sourceText: "Expected only script bindings and object definitions."));
353 }
354 }
355
356 if (metaMethod.methodName().isEmpty()) {
357 addError(loc: ast->firstSourceLocation(),
358 message: tr(sourceText: "Method or signal is missing a name script binding."));
359 return;
360 }
361
362 // Signals, slots and method share one index space. Constructors are separate.
363 // We also assume that the order and therefore the indexing of all methods is retained from
364 // moc's JSON output.
365 if (!metaMethod.isConstructor())
366 metaMethod.setMethodIndex(QQmlJSMetaMethod::RelativeFunctionIndex(m_currentMethodIndex++));
367
368 if (metaMethod.returnTypeName().isEmpty())
369 metaMethod.setReturnTypeName(QLatin1String("void"));
370
371 scope->addOwnMethod(method: metaMethod);
372}
373
374void QQmlJSTypeDescriptionReader::readProperty(UiObjectDefinition *ast, const QQmlJSScope::Ptr &scope)
375{
376 QQmlJSMetaProperty property;
377 property.setIsWritable(true); // default is writable
378 bool isRequired = false;
379
380 for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
381 UiObjectMember *member = it->member;
382 auto *script = cast<UiScriptBinding *>(member);
383 if (!script) {
384 addWarning(loc: member->firstSourceLocation(), message: tr(sourceText: "Expected script binding."));
385 continue;
386 }
387
388 QString id = toString(script->qualifiedId);
389 if (id == QLatin1String("name")) {
390 property.setPropertyName(readStringBinding(ast: script));
391 } else if (id == QLatin1String("type")) {
392 property.setTypeName(readStringBinding(ast: script));
393 } else if (id == QLatin1String("isPointer")) {
394 property.setIsPointer(readBoolBinding(ast: script));
395 } else if (id == QLatin1String("isReadonly")) {
396 property.setIsWritable(!readBoolBinding(ast: script));
397 } else if (id == QLatin1String("isRequired")) {
398 isRequired = readBoolBinding(ast: script);
399 } else if (id == QLatin1String("isList")) {
400 property.setIsList(readBoolBinding(ast: script));
401 } else if (id == QLatin1String("isFinal")) {
402 property.setIsFinal(readBoolBinding(ast: script));
403 } else if (id == QLatin1String("isTypeConstant")) {
404 property.setIsTypeConstant(readBoolBinding(ast: script));
405 } else if (id == QLatin1String("isPropertyConstant")) {
406 property.setIsPropertyConstant(readBoolBinding(ast: script));
407 } else if (id == QLatin1String("isConstant")) {
408 // support old "isConstant" for backwards compatibility
409 property.setIsPropertyConstant(readBoolBinding(ast: script));
410 } else if (id == QLatin1String("revision")) {
411 property.setRevision(readIntBinding(ast: script));
412 } else if (id == QLatin1String("bindable")) {
413 property.setBindable(readStringBinding(ast: script));
414 } else if (id == QLatin1String("read")) {
415 property.setRead(readStringBinding(ast: script));
416 } else if (id == QLatin1String("write")) {
417 property.setWrite(readStringBinding(ast: script));
418 } else if (id == QLatin1String("reset")) {
419 property.setReset(readStringBinding(ast: script));
420 } else if (id == QLatin1String("notify")) {
421 property.setNotify(readStringBinding(ast: script));
422 } else if (id == QLatin1String("index")) {
423 property.setIndex(readIntBinding(ast: script));
424 } else if (id == QLatin1String("privateClass")) {
425 property.setPrivateClass(readStringBinding(ast: script));
426 } else {
427 addWarning(loc: script->firstSourceLocation(),
428 message: tr(sourceText: "Expected only type, name, revision, isPointer, isTypeConstant, isReadonly, isRequired, "
429 "isFinal, isList, bindable, read, write, isPropertyConstant, reset, notify, index, and "
430 "privateClass and script bindings."));
431 }
432 }
433
434 if (property.propertyName().isEmpty()) {
435 addError(loc: ast->firstSourceLocation(),
436 message: tr(sourceText: "Property object is missing a name script binding."));
437 return;
438 }
439
440 scope->addOwnProperty(prop: property);
441 if (isRequired)
442 scope->setPropertyLocallyRequired(name: property.propertyName(), isRequired: true);
443}
444
445void QQmlJSTypeDescriptionReader::readEnum(UiObjectDefinition *ast, const QQmlJSScope::Ptr &scope)
446{
447 QQmlJSMetaEnum metaEnum;
448
449 for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
450 UiObjectMember *member = it->member;
451 auto *script = cast<UiScriptBinding *>(member);
452 if (!script) {
453 addWarning(loc: member->firstSourceLocation(), message: tr(sourceText: "Expected script binding."));
454 continue;
455 }
456
457 QString name = toString(script->qualifiedId);
458 if (name == QLatin1String("name")) {
459 metaEnum.setName(readStringBinding(ast: script));
460 } else if (name == QLatin1String("alias")) {
461 metaEnum.setAlias(readStringBinding(ast: script));
462 } else if (name == QLatin1String("isFlag")) {
463 metaEnum.setIsFlag(readBoolBinding(ast: script));
464 } else if (name == QLatin1String("values")) {
465 readEnumValues(ast: script, metaEnum: &metaEnum);
466 } else if (name == QLatin1String("isScoped")) {
467 metaEnum.setIsScoped(readBoolBinding(ast: script));
468 } else if (name == QLatin1String("type")) {
469 metaEnum.setTypeName(readStringBinding(ast: script));
470 } else {
471 addWarning(loc: script->firstSourceLocation(),
472 message: tr(sourceText: "Expected only name, alias, isFlag, values, isScoped, or type."));
473 }
474 }
475
476 scope->addOwnEnumeration(enumeration: metaEnum);
477}
478
479void QQmlJSTypeDescriptionReader::readParameter(UiObjectDefinition *ast, QQmlJSMetaMethod *metaMethod)
480{
481 QString name;
482 QString type;
483 bool isConstant = false;
484 bool isPointer = false;
485 bool isList = false;
486
487 for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
488 UiObjectMember *member = it->member;
489 auto *script = cast<UiScriptBinding *>(member);
490 if (!script) {
491 addWarning(loc: member->firstSourceLocation(), message: tr(sourceText: "Expected script binding."));
492 continue;
493 }
494
495 const QString id = toString(script->qualifiedId);
496 if (id == QLatin1String("name")) {
497 name = readStringBinding(ast: script);
498 } else if (id == QLatin1String("type")) {
499 type = readStringBinding(ast: script);
500 } else if (id == QLatin1String("isPointer")) {
501 isPointer = readBoolBinding(ast: script);
502 } else if (id == QLatin1String("isTypeConstant") || id == QLatin1String("isConstant")) {
503 // note: isConstant is only read for backwards compatibility
504 isConstant = readBoolBinding(ast: script);
505 } else if (id == QLatin1String("isReadonly")) {
506 // ### unhandled
507 } else if (id == QLatin1String("isList")) {
508 isList = readBoolBinding(ast: script);
509 } else {
510 addWarning(loc: script->firstSourceLocation(),
511 message: tr(sourceText: "Expected only name, type, isPointer, isTypeConstant, isReadonly, "
512 "or IsList script bindings."));
513 }
514 }
515
516 QQmlJSMetaParameter p(name, type);
517 p.setTypeQualifier(isConstant ? QQmlJSMetaParameter::Const : QQmlJSMetaParameter::NonConst);
518 p.setIsPointer(isPointer);
519 p.setIsList(isList);
520 metaMethod->addParameter(p: std::move(p));
521}
522
523QString QQmlJSTypeDescriptionReader::readStringBinding(UiScriptBinding *ast)
524{
525 Q_ASSERT(ast);
526
527 if (!ast->statement) {
528 addError(loc: ast->colonToken, message: tr(sourceText: "Expected string after colon."));
529 return QString();
530 }
531
532 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
533 if (!expStmt) {
534 addError(loc: ast->statement->firstSourceLocation(), message: tr(sourceText: "Expected string after colon."));
535 return QString();
536 }
537
538 auto *stringLit = cast<StringLiteral *>(expStmt->expression);
539 if (!stringLit) {
540 addError(loc: expStmt->firstSourceLocation(), message: tr(sourceText: "Expected string after colon."));
541 return QString();
542 }
543
544 return stringLit->value.toString();
545}
546
547bool QQmlJSTypeDescriptionReader::readBoolBinding(UiScriptBinding *ast)
548{
549 Q_ASSERT(ast);
550
551 if (!ast->statement) {
552 addError(loc: ast->colonToken, message: tr(sourceText: "Expected boolean after colon."));
553 return false;
554 }
555
556 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
557 if (!expStmt) {
558 addError(loc: ast->statement->firstSourceLocation(), message: tr(sourceText: "Expected boolean after colon."));
559 return false;
560 }
561
562 auto *trueLit = cast<TrueLiteral *>(expStmt->expression);
563 auto *falseLit = cast<FalseLiteral *>(expStmt->expression);
564 if (!trueLit && !falseLit) {
565 addError(loc: expStmt->firstSourceLocation(), message: tr(sourceText: "Expected true or false after colon."));
566 return false;
567 }
568
569 return trueLit;
570}
571
572double QQmlJSTypeDescriptionReader::readNumericBinding(UiScriptBinding *ast)
573{
574 Q_ASSERT(ast);
575
576 if (!ast->statement) {
577 addError(loc: ast->colonToken, message: tr(sourceText: "Expected numeric literal after colon."));
578 return 0;
579 }
580
581 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
582 if (!expStmt) {
583 addError(loc: ast->statement->firstSourceLocation(),
584 message: tr(sourceText: "Expected numeric literal after colon."));
585 return 0;
586 }
587
588 auto *numericLit = cast<NumericLiteral *>(expStmt->expression);
589 if (!numericLit) {
590 addError(loc: expStmt->firstSourceLocation(), message: tr(sourceText: "Expected numeric literal after colon."));
591 return 0;
592 }
593
594 return numericLit->value;
595}
596
597static QTypeRevision parseVersion(const QString &versionString)
598{
599 const int dotIdx = versionString.indexOf(ch: QLatin1Char('.'));
600 if (dotIdx == -1)
601 return QTypeRevision();
602 bool ok = false;
603 const int maybeMajor = QStringView{versionString}.left(n: dotIdx).toInt(ok: &ok);
604 if (!ok)
605 return QTypeRevision();
606 const int maybeMinor = QStringView{versionString}.mid(pos: dotIdx + 1).toInt(ok: &ok);
607 if (!ok)
608 return QTypeRevision();
609 return QTypeRevision::fromVersion(majorVersion: maybeMajor, minorVersion: maybeMinor);
610}
611
612QTypeRevision QQmlJSTypeDescriptionReader::readNumericVersionBinding(UiScriptBinding *ast)
613{
614 QTypeRevision invalidVersion;
615
616 if (!ast || !ast->statement) {
617 addError(loc: (ast ? ast->colonToken : SourceLocation()),
618 message: tr(sourceText: "Expected numeric literal after colon."));
619 return invalidVersion;
620 }
621
622 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
623 if (!expStmt) {
624 addError(loc: ast->statement->firstSourceLocation(),
625 message: tr(sourceText: "Expected numeric literal after colon."));
626 return invalidVersion;
627 }
628
629 auto *numericLit = cast<NumericLiteral *>(expStmt->expression);
630 if (!numericLit) {
631 addError(loc: expStmt->firstSourceLocation(), message: tr(sourceText: "Expected numeric literal after colon."));
632 return invalidVersion;
633 }
634
635 return parseVersion(m_source.mid(numericLit->literalToken.begin(),
636 numericLit->literalToken.length));
637}
638
639int QQmlJSTypeDescriptionReader::readIntBinding(UiScriptBinding *ast)
640{
641 double v = readNumericBinding(ast);
642 int i = static_cast<int>(v);
643
644 if (i != v) {
645 addError(loc: ast->firstSourceLocation(), message: tr(sourceText: "Expected integer after colon."));
646 return 0;
647 }
648
649 return i;
650}
651
652ArrayPattern* QQmlJSTypeDescriptionReader::getArray(UiScriptBinding *ast)
653{
654 Q_ASSERT(ast);
655
656 if (!ast->statement) {
657 addError(loc: ast->colonToken, message: tr(sourceText: "Expected array of strings after colon."));
658 return nullptr;
659 }
660
661 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
662 if (!expStmt) {
663 addError(loc: ast->statement->firstSourceLocation(),
664 message: tr(sourceText: "Expected array of strings after colon."));
665 return nullptr;
666 }
667
668 auto *arrayLit = cast<ArrayPattern *>(expStmt->expression);
669 if (!arrayLit) {
670 addError(loc: expStmt->firstSourceLocation(), message: tr(sourceText: "Expected array of strings after colon."));
671 return nullptr;
672 }
673
674 return arrayLit;
675}
676
677QList<QQmlJSScope::Export> QQmlJSTypeDescriptionReader::readExports(UiScriptBinding *ast)
678{
679 QList<QQmlJSScope::Export> exports;
680 auto *arrayLit = getArray(ast);
681
682 if (!arrayLit)
683 return exports;
684
685 for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
686 auto *stringLit = cast<StringLiteral *>(it->element->initializer);
687
688 if (!stringLit) {
689 addError(loc: arrayLit->firstSourceLocation(),
690 message: tr(sourceText: "Expected array literal with only string literal members."));
691 return exports;
692 }
693
694 QString exp = stringLit->value.toString();
695 int slashIdx = exp.indexOf(ch: QLatin1Char('/'));
696 int spaceIdx = exp.indexOf(ch: QLatin1Char(' '));
697 const QTypeRevision version = parseVersion(versionString: exp.mid(position: spaceIdx + 1));
698
699 if (spaceIdx == -1 || !version.isValid()) {
700 addError(loc: stringLit->firstSourceLocation(),
701 message: tr(sourceText: "Expected string literal to contain 'Package/Name major.minor' "
702 "or 'Name major.minor'."));
703 continue;
704 }
705 QString package;
706 if (slashIdx != -1)
707 package = exp.left(n: slashIdx);
708 QString name = exp.mid(position: slashIdx + 1, n: spaceIdx - (slashIdx+1));
709
710 // ### relocatable exports where package is empty?
711 exports.append(t: QQmlJSScope::Export(package, name, version, version));
712 }
713
714 return exports;
715}
716
717void QQmlJSTypeDescriptionReader::readAliases(
718 QQmlJS::AST::UiScriptBinding *ast, const QQmlJSScope::Ptr &scope)
719{
720 scope->setAliases(readStringList(ast));
721}
722
723void QQmlJSTypeDescriptionReader::readInterfaces(UiScriptBinding *ast, const QQmlJSScope::Ptr &scope)
724{
725 auto *arrayLit = getArray(ast);
726
727 if (!arrayLit)
728 return;
729
730 QStringList list;
731
732 for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
733 auto *stringLit = cast<StringLiteral *>(it->element->initializer);
734 if (!stringLit) {
735 addError(loc: arrayLit->firstSourceLocation(),
736 message: tr(sourceText: "Expected array literal with only string literal members."));
737 return;
738 }
739
740 list << stringLit->value.toString();
741 }
742
743 scope->setInterfaceNames(list);
744}
745
746void QQmlJSTypeDescriptionReader::checkMetaObjectRevisions(
747 UiScriptBinding *ast, QList<QQmlJSScope::Export> *exports)
748{
749 Q_ASSERT(ast);
750
751 if (!ast->statement) {
752 addError(loc: ast->colonToken, message: tr(sourceText: "Expected array of numbers after colon."));
753 return;
754 }
755
756 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
757 if (!expStmt) {
758 addError(loc: ast->statement->firstSourceLocation(),
759 message: tr(sourceText: "Expected array of numbers after colon."));
760 return;
761 }
762
763 auto *arrayLit = cast<ArrayPattern *>(expStmt->expression);
764 if (!arrayLit) {
765 addError(loc: expStmt->firstSourceLocation(), message: tr(sourceText: "Expected array of numbers after colon."));
766 return;
767 }
768
769 int exportIndex = 0;
770 const int exportCount = exports->size();
771 for (PatternElementList *it = arrayLit->elements; it; it = it->next, ++exportIndex) {
772 auto *numberLit = cast<NumericLiteral *>(it->element->initializer);
773 if (!numberLit) {
774 addError(loc: arrayLit->firstSourceLocation(),
775 message: tr(sourceText: "Expected array literal with only number literal members."));
776 return;
777 }
778
779 if (exportIndex >= exportCount) {
780 addError(loc: numberLit->firstSourceLocation(),
781 message: tr(sourceText: "Meta object revision without matching export."));
782 return;
783 }
784
785 const double v = numberLit->value;
786 const int metaObjectRevision = static_cast<int>(v);
787 if (metaObjectRevision != v) {
788 addError(loc: numberLit->firstSourceLocation(), message: tr(sourceText: "Expected integer."));
789 return;
790 }
791
792 const QTypeRevision metaObjectVersion
793 = QTypeRevision::fromEncodedVersion(value: metaObjectRevision);
794 const QQmlJSScope::Export &entry = exports->at(i: exportIndex);
795 const QTypeRevision exportVersion = entry.version();
796 if (metaObjectVersion != exportVersion) {
797 addWarning(loc: numberLit->firstSourceLocation(),
798 message: tr(sourceText: "Meta object revision and export version differ.\n"
799 "Revision %1 corresponds to version %2.%3; it should be %4.%5.")
800 .arg(a: metaObjectRevision)
801 .arg(a: metaObjectVersion.majorVersion()).arg(a: metaObjectVersion.minorVersion())
802 .arg(a: exportVersion.majorVersion()).arg(a: exportVersion.minorVersion()));
803 (*exports)[exportIndex] = QQmlJSScope::Export(entry.package(), entry.type(),
804 exportVersion, metaObjectVersion);
805 }
806 }
807}
808
809QStringList QQmlJSTypeDescriptionReader::readStringList(UiScriptBinding *ast)
810{
811 auto *arrayLit = getArray(ast);
812 if (!arrayLit)
813 return {};
814
815 QStringList list;
816
817 for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
818 auto *stringLit = cast<StringLiteral *>(it->element->initializer);
819 if (!stringLit) {
820 addError(loc: arrayLit->firstSourceLocation(),
821 message: tr(sourceText: "Expected array literal with only string literal members."));
822 return {};
823 }
824
825 list << stringLit->value.toString();
826 }
827
828 return list;
829}
830
831void QQmlJSTypeDescriptionReader::readDeferredNames(UiScriptBinding *ast,
832 const QQmlJSScope::Ptr &scope)
833{
834 scope->setOwnDeferredNames(readStringList(ast));
835}
836
837void QQmlJSTypeDescriptionReader::readImmediateNames(UiScriptBinding *ast,
838 const QQmlJSScope::Ptr &scope)
839{
840 scope->setOwnImmediateNames(readStringList(ast));
841}
842
843void QQmlJSTypeDescriptionReader::readEnumValues(UiScriptBinding *ast, QQmlJSMetaEnum *metaEnum)
844{
845 if (!ast)
846 return;
847 if (!ast->statement) {
848 addError(loc: ast->colonToken, message: tr(sourceText: "Expected object literal after colon."));
849 return;
850 }
851
852 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
853 if (!expStmt) {
854 addError(loc: ast->statement->firstSourceLocation(), message: tr(sourceText: "Expected expression after colon."));
855 return;
856 }
857
858 if (auto *objectLit = cast<ObjectPattern *>(expStmt->expression)) {
859 int currentValue = -1;
860 for (PatternPropertyList *it = objectLit->properties; it; it = it->next) {
861 if (PatternProperty *assignement = it->property) {
862 if (auto *name = cast<StringLiteralPropertyName *>(assignement->name)) {
863 metaEnum->addKey(key: name->id.toString());
864
865 if (auto *value = AST::cast<NumericLiteral *>(assignement->initializer)) {
866 currentValue = int(value->value);
867 } else if (auto *minus = AST::cast<UnaryMinusExpression *>(
868 assignement->initializer)) {
869 if (auto *value = AST::cast<NumericLiteral *>(minus->expression))
870 currentValue = -int(value->value);
871 else
872 ++currentValue;
873 } else {
874 ++currentValue;
875 }
876
877 metaEnum->addValue(value: currentValue);
878 continue;
879 }
880 }
881 addError(loc: it->firstSourceLocation(), message: tr(sourceText: "Expected strings as enum keys."));
882 }
883 } else if (auto *arrayLit = cast<ArrayPattern *>(expStmt->expression)) {
884 for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
885 if (PatternElement *element = it->element) {
886 if (auto *name = cast<StringLiteral *>(element->initializer)) {
887 metaEnum->addKey(key: name->value.toString());
888 continue;
889 }
890 }
891 addError(loc: it->firstSourceLocation(), message: tr(sourceText: "Expected strings as enum keys."));
892 }
893 } else {
894 addError(loc: ast->statement->firstSourceLocation(),
895 message: tr(sourceText: "Expected either array or object literal as enum definition."));
896 }
897}
898
899QT_END_NAMESPACE
900

source code of qtdeclarative/src/qmlcompiler/qqmljstypedescriptionreader.cpp