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 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | using namespace QQmlJS; |
16 | using namespace QQmlJS::AST; |
17 | using namespace Qt::StringLiterals; |
18 | |
19 | QString 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 | |
33 | bool 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 | |
58 | void 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 | |
106 | void 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 | |
131 | void 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 | |
140 | void 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 | |
149 | void 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 | |
167 | void 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 | |
266 | void 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 | |
336 | void 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 | |
402 | void 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 | |
436 | void 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 | |
479 | QString 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 | |
503 | bool 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 | |
528 | double 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 | |
553 | static 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 | |
568 | QTypeRevision 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 | |
595 | int 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 | |
608 | ArrayPattern* 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 | |
633 | QList<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 | |
673 | void 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 | |
696 | void 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 | |
759 | QStringList 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 | |
781 | void QQmlJSTypeDescriptionReader::readDeferredNames(UiScriptBinding *ast, |
782 | const QQmlJSScope::Ptr &scope) |
783 | { |
784 | scope->setOwnDeferredNames(readStringList(ast)); |
785 | } |
786 | |
787 | void QQmlJSTypeDescriptionReader::readImmediateNames(UiScriptBinding *ast, |
788 | const QQmlJSScope::Ptr &scope) |
789 | { |
790 | scope->setOwnImmediateNames(readStringList(ast)); |
791 | } |
792 | |
793 | void 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 | |
849 | QT_END_NAMESPACE |
850 | |