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("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 | |
277 | void 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 | |
358 | void 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 | |
424 | void 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 | |
458 | void 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 | |
501 | QString 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 | |
525 | bool 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 | |
550 | double 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 | |
575 | static 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 | |
590 | QTypeRevision 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 | |
617 | int 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 | |
630 | ArrayPattern* 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 | |
655 | QList<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 | |
695 | void QQmlJSTypeDescriptionReader::readAliases( |
696 | QQmlJS::AST::UiScriptBinding *ast, const QQmlJSScope::Ptr &scope) |
697 | { |
698 | scope->setAliases(readStringList(ast)); |
699 | } |
700 | |
701 | void 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 | |
724 | void 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 | |
787 | QStringList 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 | |
809 | void QQmlJSTypeDescriptionReader::readDeferredNames(UiScriptBinding *ast, |
810 | const QQmlJSScope::Ptr &scope) |
811 | { |
812 | scope->setOwnDeferredNames(readStringList(ast)); |
813 | } |
814 | |
815 | void QQmlJSTypeDescriptionReader::readImmediateNames(UiScriptBinding *ast, |
816 | const QQmlJSScope::Ptr &scope) |
817 | { |
818 | scope->setOwnImmediateNames(readStringList(ast)); |
819 | } |
820 | |
821 | void 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 | |
877 | QT_END_NAMESPACE |
878 |
Definitions
- toString
- operator()
- readDocument
- readModule
- addError
- addWarning
- readDependencies
- readComponent
- readSignalOrMethod
- readProperty
- readEnum
- readParameter
- readStringBinding
- readBoolBinding
- readNumericBinding
- parseVersion
- readNumericVersionBinding
- readIntBinding
- getArray
- readExports
- readAliases
- readInterfaces
- checkMetaObjectRevisions
- readStringList
- readDeferredNames
- readImmediateNames
Start learning QML with our Intro Training
Find out more