| 1 | // Copyright (C) 2023 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only | 
| 3 |  | 
| 4 | #include "qqmllsutils_p.h" | 
| 5 |  | 
| 6 | #include <QtCore/qassert.h> | 
| 7 | #include <QtLanguageServer/private/qlanguageserverspectypes_p.h> | 
| 8 | #include <QtCore/qthreadpool.h> | 
| 9 | #include <QtCore/private/qduplicatetracker_p.h> | 
| 10 | #include <QtCore/QRegularExpression> | 
| 11 | #include <QtQmlDom/private/qqmldomexternalitems_p.h> | 
| 12 | #include <QtQmlDom/private/qqmldomtop_p.h> | 
| 13 | #include <QtQmlDom/private/qqmldomscriptelements_p.h> | 
| 14 | #include <QtQmlDom/private/qqmldom_utils_p.h> | 
| 15 | #include <QtQml/private/qqmlsignalnames_p.h> | 
| 16 | #include <QtQml/private/qqmljslexer_p.h> | 
| 17 | #include <QtQmlCompiler/private/qqmljsutils_p.h> | 
| 18 |  | 
| 19 | #include <algorithm> | 
| 20 | #include <iterator> | 
| 21 | #include <memory> | 
| 22 | #include <optional> | 
| 23 | #include <set> | 
| 24 | #include <stack> | 
| 25 | #include <type_traits> | 
| 26 | #include <utility> | 
| 27 | #include <variant> | 
| 28 |  | 
| 29 | using namespace QQmlJS::Dom; | 
| 30 | using namespace Qt::StringLiterals; | 
| 31 |  | 
| 32 | QT_BEGIN_NAMESPACE | 
| 33 |  | 
| 34 | Q_LOGGING_CATEGORY(QQmlLSUtilsLog, "qt.languageserver.utils" ) | 
| 35 |  | 
| 36 | namespace QQmlLSUtils { | 
| 37 | QString qualifiersFrom(const DomItem &el) | 
| 38 | { | 
| 39 |     const bool isAccess = QQmlLSUtils::isFieldMemberAccess(item: el); | 
| 40 |     if (!isAccess && !QQmlLSUtils::isFieldMemberExpression(item: el)) | 
| 41 |         return {}; | 
| 42 |  | 
| 43 |     const DomItem fieldMemberExpressionBeginning = el.filterUp( | 
| 44 |             filter: [](DomType, const DomItem &item) { return !QQmlLSUtils::isFieldMemberAccess(item); }, | 
| 45 |             options: FilterUpOptions::ReturnOuter); | 
| 46 |     QStringList qualifiers = | 
| 47 |             QQmlLSUtils::fieldMemberExpressionBits(item: fieldMemberExpressionBeginning, stopAtChild: el); | 
| 48 |  | 
| 49 |     QString result; | 
| 50 |     for (const QString &qualifier : qualifiers) | 
| 51 |         result.append(s: qualifier).append(c: QChar(u'.')); | 
| 52 |     return result; | 
| 53 | } | 
| 54 |  | 
| 55 | /*! | 
| 56 |    \internal | 
| 57 |     Helper to check if item is a Field Member Expression \c {<someExpression>.propertyName}. | 
| 58 | */ | 
| 59 | bool isFieldMemberExpression(const DomItem &item) | 
| 60 | { | 
| 61 |     return item.internalKind() == DomType::ScriptBinaryExpression | 
| 62 |             && item.field(name: Fields::operation).value().toInteger() | 
| 63 |             == ScriptElements::BinaryExpression::FieldMemberAccess; | 
| 64 | } | 
| 65 |  | 
| 66 | /*! | 
| 67 |    \internal | 
| 68 |     Helper to check if item is a Field Member Access \c memberAccess in | 
| 69 |     \c {<someExpression>.memberAccess}. | 
| 70 | */ | 
| 71 | bool isFieldMemberAccess(const DomItem &item) | 
| 72 | { | 
| 73 |     auto parent = item.directParent(); | 
| 74 |     if (!isFieldMemberExpression(item: parent)) | 
| 75 |         return false; | 
| 76 |  | 
| 77 |     DomItem rightHandSide = parent.field(name: Fields::right); | 
| 78 |     return item == rightHandSide; | 
| 79 | } | 
| 80 |  | 
| 81 | /*! | 
| 82 |    \internal | 
| 83 |     Helper to check if item is a Field Member Base \c base in | 
| 84 |     \c {base.memberAccess}. | 
| 85 | */ | 
| 86 | bool isFieldMemberBase(const DomItem &item) | 
| 87 | { | 
| 88 |     auto parent = item.directParent(); | 
| 89 |     if (!isFieldMemberExpression(item: parent)) | 
| 90 |         return false; | 
| 91 |  | 
| 92 |     // First case, checking `a` for being a base in `a.b`: a is the left hand side of the binary | 
| 93 |     // expression B(a,b). | 
| 94 |     const DomItem leftHandSide = parent.field(name: Fields::left); | 
| 95 |     if (item == leftHandSide) | 
| 96 |         return true; | 
| 97 |  | 
| 98 |     // Second case, checking `d` for being a base in `a.b.c.d.e.f.g`: the binary expressions are | 
| 99 |     // nested as following: B(B(B(B(B(B(a,b),c),d),e),f),g) so for `d`, check whether its | 
| 100 |     // grandparent B(B(B(B(a,b),c),d),e), which has `e` on its right hand side, is a binary | 
| 101 |     // expression. | 
| 102 |     const DomItem grandParent = parent.directParent(); | 
| 103 |     return isFieldMemberExpression(item: grandParent) && grandParent.field(name: Fields::left) == parent; | 
| 104 | } | 
| 105 |  | 
| 106 | /*! | 
| 107 |    \internal | 
| 108 |     Get the bits of a field member expression, like \c{a}, \c{b} and \c{c} for \c{a.b.c}. | 
| 109 |  | 
| 110 |    stopAtChild can either be an FieldMemberExpression, a ScriptIdentifierExpression or a default | 
| 111 |    constructed DomItem: This exits early before processing Field::right of an | 
| 112 |    FieldMemberExpression stopAtChild, or before processing a ScriptIdentifierExpression stopAtChild. | 
| 113 |    No early exits if stopAtChild is default constructed. | 
| 114 | */ | 
| 115 | QStringList fieldMemberExpressionBits(const DomItem &item, const DomItem &stopAtChild) | 
| 116 | { | 
| 117 |     const bool isAccess = isFieldMemberAccess(item); | 
| 118 |     const bool isExpression = isFieldMemberExpression(item); | 
| 119 |  | 
| 120 |     // assume it is a non-qualified name | 
| 121 |     if (!isAccess && !isExpression) | 
| 122 |         return { item.value().toString() }; | 
| 123 |  | 
| 124 |     const DomItem stopMarker = | 
| 125 |             isFieldMemberExpression(item: stopAtChild) ? stopAtChild : stopAtChild.directParent(); | 
| 126 |  | 
| 127 |     QStringList result; | 
| 128 |     DomItem current = | 
| 129 |             isAccess ? item.directParent() : (isFieldMemberExpression(item) ? item : DomItem{}); | 
| 130 |  | 
| 131 |     for (; isFieldMemberExpression(item: current); current = current.field(name: Fields::right)) { | 
| 132 |         result << current.field(name: Fields::left).value().toString(); | 
| 133 |  | 
| 134 |         if (current == stopMarker) | 
| 135 |             return result; | 
| 136 |     } | 
| 137 |     result << current.value().toString(); | 
| 138 |  | 
| 139 |     return result; | 
| 140 | } | 
| 141 |  | 
| 142 | /*! | 
| 143 |    \internal | 
| 144 |    The language server protocol calls "URI" what QML calls "URL". | 
| 145 |    According to RFC 3986, a URL is a special case of URI that not only | 
| 146 |    identifies a resource but also shows how to access it. | 
| 147 |    In QML, however, URIs are distinct from URLs. URIs are the | 
| 148 |    identifiers of modules, for example "QtQuick.Controls". | 
| 149 |    In order to not confuse the terms we interpret language server URIs | 
| 150 |    as URLs in the QML code model. | 
| 151 |    This method marks a point of translation between the terms, but does | 
| 152 |    not have to change the actual URI/URL. | 
| 153 |  | 
| 154 |    \sa QQmlLSUtils::qmlUriToLspUrl | 
| 155 |  */ | 
| 156 | QByteArray lspUriToQmlUrl(const QByteArray &uri) | 
| 157 | { | 
| 158 |     return uri; | 
| 159 | } | 
| 160 |  | 
| 161 | QByteArray qmlUrlToLspUri(const QByteArray &url) | 
| 162 | { | 
| 163 |     return url; | 
| 164 | } | 
| 165 |  | 
| 166 | /*! | 
| 167 |    \internal | 
| 168 |    \brief Converts a QQmlJS::SourceLocation to a LSP Range. | 
| 169 |  | 
| 170 |    QQmlJS::SourceLocation starts counting lines and rows at 1, but the LSP Range starts at 0. | 
| 171 |  */ | 
| 172 | QLspSpecification::Range qmlLocationToLspLocation(Location qmlLocation) | 
| 173 | { | 
| 174 |     QLspSpecification::Range range; | 
| 175 |  | 
| 176 |     range.start.line = qmlLocation.sourceLocation().startLine - 1; | 
| 177 |     range.start.character = qmlLocation.sourceLocation().startColumn - 1; | 
| 178 |     range.end.line = qmlLocation.end().line; | 
| 179 |     range.end.character = qmlLocation.end().character; | 
| 180 |  | 
| 181 |     return range; | 
| 182 | } | 
| 183 |  | 
| 184 | /*! | 
| 185 |    \internal | 
| 186 |    \brief Convert a text position from (line, column) into an offset. | 
| 187 |  | 
| 188 |    Row, Column and the offset are all 0-based. | 
| 189 |    For example, \c{s[textOffsetFrom(s, 5, 55)]} returns the character of s at line 5 and column 55. | 
| 190 |  | 
| 191 |    \sa QQmlLSUtils::textRowAndColumnFrom | 
| 192 | */ | 
| 193 | qsizetype textOffsetFrom(const QString &text, int row, int column) | 
| 194 | { | 
| 195 |     return QQmlJS::SourceLocation::offsetFrom(text, line: row + 1, column: column + 1); | 
| 196 | } | 
| 197 |  | 
| 198 | /*! | 
| 199 |    \internal | 
| 200 |    \brief Convert a text position from an offset into (line, column). | 
| 201 |  | 
| 202 |    Row, Column and the offset are all 0-based. | 
| 203 |    For example, \c{textRowAndColumnFrom(s, 55)} returns the line and columns of the | 
| 204 |    character at \c {s[55]}. | 
| 205 |  | 
| 206 |    \sa QQmlLSUtils::textOffsetFrom | 
| 207 | */ | 
| 208 | TextPosition textRowAndColumnFrom(const QString &text, qsizetype offset) | 
| 209 | { | 
| 210 |     auto [row, column] = QQmlJS::SourceLocation::rowAndColumnFrom(text, offset); | 
| 211 |  | 
| 212 |     // special case: return last character when accessing after end of file | 
| 213 |     if (offset >= text.size()) | 
| 214 |         --column; | 
| 215 |  | 
| 216 |     return TextPosition{ .line: int(row - 1), .character: int(column - 1) }; | 
| 217 | } | 
| 218 |  | 
| 219 | static QList<ItemLocation>::const_iterator | 
| 220 | handlePropertyDefinitionAndBindingOverlap(const QList<ItemLocation> &items, qsizetype offsetInFile) | 
| 221 | { | 
| 222 |     auto smallest = std::min_element( | 
| 223 |             first: items.begin(), last: items.end(), comp: [](const ItemLocation &a, const ItemLocation &b) { | 
| 224 |                 return a.fileLocation->info().fullRegion.length | 
| 225 |                         < b.fileLocation->info().fullRegion.length; | 
| 226 |             }); | 
| 227 |  | 
| 228 |     if (smallest->domItem.internalKind() == DomType::Binding) { | 
| 229 |         // weird edge case: the filelocations of property definitions and property bindings are | 
| 230 |         // actually overlapping, which means that qmlls cannot distinguish between bindings and | 
| 231 |         // bindings in property definitions. Those need to be treated differently for | 
| 232 |         // autocompletion, for example. | 
| 233 |         // Therefore: when inside a binding and a propertydefinition, choose the property definition | 
| 234 |         // if offsetInFile is before the colon, like for example: | 
| 235 |         // property var helloProperty: Rectangle { /*...*/ } | 
| 236 |         // |----return propertydef---|-- return Binding ---| | 
| 237 |  | 
| 238 |         // get the smallest property definition to avoid getting the property definition that the | 
| 239 |         // current QmlObject is getting bound to! | 
| 240 |         auto smallestPropertyDefinition = std::min_element( | 
| 241 |                 first: items.begin(), last: items.end(), comp: [](const ItemLocation &a, const ItemLocation &b) { | 
| 242 |                     // make property definition smaller to avoid getting smaller items that are not | 
| 243 |                     // property definitions | 
| 244 |                     const bool aIsPropertyDefinition = | 
| 245 |                             a.domItem.internalKind() == DomType::PropertyDefinition; | 
| 246 |                     const bool bIsPropertyDefinition = | 
| 247 |                             b.domItem.internalKind() == DomType::PropertyDefinition; | 
| 248 |                     return aIsPropertyDefinition > bIsPropertyDefinition | 
| 249 |                             && a.fileLocation->info().fullRegion.length | 
| 250 |                             < b.fileLocation->info().fullRegion.length; | 
| 251 |                 }); | 
| 252 |  | 
| 253 |         if (smallestPropertyDefinition->domItem.internalKind() != DomType::PropertyDefinition) | 
| 254 |             return smallest; | 
| 255 |  | 
| 256 |         const auto propertyDefinitionColon = | 
| 257 |                 smallestPropertyDefinition->fileLocation->info().regions[ColonTokenRegion]; | 
| 258 |         const auto smallestColon = smallest->fileLocation->info().regions[ColonTokenRegion]; | 
| 259 |         // sanity check: is it the definition of the current binding? check if they both have their | 
| 260 |         // ':' at the same location | 
| 261 |         if (propertyDefinitionColon.isValid() && propertyDefinitionColon == smallestColon | 
| 262 |             && offsetInFile < smallestColon.offset) { | 
| 263 |             return smallestPropertyDefinition; | 
| 264 |         } | 
| 265 |     } | 
| 266 |     return smallest; | 
| 267 | } | 
| 268 |  | 
| 269 | static QList<ItemLocation> filterItemsFromTextLocation(const QList<ItemLocation> &items, | 
| 270 |                                                        qsizetype offsetInFile) | 
| 271 | { | 
| 272 |     if (items.size() < 2) | 
| 273 |         return items; | 
| 274 |  | 
| 275 |     // if there are multiple items, take the smallest one + its neighbors | 
| 276 |     // this allows to prefer inline components over main components, when both contain the | 
| 277 |     // current textposition, and to disregard internal structures like property maps, which | 
| 278 |     // "contain" everything from their first-appearing to last-appearing property (e.g. also | 
| 279 |     // other stuff in between those two properties). | 
| 280 |  | 
| 281 |     QList<ItemLocation> filteredItems; | 
| 282 |  | 
| 283 |     auto smallest = handlePropertyDefinitionAndBindingOverlap(items, offsetInFile); | 
| 284 |  | 
| 285 |     filteredItems.append(t: *smallest); | 
| 286 |  | 
| 287 |     const QQmlJS::SourceLocation smallestLoc = smallest->fileLocation->info().fullRegion; | 
| 288 |     const quint32 smallestBegin = smallestLoc.begin(); | 
| 289 |     const quint32 smallestEnd = smallestLoc.end(); | 
| 290 |  | 
| 291 |     for (auto it = items.begin(); it != items.end(); it++) { | 
| 292 |         if (it == smallest) | 
| 293 |             continue; | 
| 294 |  | 
| 295 |         const QQmlJS::SourceLocation itLoc = it->fileLocation->info().fullRegion; | 
| 296 |         const quint32 itBegin = itLoc.begin(); | 
| 297 |         const quint32 itEnd = itLoc.end(); | 
| 298 |         if (itBegin == smallestEnd || smallestBegin == itEnd) { | 
| 299 |             filteredItems.append(t: *it); | 
| 300 |         } | 
| 301 |     } | 
| 302 |     return filteredItems; | 
| 303 | } | 
| 304 |  | 
| 305 | /*! | 
| 306 | \internal | 
| 307 | \brief Find the DomItem representing the object situated in file at given line and | 
| 308 | character/column. | 
| 309 |  | 
| 310 | If line and character point between two objects, two objects might be returned. | 
| 311 | If line and character point to whitespace, it might return an inner node of the QmlDom-Tree. | 
| 312 |  | 
| 313 | We usually assume that sourcelocations have inclusive ends, for example | 
| 314 | we assume that auto-completion on `\n` in `someName\n` wants suggestions | 
| 315 | for `someName`, even if its technically one position "outside" the | 
| 316 | sourcelocation of `someName`. This is not true for | 
| 317 | ScriptBinaryExpressions, where auto-completion on `.` in `someName.` should | 
| 318 | not return suggestions for `someName`. | 
| 319 | The same also applies to all other binary expressions `+`, `-`, and so on. | 
| 320 |  */ | 
| 321 | QList<ItemLocation> itemsFromTextLocation(const DomItem &file, int line, int character) | 
| 322 | { | 
| 323 |     QList<ItemLocation> itemsFound; | 
| 324 |     std::shared_ptr<QmlFile> filePtr = file.ownerAs<QmlFile>(); | 
| 325 |     if (!filePtr) | 
| 326 |         return itemsFound; | 
| 327 |     FileLocations::Tree t = filePtr->fileLocationsTree(); | 
| 328 |     Q_ASSERT(t); | 
| 329 |     QString code = filePtr->code(); // do something more advanced wrt to changes wrt to this->code? | 
| 330 |     QList<ItemLocation> toDo; | 
| 331 |     qsizetype targetPos = textOffsetFrom(text: code, row: line, column: character); | 
| 332 |     Q_ASSERT(targetPos >= 0); | 
| 333 |  | 
| 334 |     enum ComparisonOption { Normal, ExcludePositionAfterLast }; | 
| 335 |     auto containsTarget = [targetPos](QQmlJS::SourceLocation l, ComparisonOption c) { | 
| 336 |         if constexpr (sizeof(qsizetype) <= sizeof(quint32)) { | 
| 337 |             return l.begin() <= quint32(targetPos) && quint32(targetPos) < l.end() + (c == Normal ? 1 : 0) ; | 
| 338 |         } else { | 
| 339 |             return l.begin() <= targetPos && targetPos < l.end() + (c == Normal ? 1 : 0); | 
| 340 |         } | 
| 341 |     }; | 
| 342 |     if (containsTarget(t->info().fullRegion, Normal)) { | 
| 343 |         ItemLocation loc; | 
| 344 |         loc.domItem = file; | 
| 345 |         loc.fileLocation = t; | 
| 346 |         toDo.append(t: loc); | 
| 347 |     } | 
| 348 |     while (!toDo.isEmpty()) { | 
| 349 |         ItemLocation iLoc = toDo.last(); | 
| 350 |         toDo.removeLast(); | 
| 351 |  | 
| 352 |         bool inParentButOutsideChildren = true; | 
| 353 |  | 
| 354 |         // Exclude the position behind the source location in ScriptBinaryExpressions to avoid | 
| 355 |         // returning `owner` in `owner.member` when completion is triggered on the \c{.}. This | 
| 356 |         // tells the code for the completion if the completion was triggered on `owner` or on `.`. | 
| 357 |         // Same is true for templateliterals, where ScriptTemplateExpressionParts and | 
| 358 |         // ScriptTemplateStringParts stop overlapping when using ExcludePositionAfterLast. | 
| 359 |         const ComparisonOption comparisonOption = | 
| 360 |                 iLoc.domItem.internalKind() == DomType::ScriptBinaryExpression | 
| 361 |                         || iLoc.domItem.directParent().internalKind() | 
| 362 |                                 == DomType::ScriptTemplateLiteral | 
| 363 |                 ? ExcludePositionAfterLast | 
| 364 |                 : Normal; | 
| 365 |  | 
| 366 |         auto subEls = iLoc.fileLocation->subItems(); | 
| 367 |         for (auto it = subEls.begin(); it != subEls.end(); ++it) { | 
| 368 |             auto subLoc = std::static_pointer_cast<AttachedInfoT<FileLocations>>(r: it.value()); | 
| 369 |             Q_ASSERT(subLoc); | 
| 370 |  | 
| 371 |             if (containsTarget(subLoc->info().fullRegion, comparisonOption)) { | 
| 372 |                 ItemLocation subItem; | 
| 373 |                 subItem.domItem = iLoc.domItem.path(p: it.key()); | 
| 374 |                 if (!subItem.domItem) { | 
| 375 |                     qCDebug(QQmlLSUtilsLog) | 
| 376 |                             << "A DomItem child is missing or the FileLocationsTree structure does "  | 
| 377 |                                "not follow the DomItem Structure." ; | 
| 378 |                     continue; | 
| 379 |                 } | 
| 380 |                 // the parser inserts empty Script Expressions for bindings that are not completely | 
| 381 |                 // written out yet. Ignore them here. | 
| 382 |                 if (subItem.domItem.internalKind() == DomType::ScriptExpression | 
| 383 |                     && subLoc->info().fullRegion.length == 0) { | 
| 384 |                     continue; | 
| 385 |                 } | 
| 386 |                 subItem.fileLocation = subLoc; | 
| 387 |                 toDo.append(t: subItem); | 
| 388 |                 inParentButOutsideChildren = false; | 
| 389 |             } | 
| 390 |         } | 
| 391 |         if (inParentButOutsideChildren) { | 
| 392 |             itemsFound.append(t: iLoc); | 
| 393 |         } | 
| 394 |     } | 
| 395 |  | 
| 396 |     // filtering step: | 
| 397 |     auto filtered = filterItemsFromTextLocation(items: itemsFound, offsetInFile: targetPos); | 
| 398 |     return filtered; | 
| 399 | } | 
| 400 |  | 
| 401 | DomItem baseObject(const DomItem &object) | 
| 402 | { | 
| 403 |     DomItem prototypes; | 
| 404 |     DomItem qmlObject = object.qmlObject(); | 
| 405 |     // object is (or is inside) an inline component definition | 
| 406 |     if (object.internalKind() == DomType::QmlComponent || !qmlObject) { | 
| 407 |         prototypes = object.component() | 
| 408 |                              .field(name: Fields::objects) | 
| 409 |                              .index(0) | 
| 410 |                              .field(name: QQmlJS::Dom::Fields::prototypes); | 
| 411 |     } else { | 
| 412 |         // object is (or is inside) a QmlObject | 
| 413 |         prototypes = qmlObject.field(name: QQmlJS::Dom::Fields::prototypes); | 
| 414 |     } | 
| 415 |     switch (prototypes.indexes()) { | 
| 416 |     case 0: | 
| 417 |         return {}; | 
| 418 |     case 1: | 
| 419 |         break; | 
| 420 |     default: | 
| 421 |         qDebug() << "Multiple prototypes found for "  << object.name() << ", taking the first one." ; | 
| 422 |         break; | 
| 423 |     } | 
| 424 |     QQmlJS::Dom::DomItem base = prototypes.index(0).proceedToScope(); | 
| 425 |     return base; | 
| 426 | } | 
| 427 |  | 
| 428 | static std::optional<Location> locationFromDomItem(const DomItem &item, FileLocationRegion region) | 
| 429 | { | 
| 430 |     auto tree = FileLocations::treeOf(item); | 
| 431 |     // tree is null for C++ defined types, for example | 
| 432 |     if (!tree) | 
| 433 |         return {}; | 
| 434 |  | 
| 435 |     QQmlJS::SourceLocation sourceLocation = FileLocations::region(fLoc: tree, region); | 
| 436 |     if (!sourceLocation.isValid() && region != QQmlJS::Dom::MainRegion) | 
| 437 |         sourceLocation = FileLocations::region(fLoc: tree, region: QQmlJS::Dom::MainRegion); | 
| 438 |  | 
| 439 |     return Location::tryFrom(fileName: item.canonicalFilePath(), sourceLocation, someItem: item); | 
| 440 | } | 
| 441 |  | 
| 442 | /*! | 
| 443 |    \internal | 
| 444 |    \brief Returns the location of the type definition pointed by object. | 
| 445 |  | 
| 446 |    For a \c PropertyDefinition, return the location of the type of the property. | 
| 447 |    For a \c Binding, return the bound item's type location if an QmlObject is bound, and otherwise | 
| 448 |    the type of the property. | 
| 449 |    For a \c QmlObject, return the location of the QmlObject's base. | 
| 450 |    For an \c Id, return the location of the object to which the id resolves. | 
| 451 |    For a \c Methodparameter, return the location of the type of the parameter. | 
| 452 |    Otherwise, return std::nullopt. | 
| 453 |  */ | 
| 454 | std::optional<Location> findTypeDefinitionOf(const DomItem &object) | 
| 455 | { | 
| 456 |     DomItem typeDefinition; | 
| 457 |  | 
| 458 |     switch (object.internalKind()) { | 
| 459 |     case QQmlJS::Dom::DomType::QmlComponent: | 
| 460 |         typeDefinition = object.field(name: Fields::objects).index(0); | 
| 461 |         break; | 
| 462 |     case QQmlJS::Dom::DomType::QmlObject: | 
| 463 |         typeDefinition = baseObject(object); | 
| 464 |         break; | 
| 465 |     case QQmlJS::Dom::DomType::Binding: { | 
| 466 |         auto binding = object.as<Binding>(); | 
| 467 |         Q_ASSERT(binding); | 
| 468 |  | 
| 469 |         // try to grab the type from the bound object | 
| 470 |         if (binding->valueKind() == BindingValueKind::Object) { | 
| 471 |             typeDefinition = baseObject(object: object.field(name: Fields::value)); | 
| 472 |             break; | 
| 473 |         } else { | 
| 474 |             // use the type of the property it is bound on for scriptexpression etc. | 
| 475 |             DomItem propertyDefinition; | 
| 476 |             const QString bindingName = binding->name(); | 
| 477 |             object.containingObject().visitLookup( | 
| 478 |                     symbolName: bindingName, | 
| 479 |                     visitor: [&propertyDefinition](const DomItem &item) { | 
| 480 |                         if (item.internalKind() == QQmlJS::Dom::DomType::PropertyDefinition) { | 
| 481 |                             propertyDefinition = item; | 
| 482 |                             return false; | 
| 483 |                         } | 
| 484 |                         return true; | 
| 485 |                     }, | 
| 486 |                     type: LookupType::PropertyDef); | 
| 487 |             typeDefinition = propertyDefinition.field(name: Fields::type).proceedToScope(); | 
| 488 |             break; | 
| 489 |         } | 
| 490 |         Q_UNREACHABLE(); | 
| 491 |     } | 
| 492 |     case QQmlJS::Dom::DomType::Id: | 
| 493 |         typeDefinition = object.field(name: Fields::referredObject).proceedToScope(); | 
| 494 |         break; | 
| 495 |     case QQmlJS::Dom::DomType::PropertyDefinition: | 
| 496 |     case QQmlJS::Dom::DomType::MethodParameter: | 
| 497 |     case QQmlJS::Dom::DomType::MethodInfo: | 
| 498 |         typeDefinition = object.field(name: Fields::type).proceedToScope(); | 
| 499 |         break; | 
| 500 |     case QQmlJS::Dom::DomType::ScriptIdentifierExpression: { | 
| 501 |         if (DomItem type = object.filterUp( | 
| 502 |                     filter: [](DomType k, const DomItem &) { return k == DomType::ScriptType; }, | 
| 503 |                     options: FilterUpOptions::ReturnOuter)) { | 
| 504 |  | 
| 505 |             const QString name = fieldMemberExpressionBits(item: type.field(name: Fields::typeName)).join(sep: u'.'); | 
| 506 |             switch (type.directParent().internalKind()) { | 
| 507 |             case DomType::QmlObject: | 
| 508 |                 // is the type name of a QmlObject, like Item in `Item {...}` | 
| 509 |                 typeDefinition = baseObject(object: type.directParent()); | 
| 510 |                 break; | 
| 511 |             case DomType::QmlComponent: | 
| 512 |                 typeDefinition = type.directParent(); | 
| 513 |                 return locationFromDomItem(item: typeDefinition, region: FileLocationRegion::IdentifierRegion); | 
| 514 |                 break; | 
| 515 |             default: | 
| 516 |                 // is a type annotation, like Item in `function f(x: Item) { ... }` | 
| 517 |                 typeDefinition = object.path(p: Paths::lookupTypePath(name)); | 
| 518 |                 if (typeDefinition.internalKind() == DomType::Export) { | 
| 519 |                     typeDefinition = typeDefinition.field(name: Fields::type).get(); | 
| 520 |                 } | 
| 521 |             } | 
| 522 |             break; | 
| 523 |         } | 
| 524 |         if (DomItem id = object.filterUp( | 
| 525 |                     filter: [](DomType k, const DomItem &) { return k == DomType::Id; }, | 
| 526 |                     options: FilterUpOptions::ReturnOuter)) { | 
| 527 |  | 
| 528 |             typeDefinition = id.field(name: Fields::referredObject).proceedToScope(); | 
| 529 |             break; | 
| 530 |         } | 
| 531 |  | 
| 532 |         auto scope = resolveExpressionType( | 
| 533 |                 item: object, ResolveOptions::ResolveActualTypeForFieldMemberExpression); | 
| 534 |         if (!scope || !scope->semanticScope) | 
| 535 |             return {}; | 
| 536 |  | 
| 537 |         if (scope->type == QmlObjectIdIdentifier) { | 
| 538 |             return Location::tryFrom(fileName: scope->semanticScope->filePath(), | 
| 539 |                                      sourceLocation: scope->semanticScope->sourceLocation(), someItem: object); | 
| 540 |         } | 
| 541 |  | 
| 542 |         typeDefinition = sourceLocationToDomItem(file: object.containingFile(), | 
| 543 |                                                  location: scope->semanticScope->sourceLocation()); | 
| 544 |         return locationFromDomItem(item: typeDefinition.component(), | 
| 545 |                                    region: FileLocationRegion::IdentifierRegion); | 
| 546 |     } | 
| 547 |     default: | 
| 548 |         qDebug() << "QQmlLSUtils::findTypeDefinitionOf: Found unimplemented Type"  | 
| 549 |                  << object.internalKindStr(); | 
| 550 |         return {}; | 
| 551 |     } | 
| 552 |  | 
| 553 |     return locationFromDomItem(item: typeDefinition, region: FileLocationRegion::MainRegion); | 
| 554 | } | 
| 555 |  | 
| 556 | static bool findDefinitionFromItem(const DomItem &item, const QString &name) | 
| 557 | { | 
| 558 |     if (const QQmlJSScope::ConstPtr &scope = item.semanticScope()) { | 
| 559 |         qCDebug(QQmlLSUtilsLog) << "Searching for definition in"  << item.internalKindStr(); | 
| 560 |         if (auto jsIdentifier = scope->ownJSIdentifier(id: name)) { | 
| 561 |             qCDebug(QQmlLSUtilsLog) << "Found scope"  << scope->baseTypeName(); | 
| 562 |             return true; | 
| 563 |         } | 
| 564 |     } | 
| 565 |     return false; | 
| 566 | } | 
| 567 |  | 
| 568 | static DomItem findJSIdentifierDefinition(const DomItem &item, const QString &name) | 
| 569 | { | 
| 570 |     DomItem definitionOfItem; | 
| 571 |     item.visitUp(visitor: [&name, &definitionOfItem](const DomItem &i) { | 
| 572 |         if (findDefinitionFromItem(item: i, name)) { | 
| 573 |             definitionOfItem = i; | 
| 574 |             return false; | 
| 575 |         } | 
| 576 |         // early exit: no JS definitions/usages outside the ScriptExpression DOM element. | 
| 577 |         if (i.internalKind() == DomType::ScriptExpression) | 
| 578 |             return false; | 
| 579 |         return true; | 
| 580 |     }); | 
| 581 |  | 
| 582 |     if (definitionOfItem) | 
| 583 |         return definitionOfItem; | 
| 584 |  | 
| 585 |     // special case: somebody asks for usages of a function parameter from its definition | 
| 586 |     // function parameters are defined in the method's scope | 
| 587 |     if (DomItem res = item.filterUp(filter: [](DomType k, const DomItem &) { return k == DomType::MethodInfo; }, | 
| 588 |                                     options: FilterUpOptions::ReturnOuter)) { | 
| 589 |         DomItem candidate = res.field(name: Fields::body).field(name: Fields::scriptElement); | 
| 590 |         if (findDefinitionFromItem(item: candidate, name)) { | 
| 591 |             return candidate; | 
| 592 |         } | 
| 593 |     } | 
| 594 |  | 
| 595 |     // lambda function parameters are defined in the FunctionExpression scope | 
| 596 |     if (DomItem res = item.filterUp( | 
| 597 |                 filter: [](DomType k, const DomItem &) { return k == DomType::ScriptFunctionExpression; }, | 
| 598 |                 options: FilterUpOptions::ReturnOuter)) { | 
| 599 |         if (findDefinitionFromItem(item: res, name)) { | 
| 600 |             return res; | 
| 601 |         } | 
| 602 |     } | 
| 603 |  | 
| 604 |     return definitionOfItem; | 
| 605 | } | 
| 606 |  | 
| 607 | /*! | 
| 608 | \internal | 
| 609 | Represents a signal, signal handler, property, property changed signal or a property changed | 
| 610 | handler. | 
| 611 |  */ | 
| 612 | struct SignalOrProperty | 
| 613 | { | 
| 614 |     /*! | 
| 615 |     \internal The name of the signal or property, independent of whether this is a changed signal | 
| 616 |     or handler. | 
| 617 |      */ | 
| 618 |     QString name; | 
| 619 |     IdentifierType type; | 
| 620 | }; | 
| 621 |  | 
| 622 | /*! | 
| 623 | \internal | 
| 624 | \brief Find out if \c{name} is a signal, signal handler, property, property changed signal, or a | 
| 625 | property changed handler in the given QQmlJSScope. | 
| 626 |  | 
| 627 | Heuristic to find if name is a property, property changed signal, .... because those can appear | 
| 628 | under different names, for example \c{mySignal} and \c{onMySignal} for a signal. | 
| 629 | This will give incorrect results as soon as properties/signals/methods are called \c{onMySignal}, | 
| 630 | \c{on<some already existing property>Changed}, ..., but the good news is that the engine also | 
| 631 | will act weird in these cases (e.g. one cannot bind to a property called like an already existing | 
| 632 | signal or a property changed handler). | 
| 633 | For future reference: you can always add additional checks to check the existence of those buggy | 
| 634 | properties/signals/methods by looking if they exist in the QQmlJSScope. | 
| 635 | */ | 
| 636 | static std::optional<SignalOrProperty> resolveNameInQmlScope(const QString &name, | 
| 637 |                                                              const QQmlJSScope::ConstPtr &owner) | 
| 638 | { | 
| 639 |     if (owner->hasProperty(name)) { | 
| 640 |         return SignalOrProperty{ .name: name, .type: PropertyIdentifier }; | 
| 641 |     } | 
| 642 |  | 
| 643 |     if (const auto propertyName = QQmlSignalNames::changedHandlerNameToPropertyName(handler: name)) { | 
| 644 |         if (owner->hasProperty(name: *propertyName)) { | 
| 645 |             const QString signalName = *QQmlSignalNames::changedHandlerNameToSignalName(changedHandler: name); | 
| 646 |             const QQmlJSMetaMethod signal = owner->methods(name: signalName).front(); | 
| 647 |             // PropertyChangedHandlers don't have parameters: treat all other as regular signal | 
| 648 |             // handlers. Even if they appear in the notify of the property. | 
| 649 |             if (signal.parameterNames().size() == 0) | 
| 650 |                 return SignalOrProperty{ .name: *propertyName, .type: PropertyChangedHandlerIdentifier }; | 
| 651 |             else | 
| 652 |                 return SignalOrProperty{ .name: signalName, .type: SignalHandlerIdentifier }; | 
| 653 |         } | 
| 654 |     } | 
| 655 |  | 
| 656 |     if (const auto signalName = QQmlSignalNames::handlerNameToSignalName(handler: name)) { | 
| 657 |         if (auto methods = owner->methods(name: *signalName); !methods.isEmpty()) { | 
| 658 |             if (methods.front().methodType() == QQmlJSMetaMethodType::Signal) { | 
| 659 |                 return SignalOrProperty{ .name: *signalName, .type: SignalHandlerIdentifier }; | 
| 660 |             } | 
| 661 |         } | 
| 662 |     } | 
| 663 |  | 
| 664 |     if (const auto propertyName = QQmlSignalNames::changedSignalNameToPropertyName(changeSignal: name)) { | 
| 665 |         if (owner->hasProperty(name: *propertyName)) { | 
| 666 |             return SignalOrProperty{ .name: *propertyName, .type: PropertyChangedSignalIdentifier }; | 
| 667 |         } | 
| 668 |     } | 
| 669 |  | 
| 670 |     if (auto methods = owner->methods(name); !methods.isEmpty()) { | 
| 671 |         if (methods.front().methodType() == QQmlJSMetaMethodType::Signal) { | 
| 672 |             return SignalOrProperty{ .name: name, .type: SignalIdentifier }; | 
| 673 |         } | 
| 674 |         return SignalOrProperty{ .name: name, .type: MethodIdentifier }; | 
| 675 |     } | 
| 676 |     return std::nullopt; | 
| 677 | } | 
| 678 |  | 
| 679 | /*! | 
| 680 | \internal | 
| 681 | Returns a list of names, that when belonging to the same targetType, should be considered equal. | 
| 682 | This is used to find signal handlers as usages of their corresponding signals, for example. | 
| 683 | */ | 
| 684 | static QStringList namesOfPossibleUsages(const QString &name, | 
| 685 |                                         const DomItem &item, | 
| 686 |                                          const QQmlJSScope::ConstPtr &targetType) | 
| 687 | { | 
| 688 |     QStringList namesToCheck = { name }; | 
| 689 |     if (item.internalKind() == DomType::EnumItem || item.internalKind() == DomType::EnumDecl) | 
| 690 |         return namesToCheck; | 
| 691 |  | 
| 692 |     auto namings = resolveNameInQmlScope(name, owner: targetType); | 
| 693 |     if (!namings) | 
| 694 |         return namesToCheck; | 
| 695 |     switch (namings->type) { | 
| 696 |     case PropertyIdentifier: { | 
| 697 |         // for a property, also find bindings to its onPropertyChanged handler + propertyChanged | 
| 698 |         // signal | 
| 699 |         const QString propertyChangedHandler = | 
| 700 |                 QQmlSignalNames::propertyNameToChangedHandlerName(property: namings->name); | 
| 701 |         namesToCheck.append(t: propertyChangedHandler); | 
| 702 |  | 
| 703 |         const QString propertyChangedSignal = | 
| 704 |                 QQmlSignalNames::propertyNameToChangedSignalName(property: namings->name); | 
| 705 |         namesToCheck.append(t: propertyChangedSignal); | 
| 706 |         break; | 
| 707 |     } | 
| 708 |     case PropertyChangedHandlerIdentifier: { | 
| 709 |         // for a property changed handler, also find the usages of its property + propertyChanged | 
| 710 |         // signal | 
| 711 |         namesToCheck.append(t: namings->name); | 
| 712 |         namesToCheck.append(t: QQmlSignalNames::propertyNameToChangedSignalName(property: namings->name)); | 
| 713 |         break; | 
| 714 |     } | 
| 715 |     case PropertyChangedSignalIdentifier: { | 
| 716 |         // for a property changed signal, also find the usages of its property + onPropertyChanged | 
| 717 |         // handlers | 
| 718 |         namesToCheck.append(t: namings->name); | 
| 719 |         namesToCheck.append(t: QQmlSignalNames::propertyNameToChangedHandlerName(property: namings->name)); | 
| 720 |         break; | 
| 721 |     } | 
| 722 |     case SignalIdentifier: { | 
| 723 |         // for a signal, also find bindings to its onSignalHandler. | 
| 724 |         namesToCheck.append(t: QQmlSignalNames::signalNameToHandlerName(signal: namings->name)); | 
| 725 |         break; | 
| 726 |     } | 
| 727 |     case SignalHandlerIdentifier: { | 
| 728 |         // for a signal handler, also find the usages of the signal it handles | 
| 729 |         namesToCheck.append(t: namings->name); | 
| 730 |         break; | 
| 731 |     } | 
| 732 |     default: { | 
| 733 |         break; | 
| 734 |     } | 
| 735 |     } | 
| 736 |     return namesToCheck; | 
| 737 | } | 
| 738 |  | 
| 739 | template<typename Predicate> | 
| 740 | QQmlJSScope::ConstPtr findDefiningScopeIf(QQmlJSScope::ConstPtr referrerScope, Predicate &&check) | 
| 741 | { | 
| 742 |     QQmlJSScope::ConstPtr result; | 
| 743 |     QQmlJSUtils::searchBaseAndExtensionTypes(referrerScope, [&](QQmlJSScope::ConstPtr scope) { | 
| 744 |         if (check(scope)) { | 
| 745 |             result = scope; | 
| 746 |             return true; | 
| 747 |         } | 
| 748 |         return false; | 
| 749 |     }); | 
| 750 |  | 
| 751 |     return result; | 
| 752 | } | 
| 753 |  | 
| 754 | /*! | 
| 755 | \internal | 
| 756 | \brief Finds the scope where a property is first defined. | 
| 757 |  | 
| 758 | Starts looking for the name starting from the given scope and traverse through base and | 
| 759 | extension types. | 
| 760 | */ | 
| 761 | QQmlJSScope::ConstPtr findDefiningScopeForProperty(const QQmlJSScope::ConstPtr &referrerScope, | 
| 762 |                                                    const QString &nameToCheck) | 
| 763 | { | 
| 764 |     return findDefiningScopeIf(referrerScope, check: [&nameToCheck](const QQmlJSScope::ConstPtr &scope) { | 
| 765 |         return scope->hasOwnProperty(name: nameToCheck); | 
| 766 |     }); | 
| 767 | } | 
| 768 |  | 
| 769 | /*! | 
| 770 | \internal | 
| 771 | See also findDefiningScopeForProperty(). | 
| 772 |  | 
| 773 | Special case: you can also bind to a signal handler. | 
| 774 | */ | 
| 775 | QQmlJSScope::ConstPtr findDefiningScopeForBinding(const QQmlJSScope::ConstPtr &referrerScope, | 
| 776 |                                                   const QString &nameToCheck) | 
| 777 | { | 
| 778 |     return findDefiningScopeIf(referrerScope, check: [&nameToCheck](const QQmlJSScope::ConstPtr &scope) { | 
| 779 |         return scope->hasOwnProperty(name: nameToCheck) || scope->hasOwnMethod(name: nameToCheck); | 
| 780 |     }); | 
| 781 | } | 
| 782 |  | 
| 783 | /*! | 
| 784 | \internal | 
| 785 | See also findDefiningScopeForProperty(). | 
| 786 | */ | 
| 787 | QQmlJSScope::ConstPtr findDefiningScopeForMethod(const QQmlJSScope::ConstPtr &referrerScope, | 
| 788 |                                                  const QString &nameToCheck) | 
| 789 | { | 
| 790 |     return findDefiningScopeIf(referrerScope, check: [&nameToCheck](const QQmlJSScope::ConstPtr &scope) { | 
| 791 |         return scope->hasOwnMethod(name: nameToCheck); | 
| 792 |     }); | 
| 793 | } | 
| 794 |  | 
| 795 | /*! | 
| 796 | \internal | 
| 797 | See also findDefiningScopeForProperty(). | 
| 798 | */ | 
| 799 | QQmlJSScope::ConstPtr findDefiningScopeForEnumeration(const QQmlJSScope::ConstPtr &referrerScope, | 
| 800 |                                                       const QString &nameToCheck) | 
| 801 | { | 
| 802 |     return findDefiningScopeIf(referrerScope, check: [&nameToCheck](const QQmlJSScope::ConstPtr &scope) { | 
| 803 |         return scope->hasOwnEnumeration(name: nameToCheck); | 
| 804 |     }); | 
| 805 | } | 
| 806 |  | 
| 807 | /*! | 
| 808 | \internal | 
| 809 | See also findDefiningScopeForProperty(). | 
| 810 | */ | 
| 811 | QQmlJSScope::ConstPtr findDefiningScopeForEnumerationKey(const QQmlJSScope::ConstPtr &referrerScope, | 
| 812 |                                                          const QString &nameToCheck) | 
| 813 | { | 
| 814 |     return findDefiningScopeIf(referrerScope, check: [&nameToCheck](const QQmlJSScope::ConstPtr &scope) { | 
| 815 |         return scope->hasOwnEnumerationKey(name: nameToCheck); | 
| 816 |     }); | 
| 817 | } | 
| 818 |  | 
| 819 | /*! | 
| 820 |     Filter away the parts of the Dom not needed for find usages, by following the profiler's | 
| 821 |    information. | 
| 822 |     1. "propertyInfos" tries to require all inherited properties of some QmlObject. That is super | 
| 823 |    slow (profiler says it eats 90% of the time needed by `tst_qmlls_utils findUsages`!) and is not | 
| 824 |    needed for usages. | 
| 825 |     2. "get" tries to resolve references, like base types saved in prototypes for example, and is not | 
| 826 |    needed to find usages. Profiler says it eats 70% of the time needed by `tst_qmlls_utils | 
| 827 |    findUsages`. | 
| 828 |     3. "defaultPropertyName" also recurses through base types and is not needed to find usages. | 
| 829 | */ | 
| 830 | static FieldFilter filterForFindUsages() | 
| 831 | { | 
| 832 |     FieldFilter filter{ {}, | 
| 833 |                         { | 
| 834 |                                 { QString(), QString::fromUtf16(Fields::propertyInfos) }, | 
| 835 |                                 { QString(), QString::fromUtf16(Fields::defaultPropertyName) }, | 
| 836 |                                 { QString(), QString::fromUtf16(Fields::get) }, | 
| 837 |                         } }; | 
| 838 |     return filter; | 
| 839 | }; | 
| 840 |  | 
| 841 | static void findUsagesOfNonJSIdentifiers(const DomItem &item, const QString &name, Usages &result) | 
| 842 | { | 
| 843 |     const auto expressionType = resolveExpressionType(item, ResolveOwnerType); | 
| 844 |     if (!expressionType) | 
| 845 |         return; | 
| 846 |  | 
| 847 |     // for Qml file components: add their filename as an usage for the renaming operation | 
| 848 |     if (expressionType->type == QmlComponentIdentifier | 
| 849 |         && !expressionType->semanticScope->isInlineComponent()) { | 
| 850 |         result.appendFilenameUsage(edit: expressionType->semanticScope->filePath()); | 
| 851 |     } | 
| 852 |  | 
| 853 |     const QStringList namesToCheck = namesOfPossibleUsages(name, item, targetType: expressionType->semanticScope); | 
| 854 |  | 
| 855 |     const auto addLocationIfTypeMatchesTarget = | 
| 856 |             [&result, &expressionType, &item](const DomItem &toBeResolved, FileLocationRegion subRegion) { | 
| 857 |                 const auto currentType = | 
| 858 |                         resolveExpressionType(item: toBeResolved, ResolveOptions::ResolveOwnerType); | 
| 859 |                 if (!currentType) | 
| 860 |                     return; | 
| 861 |  | 
| 862 |                 const QQmlJSScope::ConstPtr target = expressionType->semanticScope; | 
| 863 |                 const QQmlJSScope::ConstPtr current = currentType->semanticScope; | 
| 864 |                 if (target == current) { | 
| 865 |                     auto tree = FileLocations::treeOf(toBeResolved); | 
| 866 |                     QQmlJS::SourceLocation sourceLocation; | 
| 867 |  | 
| 868 |                     sourceLocation = FileLocations::region(fLoc: tree, region: subRegion); | 
| 869 |                     if (!sourceLocation.isValid()) | 
| 870 |                         return; | 
| 871 |  | 
| 872 |                     if (auto location = Location::tryFrom(fileName: toBeResolved.canonicalFilePath(), | 
| 873 |                                                           sourceLocation, someItem: item)) { | 
| 874 |                         result.appendUsage(edit: *location); | 
| 875 |                     } | 
| 876 |                 } | 
| 877 |             }; | 
| 878 |  | 
| 879 |     auto findUsages = [&addLocationIfTypeMatchesTarget, &name, | 
| 880 |                        &namesToCheck](Path, const DomItem ¤t, bool) -> bool { | 
| 881 |         bool continueForChildren = true; | 
| 882 |         if (auto scope = current.semanticScope()) { | 
| 883 |             // is the current property shadowed by some JS identifier? ignore current + its children | 
| 884 |             if (scope->ownJSIdentifier(id: name)) { | 
| 885 |                 return false; | 
| 886 |             } | 
| 887 |         } | 
| 888 |         switch (current.internalKind()) { | 
| 889 |         case DomType::QmlObject: | 
| 890 |         case DomType::Binding: | 
| 891 |         case DomType::MethodInfo: | 
| 892 |         case DomType::PropertyDefinition: { | 
| 893 |             const QString propertyName = current.field(name: Fields::name).value().toString(); | 
| 894 |             if (namesToCheck.contains(str: propertyName)) | 
| 895 |                 addLocationIfTypeMatchesTarget(current, IdentifierRegion); | 
| 896 |             return continueForChildren; | 
| 897 |         } | 
| 898 |         case DomType::ScriptIdentifierExpression: { | 
| 899 |             const QString identifierName = current.field(name: Fields::identifier).value().toString(); | 
| 900 |             if (namesToCheck.contains(str: identifierName)) | 
| 901 |                 addLocationIfTypeMatchesTarget(current, MainRegion); | 
| 902 |             return continueForChildren; | 
| 903 |         } | 
| 904 |         case DomType::ScriptLiteral: { | 
| 905 |             const QString literal = current.field(name: Fields::value).value().toString(); | 
| 906 |             if (namesToCheck.contains(str: literal)) | 
| 907 |                 addLocationIfTypeMatchesTarget(current, MainRegion); | 
| 908 |             return continueForChildren; | 
| 909 |         } | 
| 910 |         case DomType::EnumItem: { | 
| 911 |             // Only look for the first enum defined. The inner enums | 
| 912 |             // have no way to be accessed. | 
| 913 |             const auto parentPath = current.containingObject().pathFromOwner(); | 
| 914 |             const auto index = parentPath.last().headIndex(); | 
| 915 |             if (index != 0) | 
| 916 |                 return continueForChildren; | 
| 917 |             const QString enumValue = current.field(name: Fields::name).value().toString(); | 
| 918 |             if (namesToCheck.contains(str: enumValue)) | 
| 919 |                 addLocationIfTypeMatchesTarget(current, IdentifierRegion); | 
| 920 |             return continueForChildren; | 
| 921 |         } | 
| 922 |         case DomType::EnumDecl: { | 
| 923 |             // Only look for the first enum defined. The inner enums | 
| 924 |             // have no way to be accessed. | 
| 925 |             const auto parentPath = current.pathFromOwner(); | 
| 926 |             const auto index = parentPath.last().headIndex(); | 
| 927 |             if (index != 0) | 
| 928 |                 return continueForChildren; | 
| 929 |             const QString enumValue = current.field(name: Fields::name).value().toString(); | 
| 930 |             if (namesToCheck.contains(str: enumValue)) | 
| 931 |                 addLocationIfTypeMatchesTarget(current, IdentifierRegion); | 
| 932 |             return continueForChildren; | 
| 933 |         } | 
| 934 |         default: | 
| 935 |             return continueForChildren; | 
| 936 |         }; | 
| 937 |  | 
| 938 |         Q_UNREACHABLE_RETURN(continueForChildren); | 
| 939 |     }; | 
| 940 |  | 
| 941 |     const DomItem qmlFiles = item.top().field(name: Fields::qmlFileWithPath); | 
| 942 |     const auto filter = filterForFindUsages(); | 
| 943 |     for (const QString &file : qmlFiles.keys()) { | 
| 944 |         const DomItem currentFileComponents = | 
| 945 |                 qmlFiles.key(name: file).field(name: Fields::currentItem).field(name: Fields::components); | 
| 946 |         currentFileComponents.visitTree(basePath: Path(), visitor: emptyChildrenVisitor, | 
| 947 |                                         options: VisitOption::Recurse | VisitOption::VisitSelf, openingVisitor: findUsages, | 
| 948 |                                         closingVisitor: emptyChildrenVisitor, filter); | 
| 949 |     } | 
| 950 | } | 
| 951 |  | 
| 952 | static std::optional<Location> locationFromJSIdentifierDefinition(const DomItem &definitionOfItem, | 
| 953 |                                                                   const QString &name) | 
| 954 | { | 
| 955 |     Q_ASSERT_X(!definitionOfItem.semanticScope().isNull() | 
| 956 |                        && definitionOfItem.semanticScope()->ownJSIdentifier(name).has_value(), | 
| 957 |                "QQmlLSUtils::locationFromJSIdentifierDefinition" , | 
| 958 |                "JS definition does not actually define the JS identifier. "  | 
| 959 |                "Did you obtain definitionOfItem from findJSIdentifierDefinition() ?" ); | 
| 960 |     const QQmlJS::SourceLocation location = | 
| 961 |             definitionOfItem.semanticScope()->ownJSIdentifier(id: name).value().location; | 
| 962 |  | 
| 963 |     return Location::tryFrom(fileName: definitionOfItem.canonicalFilePath(), sourceLocation: location, someItem: definitionOfItem); | 
| 964 | } | 
| 965 |  | 
| 966 | static void findUsagesHelper(const DomItem &item, const QString &name, Usages &result) | 
| 967 | { | 
| 968 |     qCDebug(QQmlLSUtilsLog) << "Looking for JS identifier with name"  << name; | 
| 969 |     DomItem definitionOfItem = findJSIdentifierDefinition(item, name); | 
| 970 |  | 
| 971 |     // if there is no definition found: check if name was a property or an id instead | 
| 972 |     if (!definitionOfItem) { | 
| 973 |         qCDebug(QQmlLSUtilsLog) << "No defining JS-Scope found!" ; | 
| 974 |         findUsagesOfNonJSIdentifiers(item, name, result); | 
| 975 |         return; | 
| 976 |     } | 
| 977 |  | 
| 978 |     definitionOfItem.visitTree( | 
| 979 |             basePath: Path(), visitor: emptyChildrenVisitor, options: VisitOption::VisitAdopted | VisitOption::Recurse, | 
| 980 |             openingVisitor: [&name, &result](Path, const DomItem &item, bool) -> bool { | 
| 981 |                 qCDebug(QQmlLSUtilsLog) << "Visiting a "  << item.internalKindStr(); | 
| 982 |                 if (item.internalKind() == DomType::ScriptIdentifierExpression | 
| 983 |                     && item.field(name: Fields::identifier).value().toString() == name) { | 
| 984 |                     // add this usage | 
| 985 |                     auto fileLocation = FileLocations::treeOf(item); | 
| 986 |                     if (!fileLocation) { | 
| 987 |                         qCWarning(QQmlLSUtilsLog) << "Failed finding filelocation of found usage" ; | 
| 988 |                         return true; | 
| 989 |                     } | 
| 990 |                     const QQmlJS::SourceLocation sourceLocation = fileLocation->info().fullRegion; | 
| 991 |                     const QString fileName = item.canonicalFilePath(); | 
| 992 |                     if (auto location = Location::tryFrom(fileName, sourceLocation, someItem: item)) | 
| 993 |                         result.appendUsage(edit: *location); | 
| 994 |                     return true; | 
| 995 |                 } | 
| 996 |                 QQmlJSScope::ConstPtr scope = item.semanticScope(); | 
| 997 |                 // Do not visit children if current JS identifier has been redefined. | 
| 998 |                 if (scope && scope != item.directParent().semanticScope() | 
| 999 |                     && scope->ownJSIdentifier(id: name)) { | 
| 1000 |                     // Note that if the current semantic scope == parent's semantic scope, then no | 
| 1001 |                     // redefinition actually took place. This happens for example in | 
| 1002 |                     // FunctionExpressions, where the body's semantic scope is the | 
| 1003 |                     // FunctionExpression's semantic scope. | 
| 1004 |                     return false; | 
| 1005 |                 } | 
| 1006 |                 return true; | 
| 1007 |             }, | 
| 1008 |             closingVisitor: emptyChildrenVisitor, filter: filterForFindUsages()); | 
| 1009 |  | 
| 1010 |     if (const auto definition = locationFromJSIdentifierDefinition(definitionOfItem, name)) | 
| 1011 |         result.appendUsage(edit: *definition); | 
| 1012 | } | 
| 1013 |  | 
| 1014 | Usages findUsagesOf(const DomItem &item) | 
| 1015 | { | 
| 1016 |     Usages result; | 
| 1017 |  | 
| 1018 |     switch (item.internalKind()) { | 
| 1019 |     case DomType::ScriptIdentifierExpression: { | 
| 1020 |         const QString name = item.field(name: Fields::identifier).value().toString(); | 
| 1021 |         findUsagesHelper(item, name, result); | 
| 1022 |         break; | 
| 1023 |     } | 
| 1024 |     case DomType::ScriptVariableDeclarationEntry: { | 
| 1025 |         const QString name = item.field(name: Fields::identifier).value().toString(); | 
| 1026 |         findUsagesHelper(item, name, result); | 
| 1027 |         break; | 
| 1028 |     } | 
| 1029 |     case DomType::EnumDecl: | 
| 1030 |     case DomType::EnumItem: | 
| 1031 |     case DomType::QmlObject: | 
| 1032 |     case DomType::PropertyDefinition: | 
| 1033 |     case DomType::Binding: | 
| 1034 |     case DomType::MethodInfo: { | 
| 1035 |         const QString name = item.field(name: Fields::name).value().toString(); | 
| 1036 |         findUsagesHelper(item, name, result); | 
| 1037 |         break; | 
| 1038 |     } | 
| 1039 |     case DomType::QmlComponent: { | 
| 1040 |         QString name = item.field(name: Fields::name).value().toString(); | 
| 1041 |  | 
| 1042 |         // get rid of extra qualifiers | 
| 1043 |         if (const auto dotIndex = name.indexOf(ch: u'.'); dotIndex != -1) | 
| 1044 |             name = name.sliced(pos: dotIndex + 1); | 
| 1045 |         findUsagesHelper(item, name, result); | 
| 1046 |         break; | 
| 1047 |     } | 
| 1048 |     default: | 
| 1049 |         qCDebug(QQmlLSUtilsLog) << item.internalKindStr() | 
| 1050 |                                 << "was not implemented for QQmlLSUtils::findUsagesOf" ; | 
| 1051 |         return result; | 
| 1052 |     } | 
| 1053 |  | 
| 1054 |     result.sort(); | 
| 1055 |  | 
| 1056 |     if (QQmlLSUtilsLog().isDebugEnabled()) { | 
| 1057 |         qCDebug(QQmlLSUtilsLog) << "Found following usages in files:" ; | 
| 1058 |         for (auto r : result.usagesInFile()) { | 
| 1059 |             qCDebug(QQmlLSUtilsLog) << r.filename() << " @ "  << r.sourceLocation().startLine << ":"  | 
| 1060 |                                     << r.sourceLocation().startColumn << " with length "  | 
| 1061 |                                     << r.sourceLocation().length; | 
| 1062 |         } | 
| 1063 |         qCDebug(QQmlLSUtilsLog) << "And following usages in file names:"  | 
| 1064 |                                 << result.usagesInFilename(); | 
| 1065 |     } | 
| 1066 |  | 
| 1067 |     return result; | 
| 1068 | } | 
| 1069 |  | 
| 1070 | static std::optional<IdentifierType> hasMethodOrSignal(const QQmlJSScope::ConstPtr &scope, | 
| 1071 |                                                        const QString &name) | 
| 1072 | { | 
| 1073 |     auto methods = scope->methods(name); | 
| 1074 |     if (methods.isEmpty()) | 
| 1075 |         return {}; | 
| 1076 |  | 
| 1077 |     const bool isSignal = methods.front().methodType() == QQmlJSMetaMethodType::Signal; | 
| 1078 |     IdentifierType type = | 
| 1079 |             isSignal ? IdentifierType::SignalIdentifier : IdentifierType::MethodIdentifier; | 
| 1080 |     return type; | 
| 1081 | } | 
| 1082 |  | 
| 1083 | /*! | 
| 1084 | \internal | 
| 1085 | Searches for a method by traversing the parent scopes. | 
| 1086 |  | 
| 1087 | We assume here that it is possible to call methods from parent scope to simplify things, as the | 
| 1088 | linting module already warns about calling methods from parent scopes. | 
| 1089 |  | 
| 1090 | Note: in QML, one can only call methods from the current scope, and from the QML file root scope. | 
| 1091 | Everything else needs a qualifier. | 
| 1092 | */ | 
| 1093 | static std::optional<ExpressionType> | 
| 1094 | methodFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QString &name, | 
| 1095 |                         ResolveOptions options) | 
| 1096 | { | 
| 1097 |     for (QQmlJSScope::ConstPtr current = referrerScope; current; current = current->parentScope()) { | 
| 1098 |         if (auto type = hasMethodOrSignal(scope: current, name)) { | 
| 1099 |             switch (options) { | 
| 1100 |             case ResolveOwnerType: | 
| 1101 |                 return ExpressionType{ .name: name, .semanticScope: findDefiningScopeForMethod(referrerScope: current, nameToCheck: name), .type: *type }; | 
| 1102 |             case ResolveActualTypeForFieldMemberExpression: | 
| 1103 |                 // QQmlJSScopes were not implemented for methods yet, but JS functions have methods | 
| 1104 |                 // and properties see | 
| 1105 |                 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function | 
| 1106 |                 // for the list of properties/methods of functions. Therefore return a null scope. | 
| 1107 |                 // see also code below for non-qualified method access | 
| 1108 |                 return ExpressionType{ .name: name, .semanticScope: {}, .type: *type }; | 
| 1109 |             } | 
| 1110 |         } | 
| 1111 |  | 
| 1112 |         if (const auto signalName = QQmlSignalNames::handlerNameToSignalName(handler: name)) { | 
| 1113 |             if (auto type = hasMethodOrSignal(scope: current, name: *signalName)) { | 
| 1114 |                 switch (options) { | 
| 1115 |                 case ResolveOwnerType: | 
| 1116 |                     return ExpressionType{ .name: name, .semanticScope: findDefiningScopeForMethod(referrerScope: current, nameToCheck: *signalName), | 
| 1117 |                                            .type: SignalHandlerIdentifier }; | 
| 1118 |                 case ResolveActualTypeForFieldMemberExpression: | 
| 1119 |                     // Properties and methods of JS methods are not supported yet | 
| 1120 |                     return ExpressionType{ .name: name, .semanticScope: {}, .type: SignalHandlerIdentifier }; | 
| 1121 |                 } | 
| 1122 |             } | 
| 1123 |         } | 
| 1124 |     } | 
| 1125 |     return {}; | 
| 1126 | } | 
| 1127 |  | 
| 1128 |  | 
| 1129 | /*! | 
| 1130 | \internal | 
| 1131 | See comment on methodFromReferrerScope: the same applies to properties. | 
| 1132 | */ | 
| 1133 | static std::optional<ExpressionType> | 
| 1134 | propertyFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QString &propertyName, | 
| 1135 |                           ResolveOptions options) | 
| 1136 | { | 
| 1137 |     for (QQmlJSScope::ConstPtr current = referrerScope; current; current = current->parentScope()) { | 
| 1138 |         const auto resolved = resolveNameInQmlScope(name: propertyName, owner: current); | 
| 1139 |         if (!resolved) | 
| 1140 |             continue; | 
| 1141 |  | 
| 1142 |         if (auto property = current->property(name: resolved->name); property.isValid()) { | 
| 1143 |             switch (options) { | 
| 1144 |             case ResolveOwnerType: | 
| 1145 |                 return ExpressionType{ .name: propertyName, | 
| 1146 |                                        .semanticScope: findDefiningScopeForProperty(referrerScope: current, nameToCheck: propertyName), | 
| 1147 |                                        .type: resolved->type }; | 
| 1148 |             case ResolveActualTypeForFieldMemberExpression: | 
| 1149 |                 return ExpressionType{ .name: propertyName, .semanticScope: property.type(), .type: resolved->type }; | 
| 1150 |             } | 
| 1151 |         } | 
| 1152 |     } | 
| 1153 |     return {}; | 
| 1154 | } | 
| 1155 |  | 
| 1156 | /*! | 
| 1157 | \internal | 
| 1158 | See comment on methodFromReferrerScope: the same applies to property bindings. | 
| 1159 |  | 
| 1160 | If resolver is not null then it is used to resolve the id with which a generalized grouped | 
| 1161 | properties starts. | 
| 1162 | */ | 
| 1163 | static std::optional<ExpressionType> | 
| 1164 | propertyBindingFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QString &name, | 
| 1165 |                                  ResolveOptions options, QQmlJSTypeResolver *resolverForIds) | 
| 1166 | { | 
| 1167 |     auto bindings = referrerScope->propertyBindings(name); | 
| 1168 |     if (bindings.isEmpty()) | 
| 1169 |         return {}; | 
| 1170 |  | 
| 1171 |     const auto binding = bindings.front(); | 
| 1172 |  | 
| 1173 |     if ((binding.bindingType() != QQmlSA::BindingType::AttachedProperty) | 
| 1174 |         && (binding.bindingType() != QQmlSA::BindingType::GroupProperty)) | 
| 1175 |         return {}; | 
| 1176 |  | 
| 1177 |     const bool bindingIsAttached = binding.bindingType() == QQmlSA::BindingType::AttachedProperty; | 
| 1178 |  | 
| 1179 |     // Generalized grouped properties, like Bindings or PropertyChanges, for example, have bindings | 
| 1180 |     // starting in an id (like `someId.someProperty: ...`). | 
| 1181 |     // If `someid` is not a property and is a deferred name, then it should be an id. | 
| 1182 |     if (!bindingIsAttached && !referrerScope->hasProperty(name) | 
| 1183 |         && referrerScope->isNameDeferred(name)) { | 
| 1184 |         if (!resolverForIds) | 
| 1185 |             return {}; | 
| 1186 |  | 
| 1187 |         QQmlJSRegisterContent fromId = resolverForIds->scopedType( | 
| 1188 |                 scope: referrerScope, name, lookupIndex: QQmlJSRegisterContent::InvalidLookupIndex, | 
| 1189 |                 options: AssumeComponentsAreBound); | 
| 1190 |         if (fromId.variant() == QQmlJSRegisterContent::ObjectById) | 
| 1191 |             return ExpressionType{ .name: name, .semanticScope: fromId.type(), .type: QmlObjectIdIdentifier }; | 
| 1192 |  | 
| 1193 |         return ExpressionType{ .name: name, .semanticScope: {}, .type: QmlObjectIdIdentifier }; | 
| 1194 |     } | 
| 1195 |  | 
| 1196 |     const auto typeIdentifier = | 
| 1197 |             bindingIsAttached ? AttachedTypeIdentifier : GroupedPropertyIdentifier; | 
| 1198 |  | 
| 1199 |     const auto getScope = [&bindingIsAttached, &binding]() -> QQmlJSScope::ConstPtr { | 
| 1200 |         if (bindingIsAttached) | 
| 1201 |             return binding.attachingType(); | 
| 1202 |  | 
| 1203 |         return binding.groupType(); | 
| 1204 |     }; | 
| 1205 |  | 
| 1206 |     switch (options) { | 
| 1207 |     case ResolveOwnerType: { | 
| 1208 |         return ExpressionType{ .name: name, | 
| 1209 |                                // note: always return the type of the attached type as the owner. | 
| 1210 |                                // Find usages on "Keys.", for example, should yield all usages of | 
| 1211 |                                // the "Keys" attached property. | 
| 1212 |                                .semanticScope: bindingIsAttached | 
| 1213 |                                        ? getScope() | 
| 1214 |                                        : findDefiningScopeForProperty(referrerScope, nameToCheck: name), | 
| 1215 |                                .type: typeIdentifier }; | 
| 1216 |     } | 
| 1217 |     case ResolveActualTypeForFieldMemberExpression: | 
| 1218 |         return ExpressionType{ .name: name, .semanticScope: getScope(), .type: typeIdentifier }; | 
| 1219 |     } | 
| 1220 |     Q_UNREACHABLE_RETURN({}); | 
| 1221 | } | 
| 1222 |  | 
| 1223 | /*! \internal | 
| 1224 |     Finds the scope within the special elements like Connections, | 
| 1225 |     PropertyChanges, Bindings or AnchorChanges. | 
| 1226 | */ | 
| 1227 | static QQmlJSScope::ConstPtr findScopeOfSpecialItems(QQmlJSScope::ConstPtr scope, const DomItem &item) | 
| 1228 | { | 
| 1229 |     if (!scope) | 
| 1230 |         return {}; | 
| 1231 |  | 
| 1232 |     const QSet<QString> specialItems = {u"QQmlConnections"_s , | 
| 1233 |                                         u"QQuickPropertyChanges"_s , | 
| 1234 |                                         u"QQmlBind"_s , | 
| 1235 |                                         u"QQuickAnchorChanges"_s }; | 
| 1236 |  | 
| 1237 |     const auto special = QQmlJSUtils::searchBaseAndExtensionTypes( | 
| 1238 |             type: scope, check: [&specialItems](QQmlJSScope::ConstPtr visitedScope) { | 
| 1239 |                 const auto typeName = visitedScope->internalName(); | 
| 1240 |                 if (specialItems.contains(value: typeName)) | 
| 1241 |                     return true; | 
| 1242 |                 return false; | 
| 1243 |             }); | 
| 1244 |  | 
| 1245 |     if (!special) | 
| 1246 |         return {}; | 
| 1247 |  | 
| 1248 |     // Perform target name search if there is binding to property "target" | 
| 1249 |     QString targetName; | 
| 1250 |     if (scope->hasOwnPropertyBindings(name: u"target"_s )) { | 
| 1251 |         // TODO: propagate the whole binding. | 
| 1252 |         //       We can figure out the meaning of target in more cases. | 
| 1253 |  | 
| 1254 |         DomItem current = item.qmlObject(); | 
| 1255 |         auto target = current.bindings().key(name: u"target"_s ).index(0); | 
| 1256 |         if (target) { | 
| 1257 |             targetName = target.field(name: Fields::value) | 
| 1258 |                                  .field(name: Fields::scriptElement) | 
| 1259 |                                  .field(name: Fields::identifier) | 
| 1260 |                                  .value() | 
| 1261 |                                  .toString(); | 
| 1262 |         } | 
| 1263 |     } | 
| 1264 |  | 
| 1265 |     if (!targetName.isEmpty()) { | 
| 1266 |         // target: someId | 
| 1267 |         auto resolver = item.containingFile().ownerAs<QmlFile>()->typeResolver(); | 
| 1268 |         if (!resolver) | 
| 1269 |             return {}; | 
| 1270 |  | 
| 1271 |         // Note: It does not have to be an ID. It can be a property. | 
| 1272 |         return resolver->containedType(container: resolver->scopedType(scope, name: targetName)); | 
| 1273 |     } else { | 
| 1274 |         if (item.internalKind() == DomType::Binding && | 
| 1275 |             item.field(name: Fields::bindingType).value().toInteger() == int(BindingType::OnBinding)) { | 
| 1276 |                 // Binding on sth : {} syntax | 
| 1277 |                 // Target scope is the current scope | 
| 1278 |                 return scope; | 
| 1279 |         } | 
| 1280 |         return scope->parentScope(); | 
| 1281 |     } | 
| 1282 |  | 
| 1283 |     return {}; | 
| 1284 | } | 
| 1285 |  | 
| 1286 | /*! | 
| 1287 | \internal | 
| 1288 | \brief Distinguishes singleton types from attached types and "regular" qml components. | 
| 1289 |  */ | 
| 1290 | static std::optional<ExpressionType> | 
| 1291 | resolveTypeName(const std::shared_ptr<QQmlJSTypeResolver> &resolver, const QString &name, | 
| 1292 |                 const DomItem &item, ResolveOptions options) | 
| 1293 | { | 
| 1294 |     const auto scope = resolver->typeForName(name); | 
| 1295 |     if (!scope) | 
| 1296 |         return {}; | 
| 1297 |  | 
| 1298 |     if (scope->isSingleton()) | 
| 1299 |         return ExpressionType{ .name: name, .semanticScope: scope, .type: IdentifierType::SingletonIdentifier }; | 
| 1300 |  | 
| 1301 |     // A type not followed by a field member expression is just a type. Otherwise, it could either | 
| 1302 |     // be a type or an attached type! | 
| 1303 |     if (!isFieldMemberBase(item)) | 
| 1304 |         return ExpressionType{ .name: name, .semanticScope: scope, .type: QmlComponentIdentifier }; | 
| 1305 |  | 
| 1306 |     // take the right hand side and unwrap in case its a nested fieldmemberexpression | 
| 1307 |     const DomItem rightHandSide = [&item]() { | 
| 1308 |         const DomItem candidate = item.directParent().field(name: Fields::right); | 
| 1309 |         // case B(a,b) for the right hand side of `a` in `a.b` | 
| 1310 |         if (candidate != item) | 
| 1311 |             return candidate; | 
| 1312 |         // case B(B(a,b),c) for the right hand side of `b` in `a.b.c` | 
| 1313 |         return item.directParent().directParent().field(name: Fields::right); | 
| 1314 |     }(); | 
| 1315 |  | 
| 1316 |     if (rightHandSide.internalKind() != DomType::ScriptIdentifierExpression) | 
| 1317 |         return ExpressionType{ .name: name, .semanticScope: scope, .type: QmlComponentIdentifier }; | 
| 1318 |  | 
| 1319 |     const QString fieldMemberAccessName = rightHandSide.value().toString(); | 
| 1320 |     if (fieldMemberAccessName.isEmpty() || !fieldMemberAccessName.front().isLower()) | 
| 1321 |         return ExpressionType{ .name: name, .semanticScope: scope, .type: QmlComponentIdentifier }; | 
| 1322 |  | 
| 1323 |     return ExpressionType{ .name: name, .semanticScope: options == ResolveOwnerType ? scope : scope->attachedType(), | 
| 1324 |                            .type: IdentifierType::AttachedTypeIdentifier }; | 
| 1325 | } | 
| 1326 |  | 
| 1327 | static std::optional<ExpressionType> resolveFieldMemberExpressionType(const DomItem &item, | 
| 1328 |                                                                       ResolveOptions options) | 
| 1329 | { | 
| 1330 |     const QString name = item.field(name: Fields::identifier).value().toString(); | 
| 1331 |     DomItem parent = item.directParent(); | 
| 1332 |     auto owner = resolveExpressionType(item: parent.field(name: Fields::left), | 
| 1333 |                                        ResolveOptions::ResolveActualTypeForFieldMemberExpression); | 
| 1334 |     if (!owner) | 
| 1335 |         return {}; | 
| 1336 |  | 
| 1337 |     if (!owner->semanticScope) { | 
| 1338 |         // JS objects can get new members and methods during runtime and therefore has no | 
| 1339 |         // qqmljsscopes. Therefore, just label everything inside a JavaScriptIdentifier as being | 
| 1340 |         // another JavaScriptIdentifier. | 
| 1341 |         if (owner->type == JavaScriptIdentifier) { | 
| 1342 |             return ExpressionType{ .name: name, .semanticScope: {}, .type: JavaScriptIdentifier }; | 
| 1343 |         } else if (owner->type == QualifiedModuleIdentifier) { | 
| 1344 |             auto resolver = item.fileObject().as<QmlFile>()->typeResolver(); | 
| 1345 |             if (auto scope = resolveTypeName(resolver, name: u"%1.%2"_s .arg(args&: *owner->name, args: name), item, | 
| 1346 |                                              options)) { | 
| 1347 |                 // remove the qualified module name from the type name | 
| 1348 |                 scope->name = name; | 
| 1349 |                 return scope; | 
| 1350 |             } | 
| 1351 |         } | 
| 1352 |         return {}; | 
| 1353 |     } | 
| 1354 |  | 
| 1355 |     if (auto scope = methodFromReferrerScope(referrerScope: owner->semanticScope, name, options)) | 
| 1356 |         return *scope; | 
| 1357 |  | 
| 1358 |     if (auto scope = propertyBindingFromReferrerScope(referrerScope: owner->semanticScope, name, options, resolverForIds: nullptr)) | 
| 1359 |         return *scope; | 
| 1360 |  | 
| 1361 |     if (auto scope = propertyFromReferrerScope(referrerScope: owner->semanticScope, propertyName: name, options)) | 
| 1362 |         return *scope; | 
| 1363 |  | 
| 1364 |     if (owner->type == QmlComponentIdentifier || owner->type == EnumeratorIdentifier) { | 
| 1365 |         // Check if name is a enum value <TypeName>.<EnumValue> or ...<EnumName>.<EnumValue> | 
| 1366 |         // Enumerations should live under the root element scope of the file that defines the enum, | 
| 1367 |         // therefore use the DomItem to find the root element of the qml file instead of directly | 
| 1368 |         // using owner->semanticScope. | 
| 1369 |         const auto scope = item.goToFile(filePath: owner->semanticScope->filePath()) | 
| 1370 |                                    .rootQmlObject(option: GoTo::MostLikely) | 
| 1371 |                                    .semanticScope(); | 
| 1372 |         if (scope->hasEnumerationKey(name)) { | 
| 1373 |             return ExpressionType{ .name: name, .semanticScope: scope, .type: EnumeratorValueIdentifier }; | 
| 1374 |         } | 
| 1375 |         // Or it is a enum name <TypeName>.<EnumName>.<EnumValue> | 
| 1376 |         else if (scope->hasEnumeration(name)) { | 
| 1377 |             return ExpressionType{ .name: name, .semanticScope: scope, .type: EnumeratorIdentifier }; | 
| 1378 |         } | 
| 1379 |  | 
| 1380 |         // check inline components <TypeName>.<InlineComponentName> | 
| 1381 |         for (auto it = owner->semanticScope->childScopesBegin(), | 
| 1382 |                   end = owner->semanticScope->childScopesEnd(); | 
| 1383 |              it != end; ++it) { | 
| 1384 |             if ((*it)->inlineComponentName() == name) { | 
| 1385 |                 return ExpressionType{ .name: name, .semanticScope: *it, .type: QmlComponentIdentifier }; | 
| 1386 |             } | 
| 1387 |         } | 
| 1388 |         return {}; | 
| 1389 |     } | 
| 1390 |  | 
| 1391 |     qCDebug(QQmlLSUtilsLog) << "Could not find identifier expression for"  << item.internalKindStr(); | 
| 1392 |     return owner; | 
| 1393 | } | 
| 1394 |  | 
| 1395 | /*! | 
| 1396 | \internal | 
| 1397 | Resolves the expression type of a binding for signal handlers, like the function expression | 
| 1398 | \c{(x) => ...} in | 
| 1399 |  | 
| 1400 | \qml | 
| 1401 | onHelloSignal: (x) => ... | 
| 1402 | \endqml | 
| 1403 |  | 
| 1404 | would be resolved to the \c{onHelloSignal} expression type, for example. | 
| 1405 | */ | 
| 1406 | static std::optional<ExpressionType> resolveBindingIfSignalHandler(const DomItem &functionExpression) | 
| 1407 | { | 
| 1408 |     if (functionExpression.internalKind() != DomType::ScriptFunctionExpression) | 
| 1409 |         return {}; | 
| 1410 |  | 
| 1411 |     const DomItem parent = functionExpression.directParent(); | 
| 1412 |     if (parent.internalKind() != DomType::ScriptExpression) | 
| 1413 |         return {}; | 
| 1414 |  | 
| 1415 |     const DomItem grandParent = parent.directParent(); | 
| 1416 |     if (grandParent.internalKind() != DomType::Binding) | 
| 1417 |         return {}; | 
| 1418 |  | 
| 1419 |     auto bindingType = resolveExpressionType(item: grandParent, ResolveOwnerType); | 
| 1420 |     return bindingType; | 
| 1421 | } | 
| 1422 |  | 
| 1423 | /*! | 
| 1424 | \internal | 
| 1425 | In a signal handler | 
| 1426 |  | 
| 1427 | \qml | 
| 1428 |     onSomeSignal: (x, y, z) => .... | 
| 1429 | \endqml | 
| 1430 |  | 
| 1431 | the parameters \c x, \c y and \c z are not allowed to have type annotations: instead, their type is | 
| 1432 | defined by the signal definition itself. | 
| 1433 |  | 
| 1434 | This code detects signal handler parameters and resolves their type using the signal's definition. | 
| 1435 | */ | 
| 1436 | static std::optional<ExpressionType> | 
| 1437 | resolveSignalHandlerParameterType(const DomItem ¶meterDefinition, const QString &name, | 
| 1438 |                                   ResolveOptions options) | 
| 1439 | { | 
| 1440 |     const std::optional<QQmlJSScope::JavaScriptIdentifier> jsIdentifier = | 
| 1441 |             parameterDefinition.semanticScope()->jsIdentifier(id: name); | 
| 1442 |     if (!jsIdentifier || jsIdentifier->kind != QQmlJSScope::JavaScriptIdentifier::Parameter) | 
| 1443 |         return {}; | 
| 1444 |  | 
| 1445 |     const DomItem handlerFunctionExpression = | 
| 1446 |             parameterDefinition.internalKind() == DomType::ScriptBlockStatement | 
| 1447 |             ? parameterDefinition.directParent() | 
| 1448 |             : parameterDefinition; | 
| 1449 |  | 
| 1450 |     const std::optional<ExpressionType> bindingType = | 
| 1451 |             resolveBindingIfSignalHandler(functionExpression: handlerFunctionExpression); | 
| 1452 |     if (!bindingType) | 
| 1453 |         return {}; | 
| 1454 |  | 
| 1455 |     if (bindingType->type == PropertyChangedHandlerIdentifier) | 
| 1456 |         return ExpressionType{}; | 
| 1457 |  | 
| 1458 |     if (bindingType->type != SignalHandlerIdentifier) | 
| 1459 |         return {}; | 
| 1460 |  | 
| 1461 |     const DomItem parameters = handlerFunctionExpression[Fields::parameters]; | 
| 1462 |     const int indexOfParameter = [¶meters, &name]() { | 
| 1463 |         for (int i = 0; i < parameters.indexes(); ++i) { | 
| 1464 |             if (parameters[i][Fields::identifier].value().toString() == name) | 
| 1465 |                 return i; | 
| 1466 |         } | 
| 1467 |         Q_ASSERT_X(false, "resolveSignalHandlerParameter" , | 
| 1468 |                    "can't find JS identifier with Parameter kind in the parameters" ); | 
| 1469 |         Q_UNREACHABLE_RETURN(-1); | 
| 1470 |     }(); | 
| 1471 |  | 
| 1472 |     const std::optional<QString> signalName = | 
| 1473 |             QQmlSignalNames::handlerNameToSignalName(handler: *bindingType->name); | 
| 1474 |     Q_ASSERT_X(signalName.has_value(), "resolveSignalHandlerParameterType" , | 
| 1475 |                "handlerNameToSignalName failed on a SignalHandler" ); | 
| 1476 |  | 
| 1477 |     const QQmlJSMetaMethod signalDefinition = | 
| 1478 |             bindingType->semanticScope->methods(name: *signalName).front(); | 
| 1479 |     const QList<QQmlJSMetaParameter> parameterList = signalDefinition.parameters(); | 
| 1480 |  | 
| 1481 |     // not a signal handler parameter after all | 
| 1482 |     if (parameterList.size() <= indexOfParameter) | 
| 1483 |         return {}; | 
| 1484 |  | 
| 1485 |     // now we can return an ExpressionType, even if the indexOfParameter calculation result is only | 
| 1486 |     // needed to check whether this is a signal handler parameter or not. | 
| 1487 |     if (options == ResolveOwnerType) | 
| 1488 |         return ExpressionType{ .name: name, .semanticScope: bindingType->semanticScope, .type: JavaScriptIdentifier }; | 
| 1489 |     else { | 
| 1490 |         const QQmlJSScope::ConstPtr parameterType = parameterList[indexOfParameter].type(); | 
| 1491 |         return ExpressionType{ .name: name, .semanticScope: parameterType, .type: JavaScriptIdentifier }; | 
| 1492 |     } | 
| 1493 | } | 
| 1494 |  | 
| 1495 | static std::optional<ExpressionType> resolveIdentifierExpressionType(const DomItem &item, | 
| 1496 |                                                                      ResolveOptions options) | 
| 1497 | { | 
| 1498 |     if (isFieldMemberAccess(item) || isFieldMemberExpression(item)) { | 
| 1499 |         return resolveFieldMemberExpressionType(item, options); | 
| 1500 |     } | 
| 1501 |  | 
| 1502 |     const QString name = item.field(name: Fields::identifier).value().toString(); | 
| 1503 |  | 
| 1504 |     if (DomItem definitionOfItem = findJSIdentifierDefinition(item, name)) { | 
| 1505 |         Q_ASSERT_X(!definitionOfItem.semanticScope().isNull() | 
| 1506 |                             && definitionOfItem.semanticScope()->ownJSIdentifier(name), | 
| 1507 |                     "QQmlLSUtils::findDefinitionOf" , | 
| 1508 |                     "JS definition does not actually define the JS identifer. "  | 
| 1509 |                     "It should be empty." ); | 
| 1510 |         if (auto parameter = resolveSignalHandlerParameterType(parameterDefinition: definitionOfItem, name, options)) | 
| 1511 |             return parameter; | 
| 1512 |  | 
| 1513 |         const auto scope = definitionOfItem.semanticScope(); | 
| 1514 |         return ExpressionType{ .name: name, | 
| 1515 |                                .semanticScope: options == ResolveOwnerType | 
| 1516 |                                        ? scope | 
| 1517 |                                        : QQmlJSScope::ConstPtr( | 
| 1518 |                                                scope->ownJSIdentifier(id: name)->scope.toStrongRef()), | 
| 1519 |                                .type: IdentifierType::JavaScriptIdentifier }; | 
| 1520 |     } | 
| 1521 |  | 
| 1522 |     const auto referrerScope = item.nearestSemanticScope(); | 
| 1523 |     if (!referrerScope) | 
| 1524 |         return {}; | 
| 1525 |  | 
| 1526 |     // check if its a method | 
| 1527 |     if (auto scope = methodFromReferrerScope(referrerScope, name, options)) | 
| 1528 |         return scope; | 
| 1529 |  | 
| 1530 |     const auto resolver = item.containingFile().ownerAs<QmlFile>()->typeResolver(); | 
| 1531 |     if (!resolver) | 
| 1532 |         return {}; | 
| 1533 |  | 
| 1534 |     // check if its found as a property binding | 
| 1535 |     if (auto scope = propertyBindingFromReferrerScope(referrerScope, name, options, resolverForIds: resolver.get())) | 
| 1536 |         return *scope; | 
| 1537 |  | 
| 1538 |     // check if its an (unqualified) property | 
| 1539 |     if (auto scope = propertyFromReferrerScope(referrerScope, propertyName: name, options)) | 
| 1540 |         return *scope; | 
| 1541 |  | 
| 1542 |     if (resolver->seenModuleQualifiers().contains(str: name)) | 
| 1543 |         return ExpressionType{ .name: name, .semanticScope: {}, .type: QualifiedModuleIdentifier }; | 
| 1544 |  | 
| 1545 |     if (const auto scope = resolveTypeName(resolver, name, item, options)) | 
| 1546 |         return scope; | 
| 1547 |  | 
| 1548 |     // check if its an id | 
| 1549 |     QQmlJSRegisterContent fromId = | 
| 1550 |             resolver->scopedType(scope: referrerScope, name, lookupIndex: QQmlJSRegisterContent::InvalidLookupIndex, | 
| 1551 |                                  options: AssumeComponentsAreBound); | 
| 1552 |     if (fromId.variant() == QQmlJSRegisterContent::ObjectById) | 
| 1553 |         return ExpressionType{ .name: name, .semanticScope: fromId.type(), .type: QmlObjectIdIdentifier }; | 
| 1554 |  | 
| 1555 |     const QQmlJSScope::ConstPtr jsGlobal = resolver->jsGlobalObject(); | 
| 1556 |     // check if its a JS global method | 
| 1557 |     if (auto scope = methodFromReferrerScope(referrerScope: jsGlobal, name, options)) { | 
| 1558 |         scope->type = JavaScriptIdentifier; | 
| 1559 |         return scope; | 
| 1560 |     } | 
| 1561 |  | 
| 1562 |     // check if its an JS global property | 
| 1563 |     if (auto scope = propertyFromReferrerScope(referrerScope: jsGlobal, propertyName: name, options)) { | 
| 1564 |         scope->type = JavaScriptIdentifier; | 
| 1565 |         return scope; | 
| 1566 |     } | 
| 1567 |  | 
| 1568 |     return {}; | 
| 1569 | } | 
| 1570 |  | 
| 1571 | static std::optional<ExpressionType> | 
| 1572 | resolveSignalOrPropertyExpressionType(const QString &name, const QQmlJSScope::ConstPtr &scope, | 
| 1573 |                                       ResolveOptions options) | 
| 1574 | { | 
| 1575 |     auto signalOrProperty = resolveNameInQmlScope(name, owner: scope); | 
| 1576 |     if (!signalOrProperty) | 
| 1577 |         return {}; | 
| 1578 |  | 
| 1579 |     switch (signalOrProperty->type) { | 
| 1580 |     case PropertyIdentifier: | 
| 1581 |         switch (options) { | 
| 1582 |         case ResolveOwnerType: | 
| 1583 |             return ExpressionType{ .name: name, .semanticScope: findDefiningScopeForProperty(referrerScope: scope, nameToCheck: name), | 
| 1584 |                                    .type: signalOrProperty->type }; | 
| 1585 |         case ResolveActualTypeForFieldMemberExpression: | 
| 1586 |             return ExpressionType{ .name: name, .semanticScope: scope->property(name).type(), .type: signalOrProperty->type }; | 
| 1587 |         } | 
| 1588 |         Q_UNREACHABLE_RETURN({}); | 
| 1589 |     case PropertyChangedHandlerIdentifier: | 
| 1590 |         switch (options) { | 
| 1591 |         case ResolveOwnerType: | 
| 1592 |             return ExpressionType{ .name: name, | 
| 1593 |                                    .semanticScope: findDefiningScopeForProperty(referrerScope: scope, nameToCheck: signalOrProperty->name), | 
| 1594 |                                    .type: signalOrProperty->type }; | 
| 1595 |         case ResolveActualTypeForFieldMemberExpression: | 
| 1596 |             // Properties and methods are not implemented on methods. | 
| 1597 |             Q_UNREACHABLE_RETURN({}); | 
| 1598 |         } | 
| 1599 |         Q_UNREACHABLE_RETURN({}); | 
| 1600 |     case SignalHandlerIdentifier: | 
| 1601 |     case PropertyChangedSignalIdentifier: | 
| 1602 |     case SignalIdentifier: | 
| 1603 |     case MethodIdentifier: | 
| 1604 |         switch (options) { | 
| 1605 |         case ResolveOwnerType: { | 
| 1606 |             return ExpressionType{ .name: name, .semanticScope: findDefiningScopeForMethod(referrerScope: scope, nameToCheck: signalOrProperty->name), | 
| 1607 |                                    .type: signalOrProperty->type }; | 
| 1608 |         } | 
| 1609 |         case ResolveActualTypeForFieldMemberExpression: | 
| 1610 |             // Properties and methods are not implemented on methods. | 
| 1611 |             Q_UNREACHABLE_RETURN({}); | 
| 1612 |         } | 
| 1613 |         Q_UNREACHABLE_RETURN({}); | 
| 1614 |     default: | 
| 1615 |         Q_UNREACHABLE_RETURN({}); | 
| 1616 |     } | 
| 1617 | } | 
| 1618 |  | 
| 1619 | /*! | 
| 1620 |    \internal | 
| 1621 |     \brief | 
| 1622 |     Resolves the type of the given DomItem, when possible (e.g., when there are enough type | 
| 1623 |     annotations). | 
| 1624 |  | 
| 1625 |     Might return an ExpressionType without(!) semantic scope when no type information is available, for | 
| 1626 |     example resolving the type of x in someJSObject.x (where `let someJSObject = { x: 42 };`) then | 
| 1627 |     the name and type of x is known but no semantic scope can be obtained. | 
| 1628 | */ | 
| 1629 | std::optional<ExpressionType> resolveExpressionType(const QQmlJS::Dom::DomItem &item, | 
| 1630 |                                                     ResolveOptions options) | 
| 1631 | { | 
| 1632 |     switch (item.internalKind()) { | 
| 1633 |     case DomType::ScriptIdentifierExpression: { | 
| 1634 |         return resolveIdentifierExpressionType(item, options); | 
| 1635 |     } | 
| 1636 |     case DomType::PropertyDefinition: { | 
| 1637 |         auto propertyDefinition = item.as<PropertyDefinition>(); | 
| 1638 |         if (propertyDefinition && propertyDefinition->semanticScope()) { | 
| 1639 |             const auto &scope = propertyDefinition->semanticScope(); | 
| 1640 |             switch (options) { | 
| 1641 |             case ResolveOwnerType: | 
| 1642 |                 return ExpressionType{ .name: propertyDefinition->name, .semanticScope: scope, .type: PropertyIdentifier }; | 
| 1643 |             case ResolveActualTypeForFieldMemberExpression: | 
| 1644 |                 // There should not be any PropertyDefinition inside a FieldMemberExpression. | 
| 1645 |                 Q_UNREACHABLE_RETURN({}); | 
| 1646 |             } | 
| 1647 |             Q_UNREACHABLE_RETURN({}); | 
| 1648 |         } | 
| 1649 |         return {}; | 
| 1650 |     } | 
| 1651 |     case DomType::Binding: { | 
| 1652 |         auto binding = item.as<Binding>(); | 
| 1653 |         if (binding) { | 
| 1654 |             std::optional<QQmlJSScope::ConstPtr> owner = item.qmlObject().semanticScope(); | 
| 1655 |             if (!owner) | 
| 1656 |                 return {}; | 
| 1657 |             const QString name = binding->name(); | 
| 1658 |  | 
| 1659 |             if (name == u"id" ) | 
| 1660 |                 return ExpressionType{ .name: name, .semanticScope: owner.value(), .type: QmlObjectIdIdentifier }; | 
| 1661 |  | 
| 1662 |             if (QQmlJSScope::ConstPtr targetScope = findScopeOfSpecialItems(scope: owner.value(), item)) { | 
| 1663 |                 const auto signalOrProperty = resolveNameInQmlScope(name, owner: targetScope); | 
| 1664 |                 if (!signalOrProperty) | 
| 1665 |                     return {}; | 
| 1666 |                 switch (options) { | 
| 1667 |                 case ResolveOwnerType: | 
| 1668 |                     return ExpressionType{ | 
| 1669 |                         .name: name, .semanticScope: findDefiningScopeForBinding(referrerScope: targetScope, nameToCheck: signalOrProperty->name), | 
| 1670 |                         .type: signalOrProperty->type | 
| 1671 |                     }; | 
| 1672 |                 case ResolveActualTypeForFieldMemberExpression: | 
| 1673 |                     // Bindings can't be inside of FieldMemberExpressions. | 
| 1674 |                     Q_UNREACHABLE_RETURN({}); | 
| 1675 |                 } | 
| 1676 |             } | 
| 1677 |             if (auto result = resolveSignalOrPropertyExpressionType(name, scope: owner.value(), options)) { | 
| 1678 |                 return result; | 
| 1679 |             } | 
| 1680 |             qDebug(catFunc: QQmlLSUtilsLog) << "QQmlLSUtils::resolveExpressionType() could not resolve the"  | 
| 1681 |                                       "type of a Binding." ; | 
| 1682 |         } | 
| 1683 |  | 
| 1684 |         return {}; | 
| 1685 |     } | 
| 1686 |     case DomType::QmlObject: { | 
| 1687 |         auto object = item.as<QmlObject>(); | 
| 1688 |         if (!object) | 
| 1689 |             return {}; | 
| 1690 |         if (auto scope = object->semanticScope()) { | 
| 1691 |             const auto name = item.name(); | 
| 1692 |             const bool isComponent = name.front().isUpper(); | 
| 1693 |             if (isComponent) | 
| 1694 |                 scope = scope->baseType(); | 
| 1695 |             const IdentifierType type = | 
| 1696 |                     isComponent ? QmlComponentIdentifier : GroupedPropertyIdentifier; | 
| 1697 |             switch (options) { | 
| 1698 |             case ResolveOwnerType: | 
| 1699 |                 return ExpressionType{ .name: name, .semanticScope: scope, .type: type }; | 
| 1700 |             case ResolveActualTypeForFieldMemberExpression: | 
| 1701 |                 return ExpressionType{ .name: name, .semanticScope: scope, .type: type }; | 
| 1702 |             } | 
| 1703 |         } | 
| 1704 |         return {}; | 
| 1705 |     } | 
| 1706 |     case DomType::QmlComponent: { | 
| 1707 |         auto component = item.as<QmlComponent>(); | 
| 1708 |         if (!component) | 
| 1709 |             return {}; | 
| 1710 |         const auto scope = component->semanticScope(); | 
| 1711 |         if (!scope) | 
| 1712 |             return {}; | 
| 1713 |  | 
| 1714 |         QString name = item.name(); | 
| 1715 |         if (auto dotIndex = name.indexOf(ch: u'.'); dotIndex != -1) | 
| 1716 |             name = name.sliced(pos: dotIndex + 1); | 
| 1717 |         switch (options) { | 
| 1718 |         case ResolveOwnerType: | 
| 1719 |             return ExpressionType{ .name: name, .semanticScope: scope, .type: QmlComponentIdentifier }; | 
| 1720 |         case ResolveActualTypeForFieldMemberExpression: | 
| 1721 |             return ExpressionType{ .name: name, .semanticScope: scope, .type: QmlComponentIdentifier }; | 
| 1722 |         } | 
| 1723 |         Q_UNREACHABLE_RETURN({}); | 
| 1724 |     } | 
| 1725 |     case DomType::ScriptFunctionExpression: { | 
| 1726 |         return ExpressionType{ .name: {}, .semanticScope: item.semanticScope(), .type: LambdaMethodIdentifier }; | 
| 1727 |     } | 
| 1728 |     case DomType::MethodInfo: { | 
| 1729 |         auto object = item.as<MethodInfo>(); | 
| 1730 |         if (object && object->semanticScope()) { | 
| 1731 |             std::optional<QQmlJSScope::ConstPtr> scope = object->semanticScope(); | 
| 1732 |             if (!scope) | 
| 1733 |                 return {}; | 
| 1734 |  | 
| 1735 |             if (QQmlJSScope::ConstPtr targetScope = | 
| 1736 |                         findScopeOfSpecialItems(scope: scope.value()->parentScope(), item)) { | 
| 1737 |                 const auto signalOrProperty = resolveNameInQmlScope(name: object->name, owner: targetScope); | 
| 1738 |                 if (!signalOrProperty) | 
| 1739 |                     return {}; | 
| 1740 |  | 
| 1741 |                 switch (options) { | 
| 1742 |                 case ResolveOwnerType: | 
| 1743 |                     return ExpressionType{ .name: object->name, | 
| 1744 |                                            .semanticScope: findDefiningScopeForMethod(referrerScope: targetScope, | 
| 1745 |                                                                       nameToCheck: signalOrProperty->name), | 
| 1746 |                                            .type: signalOrProperty->type }; | 
| 1747 |                 case ResolveActualTypeForFieldMemberExpression: | 
| 1748 |                     // not supported for methods | 
| 1749 |                     return {}; | 
| 1750 |                 } | 
| 1751 |             } | 
| 1752 |  | 
| 1753 |             // in case scope is the semantic scope for the function bodies: grab the owner's scope | 
| 1754 |             // this happens for all methods but not for signals (they do not have a body) | 
| 1755 |             if (scope.value()->scopeType() == QQmlJSScope::ScopeType::JSFunctionScope) | 
| 1756 |                 scope = scope.value()->parentScope(); | 
| 1757 |  | 
| 1758 |             if (auto result = resolveSignalOrPropertyExpressionType(name: object->name, scope: scope.value(), | 
| 1759 |                                                                     options)) { | 
| 1760 |                 return result; | 
| 1761 |             } | 
| 1762 |             qDebug(catFunc: QQmlLSUtilsLog) << "QQmlLSUtils::resolveExpressionType() could not resolve the"  | 
| 1763 |                                       "type of a MethodInfo." ; | 
| 1764 |         } | 
| 1765 |  | 
| 1766 |         return {}; | 
| 1767 |     } | 
| 1768 |     case DomType::ScriptBinaryExpression: { | 
| 1769 |         if (isFieldMemberExpression(item)) { | 
| 1770 |             return resolveExpressionType(item: item.field(name: Fields::right), options); | 
| 1771 |         } | 
| 1772 |         return {}; | 
| 1773 |     } | 
| 1774 |     case DomType::ScriptLiteral: { | 
| 1775 |         /* special case | 
| 1776 |         Binding { target: someId; property: "someProperty"} | 
| 1777 |         */ | 
| 1778 |         const auto scope = item.qmlObject().semanticScope(); | 
| 1779 |         const auto name = item.field(name: Fields::value).value().toString(); | 
| 1780 |         if (QQmlJSScope::ConstPtr targetScope = findScopeOfSpecialItems(scope, item)) { | 
| 1781 |             const auto signalOrProperty = resolveNameInQmlScope(name, owner: targetScope); | 
| 1782 |             if (!signalOrProperty) | 
| 1783 |                 return {}; | 
| 1784 |             switch (options) { | 
| 1785 |             case ResolveOwnerType: | 
| 1786 |                 return ExpressionType{ | 
| 1787 |                     .name: name, .semanticScope: findDefiningScopeForProperty(referrerScope: targetScope, nameToCheck: signalOrProperty->name), | 
| 1788 |                     .type: signalOrProperty->type | 
| 1789 |                 }; | 
| 1790 |             case ResolveActualTypeForFieldMemberExpression: | 
| 1791 |                 // ScriptLiteral's can't be inside of FieldMemberExpression's, especially when they | 
| 1792 |                 // are inside a special item. | 
| 1793 |                 Q_UNREACHABLE_RETURN({}); | 
| 1794 |             } | 
| 1795 |         } | 
| 1796 |         return {}; | 
| 1797 |     } | 
| 1798 |     case DomType::EnumItem: { | 
| 1799 |         const QString enumValue = item.field(name: Fields::name).value().toString(); | 
| 1800 |          QQmlJSScope::ConstPtr referrerScope = item.rootQmlObject(option: GoTo::MostLikely).semanticScope(); | 
| 1801 |          if (!referrerScope->hasEnumerationKey(name: enumValue)) | 
| 1802 |              return {}; | 
| 1803 |          switch (options) { | 
| 1804 |              // special case: use the owner's scope here, as enums do not have their own | 
| 1805 |              // QQmlJSScope. | 
| 1806 |          case ResolveActualTypeForFieldMemberExpression: | 
| 1807 |          case ResolveOwnerType: | 
| 1808 |              return ExpressionType{ .name: enumValue, | 
| 1809 |                                     .semanticScope: findDefiningScopeForEnumerationKey(referrerScope, nameToCheck: enumValue), | 
| 1810 |                                     .type: EnumeratorValueIdentifier }; | 
| 1811 |          } | 
| 1812 |          Q_UNREACHABLE_RETURN({}); | 
| 1813 |     } | 
| 1814 |     case DomType::EnumDecl: { | 
| 1815 |         const QString enumName = item.field(name: Fields::name).value().toString(); | 
| 1816 |         QQmlJSScope::ConstPtr referrerScope = item.rootQmlObject(option: GoTo::MostLikely).semanticScope(); | 
| 1817 |         if (!referrerScope->hasEnumeration(name: enumName)) | 
| 1818 |             return {}; | 
| 1819 |         switch (options) { | 
| 1820 |         // special case: use the owner's scope here, as enums do not have their own QQmlJSScope. | 
| 1821 |         case ResolveActualTypeForFieldMemberExpression: | 
| 1822 |         case ResolveOwnerType: | 
| 1823 |             return ExpressionType{ .name: enumName, | 
| 1824 |                                    .semanticScope: findDefiningScopeForEnumeration(referrerScope, nameToCheck: enumName), | 
| 1825 |                                    .type: EnumeratorIdentifier }; | 
| 1826 |         } | 
| 1827 |  | 
| 1828 |         Q_UNREACHABLE_RETURN({}); | 
| 1829 |     } | 
| 1830 |     case DomType::Import: { | 
| 1831 |         // we currently only support qualified module identifiers | 
| 1832 |         if (!item[Fields::importId]) | 
| 1833 |             return {}; | 
| 1834 |         return ExpressionType{ .name: item[Fields::importId].value().toString(), | 
| 1835 |                                .semanticScope: {}, | 
| 1836 |                                .type: QualifiedModuleIdentifier }; | 
| 1837 |     } | 
| 1838 |     case DomType::ScriptNewMemberExpression: { | 
| 1839 |         const auto name = item.field(name: Fields::base).value().toString(); | 
| 1840 |         return ExpressionType{ .name: name, .semanticScope: {}, .type: JavaScriptIdentifier }; | 
| 1841 |     } | 
| 1842 |     case DomType::ScriptCallExpression: { | 
| 1843 |         const DomItem callee = item.field(name: Fields::callee); | 
| 1844 |         const auto calleeExpressionType = resolveExpressionType(item: callee, options: ResolveOwnerType); | 
| 1845 |  | 
| 1846 |         if (!calleeExpressionType || !calleeExpressionType->semanticScope | 
| 1847 |             || !calleeExpressionType->name || calleeExpressionType->type != MethodIdentifier) { | 
| 1848 |             return {}; | 
| 1849 |         } | 
| 1850 |  | 
| 1851 |         const auto methods = | 
| 1852 |                 calleeExpressionType->semanticScope->methods(name: *calleeExpressionType->name); | 
| 1853 |         if (methods.isEmpty()) | 
| 1854 |             return {}; | 
| 1855 |  | 
| 1856 |         const auto returnType = methods.front().returnType(); | 
| 1857 |         return ExpressionType{ .name: {}, .semanticScope: returnType, .type: NotAnIdentifier }; | 
| 1858 |     } | 
| 1859 |     default: { | 
| 1860 |         qCDebug(QQmlLSUtilsLog) << "Type"  << item.internalKindStr() | 
| 1861 |                                 << "is unimplemented in QQmlLSUtils::resolveExpressionType" ; | 
| 1862 |         return {}; | 
| 1863 |     } | 
| 1864 |     } | 
| 1865 |     Q_UNREACHABLE(); | 
| 1866 | } | 
| 1867 |  | 
| 1868 | DomItem sourceLocationToDomItem(const DomItem &file, const QQmlJS::SourceLocation &location) | 
| 1869 | { | 
| 1870 |     // QQmlJS::SourceLocation starts counting at 1 but the utils and the LSP start at 0. | 
| 1871 |     auto items = QQmlLSUtils::itemsFromTextLocation(file, line: location.startLine - 1, | 
| 1872 |                                                     character: location.startColumn - 1); | 
| 1873 |     switch (items.size()) { | 
| 1874 |     case 0: | 
| 1875 |         return {}; | 
| 1876 |     case 1: | 
| 1877 |         return items.front().domItem; | 
| 1878 |     case 2: { | 
| 1879 |         // special case: because location points to the beginning of the type definition, | 
| 1880 |         // itemsFromTextLocation might also return the type on its left, in case it is directly | 
| 1881 |         // adjacent to it. In this case always take the right (=with the higher column-number) | 
| 1882 |         // item. | 
| 1883 |         auto &first = items.front(); | 
| 1884 |         auto &second = items.back(); | 
| 1885 |         Q_ASSERT_X(first.fileLocation->info().fullRegion.startLine | 
| 1886 |                            == second.fileLocation->info().fullRegion.startLine, | 
| 1887 |                    "QQmlLSUtils::findTypeDefinitionOf(DomItem)" , | 
| 1888 |                    "QQmlLSUtils::itemsFromTextLocation returned non-adjacent items." ); | 
| 1889 |         if (first.fileLocation->info().fullRegion.startColumn | 
| 1890 |             > second.fileLocation->info().fullRegion.startColumn) | 
| 1891 |             return first.domItem; | 
| 1892 |         else | 
| 1893 |             return second.domItem; | 
| 1894 |         break; | 
| 1895 |     } | 
| 1896 |     default: | 
| 1897 |         qDebug() << "Found multiple candidates for type of scriptidentifierexpression" ; | 
| 1898 |         break; | 
| 1899 |     } | 
| 1900 |     return {}; | 
| 1901 | } | 
| 1902 |  | 
| 1903 | static std::optional<Location> | 
| 1904 | findMethodDefinitionOf(const DomItem &file, QQmlJS::SourceLocation location, const QString &name) | 
| 1905 | { | 
| 1906 |     DomItem owner = QQmlLSUtils::sourceLocationToDomItem(file, location).qmlObject(); | 
| 1907 |     DomItem method = owner.field(name: Fields::methods).key(name).index(0); | 
| 1908 |     auto fileLocation = FileLocations::treeOf(method); | 
| 1909 |     if (!fileLocation) | 
| 1910 |         return {}; | 
| 1911 |  | 
| 1912 |     auto regions = fileLocation->info().regions; | 
| 1913 |  | 
| 1914 |     if (auto it = regions.constFind(key: IdentifierRegion); it != regions.constEnd()) { | 
| 1915 |         return Location::tryFrom(fileName: method.canonicalFilePath(), sourceLocation: *it, someItem: file); | 
| 1916 |     } | 
| 1917 |  | 
| 1918 |     return {}; | 
| 1919 | } | 
| 1920 |  | 
| 1921 | static std::optional<Location> | 
| 1922 | findPropertyDefinitionOf(const DomItem &file, QQmlJS::SourceLocation propertyDefinitionLocation, | 
| 1923 |                          const QString &name) | 
| 1924 | { | 
| 1925 |     DomItem propertyOwner = | 
| 1926 |             QQmlLSUtils::sourceLocationToDomItem(file, location: propertyDefinitionLocation).qmlObject(); | 
| 1927 |     DomItem propertyDefinition = propertyOwner.field(name: Fields::propertyDefs).key(name).index(0); | 
| 1928 |     auto fileLocation = FileLocations::treeOf(propertyDefinition); | 
| 1929 |     if (!fileLocation) | 
| 1930 |         return {}; | 
| 1931 |  | 
| 1932 |     auto regions = fileLocation->info().regions; | 
| 1933 |  | 
| 1934 |     if (auto it = regions.constFind(key: IdentifierRegion); it != regions.constEnd()) { | 
| 1935 |         return Location::tryFrom(fileName: propertyDefinition.canonicalFilePath(), sourceLocation: *it, someItem: file); | 
| 1936 |     } | 
| 1937 |  | 
| 1938 |     return {}; | 
| 1939 | } | 
| 1940 |  | 
| 1941 | std::optional<Location> findDefinitionOf(const DomItem &item) | 
| 1942 | { | 
| 1943 |     auto resolvedExpression = resolveExpressionType(item, options: ResolveOptions::ResolveOwnerType); | 
| 1944 |  | 
| 1945 |     if (!resolvedExpression || !resolvedExpression->name | 
| 1946 |         || (!resolvedExpression->semanticScope | 
| 1947 |             && resolvedExpression->type != QualifiedModuleIdentifier)) { | 
| 1948 |         qCDebug(QQmlLSUtilsLog) << "QQmlLSUtils::findDefinitionOf: Type could not be resolved." ; | 
| 1949 |         return {}; | 
| 1950 |     } | 
| 1951 |  | 
| 1952 |     switch (resolvedExpression->type) { | 
| 1953 |     case JavaScriptIdentifier: { | 
| 1954 |         const auto jsIdentifier = | 
| 1955 |                 resolvedExpression->semanticScope->ownJSIdentifier(id: *resolvedExpression->name); | 
| 1956 |         if (!jsIdentifier) | 
| 1957 |             return {}; | 
| 1958 |  | 
| 1959 |         return Location::tryFrom(fileName: resolvedExpression->semanticScope->filePath(), | 
| 1960 |                                  sourceLocation: jsIdentifier->location, someItem: item); | 
| 1961 |     } | 
| 1962 |  | 
| 1963 |     case PropertyIdentifier: { | 
| 1964 |         const DomItem ownerFile = item.goToFile(filePath: resolvedExpression->semanticScope->filePath()); | 
| 1965 |         const QQmlJS::SourceLocation ownerLocation = | 
| 1966 |                 resolvedExpression->semanticScope->sourceLocation(); | 
| 1967 |         return findPropertyDefinitionOf(file: ownerFile, propertyDefinitionLocation: ownerLocation, name: *resolvedExpression->name); | 
| 1968 |     } | 
| 1969 |     case PropertyChangedSignalIdentifier: | 
| 1970 |     case PropertyChangedHandlerIdentifier: | 
| 1971 |     case SignalIdentifier: | 
| 1972 |     case SignalHandlerIdentifier: | 
| 1973 |     case MethodIdentifier: { | 
| 1974 |         const DomItem ownerFile = item.goToFile(filePath: resolvedExpression->semanticScope->filePath()); | 
| 1975 |         const QQmlJS::SourceLocation ownerLocation = | 
| 1976 |                 resolvedExpression->semanticScope->sourceLocation(); | 
| 1977 |         return findMethodDefinitionOf(file: ownerFile, location: ownerLocation, name: *resolvedExpression->name); | 
| 1978 |     } | 
| 1979 |     case QmlObjectIdIdentifier: { | 
| 1980 |         DomItem qmlObject = QQmlLSUtils::sourceLocationToDomItem( | 
| 1981 |                 file: item.containingFile(), location: resolvedExpression->semanticScope->sourceLocation()); | 
| 1982 |         // in the Dom, the id is saved in a QMultiHash inside the Component of an QmlObject. | 
| 1983 |         const DomItem domId = qmlObject.component() | 
| 1984 |                                       .field(name: Fields::ids) | 
| 1985 |                                       .key(name: *resolvedExpression->name) | 
| 1986 |                                       .index(0) | 
| 1987 |                                       .field(name: Fields::value); | 
| 1988 |         if (!domId) { | 
| 1989 |             qCDebug(QQmlLSUtilsLog) | 
| 1990 |                     << "QmlComponent in Dom structure has no id, was it misconstructed?" ; | 
| 1991 |             return {}; | 
| 1992 |         } | 
| 1993 |  | 
| 1994 |         return Location::tryFrom(fileName: domId.canonicalFilePath(), | 
| 1995 |                                  sourceLocation: FileLocations::treeOf(domId)->info().fullRegion, someItem: domId); | 
| 1996 |     } | 
| 1997 |     case AttachedTypeIdentifier: | 
| 1998 |     case QmlComponentIdentifier: { | 
| 1999 |         return Location::tryFrom(fileName: resolvedExpression->semanticScope->filePath(), | 
| 2000 |                                  sourceLocation: resolvedExpression->semanticScope->sourceLocation(), someItem: item); | 
| 2001 |     } | 
| 2002 |     case QualifiedModuleIdentifier: { | 
| 2003 |         const DomItem imports = item.fileObject().field(name: Fields::imports); | 
| 2004 |         for (int i = 0; i < imports.indexes(); ++i) { | 
| 2005 |             if (imports[i][Fields::importId].value().toString() == resolvedExpression->name) { | 
| 2006 |                 const auto fileLocations = FileLocations::treeOf(imports[i]); | 
| 2007 |                 if (!fileLocations) | 
| 2008 |                     continue; | 
| 2009 |  | 
| 2010 |                 return Location::tryFrom(fileName: item.canonicalFilePath(), sourceLocation: fileLocations->info().regions[IdNameRegion], someItem: item); | 
| 2011 |             } | 
| 2012 |         } | 
| 2013 |         return {}; | 
| 2014 |     } | 
| 2015 |     case SingletonIdentifier: | 
| 2016 |     case EnumeratorIdentifier: | 
| 2017 |     case EnumeratorValueIdentifier: | 
| 2018 |     case GroupedPropertyIdentifier: | 
| 2019 |     case LambdaMethodIdentifier: | 
| 2020 |     case NotAnIdentifier: | 
| 2021 |         qCDebug(QQmlLSUtilsLog) << "QQmlLSUtils::findDefinitionOf was not implemented for type"  | 
| 2022 |                                 << resolvedExpression->type; | 
| 2023 |         return {}; | 
| 2024 |     } | 
| 2025 |     Q_UNREACHABLE_RETURN({}); | 
| 2026 | } | 
| 2027 |  | 
| 2028 | static QQmlJSScope::ConstPtr propertyOwnerFrom(const QQmlJSScope::ConstPtr &type, | 
| 2029 |                                                const QString &name) | 
| 2030 | { | 
| 2031 |     Q_ASSERT(!name.isEmpty()); | 
| 2032 |     Q_ASSERT(type); | 
| 2033 |  | 
| 2034 |     QQmlJSScope::ConstPtr typeWithDefinition = type; | 
| 2035 |     while (typeWithDefinition && !typeWithDefinition->hasOwnProperty(name)) | 
| 2036 |         typeWithDefinition = typeWithDefinition->baseType(); | 
| 2037 |  | 
| 2038 |     if (!typeWithDefinition) { | 
| 2039 |         qCDebug(QQmlLSUtilsLog) | 
| 2040 |                 << "QQmlLSUtils::checkNameForRename cannot find property definition,"  | 
| 2041 |                     " ignoring." ; | 
| 2042 |     } | 
| 2043 |  | 
| 2044 |     return typeWithDefinition; | 
| 2045 | } | 
| 2046 |  | 
| 2047 | static QQmlJSScope::ConstPtr methodOwnerFrom(const QQmlJSScope::ConstPtr &type, | 
| 2048 |                                              const QString &name) | 
| 2049 | { | 
| 2050 |     Q_ASSERT(!name.isEmpty()); | 
| 2051 |     Q_ASSERT(type); | 
| 2052 |  | 
| 2053 |     QQmlJSScope::ConstPtr typeWithDefinition = type; | 
| 2054 |     while (typeWithDefinition && !typeWithDefinition->hasOwnMethod(name)) | 
| 2055 |         typeWithDefinition = typeWithDefinition->baseType(); | 
| 2056 |  | 
| 2057 |     if (!typeWithDefinition) { | 
| 2058 |         qCDebug(QQmlLSUtilsLog) | 
| 2059 |                 << "QQmlLSUtils::checkNameForRename cannot find method definition,"  | 
| 2060 |                     " ignoring." ; | 
| 2061 |     } | 
| 2062 |  | 
| 2063 |     return typeWithDefinition; | 
| 2064 | } | 
| 2065 |  | 
| 2066 | static QQmlJSScope::ConstPtr expressionTypeWithDefinition(const ExpressionType &ownerType) | 
| 2067 | { | 
| 2068 |     switch (ownerType.type) { | 
| 2069 |     case PropertyIdentifier: | 
| 2070 |         return propertyOwnerFrom(type: ownerType.semanticScope, name: *ownerType.name); | 
| 2071 |     case PropertyChangedHandlerIdentifier: { | 
| 2072 |         const auto propertyName = | 
| 2073 |                 QQmlSignalNames::changedHandlerNameToPropertyName(handler: *ownerType.name); | 
| 2074 |         return propertyOwnerFrom(type: ownerType.semanticScope, name: *propertyName); | 
| 2075 |         break; | 
| 2076 |     } | 
| 2077 |     case PropertyChangedSignalIdentifier: { | 
| 2078 |         const auto propertyName = QQmlSignalNames::changedSignalNameToPropertyName(changeSignal: *ownerType.name); | 
| 2079 |         return propertyOwnerFrom(type: ownerType.semanticScope, name: *propertyName); | 
| 2080 |     } | 
| 2081 |     case MethodIdentifier: | 
| 2082 |     case SignalIdentifier: | 
| 2083 |         return methodOwnerFrom(type: ownerType.semanticScope, name: *ownerType.name); | 
| 2084 |     case SignalHandlerIdentifier: { | 
| 2085 |         const auto signalName = QQmlSignalNames::handlerNameToSignalName(handler: *ownerType.name); | 
| 2086 |         return methodOwnerFrom(type: ownerType.semanticScope, name: *signalName); | 
| 2087 |     } | 
| 2088 |     case JavaScriptIdentifier: | 
| 2089 |     case QmlObjectIdIdentifier: | 
| 2090 |     case SingletonIdentifier: | 
| 2091 |     case EnumeratorIdentifier: | 
| 2092 |     case EnumeratorValueIdentifier: | 
| 2093 |     case AttachedTypeIdentifier: | 
| 2094 |     case GroupedPropertyIdentifier: | 
| 2095 |     case QmlComponentIdentifier: | 
| 2096 |     case QualifiedModuleIdentifier: | 
| 2097 |     case LambdaMethodIdentifier: | 
| 2098 |     case NotAnIdentifier: | 
| 2099 |         return ownerType.semanticScope; | 
| 2100 |     } | 
| 2101 |     return {}; | 
| 2102 | } | 
| 2103 |  | 
| 2104 | std::optional<ErrorMessage> checkNameForRename(const DomItem &item, const QString &dirtyNewName, | 
| 2105 |                                                const std::optional<ExpressionType> &ownerType) | 
| 2106 | { | 
| 2107 |     if (!ownerType) { | 
| 2108 |         if (const auto resolved = resolveExpressionType(item, options: ResolveOwnerType)) | 
| 2109 |             return checkNameForRename(item, dirtyNewName, ownerType: resolved); | 
| 2110 |     } | 
| 2111 |  | 
| 2112 |     // general checks for ECMAscript identifiers | 
| 2113 |     if (!isValidEcmaScriptIdentifier(view: dirtyNewName)) | 
| 2114 |         return ErrorMessage{ .code: 0, .message: u"Invalid EcmaScript identifier!"_s  }; | 
| 2115 |  | 
| 2116 |     const auto userSemanticScope = item.nearestSemanticScope(); | 
| 2117 |  | 
| 2118 |     if (!ownerType || !userSemanticScope) { | 
| 2119 |         return ErrorMessage{ .code: 0, .message: u"Requested item cannot be renamed"_s  }; | 
| 2120 |     } | 
| 2121 |  | 
| 2122 |     // type specific checks | 
| 2123 |     switch (ownerType->type) { | 
| 2124 |     case PropertyChangedSignalIdentifier: { | 
| 2125 |         if (!QQmlSignalNames::isChangedSignalName(signalName: dirtyNewName)) { | 
| 2126 |             return ErrorMessage{ .code: 0, .message: u"Invalid name for a property changed signal."_s  }; | 
| 2127 |         } | 
| 2128 |         break; | 
| 2129 |     } | 
| 2130 |     case PropertyChangedHandlerIdentifier: { | 
| 2131 |         if (!QQmlSignalNames::isChangedHandlerName(signalName: dirtyNewName)) { | 
| 2132 |             return ErrorMessage{ .code: 0, .message: u"Invalid name for a property changed handler identifier."_s  }; | 
| 2133 |         } | 
| 2134 |         break; | 
| 2135 |     } | 
| 2136 |     case SignalHandlerIdentifier: { | 
| 2137 |         if (!QQmlSignalNames::isHandlerName(signalName: dirtyNewName)) { | 
| 2138 |             return ErrorMessage{ .code: 0, .message: u"Invalid name for a signal handler identifier."_s  }; | 
| 2139 |         } | 
| 2140 |         break; | 
| 2141 |     } | 
| 2142 |     // TODO: any other specificities? | 
| 2143 |     case QmlObjectIdIdentifier: | 
| 2144 |         if (dirtyNewName.front().isLetter() && !dirtyNewName.front().isLower()) { | 
| 2145 |             return ErrorMessage{ .code: 0, .message: u"Object id names cannot start with an upper case letter."_s  }; | 
| 2146 |         } | 
| 2147 |         break; | 
| 2148 |     case JavaScriptIdentifier: | 
| 2149 |     case PropertyIdentifier: | 
| 2150 |     case SignalIdentifier: | 
| 2151 |     case MethodIdentifier: | 
| 2152 |     default: | 
| 2153 |         break; | 
| 2154 |     }; | 
| 2155 |  | 
| 2156 |     auto typeWithDefinition = expressionTypeWithDefinition(ownerType: *ownerType); | 
| 2157 |  | 
| 2158 |     if (!typeWithDefinition) { | 
| 2159 |         return ErrorMessage{ | 
| 2160 |             .code: 0, | 
| 2161 |             .message: u"Renaming has not been implemented for the requested item."_s , | 
| 2162 |         }; | 
| 2163 |     } | 
| 2164 |  | 
| 2165 |     // is it not defined in QML? | 
| 2166 |     if (!typeWithDefinition->isComposite()) { | 
| 2167 |         return ErrorMessage{ .code: 0, .message: u"Cannot rename items defined in non-QML files."_s  }; | 
| 2168 |     } | 
| 2169 |  | 
| 2170 |     // is it defined in the current module? | 
| 2171 |     const QString moduleOfDefinition = ownerType->semanticScope->moduleName(); | 
| 2172 |     const QString moduleOfCurrentItem = userSemanticScope->moduleName(); | 
| 2173 |     if (moduleOfDefinition != moduleOfCurrentItem) { | 
| 2174 |         return ErrorMessage{ | 
| 2175 |             .code: 0, | 
| 2176 |             .message: u"Cannot rename items defined in the \"%1\" module from a usage in the \"%2\" module."_s  | 
| 2177 |                     .arg(args: moduleOfDefinition, args: moduleOfCurrentItem), | 
| 2178 |         }; | 
| 2179 |     } | 
| 2180 |  | 
| 2181 |     return {}; | 
| 2182 | } | 
| 2183 |  | 
| 2184 | static std::optional<QString> oldNameFrom(const DomItem &item) | 
| 2185 | { | 
| 2186 |     switch (item.internalKind()) { | 
| 2187 |     case DomType::ScriptIdentifierExpression: { | 
| 2188 |         return item.field(name: Fields::identifier).value().toString(); | 
| 2189 |     } | 
| 2190 |     case DomType::ScriptVariableDeclarationEntry: { | 
| 2191 |         return item.field(name: Fields::identifier).value().toString(); | 
| 2192 |     } | 
| 2193 |     case DomType::PropertyDefinition: | 
| 2194 |     case DomType::Binding: | 
| 2195 |     case DomType::MethodInfo: { | 
| 2196 |         return item.field(name: Fields::name).value().toString(); | 
| 2197 |     } | 
| 2198 |     default: | 
| 2199 |         qCDebug(QQmlLSUtilsLog) << item.internalKindStr() | 
| 2200 |                                 << "was not implemented for QQmlLSUtils::renameUsagesOf" ; | 
| 2201 |         return std::nullopt; | 
| 2202 |     } | 
| 2203 |     Q_UNREACHABLE_RETURN(std::nullopt); | 
| 2204 | } | 
| 2205 |  | 
| 2206 | static std::optional<QString> newNameFrom(const QString &dirtyNewName, IdentifierType alternative) | 
| 2207 | { | 
| 2208 |     // When renaming signal/property changed handlers and property changed signals: | 
| 2209 |     // Get the actual corresponding signal name (for signal handlers) or property name (for | 
| 2210 |     // property changed signal + handlers) that will be used for the renaming. | 
| 2211 |     switch (alternative) { | 
| 2212 |     case SignalHandlerIdentifier: { | 
| 2213 |         return QQmlSignalNames::handlerNameToSignalName(handler: dirtyNewName); | 
| 2214 |     } | 
| 2215 |     case PropertyChangedHandlerIdentifier: { | 
| 2216 |         return QQmlSignalNames::changedHandlerNameToPropertyName(handler: dirtyNewName); | 
| 2217 |     } | 
| 2218 |     case PropertyChangedSignalIdentifier: { | 
| 2219 |         return QQmlSignalNames::changedSignalNameToPropertyName(changeSignal: dirtyNewName); | 
| 2220 |     } | 
| 2221 |     case SignalIdentifier: | 
| 2222 |     case PropertyIdentifier: | 
| 2223 |     default: | 
| 2224 |         return std::nullopt; | 
| 2225 |     } | 
| 2226 |     Q_UNREACHABLE_RETURN(std::nullopt); | 
| 2227 | } | 
| 2228 |  | 
| 2229 | /*! | 
| 2230 | \internal | 
| 2231 | \brief Rename the appearance of item to newName. | 
| 2232 |  | 
| 2233 | Special cases: | 
| 2234 | \list | 
| 2235 |     \li Renaming a property changed signal or property changed handler does the same as renaming | 
| 2236 |     the underlying property, except that newName gets | 
| 2237 |     \list | 
| 2238 |         \li its "on"-prefix and "Changed"-suffix chopped of if item is a property changed handlers | 
| 2239 |         \li its "Changed"-suffix chopped of if item is a property changed signals | 
| 2240 |     \endlist | 
| 2241 |     \li Renaming a signal handler does the same as renaming a signal, but the "on"-prefix in newName | 
| 2242 |     is chopped of. | 
| 2243 |  | 
| 2244 |     All of the chopping operations are done using the static helpers from QQmlSignalNames. | 
| 2245 | \endlist | 
| 2246 | */ | 
| 2247 | RenameUsages renameUsagesOf(const DomItem &item, const QString &dirtyNewName, | 
| 2248 |                             const std::optional<ExpressionType> &targetType) | 
| 2249 | { | 
| 2250 |     RenameUsages result; | 
| 2251 |     const Usages locations = findUsagesOf(item); | 
| 2252 |     if (locations.isEmpty()) | 
| 2253 |         return result; | 
| 2254 |  | 
| 2255 |     auto oldName = oldNameFrom(item); | 
| 2256 |     if (!oldName) | 
| 2257 |         return result; | 
| 2258 |  | 
| 2259 |     QQmlJSScope::ConstPtr semanticScope; | 
| 2260 |     if (targetType) { | 
| 2261 |         semanticScope = targetType->semanticScope; | 
| 2262 |     } else if (const auto resolved = | 
| 2263 |                        QQmlLSUtils::resolveExpressionType(item, options: ResolveOptions::ResolveOwnerType)) { | 
| 2264 |         semanticScope = resolved->semanticScope; | 
| 2265 |     } else { | 
| 2266 |         return result; | 
| 2267 |     } | 
| 2268 |  | 
| 2269 |     QString newName; | 
| 2270 |     if (const auto resolved = resolveNameInQmlScope(name: *oldName, owner: semanticScope)) { | 
| 2271 |         newName = newNameFrom(dirtyNewName, alternative: resolved->type).value_or(u: dirtyNewName); | 
| 2272 |         oldName = resolved->name; | 
| 2273 |     } else { | 
| 2274 |         newName = dirtyNewName; | 
| 2275 |     } | 
| 2276 |  | 
| 2277 |     const qsizetype oldNameLength = oldName->length(); | 
| 2278 |     const qsizetype oldHandlerNameLength = | 
| 2279 |             QQmlSignalNames::signalNameToHandlerName(signal: *oldName).length(); | 
| 2280 |     const qsizetype oldChangedSignalNameLength = | 
| 2281 |             QQmlSignalNames::propertyNameToChangedSignalName(property: *oldName).length(); | 
| 2282 |     const qsizetype oldChangedHandlerNameLength = | 
| 2283 |             QQmlSignalNames::propertyNameToChangedHandlerName(property: *oldName).length(); | 
| 2284 |  | 
| 2285 |     const QString newHandlerName = QQmlSignalNames::signalNameToHandlerName(signal: newName); | 
| 2286 |     const QString newChangedSignalName = QQmlSignalNames::propertyNameToChangedSignalName(property: newName); | 
| 2287 |     const QString newChangedHandlerName = | 
| 2288 |             QQmlSignalNames::propertyNameToChangedHandlerName(property: newName); | 
| 2289 |  | 
| 2290 |     // set the new name at the found usages, but add "on"-prefix and "Changed"-suffix if needed | 
| 2291 |     for (const auto &location : locations.usagesInFile()) { | 
| 2292 |         const qsizetype currentLength = location.sourceLocation().length; | 
| 2293 |         Edit edit; | 
| 2294 |         edit.location = location; | 
| 2295 |         if (oldNameLength == currentLength) { | 
| 2296 |             // normal case, nothing to do | 
| 2297 |             edit.replacement = newName; | 
| 2298 |  | 
| 2299 |         } else if (oldHandlerNameLength == currentLength) { | 
| 2300 |             // signal handler location | 
| 2301 |             edit.replacement = newHandlerName; | 
| 2302 |  | 
| 2303 |         } else if (oldChangedSignalNameLength == currentLength) { | 
| 2304 |             // property changed signal location | 
| 2305 |             edit.replacement = newChangedSignalName; | 
| 2306 |  | 
| 2307 |         } else if (oldChangedHandlerNameLength == currentLength) { | 
| 2308 |             // property changed handler location | 
| 2309 |             edit.replacement = newChangedHandlerName; | 
| 2310 |  | 
| 2311 |         } else { | 
| 2312 |             qCDebug(QQmlLSUtilsLog) << "Found usage with wrong identifier length, ignoring..." ; | 
| 2313 |             continue; | 
| 2314 |         } | 
| 2315 |         result.appendRename(edit); | 
| 2316 |     } | 
| 2317 |  | 
| 2318 |     for (const auto &filename : locations.usagesInFilename()) { | 
| 2319 |         // assumption: we only rename files ending in .qml or .ui.qml in qmlls | 
| 2320 |         QString extension; | 
| 2321 |         if (filename.endsWith(s: u".ui.qml"_s )) | 
| 2322 |             extension = u".ui.qml"_s ; | 
| 2323 |         else if (filename.endsWith(s: u".qml"_s )) | 
| 2324 |             extension = u".qml"_s ; | 
| 2325 |         else | 
| 2326 |             continue; | 
| 2327 |  | 
| 2328 |         QFileInfo info(filename); | 
| 2329 |         // do not rename the file if it has a custom type name in the qmldir | 
| 2330 |         if (!info.isFile() || info.baseName() != oldName) | 
| 2331 |             continue; | 
| 2332 |  | 
| 2333 |         const QString newFilename = | 
| 2334 |                 QDir::cleanPath(path: filename + "/.."_L1 ) + '/'_L1 + newName + extension; | 
| 2335 |         result.appendRename(edit: { .oldFilename: filename, .newFilename: newFilename }); | 
| 2336 |     } | 
| 2337 |  | 
| 2338 |     return result; | 
| 2339 | } | 
| 2340 |  | 
| 2341 | std::optional<Location> Location::tryFrom(const QString &fileName, | 
| 2342 |                                           const QQmlJS::SourceLocation &sourceLocation, | 
| 2343 |                                           const QQmlJS::Dom::DomItem &someItem) | 
| 2344 | { | 
| 2345 |     auto qmlFile = someItem.goToFile(filePath: fileName).ownerAs<QQmlJS::Dom::QmlFile>(); | 
| 2346 |     if (!qmlFile) { | 
| 2347 |         qDebug() << "Could not find file"  << fileName << "in the dom!" ; | 
| 2348 |         return {}; | 
| 2349 |     } | 
| 2350 |     return Location{ fileName, sourceLocation, | 
| 2351 |                      textRowAndColumnFrom(text: qmlFile->code(), offset: sourceLocation.end()) }; | 
| 2352 | } | 
| 2353 |  | 
| 2354 | Location Location::from(const QString &fileName, const QQmlJS::SourceLocation &sourceLocation, const QString &code) | 
| 2355 | { | 
| 2356 |     return Location{ fileName, sourceLocation, textRowAndColumnFrom(text: code, offset: sourceLocation.end()) }; | 
| 2357 | } | 
| 2358 |  | 
| 2359 | Location Location::from(const QString &fileName, const QString &code, quint32 startLine, | 
| 2360 |                         quint32 startCharacter, quint32 length) | 
| 2361 | { | 
| 2362 |     const quint32 offset = QQmlLSUtils::textOffsetFrom(text: code, row: startLine - 1, column: startCharacter - 1); | 
| 2363 |     return from(fileName, sourceLocation: QQmlJS::SourceLocation{ offset, length, startLine, startCharacter }, | 
| 2364 |                 code); | 
| 2365 | } | 
| 2366 |  | 
| 2367 | Edit Edit::from(const QString &fileName, const QString &code, quint32 startLine, | 
| 2368 |                 quint32 startCharacter, quint32 length, const QString &newName) | 
| 2369 | { | 
| 2370 |     Edit rename; | 
| 2371 |     rename.location = Location::from(fileName, code, startLine, startCharacter, length); | 
| 2372 |     rename.replacement = newName; | 
| 2373 |     return rename; | 
| 2374 | } | 
| 2375 |  | 
| 2376 | bool isValidEcmaScriptIdentifier(QStringView identifier) | 
| 2377 | { | 
| 2378 |     QQmlJS::Lexer lexer(nullptr); | 
| 2379 |     lexer.setCode(code: identifier.toString(), lineno: 0); | 
| 2380 |     const int token = lexer.lex(); | 
| 2381 |     if (token != static_cast<int>(QQmlJS::Lexer::T_IDENTIFIER)) | 
| 2382 |         return false; | 
| 2383 |     // make sure there is nothing following the lexed identifier | 
| 2384 |     const int eofToken = lexer.lex(); | 
| 2385 |     return eofToken == static_cast<int>(QQmlJS::Lexer::EOF_SYMBOL); | 
| 2386 | } | 
| 2387 |  | 
| 2388 |  | 
| 2389 | /*! | 
| 2390 | \internal | 
| 2391 | Returns the name of the cmake program along with the arguments needed to build the | 
| 2392 | qmltyperegistration. This command generates the .qmltypes, qmldir and .qrc files required for qmlls | 
| 2393 | to provide correct information on C++ defined QML elements. | 
| 2394 |  | 
| 2395 | We assume here that CMake is available in the path. This should be the case for linux and macOS by | 
| 2396 | default. | 
| 2397 | For windows, having CMake in the path is not too unrealistic, for example, | 
| 2398 | https://doc.qt.io/qt-6/windows-building.html#step-2-install-build-requirements claims that you need | 
| 2399 | to have CMake in your path to build Qt. So a developer machine running qmlls has a high chance of | 
| 2400 | having CMake in their path, if CMake is installed and used. | 
| 2401 | */ | 
| 2402 | QPair<QString, QStringList> cmakeBuildCommand(const QString &path) | 
| 2403 | { | 
| 2404 |     const QPair<QString, QStringList> result{ | 
| 2405 |         u"cmake"_s , { u"--build"_s , path, u"-t"_s , u"all_qmltyperegistrations"_s  } | 
| 2406 |     }; | 
| 2407 |     return result; | 
| 2408 | } | 
| 2409 |  | 
| 2410 | void Usages::sort() | 
| 2411 | { | 
| 2412 |     std::sort(first: m_usagesInFile.begin(), last: m_usagesInFile.end()); | 
| 2413 |     std::sort(first: m_usagesInFilename.begin(), last: m_usagesInFilename.end()); | 
| 2414 | } | 
| 2415 |  | 
| 2416 | bool Usages::isEmpty() const | 
| 2417 | { | 
| 2418 |     return m_usagesInFilename.isEmpty() && m_usagesInFile.isEmpty(); | 
| 2419 | } | 
| 2420 |  | 
| 2421 | Usages::Usages(const QList<Location> &usageInFile, const QList<QString> &usageInFilename) | 
| 2422 |     : m_usagesInFile(usageInFile), m_usagesInFilename(usageInFilename) | 
| 2423 | { | 
| 2424 |     std::sort(first: m_usagesInFile.begin(), last: m_usagesInFile.end()); | 
| 2425 |     std::sort(first: m_usagesInFilename.begin(), last: m_usagesInFilename.end()); | 
| 2426 | } | 
| 2427 |  | 
| 2428 | RenameUsages::RenameUsages(const QList<Edit> &renamesInFile, | 
| 2429 |                            const QList<FileRename> &renamesInFilename) | 
| 2430 |     : m_renamesInFile(renamesInFile), m_renamesInFilename(renamesInFilename) | 
| 2431 | { | 
| 2432 |     std::sort(first: m_renamesInFile.begin(), last: m_renamesInFile.end()); | 
| 2433 |     std::sort(first: m_renamesInFilename.begin(), last: m_renamesInFilename.end()); | 
| 2434 | } | 
| 2435 |  | 
| 2436 | } // namespace QQmlLSUtils | 
| 2437 |  | 
| 2438 | QT_END_NAMESPACE | 
| 2439 |  |