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