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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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