1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "typedescriptionreader.h"
30 
31 #include <QtQml/private/qqmljsparser_p.h>
32 #include <QtQml/private/qqmljslexer_p.h>
33 #include <QtQml/private/qqmljsengine_p.h>
34 
35 #include <QtCore/qdir.h>
36 
37 using namespace QQmlJS;
38 using namespace QQmlJS::AST;
39 
toString(const UiQualifiedId * qualifiedId,QChar delimiter=QLatin1Char ('.'))40 QString toString(const UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.'))
41 {
42     QString result;
43 
44     for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
45         if (iter != qualifiedId)
46             result += delimiter;
47 
48         result += iter->name;
49     }
50 
51     return result;
52 }
53 
operator ()(QHash<QString,ScopeTree::ConstPtr> * objects,QList<ModuleApiInfo> * moduleApis,QStringList * dependencies)54 bool TypeDescriptionReader::operator()(
55         QHash<QString, ScopeTree::ConstPtr> *objects,
56         QList<ModuleApiInfo> *moduleApis,
57         QStringList *dependencies)
58 {
59     Engine engine;
60 
61     Lexer lexer(&engine);
62     Parser parser(&engine);
63 
64     lexer.setCode(m_source, /*lineno = */ 1, /*qmlMode = */true);
65 
66     if (!parser.parse()) {
67         m_errorMessage = QString::fromLatin1("%1:%2: %3").arg(
68                     QString::number(parser.errorLineNumber()),
69                     QString::number(parser.errorColumnNumber()),
70                     parser.errorMessage());
71         return false;
72     }
73 
74     m_objects = objects;
75     m_moduleApis = moduleApis;
76     m_dependencies = dependencies;
77     readDocument(parser.ast());
78 
79     return m_errorMessage.isEmpty();
80 }
81 
readDocument(UiProgram * ast)82 void TypeDescriptionReader::readDocument(UiProgram *ast)
83 {
84     if (!ast) {
85         addError(SourceLocation(), tr("Could not parse document."));
86         return;
87     }
88 
89     if (!ast->headers || ast->headers->next || !cast<UiImport *>(ast->headers->headerItem)) {
90         addError(SourceLocation(), tr("Expected a single import."));
91         return;
92     }
93 
94     auto *import = cast<UiImport *>(ast->headers->headerItem);
95     if (toString(import->importUri) != QLatin1String("QtQuick.tooling")) {
96         addError(import->importToken, tr("Expected import of QtQuick.tooling."));
97         return;
98     }
99 
100     if (!import->version) {
101         addError(import->firstSourceLocation(), tr("Import statement without version."));
102         return;
103     }
104 
105     if (import->version->majorVersion != 1) {
106         addError(import->version->firstSourceLocation(),
107                  tr("Major version different from 1 not supported."));
108         return;
109     }
110 
111     if (!ast->members || !ast->members->member || ast->members->next) {
112         addError(SourceLocation(), tr("Expected document to contain a single object definition."));
113         return;
114     }
115 
116     auto *module = cast<UiObjectDefinition *>(ast->members->member);
117     if (!module) {
118         addError(SourceLocation(), tr("Expected document to contain a single object definition."));
119         return;
120     }
121 
122     if (toString(module->qualifiedTypeNameId) != QLatin1String("Module")) {
123         addError(SourceLocation(), tr("Expected document to contain a Module {} member."));
124         return;
125     }
126 
127     readModule(module);
128 }
129 
readModule(UiObjectDefinition * ast)130 void TypeDescriptionReader::readModule(UiObjectDefinition *ast)
131 {
132     for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
133         UiObjectMember *member = it->member;
134         auto *component = cast<UiObjectDefinition *>(member);
135 
136         auto *script = cast<UiScriptBinding *>(member);
137         if (script && (toString(script->qualifiedId) == QStringLiteral("dependencies"))) {
138             readDependencies(script);
139             continue;
140         }
141 
142         QString typeName;
143         if (component)
144             typeName = toString(component->qualifiedTypeNameId);
145 
146         if (!component || (typeName != QLatin1String("Component")
147                            && typeName != QLatin1String("ModuleApi"))) {
148             continue;
149         }
150 
151         if (typeName == QLatin1String("Component"))
152             readComponent(component);
153         else if (typeName == QLatin1String("ModuleApi"))
154             readModuleApi(component);
155     }
156 }
157 
addError(const SourceLocation & loc,const QString & message)158 void TypeDescriptionReader::addError(const SourceLocation &loc, const QString &message)
159 {
160     m_errorMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg(
161                 QDir::toNativeSeparators(m_fileName),
162                 QString::number(loc.startLine),
163                 QString::number(loc.startColumn),
164                 message);
165 }
166 
addWarning(const SourceLocation & loc,const QString & message)167 void TypeDescriptionReader::addWarning(const SourceLocation &loc, const QString &message)
168 {
169     m_warningMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg(
170                 QDir::toNativeSeparators(m_fileName),
171                 QString::number(loc.startLine),
172                 QString::number(loc.startColumn),
173                 message);
174 }
175 
readDependencies(UiScriptBinding * ast)176 void TypeDescriptionReader::readDependencies(UiScriptBinding *ast)
177 {
178     auto *stmt = cast<ExpressionStatement*>(ast->statement);
179     if (!stmt) {
180         addError(ast->statement->firstSourceLocation(), tr("Expected dependency definitions"));
181         return;
182     }
183     auto *exp = cast<ArrayPattern *>(stmt->expression);
184     if (!exp) {
185         addError(stmt->expression->firstSourceLocation(), tr("Expected dependency definitions"));
186         return;
187     }
188     for (PatternElementList *l = exp->elements; l; l = l->next) {
189         auto *str = cast<StringLiteral *>(l->element->initializer);
190         *m_dependencies << str->value.toString();
191     }
192 }
193 
readComponent(UiObjectDefinition * ast)194 void TypeDescriptionReader::readComponent(UiObjectDefinition *ast)
195 {
196     ScopeTree::Ptr scope(new ScopeTree(ScopeType::QMLScope));
197 
198     for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
199         UiObjectMember *member = it->member;
200         auto *component = cast<UiObjectDefinition *>(member);
201         auto *script = cast<UiScriptBinding *>(member);
202         if (component) {
203             QString name = toString(component->qualifiedTypeNameId);
204             if (name == QLatin1String("Property"))
205                 readProperty(component, scope);
206             else if (name == QLatin1String("Method") || name == QLatin1String("Signal"))
207                 readSignalOrMethod(component, name == QLatin1String("Method"), scope);
208             else if (name == QLatin1String("Enum"))
209                 readEnum(component, scope);
210             else
211                 addWarning(component->firstSourceLocation(),
212                            tr("Expected only Property, Method, Signal and Enum object definitions, "
213                               "not \"%1\".").arg(name));
214         } else if (script) {
215             QString name = toString(script->qualifiedId);
216             if (name == QLatin1String("name")) {
217                 scope->setClassName(readStringBinding(script));
218             } else if (name == QLatin1String("prototype")) {
219                 scope->setSuperclassName(readStringBinding(script));
220             } else if (name == QLatin1String("defaultProperty")) {
221                 scope->setDefaultPropertyName(readStringBinding(script));
222             } else if (name == QLatin1String("exports")) {
223                 readExports(script, scope);
224             } else if (name == QLatin1String("exportMetaObjectRevisions")) {
225                 readMetaObjectRevisions(script, scope);
226             } else if (name == QLatin1String("attachedType")) {
227                 scope->setAttachedTypeName(readStringBinding(script));
228             } else if (name == QLatin1String("isSingleton")) {
229                 scope->setIsSingleton(readBoolBinding(script));
230             } else if (name == QLatin1String("isCreatable")) {
231                 scope->setIsCreatable(readBoolBinding(script));
232             } else if (name == QLatin1String("isComposite")) {
233                 scope->setIsComposite(readBoolBinding(script));
234             } else {
235                 addWarning(script->firstSourceLocation(),
236                            tr("Expected only name, prototype, defaultProperty, attachedType, "
237                               "exports, isSingleton, isCreatable, isComposite and "
238                               "exportMetaObjectRevisions script bindings, not \"%1\".").arg(name));
239             }
240         } else {
241             addWarning(member->firstSourceLocation(),
242                        tr("Expected only script bindings and object definitions."));
243         }
244     }
245 
246     if (scope->className().isEmpty()) {
247         addError(ast->firstSourceLocation(), tr("Component definition is missing a name binding."));
248         return;
249     }
250 
251     // ### add implicit export into the package of c++ types
252     scope->addExport(scope->className(), QStringLiteral("<cpp>"), ComponentVersion());
253     m_objects->insert(scope->className(), scope);
254 }
255 
readModuleApi(UiObjectDefinition * ast)256 void TypeDescriptionReader::readModuleApi(UiObjectDefinition *ast)
257 {
258     ModuleApiInfo apiInfo;
259 
260     for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
261         UiObjectMember *member = it->member;
262         auto *script = cast<UiScriptBinding *>(member);
263 
264         if (script) {
265             const QString name = toString(script->qualifiedId);
266             if (name == QLatin1String("uri")) {
267                 apiInfo.uri = readStringBinding(script);
268             } else if (name == QLatin1String("version")) {
269                 apiInfo.version = readNumericVersionBinding(script);
270             } else if (name == QLatin1String("name")) {
271                 apiInfo.cppName = readStringBinding(script);
272             } else {
273                 addWarning(script->firstSourceLocation(),
274                            tr("Expected only uri, version and name script bindings."));
275             }
276         } else {
277             addWarning(member->firstSourceLocation(), tr("Expected only script bindings."));
278         }
279     }
280 
281     if (!apiInfo.version.isValid()) {
282         addError(ast->firstSourceLocation(),
283                  tr("ModuleApi definition has no or invalid version binding."));
284         return;
285     }
286 
287     if (m_moduleApis)
288         m_moduleApis->append(apiInfo);
289 }
290 
readSignalOrMethod(UiObjectDefinition * ast,bool isMethod,const ScopeTree::Ptr & scope)291 void TypeDescriptionReader::readSignalOrMethod(UiObjectDefinition *ast, bool isMethod,
292                                                const ScopeTree::Ptr &scope)
293 {
294     MetaMethod metaMethod;
295     // ### confusion between Method and Slot. Method should be removed.
296     if (isMethod)
297         metaMethod.setMethodType(MetaMethod::Slot);
298     else
299         metaMethod.setMethodType(MetaMethod::Signal);
300 
301     for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
302         UiObjectMember *member = it->member;
303         auto *component = cast<UiObjectDefinition *>(member);
304         auto *script = cast<UiScriptBinding *>(member);
305         if (component) {
306             QString name = toString(component->qualifiedTypeNameId);
307             if (name == QLatin1String("Parameter")) {
308                 readParameter(component, &metaMethod);
309             } else {
310                 addWarning(component->firstSourceLocation(),
311                            tr("Expected only Parameter object definitions."));
312             }
313         } else if (script) {
314             QString name = toString(script->qualifiedId);
315             if (name == QLatin1String("name")) {
316                 metaMethod.setMethodName(readStringBinding(script));
317             } else if (name == QLatin1String("type")) {
318                 metaMethod.setReturnType(readStringBinding(script));
319             } else if (name == QLatin1String("revision")) {
320                 metaMethod.setRevision(readIntBinding(script));
321             } else {
322                 addWarning(script->firstSourceLocation(),
323                            tr("Expected only name and type script bindings."));
324             }
325         } else {
326             addWarning(member->firstSourceLocation(),
327                        tr("Expected only script bindings and object definitions."));
328         }
329     }
330 
331     if (metaMethod.methodName().isEmpty()) {
332         addError(ast->firstSourceLocation(),
333                  tr("Method or signal is missing a name script binding."));
334         return;
335     }
336 
337     scope->addMethod(metaMethod);
338 }
339 
readProperty(UiObjectDefinition * ast,const ScopeTree::Ptr & scope)340 void TypeDescriptionReader::readProperty(UiObjectDefinition *ast, const ScopeTree::Ptr &scope)
341 {
342     QString name;
343     QString type;
344     bool isPointer = false;
345     bool isReadonly = false;
346     bool isList = false;
347     int revision = 0;
348 
349     for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
350         UiObjectMember *member = it->member;
351         auto *script = cast<UiScriptBinding *>(member);
352         if (!script) {
353             addWarning(member->firstSourceLocation(), tr("Expected script binding."));
354             continue;
355         }
356 
357         QString id = toString(script->qualifiedId);
358         if (id == QLatin1String("name")) {
359             name = readStringBinding(script);
360         } else if (id == QLatin1String("type")) {
361             type = readStringBinding(script);
362         } else if (id == QLatin1String("isPointer")) {
363             isPointer = readBoolBinding(script);
364         } else if (id == QLatin1String("isReadonly")) {
365             isReadonly = readBoolBinding(script);
366         } else if (id == QLatin1String("isList")) {
367             isList = readBoolBinding(script);
368         } else if (id == QLatin1String("revision")) {
369             revision = readIntBinding(script);
370         } else {
371             addWarning(script->firstSourceLocation(),
372                        tr("Expected only type, name, revision, isPointer, isReadonly and"
373                           " isList script bindings."));
374         }
375     }
376 
377     if (name.isEmpty() || type.isEmpty()) {
378         addError(ast->firstSourceLocation(),
379                  tr("Property object is missing a name or type script binding."));
380         return;
381     }
382 
383     scope->addProperty(MetaProperty(name, type, isList, !isReadonly, isPointer, false, revision));
384 }
385 
readEnum(UiObjectDefinition * ast,const ScopeTree::Ptr & scope)386 void TypeDescriptionReader::readEnum(UiObjectDefinition *ast, const ScopeTree::Ptr &scope)
387 {
388     MetaEnum metaEnum;
389 
390     for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
391         UiObjectMember *member = it->member;
392         auto *script = cast<UiScriptBinding *>(member);
393         if (!script) {
394             addWarning(member->firstSourceLocation(), tr("Expected script binding."));
395             continue;
396         }
397 
398         QString name = toString(script->qualifiedId);
399         if (name == QLatin1String("name")) {
400             metaEnum.setName(readStringBinding(script));
401         } else if (name == QLatin1String("alias")) {
402             metaEnum.setAlias(readStringBinding(script));
403         } else if (name == QLatin1String("isFlag")) {
404             metaEnum.setIsFlag(readBoolBinding(script));
405         } else if (name == QLatin1String("values")) {
406             readEnumValues(script, &metaEnum);
407         } else {
408             addWarning(script->firstSourceLocation(),
409                        tr("Expected only name and values script bindings."));
410         }
411     }
412 
413     scope->addEnum(metaEnum);
414 }
415 
readParameter(UiObjectDefinition * ast,MetaMethod * metaMethod)416 void TypeDescriptionReader::readParameter(UiObjectDefinition *ast, MetaMethod *metaMethod)
417 {
418     QString name;
419     QString type;
420 
421     for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
422         UiObjectMember *member = it->member;
423         auto *script = cast<UiScriptBinding *>(member);
424         if (!script) {
425             addWarning(member->firstSourceLocation(), tr("Expected script binding."));
426             continue;
427         }
428 
429         const QString id = toString(script->qualifiedId);
430         if (id == QLatin1String("name")) {
431             name = readStringBinding(script);
432         } else if (id == QLatin1String("type")) {
433             type = readStringBinding(script);
434         } else if (id == QLatin1String("isPointer")) {
435             // ### unhandled
436         } else if (id == QLatin1String("isReadonly")) {
437             // ### unhandled
438         } else if (id == QLatin1String("isList")) {
439             // ### unhandled
440         } else {
441             addWarning(script->firstSourceLocation(),
442                        tr("Expected only name and type script bindings."));
443         }
444     }
445 
446     metaMethod->addParameter(name, type);
447 }
448 
readStringBinding(UiScriptBinding * ast)449 QString TypeDescriptionReader::readStringBinding(UiScriptBinding *ast)
450 {
451     Q_ASSERT(ast);
452 
453     if (!ast->statement) {
454         addError(ast->colonToken, tr("Expected string after colon."));
455         return QString();
456     }
457 
458     auto *expStmt = cast<ExpressionStatement *>(ast->statement);
459     if (!expStmt) {
460         addError(ast->statement->firstSourceLocation(), tr("Expected string after colon."));
461         return QString();
462     }
463 
464     auto *stringLit = cast<StringLiteral *>(expStmt->expression);
465     if (!stringLit) {
466         addError(expStmt->firstSourceLocation(), tr("Expected string after colon."));
467         return QString();
468     }
469 
470     return stringLit->value.toString();
471 }
472 
readBoolBinding(UiScriptBinding * ast)473 bool TypeDescriptionReader::readBoolBinding(UiScriptBinding *ast)
474 {
475     Q_ASSERT(ast);
476 
477     if (!ast->statement) {
478         addError(ast->colonToken, tr("Expected boolean after colon."));
479         return false;
480     }
481 
482     auto *expStmt = cast<ExpressionStatement *>(ast->statement);
483     if (!expStmt) {
484         addError(ast->statement->firstSourceLocation(), tr("Expected boolean after colon."));
485         return false;
486     }
487 
488     auto *trueLit = cast<TrueLiteral *>(expStmt->expression);
489     auto *falseLit = cast<FalseLiteral *>(expStmt->expression);
490     if (!trueLit && !falseLit) {
491         addError(expStmt->firstSourceLocation(), tr("Expected true or false after colon."));
492         return false;
493     }
494 
495     return trueLit;
496 }
497 
readNumericBinding(UiScriptBinding * ast)498 double TypeDescriptionReader::readNumericBinding(UiScriptBinding *ast)
499 {
500     Q_ASSERT(ast);
501 
502     if (!ast->statement) {
503         addError(ast->colonToken, tr("Expected numeric literal after colon."));
504         return 0;
505     }
506 
507     auto *expStmt = cast<ExpressionStatement *>(ast->statement);
508     if (!expStmt) {
509         addError(ast->statement->firstSourceLocation(),
510                  tr("Expected numeric literal after colon."));
511         return 0;
512     }
513 
514     auto *numericLit = cast<NumericLiteral *>(expStmt->expression);
515     if (!numericLit) {
516         addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon."));
517         return 0;
518     }
519 
520     return numericLit->value;
521 }
522 
readNumericVersionBinding(UiScriptBinding * ast)523 ComponentVersion TypeDescriptionReader::readNumericVersionBinding(UiScriptBinding *ast)
524 {
525     ComponentVersion invalidVersion;
526 
527     if (!ast || !ast->statement) {
528         addError((ast ? ast->colonToken : SourceLocation()),
529                  tr("Expected numeric literal after colon."));
530         return invalidVersion;
531     }
532 
533     auto *expStmt = cast<ExpressionStatement *>(ast->statement);
534     if (!expStmt) {
535         addError(ast->statement->firstSourceLocation(),
536                  tr("Expected numeric literal after colon."));
537         return invalidVersion;
538     }
539 
540     auto *numericLit = cast<NumericLiteral *>(expStmt->expression);
541     if (!numericLit) {
542         addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon."));
543         return invalidVersion;
544     }
545 
546     return ComponentVersion(m_source.mid(numericLit->literalToken.begin(),
547                                          numericLit->literalToken.length));
548 }
549 
readIntBinding(UiScriptBinding * ast)550 int TypeDescriptionReader::readIntBinding(UiScriptBinding *ast)
551 {
552     double v = readNumericBinding(ast);
553     int i = static_cast<int>(v);
554 
555     if (i != v) {
556         addError(ast->firstSourceLocation(), tr("Expected integer after colon."));
557         return 0;
558     }
559 
560     return i;
561 }
562 
readExports(UiScriptBinding * ast,const ScopeTree::Ptr & scope)563 void TypeDescriptionReader::readExports(UiScriptBinding *ast, const ScopeTree::Ptr &scope)
564 {
565     Q_ASSERT(ast);
566 
567     if (!ast->statement) {
568         addError(ast->colonToken, tr("Expected array of strings after colon."));
569         return;
570     }
571 
572     auto *expStmt = cast<ExpressionStatement *>(ast->statement);
573     if (!expStmt) {
574         addError(ast->statement->firstSourceLocation(),
575                  tr("Expected array of strings after colon."));
576         return;
577     }
578 
579     auto *arrayLit = cast<ArrayPattern *>(expStmt->expression);
580     if (!arrayLit) {
581         addError(expStmt->firstSourceLocation(), tr("Expected array of strings after colon."));
582         return;
583     }
584 
585     for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
586         auto *stringLit = cast<StringLiteral *>(it->element->initializer);
587         if (!stringLit) {
588             addError(arrayLit->firstSourceLocation(),
589                      tr("Expected array literal with only string literal members."));
590             return;
591         }
592         QString exp = stringLit->value.toString();
593         int slashIdx = exp.indexOf(QLatin1Char('/'));
594         int spaceIdx = exp.indexOf(QLatin1Char(' '));
595         ComponentVersion version(exp.mid(spaceIdx + 1));
596 
597         if (spaceIdx == -1 || !version.isValid()) {
598             addError(stringLit->firstSourceLocation(),
599                      tr("Expected string literal to contain 'Package/Name major.minor' "
600                         "or 'Name major.minor'."));
601             continue;
602         }
603         QString package;
604         if (slashIdx != -1)
605             package = exp.left(slashIdx);
606         QString name = exp.mid(slashIdx + 1, spaceIdx - (slashIdx+1));
607 
608         // ### relocatable exports where package is empty?
609         scope->addExport(name, package, version);
610     }
611 }
612 
readMetaObjectRevisions(UiScriptBinding * ast,const ScopeTree::Ptr & scope)613 void TypeDescriptionReader::readMetaObjectRevisions(UiScriptBinding *ast,
614                                                     const ScopeTree::Ptr &scope)
615 {
616     Q_ASSERT(ast);
617 
618     if (!ast->statement) {
619         addError(ast->colonToken, tr("Expected array of numbers after colon."));
620         return;
621     }
622 
623     auto *expStmt = cast<ExpressionStatement *>(ast->statement);
624     if (!expStmt) {
625         addError(ast->statement->firstSourceLocation(),
626                  tr("Expected array of numbers after colon."));
627         return;
628     }
629 
630     auto *arrayLit = cast<ArrayPattern *>(expStmt->expression);
631     if (!arrayLit) {
632         addError(expStmt->firstSourceLocation(), tr("Expected array of numbers after colon."));
633         return;
634     }
635 
636     int exportIndex = 0;
637     const int exportCount = scope->exports().size();
638     for (PatternElementList *it = arrayLit->elements; it; it = it->next, ++exportIndex) {
639         auto *numberLit = cast<NumericLiteral *>(it->element->initializer);
640         if (!numberLit) {
641             addError(arrayLit->firstSourceLocation(),
642                      tr("Expected array literal with only number literal members."));
643             return;
644         }
645 
646         if (exportIndex >= exportCount) {
647             addError(numberLit->firstSourceLocation(),
648                      tr("Meta object revision without matching export."));
649             return;
650         }
651 
652         const double v = numberLit->value;
653         const int metaObjectRevision = static_cast<int>(v);
654         if (metaObjectRevision != v) {
655             addError(numberLit->firstSourceLocation(), tr("Expected integer."));
656             return;
657         }
658 
659         scope->setExportMetaObjectRevision(exportIndex, metaObjectRevision);
660     }
661 }
662 
readEnumValues(UiScriptBinding * ast,MetaEnum * metaEnum)663 void TypeDescriptionReader::readEnumValues(UiScriptBinding *ast, MetaEnum *metaEnum)
664 {
665     if (!ast)
666         return;
667     if (!ast->statement) {
668         addError(ast->colonToken, tr("Expected object literal after colon."));
669         return;
670     }
671 
672     auto *expStmt = cast<ExpressionStatement *>(ast->statement);
673     if (!expStmt) {
674         addError(ast->statement->firstSourceLocation(), tr("Expected expression after colon."));
675         return;
676     }
677 
678     if (auto *objectLit = cast<ObjectPattern *>(expStmt->expression)) {
679         for (PatternPropertyList *it = objectLit->properties; it; it = it->next) {
680             if (PatternProperty *assignement = it->property) {
681                 if (auto *name = cast<StringLiteralPropertyName *>(assignement->name)) {
682                     metaEnum->addKey(name->id.toString());
683                     continue;
684                 }
685             }
686             addError(it->firstSourceLocation(), tr("Expected strings as enum keys."));
687         }
688     } else if (auto *arrayLit = cast<ArrayPattern *>(expStmt->expression)) {
689         for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
690             if (PatternElement *element = it->element) {
691                 if (auto *name = cast<StringLiteral *>(element->initializer)) {
692                     metaEnum->addKey(name->value.toString());
693                     continue;
694                 }
695             }
696             addError(it->firstSourceLocation(), tr("Expected strings as enum keys."));
697         }
698     } else {
699         addError(ast->statement->firstSourceLocation(),
700                  tr("Expected either array or object literal as enum definition."));
701     }
702 }
703