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 "scopetree.h" |
30 | #include "qcoloroutput.h" |
31 | |
32 | #include <QtCore/qqueue.h> |
33 | |
34 | #include <algorithm> |
35 | |
36 | ScopeTree::ScopeTree(ScopeType type, QString name, ScopeTree *parentScope) |
37 | : m_parentScope(parentScope), m_name(std::move(name)), m_scopeType(type) {} |
38 | |
39 | ScopeTree::Ptr ScopeTree::createNewChildScope(ScopeType type, const QString &name) |
40 | { |
41 | Q_ASSERT(type != ScopeType::QMLScope |
42 | || !m_parentScope |
43 | || m_parentScope->m_scopeType == ScopeType::QMLScope |
44 | || m_parentScope->m_name == "global" ); |
45 | auto childScope = ScopeTree::Ptr(new ScopeTree{type, name, this}); |
46 | m_childScopes.push_back(t: childScope); |
47 | return childScope; |
48 | } |
49 | |
50 | void ScopeTree::insertJSIdentifier(const QString &id, QQmlJS::AST::VariableScope scope) |
51 | { |
52 | Q_ASSERT(m_scopeType != ScopeType::QMLScope); |
53 | if (scope == QQmlJS::AST::VariableScope::Var) { |
54 | auto targetScope = this; |
55 | while (targetScope->scopeType() != ScopeType::JSFunctionScope) { |
56 | targetScope = targetScope->m_parentScope; |
57 | } |
58 | targetScope->m_jsIdentifiers.insert(value: id); |
59 | } else { |
60 | m_jsIdentifiers.insert(value: id); |
61 | } |
62 | } |
63 | |
64 | void ScopeTree::insertSignalIdentifier(const QString &id, const MetaMethod &method, |
65 | const QQmlJS::SourceLocation &loc, |
66 | bool hasMultilineHandlerBody) |
67 | { |
68 | Q_ASSERT(m_scopeType == ScopeType::QMLScope); |
69 | m_injectedSignalIdentifiers.insert(akey: id, avalue: {.method: method, .loc: loc, .hasMultilineHandlerBody: hasMultilineHandlerBody}); |
70 | } |
71 | |
72 | void ScopeTree::insertPropertyIdentifier(const MetaProperty &property) |
73 | { |
74 | addProperty(prop: property); |
75 | MetaMethod method(property.propertyName() + QLatin1String("Changed" ), "void" ); |
76 | addMethod(method); |
77 | } |
78 | |
79 | void ScopeTree::addUnmatchedSignalHandler(const QString &handler, |
80 | const QQmlJS::SourceLocation &location) |
81 | { |
82 | m_unmatchedSignalHandlers.append(t: qMakePair(x: handler, y: location)); |
83 | } |
84 | |
85 | bool ScopeTree::isIdInCurrentScope(const QString &id) const |
86 | { |
87 | return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id); |
88 | } |
89 | |
90 | void ScopeTree::addIdToAccessed(const QString &id, const QQmlJS::SourceLocation &location) { |
91 | m_currentFieldMember = new FieldMemberList {.m_name: id, .m_parentType: QString(), .m_location: location, .m_child: {}}; |
92 | m_accessedIdentifiers.push_back(x: std::unique_ptr<FieldMemberList>(m_currentFieldMember)); |
93 | } |
94 | |
95 | void ScopeTree::accessMember(const QString &name, const QString &parentType, |
96 | const QQmlJS::SourceLocation &location) |
97 | { |
98 | Q_ASSERT(m_currentFieldMember); |
99 | auto *fieldMember = new FieldMemberList {.m_name: name, .m_parentType: parentType, .m_location: location, .m_child: {}}; |
100 | m_currentFieldMember->m_child.reset(p: fieldMember); |
101 | m_currentFieldMember = fieldMember; |
102 | } |
103 | |
104 | void ScopeTree::resetMemberScope() |
105 | { |
106 | m_currentFieldMember = nullptr; |
107 | } |
108 | |
109 | bool ScopeTree::isVisualRootScope() const |
110 | { |
111 | return m_parentScope && m_parentScope->m_parentScope |
112 | && m_parentScope->m_parentScope->m_parentScope == nullptr; |
113 | } |
114 | |
115 | class IssueLocationWithContext |
116 | { |
117 | public: |
118 | IssueLocationWithContext(const QString &code, const QQmlJS::SourceLocation &location) { |
119 | int before = std::max(a: 0,b: code.lastIndexOf(c: '\n', from: location.offset)); |
120 | m_beforeText = code.midRef(position: before + 1, n: int(location.offset - (before + 1))); |
121 | m_issueText = code.midRef(position: location.offset, n: location.length); |
122 | int after = code.indexOf(c: '\n', from: int(location.offset + location.length)); |
123 | m_afterText = code.midRef(position: int(location.offset + location.length), |
124 | n: int(after - (location.offset+location.length))); |
125 | } |
126 | |
127 | QStringRef beforeText() const { return m_beforeText; } |
128 | QStringRef issueText() const { return m_issueText; } |
129 | QStringRef afterText() const { return m_afterText; } |
130 | |
131 | private: |
132 | QStringRef m_beforeText; |
133 | QStringRef m_issueText; |
134 | QStringRef m_afterText; |
135 | }; |
136 | |
137 | static const QStringList unknownBuiltins = { |
138 | // TODO: "string" should be added to builtins.qmltypes, and the special handling below removed |
139 | QStringLiteral("alias" ), // TODO: we cannot properly resolve aliases, yet |
140 | QStringLiteral("QRectF" ), // TODO: should be added to builtins.qmltypes |
141 | QStringLiteral("QFont" ), // TODO: should be added to builtins.qmltypes |
142 | QStringLiteral("QJSValue" ), // We cannot say anything intelligent about untyped JS values. |
143 | QStringLiteral("variant" ), // Same for generic variants |
144 | }; |
145 | |
146 | bool ScopeTree::checkMemberAccess( |
147 | const QString &code, |
148 | FieldMemberList *members, |
149 | const ScopeTree *scope, |
150 | const QHash<QString, ScopeTree::ConstPtr> &types, |
151 | ColorOutput& colorOut) const |
152 | { |
153 | if (!members->m_child) |
154 | return true; |
155 | |
156 | Q_ASSERT(scope != nullptr); |
157 | |
158 | const QString scopeName = scope->name().isEmpty() ? scope->className() : scope->name(); |
159 | const auto &access = members->m_child; |
160 | |
161 | const auto scopeIt = scope->m_properties.find(akey: access->m_name); |
162 | if (scopeIt != scope->m_properties.end()) { |
163 | const QString typeName = access->m_parentType.isEmpty() ? scopeIt->typeName() |
164 | : access->m_parentType; |
165 | if (scopeIt->isList() || typeName == QLatin1String("string" )) { |
166 | if (access->m_child && access->m_child->m_name != QLatin1String("length" )) { |
167 | colorOut.write(message: "Warning: " , color: Warning); |
168 | colorOut.write( |
169 | message: QString::fromLatin1( |
170 | str: "\"%1\" is a %2. You cannot access \"%3\" on it at %4:%5\n" ) |
171 | .arg(a: access->m_name) |
172 | .arg(a: QLatin1String(scopeIt->isList() ? "list" : "string" )) |
173 | .arg(a: access->m_child->m_name) |
174 | .arg(a: access->m_child->m_location.startLine) |
175 | .arg(a: access->m_child->m_location.startColumn), color: Normal); |
176 | printContext(colorOut, code, location: access->m_child->m_location); |
177 | return false; |
178 | } |
179 | return true; |
180 | } |
181 | |
182 | if (!access->m_child) |
183 | return true; |
184 | |
185 | if (const ScopeTree *type = scopeIt->type()) { |
186 | if (access->m_parentType.isEmpty()) |
187 | return checkMemberAccess(code, members: access.get(), scope: type, types, colorOut); |
188 | } |
189 | |
190 | if (unknownBuiltins.contains(str: typeName)) |
191 | return true; |
192 | |
193 | const auto it = types.find(akey: typeName); |
194 | if (it != types.end()) |
195 | return checkMemberAccess(code, members: access.get(), scope: it->get(), types, colorOut); |
196 | |
197 | colorOut.write(message: "Warning: " , color: Warning); |
198 | colorOut.write( |
199 | message: QString::fromLatin1(str: "Type \"%1\" of member \"%2\" not found at %3:%4.\n" ) |
200 | .arg(a: typeName) |
201 | .arg(a: access->m_name) |
202 | .arg(a: access->m_location.startLine) |
203 | .arg(a: access->m_location.startColumn), color: Normal); |
204 | printContext(colorOut, code, location: access->m_location); |
205 | return false; |
206 | } |
207 | |
208 | const auto scopeMethodIt = scope->m_methods.find(akey: access->m_name); |
209 | if (scopeMethodIt != scope->m_methods.end()) |
210 | return true; // Access to property of JS function |
211 | |
212 | for (const auto &enumerator : scope->m_enums) { |
213 | for (const QString &key : enumerator.keys()) { |
214 | if (access->m_name != key) |
215 | continue; |
216 | |
217 | if (!access->m_child) |
218 | return true; |
219 | |
220 | colorOut.write(message: "Warning: " , color: Warning); |
221 | colorOut.write(message: QString::fromLatin1( |
222 | str: "\"%1\" is an enum value. You cannot access \"%2\" on it at %3:%4\n" ) |
223 | .arg(a: access->m_name) |
224 | .arg(a: access->m_child->m_name) |
225 | .arg(a: access->m_child->m_location.startLine) |
226 | .arg(a: access->m_child->m_location.startColumn), color: Normal); |
227 | printContext(colorOut, code, location: access->m_child->m_location); |
228 | return false; |
229 | } |
230 | } |
231 | |
232 | auto type = types.value(akey: access->m_parentType.isEmpty() ? scopeName : access->m_parentType); |
233 | while (type) { |
234 | const auto typeIt = type->m_properties.find(akey: access->m_name); |
235 | if (typeIt != type->m_properties.end()) { |
236 | const ScopeTree *propType = typeIt->type(); |
237 | return checkMemberAccess(code, members: access.get(), |
238 | scope: propType ? propType : types.value(akey: typeIt->typeName()).get(), |
239 | types, colorOut); |
240 | } |
241 | |
242 | const auto typeMethodIt = type->m_methods.find(akey: access->m_name); |
243 | if (typeMethodIt != type->m_methods.end()) { |
244 | if (access->m_child == nullptr) |
245 | return true; |
246 | |
247 | colorOut.write(message: "Warning: " , color: Warning); |
248 | colorOut.write(message: QString::fromLatin1( |
249 | str: "\"%1\" is a method. You cannot access \"%2\" on it at %3:%4\n" ) |
250 | .arg(a: access->m_name) |
251 | .arg(a: access->m_child->m_name) |
252 | .arg(a: access->m_child->m_location.startLine) |
253 | .arg(a: access->m_child->m_location.startColumn), color: Normal); |
254 | printContext(colorOut, code, location: access->m_child->m_location); |
255 | return false; |
256 | } |
257 | |
258 | type = types.value(akey: type->superclassName()); |
259 | } |
260 | |
261 | if (access->m_name.front().isUpper() && scope->scopeType() == ScopeType::QMLScope) { |
262 | // may be an attached type |
263 | const auto it = types.find(akey: access->m_name); |
264 | if (it != types.end() && !(*it)->attachedTypeName().isEmpty()) { |
265 | const auto attached = types.find(akey: (*it)->attachedTypeName()); |
266 | if (attached != types.end()) |
267 | return checkMemberAccess(code, members: access.get(), scope: attached->get(), types, colorOut); |
268 | } |
269 | } |
270 | |
271 | colorOut.write(message: "Warning: " , color: Warning); |
272 | colorOut.write(message: QString::fromLatin1( |
273 | str: "Property \"%1\" not found on type \"%2\" at %3:%4\n" ) |
274 | .arg(a: access->m_name) |
275 | .arg(a: scopeName) |
276 | .arg(a: access->m_location.startLine) |
277 | .arg(a: access->m_location.startColumn), color: Normal); |
278 | printContext(colorOut, code, location: access->m_location); |
279 | return false; |
280 | } |
281 | |
282 | bool ScopeTree::recheckIdentifiers( |
283 | const QString &code, |
284 | const QHash<QString, const ScopeTree *> &qmlIDs, |
285 | const QHash<QString, ScopeTree::ConstPtr> &types, |
286 | const ScopeTree *root, const QString &rootId, |
287 | ColorOutput& colorOut) const |
288 | { |
289 | bool noUnqualifiedIdentifier = true; |
290 | |
291 | // revisit all scopes |
292 | QQueue<const ScopeTree *> workQueue; |
293 | workQueue.enqueue(t: this); |
294 | while (!workQueue.empty()) { |
295 | const ScopeTree *currentScope = workQueue.dequeue(); |
296 | for (const auto &handler : currentScope->m_unmatchedSignalHandlers) { |
297 | colorOut.write(message: "Warning: " , color: Warning); |
298 | colorOut.write(message: QString::fromLatin1( |
299 | str: "no matching signal found for handler \"%1\" at %2:%3\n" ) |
300 | .arg(a: handler.first).arg(a: handler.second.startLine) |
301 | .arg(a: handler.second.startColumn), color: Normal); |
302 | printContext(colorOut, code, location: handler.second); |
303 | } |
304 | |
305 | for (const auto &memberAccessTree : qAsConst(t: currentScope->m_accessedIdentifiers)) { |
306 | if (currentScope->isIdInCurrentJSScopes(id: memberAccessTree->m_name)) |
307 | continue; |
308 | |
309 | auto it = qmlIDs.find(akey: memberAccessTree->m_name); |
310 | if (it != qmlIDs.end()) { |
311 | if (*it != nullptr) { |
312 | if (!checkMemberAccess(code, members: memberAccessTree.get(), scope: *it, types, colorOut)) |
313 | noUnqualifiedIdentifier = false; |
314 | continue; |
315 | } else if (memberAccessTree->m_child |
316 | && memberAccessTree->m_child->m_name.front().isUpper()) { |
317 | // It could be a qualified type name |
318 | const QString qualified = memberAccessTree->m_name + QLatin1Char('.') |
319 | + memberAccessTree->m_child->m_name; |
320 | const auto typeIt = types.find(akey: qualified); |
321 | if (typeIt != types.end()) { |
322 | if (!checkMemberAccess(code, members: memberAccessTree->m_child.get(), scope: typeIt->get(), |
323 | types, colorOut)) { |
324 | noUnqualifiedIdentifier = false; |
325 | } |
326 | continue; |
327 | } |
328 | } |
329 | } |
330 | |
331 | auto qmlScope = currentScope->currentQMLScope(); |
332 | if (qmlScope->methods().contains(akey: memberAccessTree->m_name)) { |
333 | // a property of a JavaScript function |
334 | continue; |
335 | } |
336 | |
337 | const auto qmlIt = qmlScope->m_properties.find(akey: memberAccessTree->m_name); |
338 | if (qmlIt != qmlScope->m_properties.end()) { |
339 | if (!memberAccessTree->m_child || unknownBuiltins.contains(str: qmlIt->typeName())) |
340 | continue; |
341 | |
342 | if (!qmlIt->type()) { |
343 | colorOut.write(message: "Warning: " , color: Warning); |
344 | colorOut.write(message: QString::fromLatin1( |
345 | str: "Type of property \"%2\" not found at %3:%4\n" ) |
346 | .arg(a: memberAccessTree->m_name) |
347 | .arg(a: memberAccessTree->m_location.startLine) |
348 | .arg(a: memberAccessTree->m_location.startColumn), color: Normal); |
349 | printContext(colorOut, code, location: memberAccessTree->m_location); |
350 | noUnqualifiedIdentifier = false; |
351 | } else if (!checkMemberAccess(code, members: memberAccessTree.get(), scope: qmlIt->type(), types, |
352 | colorOut)) { |
353 | noUnqualifiedIdentifier = false; |
354 | } |
355 | |
356 | continue; |
357 | } |
358 | |
359 | // TODO: Lots of builtins are missing |
360 | if (memberAccessTree->m_name == "Qt" ) |
361 | continue; |
362 | |
363 | const auto typeIt = types.find(akey: memberAccessTree->m_name); |
364 | if (typeIt != types.end()) { |
365 | if (!checkMemberAccess(code, members: memberAccessTree.get(), scope: typeIt->get(), types, |
366 | colorOut)) { |
367 | noUnqualifiedIdentifier = false; |
368 | } |
369 | continue; |
370 | } |
371 | |
372 | noUnqualifiedIdentifier = false; |
373 | colorOut.write(message: "Warning: " , color: Warning); |
374 | auto location = memberAccessTree->m_location; |
375 | colorOut.write(message: QString::fromLatin1(str: "unqualified access at %1:%2\n" ) |
376 | .arg(a: location.startLine).arg(a: location.startColumn), |
377 | color: Normal); |
378 | |
379 | printContext(colorOut, code, location); |
380 | |
381 | // root(JS) --> program(qml) --> (first element) |
382 | const auto firstElement = root->m_childScopes[0]->m_childScopes[0]; |
383 | if (firstElement->m_properties.contains(akey: memberAccessTree->m_name) |
384 | || firstElement->m_methods.contains(akey: memberAccessTree->m_name) |
385 | || firstElement->m_enums.contains(akey: memberAccessTree->m_name)) { |
386 | colorOut.write(message: "Note: " , color: Info); |
387 | colorOut.write(message: memberAccessTree->m_name + QLatin1String(" is a member of the root element\n" ), color: Normal ); |
388 | colorOut.write(message: QLatin1String(" You can qualify the access with its id to avoid this warning:\n" ), color: Normal); |
389 | if (rootId == QLatin1String("<id>" )) { |
390 | colorOut.write(message: "Note: " , color: Warning); |
391 | colorOut.write(message: ("You first have to give the root element an id\n" )); |
392 | } |
393 | IssueLocationWithContext issueLocationWithContext {code, location}; |
394 | colorOut.write(message: issueLocationWithContext.beforeText().toString(), color: Normal); |
395 | colorOut.write(message: rootId + QLatin1Char('.'), color: Hint); |
396 | colorOut.write(message: issueLocationWithContext.issueText().toString(), color: Normal); |
397 | colorOut.write(message: issueLocationWithContext.afterText() + QLatin1Char('\n'), color: Normal); |
398 | } else if (currentScope->isIdInjectedFromSignal(id: memberAccessTree->m_name)) { |
399 | auto methodUsages = currentScope->currentQMLScope()->m_injectedSignalIdentifiers |
400 | .values(akey: memberAccessTree->m_name); |
401 | auto location = memberAccessTree->m_location; |
402 | // sort the list of signal handlers by their occurrence in the source code |
403 | // then, we select the first one whose location is after the unqualified id |
404 | // and go one step backwards to get the one which we actually need |
405 | std::sort(first: methodUsages.begin(), last: methodUsages.end(), |
406 | comp: [](const MethodUsage &m1, const MethodUsage &m2) { |
407 | return m1.loc.startLine < m2.loc.startLine |
408 | || (m1.loc.startLine == m2.loc.startLine |
409 | && m1.loc.startColumn < m2.loc.startColumn); |
410 | }); |
411 | auto oneBehindIt = std::find_if(first: methodUsages.begin(), last: methodUsages.end(), |
412 | pred: [&location](const MethodUsage &methodUsage) { |
413 | return location.startLine < methodUsage.loc.startLine |
414 | || (location.startLine == methodUsage.loc.startLine |
415 | && location.startColumn < methodUsage.loc.startColumn); |
416 | }); |
417 | auto methodUsage = *(--oneBehindIt); |
418 | colorOut.write(message: "Note:" , color: Info); |
419 | colorOut.write( |
420 | message: memberAccessTree->m_name + QString::fromLatin1( |
421 | str: " is accessible in this scope because " |
422 | "you are handling a signal at %1:%2\n" ) |
423 | .arg(a: methodUsage.loc.startLine).arg(a: methodUsage.loc.startColumn), |
424 | color: Normal); |
425 | colorOut.write(message: "Consider using a function instead\n" , color: Normal); |
426 | IssueLocationWithContext context {code, methodUsage.loc}; |
427 | colorOut.write(message: context.beforeText() + QLatin1Char(' ')); |
428 | colorOut.write(message: methodUsage.hasMultilineHandlerBody ? "function(" : "(" , color: Hint); |
429 | const auto parameters = methodUsage.method.parameterNames(); |
430 | for (int numParams = parameters.size(); numParams > 0; --numParams) { |
431 | colorOut.write(message: parameters.at(i: parameters.size() - numParams), color: Hint); |
432 | if (numParams > 1) |
433 | colorOut.write(message: ", " , color: Hint); |
434 | } |
435 | colorOut.write(message: methodUsage.hasMultilineHandlerBody ? ")" : ") => " , color: Hint); |
436 | colorOut.write(message: " {..." , color: Normal); |
437 | } |
438 | colorOut.write(message: "\n\n\n" , color: Normal); |
439 | } |
440 | for (auto const &childScope: currentScope->m_childScopes) |
441 | workQueue.enqueue(t: childScope.get()); |
442 | } |
443 | return noUnqualifiedIdentifier; |
444 | } |
445 | |
446 | bool ScopeTree::isIdInCurrentQMlScopes(const QString &id) const |
447 | { |
448 | const auto *qmlScope = currentQMLScope(); |
449 | return qmlScope->m_properties.contains(akey: id) |
450 | || qmlScope->m_methods.contains(akey: id) |
451 | || qmlScope->m_enums.contains(akey: id); |
452 | } |
453 | |
454 | bool ScopeTree::isIdInCurrentJSScopes(const QString &id) const |
455 | { |
456 | auto jsScope = this; |
457 | while (jsScope) { |
458 | if (jsScope->m_scopeType != ScopeType::QMLScope && jsScope->m_jsIdentifiers.contains(value: id)) |
459 | return true; |
460 | jsScope = jsScope->m_parentScope; |
461 | } |
462 | return false; |
463 | } |
464 | |
465 | bool ScopeTree::isIdInjectedFromSignal(const QString &id) const |
466 | { |
467 | return currentQMLScope()->m_injectedSignalIdentifiers.contains(akey: id); |
468 | } |
469 | |
470 | const ScopeTree *ScopeTree::currentQMLScope() const |
471 | { |
472 | auto qmlScope = this; |
473 | while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) |
474 | qmlScope = qmlScope->m_parentScope; |
475 | return qmlScope; |
476 | } |
477 | |
478 | void ScopeTree::printContext(ColorOutput &colorOut, const QString &code, |
479 | const QQmlJS::SourceLocation &location) const |
480 | { |
481 | IssueLocationWithContext issueLocationWithContext {code, location}; |
482 | colorOut.write(message: issueLocationWithContext.beforeText().toString(), color: Normal); |
483 | colorOut.write(message: issueLocationWithContext.issueText().toString(), color: Error); |
484 | colorOut.write(message: issueLocationWithContext.afterText().toString() + QLatin1Char('\n'), color: Normal); |
485 | int tabCount = issueLocationWithContext.beforeText().count(c: QLatin1Char('\t')); |
486 | colorOut.write(message: QString(" " ).repeated(times: issueLocationWithContext.beforeText().length() - tabCount) |
487 | + QString("\t" ).repeated(times: tabCount) |
488 | + QString("^" ).repeated(times: location.length) |
489 | + QLatin1Char('\n'), color: Normal); |
490 | } |
491 | |
492 | void ScopeTree::addExport(const QString &name, const QString &package, |
493 | const ComponentVersion &version) |
494 | { |
495 | m_exports.append(t: Export(package, name, version, 0)); |
496 | } |
497 | |
498 | void ScopeTree::setExportMetaObjectRevision(int exportIndex, int metaObjectRevision) |
499 | { |
500 | m_exports[exportIndex].setMetaObjectRevision(metaObjectRevision); |
501 | } |
502 | |
503 | void ScopeTree::updateParentProperty(const ScopeTree *scope) |
504 | { |
505 | auto it = m_properties.find(akey: QLatin1String("parent" )); |
506 | if (it != m_properties.end() |
507 | && scope->name() != QLatin1String("Component" ) |
508 | && scope->name() != QLatin1String("program" )) |
509 | it->setType(scope); |
510 | } |
511 | |
512 | ScopeTree::Export::Export(QString package, QString type, const ComponentVersion &version, |
513 | int metaObjectRevision) : |
514 | m_package(std::move(package)), |
515 | m_type(std::move(type)), |
516 | m_version(version), |
517 | m_metaObjectRevision(metaObjectRevision) |
518 | { |
519 | } |
520 | |
521 | bool ScopeTree::Export::isValid() const |
522 | { |
523 | return m_version.isValid() || !m_package.isEmpty() || !m_type.isEmpty(); |
524 | } |
525 | |