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 QQmlJSScope::Ptr scope = QQmlJSScope::create();
171 QList<QQmlJSScope::Export> exports;
172
173 UiScriptBinding *metaObjectRevisions = nullptr;
174 for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
175 UiObjectMember *member = it->member;
176 auto *component = cast<UiObjectDefinition *>(member);
177 auto *script = cast<UiScriptBinding *>(member);
178 if (component) {
179 QString name = toString(component->qualifiedTypeNameId);
180 if (name == QLatin1String("Property"))
181 readProperty(ast: component, scope);
182 else if (name == QLatin1String("Method") || name == QLatin1String("Signal"))
183 readSignalOrMethod(ast: component, isMethod: name == QLatin1String("Method"), scope);
184 else if (name == QLatin1String("Enum"))
185 readEnum(ast: component, scope);
186 else
187 addWarning(loc: component->firstSourceLocation(),
188 message: tr(sourceText: "Expected only Property, Method, Signal and Enum object definitions, "
189 "not \"%1\".").arg(a: name));
190 } else if (script) {
191 QString name = toString(script->qualifiedId);
192 if (name == QLatin1String("file")) {
193 scope->setFilePath(readStringBinding(ast: script));
194 } else if (name == QLatin1String("name")) {
195 scope->setInternalName(readStringBinding(ast: script));
196 } else if (name == QLatin1String("prototype")) {
197 scope->setBaseTypeName(readStringBinding(ast: script));
198 } else if (name == QLatin1String("defaultProperty")) {
199 scope->setOwnDefaultPropertyName(readStringBinding(ast: script));
200 } else if (name == QLatin1String("parentProperty")) {
201 scope->setOwnParentPropertyName(readStringBinding(ast: script));
202 } else if (name == QLatin1String("exports")) {
203 exports = readExports(ast: script);
204 } else if (name == QLatin1String("interfaces")) {
205 readInterfaces(ast: script, scope);
206 } else if (name == QLatin1String("exportMetaObjectRevisions")) {
207 metaObjectRevisions = script;
208 } else if (name == QLatin1String("attachedType")) {
209 scope->setOwnAttachedTypeName(readStringBinding(ast: script));
210 } else if (name == QLatin1String("valueType")) {
211 scope->setValueTypeName(readStringBinding(ast: script));
212 } else if (name == QLatin1String("isSingleton")) {
213 scope->setIsSingleton(readBoolBinding(ast: script));
214 } else if (name == QLatin1String("isCreatable")) {
215 scope->setCreatableFlag(readBoolBinding(ast: script));
216 } else if (name == QLatin1String("isComposite")) {
217 scope->setIsComposite(readBoolBinding(ast: script));
218 } else if (name == QLatin1String("hasCustomParser")) {
219 scope->setHasCustomParser(readBoolBinding(ast: script));
220 } else if (name == QLatin1String("accessSemantics")) {
221 const QString semantics = readStringBinding(ast: script);
222 if (semantics == QLatin1String("reference")) {
223 scope->setAccessSemantics(QQmlJSScope::AccessSemantics::Reference);
224 } else if (semantics == QLatin1String("value")) {
225 scope->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
226 } else if (semantics == QLatin1String("none")) {
227 scope->setAccessSemantics(QQmlJSScope::AccessSemantics::None);
228 } else if (semantics == QLatin1String("sequence")) {
229 scope->setAccessSemantics(QQmlJSScope::AccessSemantics::Sequence);
230 } else {
231 addWarning(loc: script->firstSourceLocation(),
232 message: tr(sourceText: "Unknown access semantics \"%1\".").arg(a: semantics));
233 }
234 } else if (name == QLatin1String("extension")) {
235 scope->setExtensionTypeName(readStringBinding(ast: script));
236 } else if (name == QLatin1String("extensionIsNamespace")) {
237 scope->setExtensionIsNamespace(readBoolBinding(ast: script));
238 } else if (name == QLatin1String("deferredNames")) {
239 readDeferredNames(ast: script, scope);
240 } else if (name == QLatin1String("immediateNames")) {
241 readImmediateNames(ast: script, scope);
242 } else {
243 addWarning(loc: script->firstSourceLocation(),
244 message: tr(sourceText: "Expected only name, prototype, defaultProperty, attachedType, "
245 "valueType, exports, interfaces, isSingleton, isCreatable, "
246 "isComposite, hasCustomParser, exportMetaObjectRevisions, "
247 "deferredNames, and immediateNames in script bindings, not \"%1\".")
248 .arg(a: name));
249 }
250 } else {
251 addWarning(loc: member->firstSourceLocation(),
252 message: tr(sourceText: "Expected only script bindings and object definitions."));
253 }
254 }
255
256 if (scope->internalName().isEmpty()) {
257 addError(loc: ast->firstSourceLocation(), message: tr(sourceText: "Component definition is missing a name binding."));
258 return;
259 }
260
261 if (metaObjectRevisions)
262 checkMetaObjectRevisions(ast: metaObjectRevisions, exports: &exports);
263 m_objects->append(t: {.scope: scope, .exports: exports});
264}
265
266void QQmlJSTypeDescriptionReader::readSignalOrMethod(
267 UiObjectDefinition *ast, bool isMethod, const QQmlJSScope::Ptr &scope)
268{
269 QQmlJSMetaMethod metaMethod;
270 // ### confusion between Method and Slot. Method should be removed.
271 if (isMethod)
272 metaMethod.setMethodType(QQmlJSMetaMethodType::Slot);
273 else
274 metaMethod.setMethodType(QQmlJSMetaMethodType::Signal);
275
276 for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
277 UiObjectMember *member = it->member;
278 auto *component = cast<UiObjectDefinition *>(member);
279 auto *script = cast<UiScriptBinding *>(member);
280 if (component) {
281 QString name = toString(component->qualifiedTypeNameId);
282 if (name == QLatin1String("Parameter")) {
283 readParameter(ast: component, metaMethod: &metaMethod);
284 } else {
285 addWarning(loc: component->firstSourceLocation(),
286 message: tr(sourceText: "Expected only Parameter in object definitions."));
287 }
288 } else if (script) {
289 QString name = toString(script->qualifiedId);
290 if (name == QLatin1String("name")) {
291 metaMethod.setMethodName(readStringBinding(ast: script));
292 } else if (name == QLatin1String("type")) {
293 metaMethod.setReturnTypeName(readStringBinding(ast: script));
294 } else if (name == QLatin1String("revision")) {
295 metaMethod.setRevision(readIntBinding(ast: script));
296 } else if (name == QLatin1String("isCloned")) {
297 metaMethod.setIsCloned(true);
298 } else if (name == QLatin1String("isConstructor")) {
299 metaMethod.setIsConstructor(true);
300
301 // The constructors in the moc json output are ordered the same
302 // way as the ones in the metaobject. qmltyperegistrar moves them into
303 // the same list as the other members, but maintains their order.
304 metaMethod.setConstructorIndex(
305 QQmlJSMetaMethod::RelativeFunctionIndex(m_currentCtorIndex++));
306
307 } else if (name == QLatin1String("isJavaScriptFunction")) {
308 metaMethod.setIsJavaScriptFunction(true);
309 } else if (name == QLatin1String("isList")) {
310 // TODO: Theoretically this can happen. QQmlJSMetaMethod should store it.
311 } else if (name == QLatin1String("isPointer")) {
312 // TODO: We don't need this information. We can probably drop all isPointer members
313 // once we make sure that the type information is always complete. The
314 // description of the type being referenced has access semantics after all.
315 } else {
316 addWarning(loc: script->firstSourceLocation(),
317 message: tr(sourceText: "Expected only name, type, revision, isPointer, isList, "
318 "isCloned, isConstructor, and "
319 "isJavaScriptFunction in script bindings."));
320 }
321 } else {
322 addWarning(loc: member->firstSourceLocation(),
323 message: tr(sourceText: "Expected only script bindings and object definitions."));
324 }
325 }
326
327 if (metaMethod.methodName().isEmpty()) {
328 addError(loc: ast->firstSourceLocation(),
329 message: tr(sourceText: "Method or signal is missing a name script binding."));
330 return;
331 }
332
333 scope->addOwnMethod(method: metaMethod);
334}
335
336void QQmlJSTypeDescriptionReader::readProperty(UiObjectDefinition *ast, const QQmlJSScope::Ptr &scope)
337{
338 QQmlJSMetaProperty property;
339 property.setIsWritable(true); // default is writable
340 bool isRequired = false;
341
342 for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
343 UiObjectMember *member = it->member;
344 auto *script = cast<UiScriptBinding *>(member);
345 if (!script) {
346 addWarning(loc: member->firstSourceLocation(), message: tr(sourceText: "Expected script binding."));
347 continue;
348 }
349
350 QString id = toString(script->qualifiedId);
351 if (id == QLatin1String("name")) {
352 property.setPropertyName(readStringBinding(ast: script));
353 } else if (id == QLatin1String("type")) {
354 property.setTypeName(readStringBinding(ast: script));
355 } else if (id == QLatin1String("isPointer")) {
356 property.setIsPointer(readBoolBinding(ast: script));
357 } else if (id == QLatin1String("isReadonly")) {
358 property.setIsWritable(!readBoolBinding(ast: script));
359 } else if (id == QLatin1String("isRequired")) {
360 isRequired = readBoolBinding(ast: script);
361 } else if (id == QLatin1String("isList")) {
362 property.setIsList(readBoolBinding(ast: script));
363 } else if (id == QLatin1String("isFinal")) {
364 property.setIsFinal(readBoolBinding(ast: script));
365 } else if (id == QLatin1String("isConstant")) {
366 property.setIsConstant(readBoolBinding(ast: script));
367 } else if (id == QLatin1String("revision")) {
368 property.setRevision(readIntBinding(ast: script));
369 } else if (id == QLatin1String("bindable")) {
370 property.setBindable(readStringBinding(ast: script));
371 } else if (id == QLatin1String("read")) {
372 property.setRead(readStringBinding(ast: script));
373 } else if (id == QLatin1String("write")) {
374 property.setWrite(readStringBinding(ast: script));
375 } else if (id == QLatin1String("reset")) {
376 property.setReset(readStringBinding(ast: script));
377 } else if (id == QLatin1String("notify")) {
378 property.setNotify(readStringBinding(ast: script));
379 } else if (id == QLatin1String("index")) {
380 property.setIndex(readIntBinding(ast: script));
381 } else if (id == QLatin1String("privateClass")) {
382 property.setPrivateClass(readStringBinding(ast: script));
383 } else {
384 addWarning(loc: script->firstSourceLocation(),
385 message: tr(sourceText: "Expected only type, name, revision, isPointer, isReadonly, isRequired, "
386 "isFinal, isList, bindable, read, write, reset, notify, index, and "
387 "privateClass and script bindings."));
388 }
389 }
390
391 if (property.propertyName().isEmpty()) {
392 addError(loc: ast->firstSourceLocation(),
393 message: tr(sourceText: "Property object is missing a name script binding."));
394 return;
395 }
396
397 scope->addOwnProperty(prop: property);
398 if (isRequired)
399 scope->setPropertyLocallyRequired(name: property.propertyName(), isRequired: true);
400}
401
402void QQmlJSTypeDescriptionReader::readEnum(UiObjectDefinition *ast, const QQmlJSScope::Ptr &scope)
403{
404 QQmlJSMetaEnum metaEnum;
405
406 for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
407 UiObjectMember *member = it->member;
408 auto *script = cast<UiScriptBinding *>(member);
409 if (!script) {
410 addWarning(loc: member->firstSourceLocation(), message: tr(sourceText: "Expected script binding."));
411 continue;
412 }
413
414 QString name = toString(script->qualifiedId);
415 if (name == QLatin1String("name")) {
416 metaEnum.setName(readStringBinding(ast: script));
417 } else if (name == QLatin1String("alias")) {
418 metaEnum.setAlias(readStringBinding(ast: script));
419 } else if (name == QLatin1String("isFlag")) {
420 metaEnum.setIsFlag(readBoolBinding(ast: script));
421 } else if (name == QLatin1String("values")) {
422 readEnumValues(ast: script, metaEnum: &metaEnum);
423 } else if (name == QLatin1String("scoped")) {
424 metaEnum.setScoped(readBoolBinding(ast: script));
425 } else if (name == QLatin1String("type")) {
426 metaEnum.setTypeName(readStringBinding(ast: script));
427 } else {
428 addWarning(loc: script->firstSourceLocation(),
429 message: tr(sourceText: "Expected only name, alias, isFlag, values, scoped, or type."));
430 }
431 }
432
433 scope->addOwnEnumeration(enumeration: metaEnum);
434}
435
436void QQmlJSTypeDescriptionReader::readParameter(UiObjectDefinition *ast, QQmlJSMetaMethod *metaMethod)
437{
438 QString name;
439 QString type;
440 bool isConstant = false;
441 bool isPointer = false;
442 bool isList = false;
443
444 for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) {
445 UiObjectMember *member = it->member;
446 auto *script = cast<UiScriptBinding *>(member);
447 if (!script) {
448 addWarning(loc: member->firstSourceLocation(), message: tr(sourceText: "Expected script binding."));
449 continue;
450 }
451
452 const QString id = toString(script->qualifiedId);
453 if (id == QLatin1String("name")) {
454 name = readStringBinding(ast: script);
455 } else if (id == QLatin1String("type")) {
456 type = readStringBinding(ast: script);
457 } else if (id == QLatin1String("isPointer")) {
458 isPointer = readBoolBinding(ast: script);
459 } else if (id == QLatin1String("isConstant")) {
460 isConstant = readBoolBinding(ast: script);
461 } else if (id == QLatin1String("isReadonly")) {
462 // ### unhandled
463 } else if (id == QLatin1String("isList")) {
464 isList = readBoolBinding(ast: script);
465 } else {
466 addWarning(loc: script->firstSourceLocation(),
467 message: tr(sourceText: "Expected only name, type, isPointer, isConstant, isReadonly, "
468 "or IsList script bindings."));
469 }
470 }
471
472 QQmlJSMetaParameter p(name, type);
473 p.setTypeQualifier(isConstant ? QQmlJSMetaParameter::Const : QQmlJSMetaParameter::NonConst);
474 p.setIsPointer(isPointer);
475 p.setIsList(isList);
476 metaMethod->addParameter(p: std::move(p));
477}
478
479QString QQmlJSTypeDescriptionReader::readStringBinding(UiScriptBinding *ast)
480{
481 Q_ASSERT(ast);
482
483 if (!ast->statement) {
484 addError(loc: ast->colonToken, message: tr(sourceText: "Expected string after colon."));
485 return QString();
486 }
487
488 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
489 if (!expStmt) {
490 addError(loc: ast->statement->firstSourceLocation(), message: tr(sourceText: "Expected string after colon."));
491 return QString();
492 }
493
494 auto *stringLit = cast<StringLiteral *>(expStmt->expression);
495 if (!stringLit) {
496 addError(loc: expStmt->firstSourceLocation(), message: tr(sourceText: "Expected string after colon."));
497 return QString();
498 }
499
500 return stringLit->value.toString();
501}
502
503bool QQmlJSTypeDescriptionReader::readBoolBinding(UiScriptBinding *ast)
504{
505 Q_ASSERT(ast);
506
507 if (!ast->statement) {
508 addError(loc: ast->colonToken, message: tr(sourceText: "Expected boolean after colon."));
509 return false;
510 }
511
512 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
513 if (!expStmt) {
514 addError(loc: ast->statement->firstSourceLocation(), message: tr(sourceText: "Expected boolean after colon."));
515 return false;
516 }
517
518 auto *trueLit = cast<TrueLiteral *>(expStmt->expression);
519 auto *falseLit = cast<FalseLiteral *>(expStmt->expression);
520 if (!trueLit && !falseLit) {
521 addError(loc: expStmt->firstSourceLocation(), message: tr(sourceText: "Expected true or false after colon."));
522 return false;
523 }
524
525 return trueLit;
526}
527
528double QQmlJSTypeDescriptionReader::readNumericBinding(UiScriptBinding *ast)
529{
530 Q_ASSERT(ast);
531
532 if (!ast->statement) {
533 addError(loc: ast->colonToken, message: tr(sourceText: "Expected numeric literal after colon."));
534 return 0;
535 }
536
537 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
538 if (!expStmt) {
539 addError(loc: ast->statement->firstSourceLocation(),
540 message: tr(sourceText: "Expected numeric literal after colon."));
541 return 0;
542 }
543
544 auto *numericLit = cast<NumericLiteral *>(expStmt->expression);
545 if (!numericLit) {
546 addError(loc: expStmt->firstSourceLocation(), message: tr(sourceText: "Expected numeric literal after colon."));
547 return 0;
548 }
549
550 return numericLit->value;
551}
552
553static QTypeRevision parseVersion(const QString &versionString)
554{
555 const int dotIdx = versionString.indexOf(c: QLatin1Char('.'));
556 if (dotIdx == -1)
557 return QTypeRevision();
558 bool ok = false;
559 const int maybeMajor = QStringView{versionString}.left(n: dotIdx).toInt(ok: &ok);
560 if (!ok)
561 return QTypeRevision();
562 const int maybeMinor = QStringView{versionString}.mid(pos: dotIdx + 1).toInt(ok: &ok);
563 if (!ok)
564 return QTypeRevision();
565 return QTypeRevision::fromVersion(majorVersion: maybeMajor, minorVersion: maybeMinor);
566}
567
568QTypeRevision QQmlJSTypeDescriptionReader::readNumericVersionBinding(UiScriptBinding *ast)
569{
570 QTypeRevision invalidVersion;
571
572 if (!ast || !ast->statement) {
573 addError(loc: (ast ? ast->colonToken : SourceLocation()),
574 message: tr(sourceText: "Expected numeric literal after colon."));
575 return invalidVersion;
576 }
577
578 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
579 if (!expStmt) {
580 addError(loc: ast->statement->firstSourceLocation(),
581 message: tr(sourceText: "Expected numeric literal after colon."));
582 return invalidVersion;
583 }
584
585 auto *numericLit = cast<NumericLiteral *>(expStmt->expression);
586 if (!numericLit) {
587 addError(loc: expStmt->firstSourceLocation(), message: tr(sourceText: "Expected numeric literal after colon."));
588 return invalidVersion;
589 }
590
591 return parseVersion(m_source.mid(position: numericLit->literalToken.begin(),
592 n: numericLit->literalToken.length));
593}
594
595int QQmlJSTypeDescriptionReader::readIntBinding(UiScriptBinding *ast)
596{
597 double v = readNumericBinding(ast);
598 int i = static_cast<int>(v);
599
600 if (i != v) {
601 addError(loc: ast->firstSourceLocation(), message: tr(sourceText: "Expected integer after colon."));
602 return 0;
603 }
604
605 return i;
606}
607
608ArrayPattern* QQmlJSTypeDescriptionReader::getArray(UiScriptBinding *ast)
609{
610 Q_ASSERT(ast);
611
612 if (!ast->statement) {
613 addError(loc: ast->colonToken, message: tr(sourceText: "Expected array of strings after colon."));
614 return nullptr;
615 }
616
617 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
618 if (!expStmt) {
619 addError(loc: ast->statement->firstSourceLocation(),
620 message: tr(sourceText: "Expected array of strings after colon."));
621 return nullptr;
622 }
623
624 auto *arrayLit = cast<ArrayPattern *>(expStmt->expression);
625 if (!arrayLit) {
626 addError(loc: expStmt->firstSourceLocation(), message: tr(sourceText: "Expected array of strings after colon."));
627 return nullptr;
628 }
629
630 return arrayLit;
631}
632
633QList<QQmlJSScope::Export> QQmlJSTypeDescriptionReader::readExports(UiScriptBinding *ast)
634{
635 QList<QQmlJSScope::Export> exports;
636 auto *arrayLit = getArray(ast);
637
638 if (!arrayLit)
639 return exports;
640
641 for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
642 auto *stringLit = cast<StringLiteral *>(it->element->initializer);
643
644 if (!stringLit) {
645 addError(loc: arrayLit->firstSourceLocation(),
646 message: tr(sourceText: "Expected array literal with only string literal members."));
647 return exports;
648 }
649
650 QString exp = stringLit->value.toString();
651 int slashIdx = exp.indexOf(c: QLatin1Char('/'));
652 int spaceIdx = exp.indexOf(c: QLatin1Char(' '));
653 const QTypeRevision version = parseVersion(versionString: exp.mid(position: spaceIdx + 1));
654
655 if (spaceIdx == -1 || !version.isValid()) {
656 addError(loc: stringLit->firstSourceLocation(),
657 message: tr(sourceText: "Expected string literal to contain 'Package/Name major.minor' "
658 "or 'Name major.minor'."));
659 continue;
660 }
661 QString package;
662 if (slashIdx != -1)
663 package = exp.left(n: slashIdx);
664 QString name = exp.mid(position: slashIdx + 1, n: spaceIdx - (slashIdx+1));
665
666 // ### relocatable exports where package is empty?
667 exports.append(t: QQmlJSScope::Export(package, name, version, version));
668 }
669
670 return exports;
671}
672
673void QQmlJSTypeDescriptionReader::readInterfaces(UiScriptBinding *ast, const QQmlJSScope::Ptr &scope)
674{
675 auto *arrayLit = getArray(ast);
676
677 if (!arrayLit)
678 return;
679
680 QStringList list;
681
682 for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
683 auto *stringLit = cast<StringLiteral *>(it->element->initializer);
684 if (!stringLit) {
685 addError(loc: arrayLit->firstSourceLocation(),
686 message: tr(sourceText: "Expected array literal with only string literal members."));
687 return;
688 }
689
690 list << stringLit->value.toString();
691 }
692
693 scope->setInterfaceNames(list);
694}
695
696void QQmlJSTypeDescriptionReader::checkMetaObjectRevisions(
697 UiScriptBinding *ast, QList<QQmlJSScope::Export> *exports)
698{
699 Q_ASSERT(ast);
700
701 if (!ast->statement) {
702 addError(loc: ast->colonToken, message: tr(sourceText: "Expected array of numbers after colon."));
703 return;
704 }
705
706 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
707 if (!expStmt) {
708 addError(loc: ast->statement->firstSourceLocation(),
709 message: tr(sourceText: "Expected array of numbers after colon."));
710 return;
711 }
712
713 auto *arrayLit = cast<ArrayPattern *>(expStmt->expression);
714 if (!arrayLit) {
715 addError(loc: expStmt->firstSourceLocation(), message: tr(sourceText: "Expected array of numbers after colon."));
716 return;
717 }
718
719 int exportIndex = 0;
720 const int exportCount = exports->size();
721 for (PatternElementList *it = arrayLit->elements; it; it = it->next, ++exportIndex) {
722 auto *numberLit = cast<NumericLiteral *>(it->element->initializer);
723 if (!numberLit) {
724 addError(loc: arrayLit->firstSourceLocation(),
725 message: tr(sourceText: "Expected array literal with only number literal members."));
726 return;
727 }
728
729 if (exportIndex >= exportCount) {
730 addError(loc: numberLit->firstSourceLocation(),
731 message: tr(sourceText: "Meta object revision without matching export."));
732 return;
733 }
734
735 const double v = numberLit->value;
736 const int metaObjectRevision = static_cast<int>(v);
737 if (metaObjectRevision != v) {
738 addError(loc: numberLit->firstSourceLocation(), message: tr(sourceText: "Expected integer."));
739 return;
740 }
741
742 const QTypeRevision metaObjectVersion
743 = QTypeRevision::fromEncodedVersion(value: metaObjectRevision);
744 const QQmlJSScope::Export &entry = exports->at(i: exportIndex);
745 const QTypeRevision exportVersion = entry.version();
746 if (metaObjectVersion != exportVersion) {
747 addWarning(loc: numberLit->firstSourceLocation(),
748 message: tr(sourceText: "Meta object revision and export version differ.\n"
749 "Revision %1 corresponds to version %2.%3; it should be %4.%5.")
750 .arg(a: metaObjectRevision)
751 .arg(a: metaObjectVersion.majorVersion()).arg(a: metaObjectVersion.minorVersion())
752 .arg(a: exportVersion.majorVersion()).arg(a: exportVersion.minorVersion()));
753 (*exports)[exportIndex] = QQmlJSScope::Export(entry.package(), entry.type(),
754 exportVersion, metaObjectVersion);
755 }
756 }
757}
758
759QStringList QQmlJSTypeDescriptionReader::readStringList(UiScriptBinding *ast)
760{
761 auto *arrayLit = getArray(ast);
762 if (!arrayLit)
763 return {};
764
765 QStringList list;
766
767 for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
768 auto *stringLit = cast<StringLiteral *>(it->element->initializer);
769 if (!stringLit) {
770 addError(loc: arrayLit->firstSourceLocation(),
771 message: tr(sourceText: "Expected array literal with only string literal members."));
772 return {};
773 }
774
775 list << stringLit->value.toString();
776 }
777
778 return list;
779}
780
781void QQmlJSTypeDescriptionReader::readDeferredNames(UiScriptBinding *ast,
782 const QQmlJSScope::Ptr &scope)
783{
784 scope->setOwnDeferredNames(readStringList(ast));
785}
786
787void QQmlJSTypeDescriptionReader::readImmediateNames(UiScriptBinding *ast,
788 const QQmlJSScope::Ptr &scope)
789{
790 scope->setOwnImmediateNames(readStringList(ast));
791}
792
793void QQmlJSTypeDescriptionReader::readEnumValues(UiScriptBinding *ast, QQmlJSMetaEnum *metaEnum)
794{
795 if (!ast)
796 return;
797 if (!ast->statement) {
798 addError(loc: ast->colonToken, message: tr(sourceText: "Expected object literal after colon."));
799 return;
800 }
801
802 auto *expStmt = cast<ExpressionStatement *>(ast->statement);
803 if (!expStmt) {
804 addError(loc: ast->statement->firstSourceLocation(), message: tr(sourceText: "Expected expression after colon."));
805 return;
806 }
807
808 if (auto *objectLit = cast<ObjectPattern *>(expStmt->expression)) {
809 int currentValue = -1;
810 for (PatternPropertyList *it = objectLit->properties; it; it = it->next) {
811 if (PatternProperty *assignement = it->property) {
812 if (auto *name = cast<StringLiteralPropertyName *>(assignement->name)) {
813 metaEnum->addKey(key: name->id.toString());
814
815 if (auto *value = AST::cast<NumericLiteral *>(assignement->initializer)) {
816 currentValue = int(value->value);
817 } else if (auto *minus = AST::cast<UnaryMinusExpression *>(
818 assignement->initializer)) {
819 if (auto *value = AST::cast<NumericLiteral *>(minus->expression))
820 currentValue = -int(value->value);
821 else
822 ++currentValue;
823 } else {
824 ++currentValue;
825 }
826
827 metaEnum->addValue(value: currentValue);
828 continue;
829 }
830 }
831 addError(loc: it->firstSourceLocation(), message: tr(sourceText: "Expected strings as enum keys."));
832 }
833 } else if (auto *arrayLit = cast<ArrayPattern *>(expStmt->expression)) {
834 for (PatternElementList *it = arrayLit->elements; it; it = it->next) {
835 if (PatternElement *element = it->element) {
836 if (auto *name = cast<StringLiteral *>(element->initializer)) {
837 metaEnum->addKey(key: name->value.toString());
838 continue;
839 }
840 }
841 addError(loc: it->firstSourceLocation(), message: tr(sourceText: "Expected strings as enum keys."));
842 }
843 } else {
844 addError(loc: ast->statement->firstSourceLocation(),
845 message: tr(sourceText: "Expected either array or object literal as enum definition."));
846 }
847}
848
849QT_END_NAMESPACE
850

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