| 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 | |