| 1 | // Copyright (C) 2024 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 "qqmllscompletion_p.h" | 
| 5 |  | 
| 6 | using namespace QLspSpecification; | 
| 7 | using namespace QQmlJS::Dom; | 
| 8 | using namespace Qt::StringLiterals; | 
| 9 |  | 
| 10 | QT_BEGIN_NAMESPACE | 
| 11 |  | 
| 12 | Q_LOGGING_CATEGORY(QQmlLSCompletionLog, "qt.languageserver.completions" ) | 
| 13 |  | 
| 14 | /*! | 
| 15 | \class QQmlLSCompletion | 
| 16 | \internal | 
| 17 | \brief QQmlLSCompletion provides completions for all kinds of QML and JS constructs. | 
| 18 |  | 
| 19 | Use the \l{completions} method to obtain completions at a certain DomItem. | 
| 20 |  | 
| 21 | All the other methods in this class are helper methods: some compute completions for specific QML | 
| 22 | and JS constructs and some are shared between multiple QML or JS constructs to avoid code | 
| 23 | duplication. Most of the helper methods add their completion items via a BackInsertIterator. | 
| 24 |  | 
| 25 | Some helper methods are called "suggest*" and will try to suggest code that does not exist yet. For | 
| 26 | example, any JS statement can be expected inside a Blockstatement so suggestJSStatementCompletion() | 
| 27 | is used to suggest JS statements inside of BlockStatements. Another example might be | 
| 28 | suggestReachableTypes() that will suggest Types for type annotations, attached types or Qml Object | 
| 29 | hierarchies, or suggestCaseAndDefaultStatementCompletion() that will only suggest "case" and | 
| 30 | "default" clauses for switch statements. | 
| 31 |  | 
| 32 | Some helper methods are called "inside*" and will try to suggest code inside an existing structure. | 
| 33 | For example, insideForStatementCompletion() will try to suggest completion for the different code | 
| 34 | pieces initializer, condition, increment and statement that exist inside of: | 
| 35 | \badcode | 
| 36 | for(initializer; condition; increment) | 
| 37 |     statement | 
| 38 | \endcode | 
| 39 | */ | 
| 40 |  | 
| 41 | CompletionItem QQmlLSCompletion::makeSnippet(QUtf8StringView qualifier, QUtf8StringView label, | 
| 42 |                                              QUtf8StringView insertText) | 
| 43 | { | 
| 44 |     CompletionItem res; | 
| 45 |     if (!qualifier.isEmpty()) { | 
| 46 |         res.label = qualifier.data(); | 
| 47 |         res.label += '.'; | 
| 48 |     } | 
| 49 |     res.label += label.data(); | 
| 50 |     res.insertTextFormat = InsertTextFormat::Snippet; | 
| 51 |     if (!qualifier.isEmpty()) { | 
| 52 |         res.insertText = qualifier.data(); | 
| 53 |         *res.insertText += '.'; | 
| 54 |         *res.insertText += insertText.data(); | 
| 55 |     } else { | 
| 56 |         res.insertText = insertText.data(); | 
| 57 |     } | 
| 58 |     res.kind = int(CompletionItemKind::Snippet); | 
| 59 |     res.insertTextMode = InsertTextMode::AdjustIndentation; | 
| 60 |     return res; | 
| 61 | } | 
| 62 |  | 
| 63 | CompletionItem QQmlLSCompletion::makeSnippet(QUtf8StringView label, QUtf8StringView insertText) | 
| 64 | { | 
| 65 |     return makeSnippet(qualifier: QByteArray(), label, insertText); | 
| 66 | } | 
| 67 |  | 
| 68 | /*! | 
| 69 | \internal | 
| 70 | \brief Compare left and right locations to the position denoted by ctx, see special cases below. | 
| 71 |  | 
| 72 | Statements and expressions need to provide different completions depending on where the cursor is. | 
| 73 | For example, lets take following for-statement: | 
| 74 | \badcode | 
| 75 | for (let i = 0; <here> ; ++i) {} | 
| 76 | \endcode | 
| 77 | We want to provide script expression completion (method names, property names, available JS | 
| 78 | variables names, QML objects ids, and so on) at the place denoted by \c{<here>}. | 
| 79 | The question is: how do we know that the cursor is really at \c{<here>}? In the case of the | 
| 80 | for-loop, we can compare the position of the cursor with the first and the second semicolon of the | 
| 81 | for loop. | 
| 82 |  | 
| 83 | If the first semicolon does not exist, it has an invalid sourcelocation and the cursor is | 
| 84 | definitively \e{not} at \c{<here>}. Therefore, return false when \c{left} is invalid. | 
| 85 |  | 
| 86 | If the second semicolon does not exist, then just ignore it: it might not have been written yet. | 
| 87 | */ | 
| 88 | bool QQmlLSCompletion::betweenLocations(QQmlJS::SourceLocation left, | 
| 89 |                                         const QQmlLSCompletionPosition &positionInfo, | 
| 90 |                                         QQmlJS::SourceLocation right) const | 
| 91 | { | 
| 92 |     if (!left.isValid()) | 
| 93 |         return false; | 
| 94 |     // note: left.end() == ctx.offset() means that the cursor lies exactly after left | 
| 95 |     if (!(left.end() <= positionInfo.offset())) | 
| 96 |         return false; | 
| 97 |     if (!right.isValid()) | 
| 98 |         return true; | 
| 99 |  | 
| 100 |     // note: ctx.offset() == right.begin() means that the cursor lies exactly before right | 
| 101 |     return positionInfo.offset() <= right.begin(); | 
| 102 | } | 
| 103 |  | 
| 104 | /*! | 
| 105 | \internal | 
| 106 | Returns true if ctx denotes an offset lying behind left.end(), and false otherwise. | 
| 107 | */ | 
| 108 | bool QQmlLSCompletion::afterLocation(QQmlJS::SourceLocation left, | 
| 109 |                                      const QQmlLSCompletionPosition &positionInfo) const | 
| 110 | { | 
| 111 |     return betweenLocations(left, positionInfo, right: QQmlJS::SourceLocation{}); | 
| 112 | } | 
| 113 |  | 
| 114 | /*! | 
| 115 | \internal | 
| 116 | Returns true if ctx denotes an offset lying before right.begin(), and false otherwise. | 
| 117 | */ | 
| 118 | bool QQmlLSCompletion::beforeLocation(const QQmlLSCompletionPosition &ctx, | 
| 119 |                                       QQmlJS::SourceLocation right) const | 
| 120 | { | 
| 121 |     if (!right.isValid()) | 
| 122 |         return true; | 
| 123 |  | 
| 124 |     // note: ctx.offset() == right.begin() means that the cursor lies exactly before right | 
| 125 |     if (ctx.offset() <= right.begin()) | 
| 126 |         return true; | 
| 127 |  | 
| 128 |     return false; | 
| 129 | } | 
| 130 |  | 
| 131 | bool QQmlLSCompletion::ctxBeforeStatement(const QQmlLSCompletionPosition &positionInfo, | 
| 132 |                                           const DomItem &parentForContext, | 
| 133 |                                           FileLocationRegion firstRegion) const | 
| 134 | { | 
| 135 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 136 |     const bool result = beforeLocation(ctx: positionInfo, right: regions[firstRegion]); | 
| 137 |     return result; | 
| 138 | } | 
| 139 |  | 
| 140 | void | 
| 141 | QQmlLSCompletion::suggestBindingCompletion(const DomItem &itemAtPosition, BackInsertIterator it) const | 
| 142 | { | 
| 143 |     suggestReachableTypes(context: itemAtPosition, typeCompletionType: LocalSymbolsType::AttachedType, kind: CompletionItemKind::Class, | 
| 144 |                           it); | 
| 145 |  | 
| 146 |     const auto scope = [&]() -> QQmlJSScope::ConstPtr { | 
| 147 |         const DomItem owner = ownerOfQualifiedExpression(qualifiedExpression: itemAtPosition); | 
| 148 |         if (!owner) | 
| 149 |             return itemAtPosition.qmlObject().semanticScope(); | 
| 150 |  | 
| 151 |         const auto expressionType = QQmlLSUtils::resolveExpressionType( | 
| 152 |                 item: owner, QQmlLSUtils::ResolveActualTypeForFieldMemberExpression); | 
| 153 |  | 
| 154 |         // no properties nor signal handlers inside a qualified import | 
| 155 |         if (!expressionType || expressionType->type == QQmlLSUtils::QualifiedModuleIdentifier) | 
| 156 |             return {}; | 
| 157 |  | 
| 158 |         return expressionType->semanticScope; | 
| 159 |     }(); | 
| 160 |  | 
| 161 |     if (!scope) | 
| 162 |         return; | 
| 163 |  | 
| 164 |     propertyCompletion(scope, usedNames: nullptr, it); | 
| 165 |     signalHandlerCompletion(scope, usedNames: nullptr, it); | 
| 166 | } | 
| 167 |  | 
| 168 | void QQmlLSCompletion::insideImportCompletionHelper(const DomItem &file, | 
| 169 |                                                     const QQmlLSCompletionPosition &positionInfo, | 
| 170 |                                                     BackInsertIterator it) const | 
| 171 | { | 
| 172 |     // returns completions for import statements, ctx is supposed to be in an import statement | 
| 173 |     const CompletionContextStrings &ctx = positionInfo.cursorPosition; | 
| 174 |     ImportCompletionType importCompletionType = ImportCompletionType::None; | 
| 175 |     QRegularExpression spaceRe(uR"(\s+)"_s ); | 
| 176 |     QList<QStringView> linePieces = ctx.preLine().split(sep: spaceRe, behavior: Qt::SkipEmptyParts); | 
| 177 |     qsizetype effectiveLength = linePieces.size() | 
| 178 |             + ((!ctx.preLine().isEmpty() && ctx.preLine().last().isSpace()) ? 1 : 0); | 
| 179 |     if (effectiveLength < 2) { | 
| 180 |         CompletionItem comp; | 
| 181 |         comp.label = "import" ; | 
| 182 |         comp.kind = int(CompletionItemKind::Keyword); | 
| 183 |         it = comp; | 
| 184 |     } | 
| 185 |     if (linePieces.isEmpty() || linePieces.first() != u"import" ) | 
| 186 |         return; | 
| 187 |     if (effectiveLength == 2) { | 
| 188 |         // the cursor is after the import, possibly in a partial module name | 
| 189 |         importCompletionType = ImportCompletionType::Module; | 
| 190 |     } else if (effectiveLength == 3) { | 
| 191 |         if (linePieces.last() != u"as" ) { | 
| 192 |             // the cursor is after the module, possibly in a partial version token (or partial as) | 
| 193 |             CompletionItem comp; | 
| 194 |             comp.label = "as" ; | 
| 195 |             comp.kind = int(CompletionItemKind::Keyword); | 
| 196 |             it = comp; | 
| 197 |             importCompletionType = ImportCompletionType::Version; | 
| 198 |         } | 
| 199 |     } | 
| 200 |     DomItem env = file.environment(); | 
| 201 |     if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) { | 
| 202 |         switch (importCompletionType) { | 
| 203 |         case ImportCompletionType::None: | 
| 204 |             break; | 
| 205 |         case ImportCompletionType::Module: { | 
| 206 |             QDuplicateTracker<QString> modulesSeen; | 
| 207 |             for (const QString &uri : envPtr->moduleIndexUris(self: env)) { | 
| 208 |                 QStringView base = ctx.base(); // if we allow spaces we should get rid of them | 
| 209 |                 if (uri.startsWith(s: base)) { | 
| 210 |                     QStringList rest = uri.mid(position: base.size()).split(sep: u'.'); | 
| 211 |                     if (rest.isEmpty()) | 
| 212 |                         continue; | 
| 213 |  | 
| 214 |                     const QString label = rest.first(); | 
| 215 |                     if (!modulesSeen.hasSeen(s: label)) { | 
| 216 |                         CompletionItem comp; | 
| 217 |                         comp.label = label.toUtf8(); | 
| 218 |                         comp.kind = int(CompletionItemKind::Module); | 
| 219 |                         it = comp; | 
| 220 |                     } | 
| 221 |                 } | 
| 222 |             } | 
| 223 |             break; | 
| 224 |         } | 
| 225 |         case ImportCompletionType::Version: | 
| 226 |             if (ctx.base().isEmpty()) { | 
| 227 |                 for (int majorV : | 
| 228 |                      envPtr->moduleIndexMajorVersions(self: env, uri: linePieces.at(i: 1).toString())) { | 
| 229 |                     CompletionItem comp; | 
| 230 |                     comp.label = QString::number(majorV).toUtf8(); | 
| 231 |                     comp.kind = int(CompletionItemKind::Constant); | 
| 232 |                     it = comp; | 
| 233 |                 } | 
| 234 |             } else { | 
| 235 |                 bool hasMajorVersion = ctx.base().endsWith(c: u'.'); | 
| 236 |                 int majorV = -1; | 
| 237 |                 if (hasMajorVersion) | 
| 238 |                     majorV = ctx.base().mid(pos: 0, n: ctx.base().size() - 1).toInt(ok: &hasMajorVersion); | 
| 239 |                 if (!hasMajorVersion) | 
| 240 |                     break; | 
| 241 |                 if (std::shared_ptr<ModuleIndex> mIndex = | 
| 242 |                             envPtr->moduleIndexWithUri(self: env, uri: linePieces.at(i: 1).toString(), majorVersion: majorV)) { | 
| 243 |                     for (int minorV : mIndex->minorVersions()) { | 
| 244 |                         CompletionItem comp; | 
| 245 |                         comp.label = QString::number(minorV).toUtf8(); | 
| 246 |                         comp.kind = int(CompletionItemKind::Constant); | 
| 247 |                         it = comp; | 
| 248 |                     } | 
| 249 |                 } | 
| 250 |             } | 
| 251 |             break; | 
| 252 |         } | 
| 253 |     } | 
| 254 | } | 
| 255 |  | 
| 256 | void QQmlLSCompletion::idsCompletions(const DomItem &component, BackInsertIterator it) const | 
| 257 | { | 
| 258 |     qCDebug(QQmlLSCompletionLog) << "adding ids completions" ; | 
| 259 |     for (const QString &k : component.field(name: Fields::ids).keys()) { | 
| 260 |         CompletionItem comp; | 
| 261 |         comp.label = k.toUtf8(); | 
| 262 |         comp.kind = int(CompletionItemKind::Value); | 
| 263 |         it = comp; | 
| 264 |     } | 
| 265 | } | 
| 266 |  | 
| 267 | static bool testScopeSymbol(const QQmlJSScope::ConstPtr &scope, LocalSymbolsTypes options, | 
| 268 |                             CompletionItemKind kind) | 
| 269 | { | 
| 270 |     const bool currentIsSingleton = scope->isSingleton(); | 
| 271 |     const bool currentIsAttached = !scope->attachedType().isNull(); | 
| 272 |     if ((options & LocalSymbolsType::Singleton) && currentIsSingleton) { | 
| 273 |         return true; | 
| 274 |     } | 
| 275 |     if ((options & LocalSymbolsType::AttachedType) && currentIsAttached) { | 
| 276 |         return true; | 
| 277 |     } | 
| 278 |     const bool isObjectType = scope->isReferenceType(); | 
| 279 |     if (options & LocalSymbolsType::ObjectType && !currentIsSingleton && isObjectType) { | 
| 280 |         return kind != CompletionItemKind::Constructor || scope->isCreatable(); | 
| 281 |     } | 
| 282 |     if (options & LocalSymbolsType::ValueType && !currentIsSingleton && !isObjectType) { | 
| 283 |         return true; | 
| 284 |     } | 
| 285 |     return false; | 
| 286 | } | 
| 287 |  | 
| 288 | /*! | 
| 289 | \internal | 
| 290 | Obtain the types reachable from \c{el} as a CompletionItems. | 
| 291 | */ | 
| 292 | void QQmlLSCompletion::suggestReachableTypes(const DomItem &el, LocalSymbolsTypes options, | 
| 293 |                                              CompletionItemKind kind, BackInsertIterator it) const | 
| 294 | { | 
| 295 |     auto file = el.containingFile().as<QmlFile>(); | 
| 296 |     if (!file) | 
| 297 |         return; | 
| 298 |     auto resolver = file->typeResolver(); | 
| 299 |     if (!resolver) | 
| 300 |         return; | 
| 301 |  | 
| 302 |     const QString requiredQualifiers = QQmlLSUtils::qualifiersFrom(el); | 
| 303 |     const auto keyValueRange = resolver->importedTypes().asKeyValueRange(); | 
| 304 |     for (const auto &type : keyValueRange) { | 
| 305 |         // ignore special QQmlJSImporterMarkers | 
| 306 |         const bool isMarkerType = type.first.contains(s: u"$internal$." ) | 
| 307 |                 || type.first.contains(s: u"$anonymous$." ) || type.first.contains(s: u"$module$." ); | 
| 308 |         if (isMarkerType || !type.first.startsWith(s: requiredQualifiers)) | 
| 309 |             continue; | 
| 310 |  | 
| 311 |         auto &scope = type.second.scope; | 
| 312 |         if (!scope) | 
| 313 |             continue; | 
| 314 |  | 
| 315 |         if (!testScopeSymbol(scope, options, kind)) | 
| 316 |             continue; | 
| 317 |  | 
| 318 |         CompletionItem completion; | 
| 319 |         completion.label = QStringView(type.first).sliced(pos: requiredQualifiers.size()).toUtf8(); | 
| 320 |         completion.kind = int(kind); | 
| 321 |         it = completion; | 
| 322 |     } | 
| 323 | } | 
| 324 |  | 
| 325 | void QQmlLSCompletion::jsIdentifierCompletion(const QQmlJSScope::ConstPtr &scope, | 
| 326 |                                               QDuplicateTracker<QString> *usedNames, | 
| 327 |                                               BackInsertIterator it) const | 
| 328 | { | 
| 329 |     for (const auto &[name, jsIdentifier] : scope->ownJSIdentifiers().asKeyValueRange()) { | 
| 330 |         CompletionItem completion; | 
| 331 |         if (usedNames && usedNames->hasSeen(s: name)) { | 
| 332 |             continue; | 
| 333 |         } | 
| 334 |         completion.label = name.toUtf8(); | 
| 335 |         completion.kind = int(CompletionItemKind::Variable); | 
| 336 |         QString detail = u"has type "_s ; | 
| 337 |         if (jsIdentifier.typeName) { | 
| 338 |             if (jsIdentifier.isConst) { | 
| 339 |                 detail.append(v: u"const " ); | 
| 340 |             } | 
| 341 |             detail.append(s: *jsIdentifier.typeName); | 
| 342 |         } else { | 
| 343 |             detail.append(s: jsIdentifier.isConst ? u"const"_s  : u"var"_s ); | 
| 344 |         } | 
| 345 |         completion.detail = detail.toUtf8(); | 
| 346 |         it = completion; | 
| 347 |     } | 
| 348 | } | 
| 349 |  | 
| 350 | void QQmlLSCompletion::methodCompletion(const QQmlJSScope::ConstPtr &scope, | 
| 351 |                                         QDuplicateTracker<QString> *usedNames, | 
| 352 |                                         BackInsertIterator it) const | 
| 353 | { | 
| 354 |     // JS functions in current and base scopes | 
| 355 |     for (const auto &[name, method] : scope->methods().asKeyValueRange()) { | 
| 356 |         if (method.access() != QQmlJSMetaMethod::Public) | 
| 357 |             continue; | 
| 358 |         if (usedNames && usedNames->hasSeen(s: name)) { | 
| 359 |             continue; | 
| 360 |         } | 
| 361 |         CompletionItem completion; | 
| 362 |         completion.label = name.toUtf8(); | 
| 363 |         completion.kind = int(CompletionItemKind::Method); | 
| 364 |         it = completion; | 
| 365 |         // TODO: QQmlLSUtils::reachableSymbols seems to be able to do documentation and detail | 
| 366 |         // and co, it should also be done here if possible. | 
| 367 |     } | 
| 368 | } | 
| 369 |  | 
| 370 | void QQmlLSCompletion::propertyCompletion(const QQmlJSScope::ConstPtr &scope, | 
| 371 |                                           QDuplicateTracker<QString> *usedNames, | 
| 372 |                                           BackInsertIterator it) const | 
| 373 | { | 
| 374 |     for (const auto &[name, property] : scope->properties().asKeyValueRange()) { | 
| 375 |         if (usedNames && usedNames->hasSeen(s: name)) { | 
| 376 |             continue; | 
| 377 |         } | 
| 378 |         CompletionItem completion; | 
| 379 |         completion.label = name.toUtf8(); | 
| 380 |         completion.kind = int(CompletionItemKind::Property); | 
| 381 |         QString detail{ u"has type "_s  }; | 
| 382 |         if (!property.isWritable()) | 
| 383 |             detail.append(s: u"readonly "_s ); | 
| 384 |         detail.append(s: property.typeName().isEmpty() ? u"var"_s  : property.typeName()); | 
| 385 |         completion.detail = detail.toUtf8(); | 
| 386 |         it = completion; | 
| 387 |     } | 
| 388 | } | 
| 389 |  | 
| 390 | void QQmlLSCompletion::enumerationCompletion(const QQmlJSScope::ConstPtr &scope, | 
| 391 |                                              QDuplicateTracker<QString> *usedNames, | 
| 392 |                                              BackInsertIterator it) const | 
| 393 | { | 
| 394 |     for (const QQmlJSMetaEnum &enumerator : scope->enumerations()) { | 
| 395 |         if (usedNames && usedNames->hasSeen(s: enumerator.name())) { | 
| 396 |             continue; | 
| 397 |         } | 
| 398 |         CompletionItem completion; | 
| 399 |         completion.label = enumerator.name().toUtf8(); | 
| 400 |         completion.kind = static_cast<int>(CompletionItemKind::Enum); | 
| 401 |         it = completion; | 
| 402 |     } | 
| 403 | } | 
| 404 |  | 
| 405 | void QQmlLSCompletion::enumerationValueCompletionHelper(const QStringList &enumeratorKeys, | 
| 406 |                                                         BackInsertIterator it) const | 
| 407 | { | 
| 408 |     for (const QString &enumeratorKey : enumeratorKeys) { | 
| 409 |         CompletionItem completion; | 
| 410 |         completion.label = enumeratorKey.toUtf8(); | 
| 411 |         completion.kind = static_cast<int>(CompletionItemKind::EnumMember); | 
| 412 |         it = completion; | 
| 413 |     } | 
| 414 | } | 
| 415 |  | 
| 416 | /*! | 
| 417 | \internal | 
| 418 | Creates completion items for enumerationvalues. | 
| 419 | If enumeratorName is a valid enumerator then only do completion for the requested enumerator, and | 
| 420 | otherwise do completion for \b{all other possible} enumerators. | 
| 421 |  | 
| 422 | For example: | 
| 423 | ``` | 
| 424 | id: someItem | 
| 425 | enum Hello { World } | 
| 426 | enum MyEnum { ValueOne, ValueTwo } | 
| 427 |  | 
| 428 | // Hello does refer to a enumerator: | 
| 429 | property var a: Hello.<complete only World here> | 
| 430 |  | 
| 431 | // someItem does not refer to a enumerator: | 
| 432 | property var b: someItem.<complete World, ValueOne and ValueTwo here> | 
| 433 | ``` | 
| 434 | */ | 
| 435 |  | 
| 436 | void QQmlLSCompletion::enumerationValueCompletion(const QQmlJSScope::ConstPtr &scope, | 
| 437 |                                                   const QString &enumeratorName, | 
| 438 |                                                   BackInsertIterator result) const | 
| 439 | { | 
| 440 |     auto enumerator = scope->enumeration(name: enumeratorName); | 
| 441 |     if (enumerator.isValid()) { | 
| 442 |         enumerationValueCompletionHelper(enumeratorKeys: enumerator.keys(), it: result); | 
| 443 |         return; | 
| 444 |     } | 
| 445 |  | 
| 446 |     for (const QQmlJSMetaEnum &enumerator : scope->enumerations()) { | 
| 447 |         enumerationValueCompletionHelper(enumeratorKeys: enumerator.keys(), it: result); | 
| 448 |     } | 
| 449 | } | 
| 450 |  | 
| 451 | /*! | 
| 452 | \internal | 
| 453 | Calls F on all JavaScript-parents of scope. For example, you can use this method to | 
| 454 | collect all the JavaScript Identifiers from following code: | 
| 455 | ``` | 
| 456 | { // this block statement contains only 'x' | 
| 457 |     let x = 3; | 
| 458 |     { // this block statement contains only 'y', and 'x' has to be retrieved via its parent. | 
| 459 |         let y = 4; | 
| 460 |     } | 
| 461 | } | 
| 462 | ``` | 
| 463 | */ | 
| 464 | template<typename F> | 
| 465 | void collectFromAllJavaScriptParents(const F &&f, const QQmlJSScope::ConstPtr &scope) | 
| 466 | { | 
| 467 |     for (QQmlJSScope::ConstPtr current = scope; current; current = current->parentScope()) { | 
| 468 |         f(current); | 
| 469 |         if (current->scopeType() == QQmlSA::ScopeType::QMLScope) | 
| 470 |             return; | 
| 471 |     } | 
| 472 | } | 
| 473 |  | 
| 474 | /*! | 
| 475 | \internal | 
| 476 | Suggest enumerations (if applicable) and enumeration values from \c scope, for example \c | 
| 477 | Asynchronous from the \c CompilationMode enum: | 
| 478 |  | 
| 479 | \qml | 
| 480 | property var xxx: Component.Asynchronous // Component contains the \c CompilationMode enum | 
| 481 | property var xxx2: CompilationMode.Asynchronous | 
| 482 | \endqml | 
| 483 | */ | 
| 484 | void QQmlLSCompletion::suggestEnumerationsAndEnumerationValues( | 
| 485 |         const QQmlJSScope::ConstPtr &scope, const QString &enumName, | 
| 486 |         QDuplicateTracker<QString> &usedNames, BackInsertIterator result) const | 
| 487 | { | 
| 488 |     enumerationValueCompletion(scope, enumeratorName: enumName, result); | 
| 489 |  | 
| 490 |     // skip enumeration types if already inside an enumeration type | 
| 491 |     if (auto enumerator = scope->enumeration(name: enumName); !enumerator.isValid()) { | 
| 492 |         enumerationCompletion(scope, usedNames: &usedNames, it: result); | 
| 493 |     } | 
| 494 | } | 
| 495 |  | 
| 496 | /*! | 
| 497 | \internal | 
| 498 |  | 
| 499 | Returns the owner of a qualified expression for further resolving, for example: | 
| 500 | 1. \c owner from the \c member ScriptExpression in \c {owner.member}. This happens when completion | 
| 501 | is requested on \c member. | 
| 502 | 2. \c owner from the ScriptBinaryExpression \c {owner.member}. This happens when completion is | 
| 503 | requested on the dot between \c owner and \c member. | 
| 504 | 3. An empty DomItem otherwise. | 
| 505 | */ | 
| 506 | DomItem QQmlLSCompletion::ownerOfQualifiedExpression(const DomItem &qualifiedExpression) const | 
| 507 | { | 
| 508 |     // note: there is an edge case, where the user asks for completion right after the dot | 
| 509 |     // of some qualified expression like `root.hello`. In this case, scriptIdentifier is actually | 
| 510 |     // the BinaryExpression instead of the left-hand-side that has not be written down yet. | 
| 511 |     const bool askForCompletionOnDot = QQmlLSUtils::isFieldMemberExpression(item: qualifiedExpression); | 
| 512 |     const bool hasQualifier = | 
| 513 |             QQmlLSUtils::isFieldMemberAccess(item: qualifiedExpression) || askForCompletionOnDot; | 
| 514 |  | 
| 515 |     if (!hasQualifier) | 
| 516 |         return {}; | 
| 517 |  | 
| 518 |     const DomItem owner = | 
| 519 |             (askForCompletionOnDot ? qualifiedExpression : qualifiedExpression.directParent()) | 
| 520 |                     .field(name: Fields::left); | 
| 521 |     return owner; | 
| 522 | } | 
| 523 |  | 
| 524 | /*! | 
| 525 | \internal | 
| 526 | Generate autocompletions for JS expressions, suggest possible properties, methods, etc. | 
| 527 |  | 
| 528 | If scriptIdentifier is inside a Field Member Expression, like \c{onCompleted} in | 
| 529 | \c{Component.onCompleted} for example, then this method will only suggest properties, methods, etc | 
| 530 | from the correct type. For the previous example that would be properties, methods, etc. from the | 
| 531 | Component attached type. | 
| 532 | */ | 
| 533 | void QQmlLSCompletion::suggestJSExpressionCompletion(const DomItem &scriptIdentifier, | 
| 534 |                                                      BackInsertIterator result) const | 
| 535 | { | 
| 536 |     QDuplicateTracker<QString> usedNames; | 
| 537 |     QQmlJSScope::ConstPtr nearestScope; | 
| 538 |  | 
| 539 |     const DomItem owner = ownerOfQualifiedExpression(qualifiedExpression: scriptIdentifier); | 
| 540 |  | 
| 541 |     if (!owner) { | 
| 542 |         for (QUtf8StringView view : std::array<QUtf8StringView, 3>{ "null" , "false" , "true"  }) { | 
| 543 |             CompletionItem completion; | 
| 544 |             completion.label = view.data(); | 
| 545 |             completion.kind = int(CompletionItemKind::Value); | 
| 546 |             result = completion; | 
| 547 |         } | 
| 548 |         idsCompletions(component: scriptIdentifier.component(), it: result); | 
| 549 |         suggestReachableTypes(el: scriptIdentifier, | 
| 550 |                               options: LocalSymbolsType::Singleton | LocalSymbolsType::AttachedType, | 
| 551 |                               kind: CompletionItemKind::Class, it: result); | 
| 552 |  | 
| 553 |         auto scope = scriptIdentifier.nearestSemanticScope(); | 
| 554 |         if (!scope) | 
| 555 |             return; | 
| 556 |         nearestScope = scope; | 
| 557 |  | 
| 558 |         enumerationCompletion(scope: nearestScope, usedNames: &usedNames, it: result); | 
| 559 |     } else { | 
| 560 |         auto ownerExpressionType = QQmlLSUtils::resolveExpressionType( | 
| 561 |                 item: owner, QQmlLSUtils::ResolveActualTypeForFieldMemberExpression); | 
| 562 |         if (!ownerExpressionType || !ownerExpressionType->semanticScope) | 
| 563 |             return; | 
| 564 |         nearestScope = ownerExpressionType->semanticScope; | 
| 565 |  | 
| 566 |         switch (ownerExpressionType->type) { | 
| 567 |         case QQmlLSUtils::EnumeratorValueIdentifier: | 
| 568 |             return; | 
| 569 |         case QQmlLSUtils::EnumeratorIdentifier: | 
| 570 |             suggestEnumerationsAndEnumerationValues(scope: nearestScope, enumName: *ownerExpressionType->name, | 
| 571 |                                                     usedNames, result); | 
| 572 |             return; | 
| 573 |         case QQmlLSUtils::QmlComponentIdentifier: | 
| 574 |             // Suggest members of the attached type, for example suggest `progress` in | 
| 575 |             // `property real p: Component.progress`. | 
| 576 |             if (QQmlJSScope::ConstPtr attachedType = | 
| 577 |                 ownerExpressionType->semanticScope->attachedType()) { | 
| 578 |                 methodCompletion(scope: attachedType, usedNames: &usedNames, it: result); | 
| 579 |                 propertyCompletion(scope: attachedType, usedNames: &usedNames, it: result); | 
| 580 |                 suggestEnumerationsAndEnumerationValues( | 
| 581 |                         scope: attachedType, enumName: *ownerExpressionType->name, usedNames, result); | 
| 582 |             } | 
| 583 |             Q_FALLTHROUGH(); | 
| 584 |         case QQmlLSUtils::SingletonIdentifier: | 
| 585 |             if (ownerExpressionType->name) | 
| 586 |                 suggestEnumerationsAndEnumerationValues(scope: nearestScope, enumName: *ownerExpressionType->name, | 
| 587 |                                                         usedNames, result); | 
| 588 |             break; | 
| 589 |         default: | 
| 590 |             break; | 
| 591 |         } | 
| 592 |     } | 
| 593 |  | 
| 594 |     Q_ASSERT(nearestScope); | 
| 595 |  | 
| 596 |     methodCompletion(scope: nearestScope, usedNames: &usedNames, it: result); | 
| 597 |     propertyCompletion(scope: nearestScope, usedNames: &usedNames, it: result); | 
| 598 |  | 
| 599 |     if (!owner) { | 
| 600 |         // collect all of the stuff from parents | 
| 601 |         collectFromAllJavaScriptParents( | 
| 602 |                 f: [this, &usedNames, result](const QQmlJSScope::ConstPtr &scope) { | 
| 603 |                     jsIdentifierCompletion(scope, usedNames: &usedNames, it: result); | 
| 604 |                 }, | 
| 605 |                 scope: nearestScope); | 
| 606 |         collectFromAllJavaScriptParents( | 
| 607 |                 f: [this, &usedNames, result](const QQmlJSScope::ConstPtr &scope) { | 
| 608 |                     methodCompletion(scope, usedNames: &usedNames, it: result); | 
| 609 |                 }, | 
| 610 |                 scope: nearestScope); | 
| 611 |         collectFromAllJavaScriptParents( | 
| 612 |                 f: [this, &usedNames, result](const QQmlJSScope::ConstPtr &scope) { | 
| 613 |                     propertyCompletion(scope, usedNames: &usedNames, it: result); | 
| 614 |                 }, | 
| 615 |                 scope: nearestScope); | 
| 616 |  | 
| 617 |         auto file = scriptIdentifier.containingFile().as<QmlFile>(); | 
| 618 |         if (!file) | 
| 619 |             return; | 
| 620 |         auto resolver = file->typeResolver(); | 
| 621 |         if (!resolver) | 
| 622 |             return; | 
| 623 |  | 
| 624 |         const auto globals = resolver->jsGlobalObject(); | 
| 625 |         methodCompletion(scope: globals, usedNames: &usedNames, it: result); | 
| 626 |         propertyCompletion(scope: globals, usedNames: &usedNames, it: result); | 
| 627 |     } | 
| 628 | } | 
| 629 |  | 
| 630 | static const QQmlJSScope *resolve(const QQmlJSScope *current, const QStringList &names) | 
| 631 | { | 
| 632 |     for (const QString &name : names) { | 
| 633 |         if (auto property = current->property(name); property.isValid()) { | 
| 634 |             if (auto propertyType = property.type().get()) { | 
| 635 |                 current = propertyType; | 
| 636 |                 continue; | 
| 637 |             } | 
| 638 |         } | 
| 639 |         return {}; | 
| 640 |     } | 
| 641 |     return current; | 
| 642 | } | 
| 643 |  | 
| 644 | bool QQmlLSCompletion::cursorInFrontOfItem(const DomItem &parentForContext, | 
| 645 |                                            const QQmlLSCompletionPosition &positionInfo) | 
| 646 | { | 
| 647 |     auto fileLocations = FileLocations::treeOf(parentForContext)->info().fullRegion; | 
| 648 |     return positionInfo.offset() <= fileLocations.offset; | 
| 649 | } | 
| 650 |  | 
| 651 | bool QQmlLSCompletion::cursorAfterColon(const DomItem ¤tItem, | 
| 652 |                                         const QQmlLSCompletionPosition &positionInfo) | 
| 653 | { | 
| 654 |     auto location = FileLocations::treeOf(currentItem)->info(); | 
| 655 |     auto region = location.regions.constFind(key: ColonTokenRegion); | 
| 656 |  | 
| 657 |     if (region == location.regions.constEnd()) | 
| 658 |         return false; | 
| 659 |  | 
| 660 |     if (region.value().isValid() && region.value().offset < positionInfo.offset()) { | 
| 661 |         return true; | 
| 662 |     } | 
| 663 |     return false; | 
| 664 | } | 
| 665 |  | 
| 666 | /*! | 
| 667 | \internal | 
| 668 | \brief Mapping from pragma names to allowed pragma values. | 
| 669 |  | 
| 670 | This mapping of pragma names to pragma values is not complete. In fact, it only contains the | 
| 671 | pragma names and values that one should see autocompletion for. | 
| 672 | Some pragmas like FunctionSignatureBehavior or Strict or the Reference/Value of ValueTypeBehavior, | 
| 673 | for example, should currently not be proposed as completion items by qmlls. | 
| 674 |  | 
| 675 | An empty QList-value in the QMap means that the pragma does not accept pragma values. | 
| 676 | */ | 
| 677 | static const QMap<QString, QList<QString>> valuesForPragmas{ | 
| 678 |     { u"ComponentBehavior"_s , { u"Unbound"_s , u"Bound"_s  } }, | 
| 679 |     { u"NativeMethodBehavior"_s , { u"AcceptThisObject"_s , u"RejectThisObject"_s  } }, | 
| 680 |     { u"ListPropertyAssignBehavior"_s , { u"Append"_s , u"Replace"_s , u"ReplaceIfNotDefault"_s  } }, | 
| 681 |     { u"Singleton"_s , {} }, | 
| 682 |     { u"ValueTypeBehavior"_s , { u"Addressable"_s , u"Inaddressable"_s  } }, | 
| 683 | }; | 
| 684 |  | 
| 685 | void QQmlLSCompletion::insidePragmaCompletion(QQmlJS::Dom::DomItem currentItem, | 
| 686 |                                               const QQmlLSCompletionPosition &positionInfo, | 
| 687 |                                               BackInsertIterator result) const | 
| 688 | { | 
| 689 |     if (cursorAfterColon(currentItem, positionInfo)) { | 
| 690 |         const QString name = currentItem.field(name: Fields::name).value().toString(); | 
| 691 |         auto values = valuesForPragmas.constFind(key: name); | 
| 692 |         if (values == valuesForPragmas.constEnd()) | 
| 693 |             return; | 
| 694 |  | 
| 695 |         for (const auto &value : *values) { | 
| 696 |             CompletionItem comp; | 
| 697 |             comp.label = value.toUtf8(); | 
| 698 |             comp.kind = static_cast<int>(CompletionItemKind::Value); | 
| 699 |             result = comp; | 
| 700 |         } | 
| 701 |         return; | 
| 702 |     } | 
| 703 |  | 
| 704 |     for (const auto &pragma : valuesForPragmas.asKeyValueRange()) { | 
| 705 |         CompletionItem comp; | 
| 706 |         comp.label = pragma.first.toUtf8(); | 
| 707 |         if (!pragma.second.isEmpty()) { | 
| 708 |             comp.insertText = QString(pragma.first).append(v: u": " ).toUtf8(); | 
| 709 |         } | 
| 710 |         comp.kind = static_cast<int>(CompletionItemKind::Value); | 
| 711 |         result = comp; | 
| 712 |     } | 
| 713 | } | 
| 714 |  | 
| 715 | void QQmlLSCompletion::insideQmlObjectCompletion(const DomItem &parentForContext, | 
| 716 |                                                  const QQmlLSCompletionPosition &positionInfo, | 
| 717 |                                                  BackInsertIterator result) const | 
| 718 | { | 
| 719 |  | 
| 720 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 721 |  | 
| 722 |     const QQmlJS::SourceLocation leftBrace = regions[LeftBraceRegion]; | 
| 723 |     const QQmlJS::SourceLocation rightBrace = regions[RightBraceRegion]; | 
| 724 |  | 
| 725 |     if (beforeLocation(ctx: positionInfo, right: leftBrace)) { | 
| 726 |         LocalSymbolsTypes options; | 
| 727 |         options.setFlag(flag: LocalSymbolsType::ObjectType); | 
| 728 |         suggestReachableTypes(el: positionInfo.itemAtPosition, options, kind: CompletionItemKind::Constructor, | 
| 729 |                               it: result); | 
| 730 |         if (parentForContext.directParent().internalKind() == DomType::Binding) | 
| 731 |             suggestSnippetsForRightHandSideOfBinding(items: positionInfo.itemAtPosition, result); | 
| 732 |         else | 
| 733 |             suggestSnippetsForLeftHandSideOfBinding(items: positionInfo.itemAtPosition, result); | 
| 734 |  | 
| 735 |         if (QQmlLSUtils::isFieldMemberExpression(item: positionInfo.itemAtPosition)) { | 
| 736 |             /*! | 
| 737 |                 \internal | 
| 738 |                 In the case that a missing identifier is followed by an assignment to the default | 
| 739 |                 property, the parser will create a QmlObject out of both binding and default | 
| 740 |                binding. For example, in \code property int x: root. Item {} \endcode the parser will | 
| 741 |                create one binding containing one QmlObject of type `root.Item`, instead of two | 
| 742 |                bindings (one for `x` and one for the default property). For this special case, if | 
| 743 |                completion is requested inside `root.Item`, then try to also suggest JS expressions. | 
| 744 |  | 
| 745 |                 Note: suggestJSExpressionCompletion() will suggest nothing if the | 
| 746 |                fieldMemberExpression starts with the name of a qualified module or a filename, so | 
| 747 |                this only adds invalid suggestions in the case that there is something shadowing the | 
| 748 |                qualified module name or filename, like a property name for example. | 
| 749 |  | 
| 750 |                 Note 2: This does not happen for field member accesses. For example, in | 
| 751 |                 \code | 
| 752 |                 property int x: root.x | 
| 753 |                 Item {} | 
| 754 |                 \endcode | 
| 755 |                 The parser will create both bindings correctly. | 
| 756 |             */ | 
| 757 |             suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 758 |         } | 
| 759 |         return; | 
| 760 |     } | 
| 761 |  | 
| 762 |     if (betweenLocations(left: leftBrace, positionInfo, right: rightBrace)) { | 
| 763 |         // default/required property completion | 
| 764 |         for (QUtf8StringView view : | 
| 765 |              std::array<QUtf8StringView, 6>{ "" , "readonly " , "default " , "default required " , | 
| 766 |                                              "required default " , "required "  }) { | 
| 767 |             // readonly properties require an initializer | 
| 768 |             if (view != QUtf8StringView("readonly " )) { | 
| 769 |                 result = makeSnippet( | 
| 770 |                         label: QByteArray(view.data()).append(s: "property type name;" ), | 
| 771 |                         insertText: QByteArray(view.data()).append(s: "property ${1:type} ${0:name};" )); | 
| 772 |             } | 
| 773 |  | 
| 774 |             result = makeSnippet( | 
| 775 |                     label: QByteArray(view.data()).append(s: "property type name: value;" ), | 
| 776 |                     insertText: QByteArray(view.data()).append(s: "property ${1:type} ${2:name}: ${0:value};" )); | 
| 777 |         } | 
| 778 |  | 
| 779 |         // signal | 
| 780 |         result = makeSnippet(label: "signal name(arg1:type1, ...)" , insertText: "signal ${1:name}($0)" ); | 
| 781 |  | 
| 782 |         // signal without parameters | 
| 783 |         result = makeSnippet(label: "signal name;" , insertText: "signal ${0:name};" ); | 
| 784 |  | 
| 785 |         // make already existing property required | 
| 786 |         result = makeSnippet(label: "required name;" , insertText: "required ${0:name};" ); | 
| 787 |  | 
| 788 |         // function | 
| 789 |         result = makeSnippet(label: "function name(args...): returnType { statements...}" , | 
| 790 |                              insertText: "function ${1:name}($2): ${3:returnType} {\n\t$0\n}" ); | 
| 791 |  | 
| 792 |         // enum | 
| 793 |         result = makeSnippet(label: "enum name { Values...}" , insertText: "enum ${1:name} {\n\t${0:values}\n}" ); | 
| 794 |  | 
| 795 |         // inline component | 
| 796 |         result = makeSnippet(label: "component Name: BaseType { ... }" , | 
| 797 |                              insertText: "component ${1:name}: ${2:baseType} {\n\t$0\n}" ); | 
| 798 |  | 
| 799 |         suggestBindingCompletion(itemAtPosition: positionInfo.itemAtPosition, it: result); | 
| 800 |  | 
| 801 |         // add Qml Types for default binding | 
| 802 |         const DomItem containingFile = parentForContext.containingFile(); | 
| 803 |         suggestReachableTypes(el: containingFile, options: LocalSymbolsType::ObjectType, | 
| 804 |                               kind: CompletionItemKind::Constructor, it: result); | 
| 805 |         suggestSnippetsForLeftHandSideOfBinding(items: positionInfo.itemAtPosition, result); | 
| 806 |         return; | 
| 807 |     } | 
| 808 | } | 
| 809 |  | 
| 810 | void QQmlLSCompletion::insidePropertyDefinitionCompletion( | 
| 811 |         const DomItem ¤tItem, const QQmlLSCompletionPosition &positionInfo, | 
| 812 |         BackInsertIterator result) const | 
| 813 | { | 
| 814 |     auto info = FileLocations::treeOf(currentItem)->info(); | 
| 815 |     const QQmlJS::SourceLocation propertyKeyword = info.regions[PropertyKeywordRegion]; | 
| 816 |  | 
| 817 |     // do completions for the keywords | 
| 818 |     if (positionInfo.offset() < propertyKeyword.offset + propertyKeyword.length) { | 
| 819 |         const QQmlJS::SourceLocation readonlyKeyword = info.regions[ReadonlyKeywordRegion]; | 
| 820 |         const QQmlJS::SourceLocation defaultKeyword = info.regions[DefaultKeywordRegion]; | 
| 821 |         const QQmlJS::SourceLocation requiredKeyword = info.regions[RequiredKeywordRegion]; | 
| 822 |  | 
| 823 |         bool completeReadonly = true; | 
| 824 |         bool completeRequired = true; | 
| 825 |         bool completeDefault = true; | 
| 826 |  | 
| 827 |         // if there is already a readonly keyword before the cursor: do not auto complete it again | 
| 828 |         if (readonlyKeyword.isValid() && readonlyKeyword.offset < positionInfo.offset()) { | 
| 829 |             completeReadonly = false; | 
| 830 |             // also, required keywords do not like readonly keywords | 
| 831 |             completeRequired = false; | 
| 832 |         } | 
| 833 |  | 
| 834 |         // same for required | 
| 835 |         if (requiredKeyword.isValid() && requiredKeyword.offset < positionInfo.offset()) { | 
| 836 |             completeRequired = false; | 
| 837 |             // also, required keywords do not like readonly keywords | 
| 838 |             completeReadonly = false; | 
| 839 |         } | 
| 840 |  | 
| 841 |         // same for default | 
| 842 |         if (defaultKeyword.isValid() && defaultKeyword.offset < positionInfo.offset()) { | 
| 843 |             completeDefault = false; | 
| 844 |         } | 
| 845 |         auto addCompletionKeyword = [&result](QUtf8StringView view, bool complete) { | 
| 846 |             if (!complete) | 
| 847 |                 return; | 
| 848 |             CompletionItem item; | 
| 849 |             item.label = view.data(); | 
| 850 |             item.kind = int(CompletionItemKind::Keyword); | 
| 851 |             result = item; | 
| 852 |         }; | 
| 853 |         addCompletionKeyword(u8"readonly" , completeReadonly); | 
| 854 |         addCompletionKeyword(u8"required" , completeRequired); | 
| 855 |         addCompletionKeyword(u8"default" , completeDefault); | 
| 856 |         addCompletionKeyword(u8"property" , true); | 
| 857 |  | 
| 858 |         return; | 
| 859 |     } | 
| 860 |  | 
| 861 |     const QQmlJS::SourceLocation propertyIdentifier = info.regions[IdentifierRegion]; | 
| 862 |     if (propertyKeyword.end() <= positionInfo.offset() | 
| 863 |         && positionInfo.offset() < propertyIdentifier.offset) { | 
| 864 |         suggestReachableTypes(el: currentItem, | 
| 865 |                               options: LocalSymbolsType::ObjectType | LocalSymbolsType::ValueType, | 
| 866 |                               kind: CompletionItemKind::Class, it: result); | 
| 867 |     } | 
| 868 |     // do not autocomplete the rest | 
| 869 |     return; | 
| 870 | } | 
| 871 |  | 
| 872 | void QQmlLSCompletion::insideBindingCompletion(const DomItem ¤tItem, | 
| 873 |                                                const QQmlLSCompletionPosition &positionInfo, | 
| 874 |                                                BackInsertIterator result) const | 
| 875 | { | 
| 876 |     const DomItem containingBinding = currentItem.filterUp( | 
| 877 |             filter: [](DomType type, const QQmlJS::Dom::DomItem &) { return type == DomType::Binding; }, | 
| 878 |             options: FilterUpOptions::ReturnOuter); | 
| 879 |  | 
| 880 |     // do scriptidentifiercompletion after the ':' of a binding | 
| 881 |     if (cursorAfterColon(currentItem: containingBinding, positionInfo)) { | 
| 882 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 883 |  | 
| 884 |         if (auto type = QQmlLSUtils::resolveExpressionType(item: currentItem, | 
| 885 |                                                            QQmlLSUtils::ResolveOwnerType)) { | 
| 886 |             const QStringList names = currentItem.field(name: Fields::name).toString().split(sep: u'.'); | 
| 887 |             const QQmlJSScope *current = resolve(current: type->semanticScope.get(), names); | 
| 888 |             // add type names when binding to an object type or a property with var type | 
| 889 |             if (!current || current->accessSemantics() == QQmlSA::AccessSemantics::Reference) { | 
| 890 |                 LocalSymbolsTypes options; | 
| 891 |                 options.setFlag(flag: LocalSymbolsType::ObjectType); | 
| 892 |                 suggestReachableTypes(el: positionInfo.itemAtPosition, options, | 
| 893 |                                       kind: CompletionItemKind::Constructor, it: result); | 
| 894 |                 suggestSnippetsForRightHandSideOfBinding(items: positionInfo.itemAtPosition, result); | 
| 895 |             } | 
| 896 |         } | 
| 897 |         return; | 
| 898 |     } | 
| 899 |  | 
| 900 |     // ignore the binding if asking for completion in front of the binding | 
| 901 |     if (cursorInFrontOfItem(parentForContext: containingBinding, positionInfo)) { | 
| 902 |         insideQmlObjectCompletion(parentForContext: currentItem.containingObject(), positionInfo, result); | 
| 903 |         return; | 
| 904 |     } | 
| 905 |  | 
| 906 |     const DomItem containingObject = currentItem.qmlObject(); | 
| 907 |  | 
| 908 |     suggestBindingCompletion(itemAtPosition: positionInfo.itemAtPosition, it: result); | 
| 909 |  | 
| 910 |     // add Qml Types for default binding | 
| 911 |     suggestReachableTypes(el: positionInfo.itemAtPosition, options: LocalSymbolsType::ObjectType, | 
| 912 |                           kind: CompletionItemKind::Constructor, it: result); | 
| 913 |     suggestSnippetsForLeftHandSideOfBinding(items: positionInfo.itemAtPosition, result); | 
| 914 | } | 
| 915 |  | 
| 916 | void QQmlLSCompletion::insideImportCompletion(const DomItem ¤tItem, | 
| 917 |                                               const QQmlLSCompletionPosition &positionInfo, | 
| 918 |                                               BackInsertIterator result) const | 
| 919 | { | 
| 920 |     const DomItem containingFile = currentItem.containingFile(); | 
| 921 |     insideImportCompletionHelper(file: containingFile, positionInfo, it: result); | 
| 922 |  | 
| 923 |     // when in front of the import statement: propose types for root Qml Object completion | 
| 924 |     if (cursorInFrontOfItem(parentForContext: currentItem, positionInfo)) { | 
| 925 |         suggestReachableTypes(el: containingFile, options: LocalSymbolsType::ObjectType, | 
| 926 |                               kind: CompletionItemKind::Constructor, it: result); | 
| 927 |     } | 
| 928 | } | 
| 929 |  | 
| 930 | void QQmlLSCompletion::insideQmlFileCompletion(const DomItem ¤tItem, | 
| 931 |                                                const QQmlLSCompletionPosition &positionInfo, | 
| 932 |                                                BackInsertIterator result) const | 
| 933 | { | 
| 934 |     const DomItem containingFile = currentItem.containingFile(); | 
| 935 |     // completions for code outside the root Qml Object | 
| 936 |     // global completions | 
| 937 |     if (positionInfo.cursorPosition.atLineStart()) { | 
| 938 |         if (positionInfo.cursorPosition.base().isEmpty()) { | 
| 939 |             for (const QStringView &s : std::array<QStringView, 2>({ u"pragma" , u"import"  })) { | 
| 940 |                 CompletionItem comp; | 
| 941 |                 comp.label = s.toUtf8(); | 
| 942 |                 comp.kind = int(CompletionItemKind::Keyword); | 
| 943 |                 result = comp; | 
| 944 |             } | 
| 945 |         } | 
| 946 |     } | 
| 947 |     // Types for root Qml Object completion | 
| 948 |     suggestReachableTypes(el: containingFile, options: LocalSymbolsType::ObjectType, | 
| 949 |                           kind: CompletionItemKind::Constructor, it: result); | 
| 950 | } | 
| 951 |  | 
| 952 | /*! | 
| 953 | \internal | 
| 954 | Generate the snippets for let, var and const variable declarations. | 
| 955 | */ | 
| 956 | void QQmlLSCompletion::suggestVariableDeclarationStatementCompletion( | 
| 957 |         BackInsertIterator result, AppendOption option) const | 
| 958 | { | 
| 959 |     // let/var/const statement | 
| 960 |     for (auto view : std::array<QUtf8StringView, 3>{ "let" , "var" , "const"  }) { | 
| 961 |         auto snippet = makeSnippet(label: QByteArray(view.data()).append(s: " variable = value" ), | 
| 962 |                              insertText: QByteArray(view.data()).append(s: " ${1:variable} = $0" )); | 
| 963 |         if (option == AppendSemicolon) { | 
| 964 |             snippet.insertText->append(s: ";" ); | 
| 965 |             snippet.label.append(s: ";" ); | 
| 966 |         } | 
| 967 |         result = snippet; | 
| 968 |     } | 
| 969 | } | 
| 970 |  | 
| 971 | /*! | 
| 972 | \internal | 
| 973 | Generate the snippets for case and default statements. | 
| 974 | */ | 
| 975 | void QQmlLSCompletion::suggestCaseAndDefaultStatementCompletion(BackInsertIterator result) const | 
| 976 | { | 
| 977 |     // case snippet | 
| 978 |     result = makeSnippet(label: "case value: statements..." , insertText: "case ${1:value}:\n\t$0" ); | 
| 979 |     // case + brackets snippet | 
| 980 |     result = makeSnippet(label: "case value: { statements... }" , insertText: "case ${1:value}: {\n\t$0\n}" ); | 
| 981 |  | 
| 982 |     // default snippet | 
| 983 |     result = makeSnippet(label: "default: statements..." , insertText: "default:\n\t$0" ); | 
| 984 |     // default + brackets snippet | 
| 985 |     result = makeSnippet(label: "default: { statements... }" , insertText: "default: {\n\t$0\n}" ); | 
| 986 | } | 
| 987 |  | 
| 988 | /*! | 
| 989 | \internal | 
| 990 | Break and continue can be inserted only in following situations: | 
| 991 | \list | 
| 992 |     \li Break and continue inside a loop. | 
| 993 |     \li Break inside a (nested) LabelledStatement | 
| 994 |     \li Break inside a (nested) SwitchStatement | 
| 995 | \endlist | 
| 996 | */ | 
| 997 | void QQmlLSCompletion::suggestContinueAndBreakStatementIfNeeded(const DomItem &itemAtPosition, | 
| 998 |                                                                 BackInsertIterator result) const | 
| 999 | { | 
| 1000 |     bool alreadyInLabel = false; | 
| 1001 |     bool alreadyInSwitch = false; | 
| 1002 |     for (DomItem current = itemAtPosition; current; current = current.directParent()) { | 
| 1003 |         switch (current.internalKind()) { | 
| 1004 |         case DomType::ScriptExpression: | 
| 1005 |             // reached end of script expression | 
| 1006 |             return; | 
| 1007 |  | 
| 1008 |         case DomType::ScriptForStatement: | 
| 1009 |         case DomType::ScriptForEachStatement: | 
| 1010 |         case DomType::ScriptWhileStatement: | 
| 1011 |         case DomType::ScriptDoWhileStatement: { | 
| 1012 |             CompletionItem continueKeyword; | 
| 1013 |             continueKeyword.label = "continue" ; | 
| 1014 |             continueKeyword.kind = int(CompletionItemKind::Keyword); | 
| 1015 |             result = continueKeyword; | 
| 1016 |  | 
| 1017 |             // do not add break twice | 
| 1018 |             if (!alreadyInSwitch && !alreadyInLabel) { | 
| 1019 |                 CompletionItem breakKeyword; | 
| 1020 |                 breakKeyword.label = "break" ; | 
| 1021 |                 breakKeyword.kind = int(CompletionItemKind::Keyword); | 
| 1022 |                 result = breakKeyword; | 
| 1023 |             } | 
| 1024 |             // early exit: cannot suggest more completions | 
| 1025 |             return; | 
| 1026 |         } | 
| 1027 |         case DomType::ScriptSwitchStatement: { | 
| 1028 |             // check if break was already inserted | 
| 1029 |             if (alreadyInSwitch || alreadyInLabel) | 
| 1030 |                 break; | 
| 1031 |             alreadyInSwitch = true; | 
| 1032 |  | 
| 1033 |             CompletionItem breakKeyword; | 
| 1034 |             breakKeyword.label = "break" ; | 
| 1035 |             breakKeyword.kind = int(CompletionItemKind::Keyword); | 
| 1036 |             result = breakKeyword; | 
| 1037 |             break; | 
| 1038 |         } | 
| 1039 |         case DomType::ScriptLabelledStatement: { | 
| 1040 |             // check if break was already inserted because of switch or loop | 
| 1041 |             if (alreadyInSwitch || alreadyInLabel) | 
| 1042 |                 break; | 
| 1043 |             alreadyInLabel = true; | 
| 1044 |  | 
| 1045 |             CompletionItem breakKeyword; | 
| 1046 |             breakKeyword.label = "break" ; | 
| 1047 |             breakKeyword.kind = int(CompletionItemKind::Keyword); | 
| 1048 |             result = breakKeyword; | 
| 1049 |             break; | 
| 1050 |         } | 
| 1051 |         default: | 
| 1052 |             break; | 
| 1053 |         } | 
| 1054 |     } | 
| 1055 | } | 
| 1056 |  | 
| 1057 | /*! | 
| 1058 | \internal | 
| 1059 | Generates snippets or keywords for all possible JS statements where it makes sense. To use whenever | 
| 1060 | any JS statement can be expected, but when no JS statement is there yet. | 
| 1061 |  | 
| 1062 | Only generates JS expression completions when itemAtPosition is a qualified name. | 
| 1063 |  | 
| 1064 | Here is a list of statements that do \e{not} get any snippets: | 
| 1065 | \list | 
| 1066 |     \li BlockStatement does not need a code snippet, editors automatically include the closing | 
| 1067 | bracket anyway. \li EmptyStatement completion would only generate a single \c{;} \li | 
| 1068 | ExpressionStatement completion cannot generate any snippet, only identifiers \li WithStatement | 
| 1069 | completion is not recommended: qmllint will warn about usage of with statements \li | 
| 1070 | LabelledStatement completion might need to propose labels (TODO?) \li DebuggerStatement completion | 
| 1071 | does not strike as being very useful \endlist | 
| 1072 | */ | 
| 1073 | void QQmlLSCompletion::suggestJSStatementCompletion(const DomItem &itemAtPosition, | 
| 1074 |                                                     BackInsertIterator result) const | 
| 1075 | { | 
| 1076 |     suggestJSExpressionCompletion(scriptIdentifier: itemAtPosition, result); | 
| 1077 |  | 
| 1078 |     if (QQmlLSUtils::isFieldMemberAccess(item: itemAtPosition) | 
| 1079 |         || QQmlLSUtils::isFieldMemberExpression(item: itemAtPosition)) | 
| 1080 |         return; | 
| 1081 |  | 
| 1082 |     // expression statements | 
| 1083 |     suggestVariableDeclarationStatementCompletion(result); | 
| 1084 |     // block statement | 
| 1085 |     result = makeSnippet(label: "{ statements... }" , insertText: "{\n\t$0\n}" ); | 
| 1086 |  | 
| 1087 |     // if + brackets statement | 
| 1088 |     result = makeSnippet(label: "if (condition) { statements }" , insertText: "if ($1) {\n\t$0\n}" ); | 
| 1089 |  | 
| 1090 |     // do statement | 
| 1091 |     result = makeSnippet(label: "do { statements } while (condition);" , insertText: "do {\n\t$1\n} while ($0);" ); | 
| 1092 |  | 
| 1093 |     // while + brackets statement | 
| 1094 |     result = makeSnippet(label: "while (condition) { statements...}" , insertText: "while ($1) {\n\t$0\n}" ); | 
| 1095 |  | 
| 1096 |     // for + brackets loop statement | 
| 1097 |     result = makeSnippet(label: "for (initializer; condition; increment) { statements... }" , | 
| 1098 |                          insertText: "for ($1;$2;$3) {\n\t$0\n}" ); | 
| 1099 |  | 
| 1100 |     // for ... in + brackets loop statement | 
| 1101 |     result = makeSnippet(label: "for (property in object) { statements... }" , insertText: "for ($1 in $2) {\n\t$0\n}" ); | 
| 1102 |  | 
| 1103 |     // for ... of + brackets loop statement | 
| 1104 |     result = makeSnippet(label: "for (element of array) { statements... }" , insertText: "for ($1 of $2) {\n\t$0\n}" ); | 
| 1105 |  | 
| 1106 |     // try + catch statement | 
| 1107 |     result = makeSnippet(label: "try { statements... } catch(error) { statements... }" , | 
| 1108 |                          insertText: "try {\n\t$1\n} catch($2) {\n\t$0\n}" ); | 
| 1109 |  | 
| 1110 |     // try + finally statement | 
| 1111 |     result = makeSnippet(label: "try { statements... } finally { statements... }" , | 
| 1112 |                          insertText: "try {\n\t$1\n} finally {\n\t$0\n}" ); | 
| 1113 |  | 
| 1114 |     // try + catch + finally statement | 
| 1115 |     result = makeSnippet( | 
| 1116 |             label: "try { statements... } catch(error) { statements... } finally { statements... }" , | 
| 1117 |             insertText: "try {\n\t$1\n} catch($2) {\n\t$3\n} finally {\n\t$0\n}" ); | 
| 1118 |  | 
| 1119 |     // one can always assume that JS code in QML is inside a function, so always propose `return` | 
| 1120 |     for (auto &&view : { "return"_ba , "throw"_ba  }) { | 
| 1121 |         CompletionItem item; | 
| 1122 |         item.label = std::move(view); | 
| 1123 |         item.kind = int(CompletionItemKind::Keyword); | 
| 1124 |         result = item; | 
| 1125 |     } | 
| 1126 |  | 
| 1127 |     // rules for case+default statements: | 
| 1128 |     // 1) when inside a CaseBlock, or | 
| 1129 |     // 2) inside a CaseClause, as an (non-nested) element of the CaseClause statementlist. | 
| 1130 |     // 3) inside a DefaultClause, as an (non-nested) element of the DefaultClause statementlist, | 
| 1131 |     // | 
| 1132 |     // switch (x) { | 
| 1133 |     // // (1) | 
| 1134 |     // case 1: | 
| 1135 |     //      myProperty = 5; | 
| 1136 |     //      // (2) -> could be another statement of current case, but also a new case or default! | 
| 1137 |     // default: | 
| 1138 |     //      myProperty = 5; | 
| 1139 |     //      // (3) -> could be another statement of current default, but also a new case or default! | 
| 1140 |     // } | 
| 1141 |     const DomType currentKind = itemAtPosition.internalKind(); | 
| 1142 |     const DomType parentKind = itemAtPosition.directParent().internalKind(); | 
| 1143 |     if (currentKind == DomType::ScriptCaseBlock || currentKind == DomType::ScriptCaseClause | 
| 1144 |         || currentKind == DomType::ScriptDefaultClause | 
| 1145 |         || (currentKind == DomType::List | 
| 1146 |             && (parentKind == DomType::ScriptCaseClause | 
| 1147 |                 || parentKind == DomType::ScriptDefaultClause))) { | 
| 1148 |         suggestCaseAndDefaultStatementCompletion(result); | 
| 1149 |     } | 
| 1150 |     suggestContinueAndBreakStatementIfNeeded(itemAtPosition, result); | 
| 1151 | } | 
| 1152 |  | 
| 1153 | void QQmlLSCompletion::insideForStatementCompletion(const DomItem &parentForContext, | 
| 1154 |                                                     const QQmlLSCompletionPosition &positionInfo, | 
| 1155 |                                                     BackInsertIterator result) const | 
| 1156 | { | 
| 1157 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1158 |  | 
| 1159 |     const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion]; | 
| 1160 |     const QQmlJS::SourceLocation firstSemicolon = regions[FirstSemicolonTokenRegion]; | 
| 1161 |     const QQmlJS::SourceLocation secondSemicolon = regions[SecondSemicolonRegion]; | 
| 1162 |     const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion]; | 
| 1163 |  | 
| 1164 |     if (betweenLocations(left: leftParenthesis, positionInfo, right: firstSemicolon)) { | 
| 1165 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1166 |         suggestVariableDeclarationStatementCompletion(result, | 
| 1167 |                                                       option: AppendOption::AppendNothing); | 
| 1168 |         return; | 
| 1169 |     } | 
| 1170 |     if (betweenLocations(left: firstSemicolon, positionInfo, right: secondSemicolon) | 
| 1171 |         || betweenLocations(left: secondSemicolon, positionInfo, right: rightParenthesis)) { | 
| 1172 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1173 |         return; | 
| 1174 |     } | 
| 1175 |  | 
| 1176 |     if (afterLocation(left: rightParenthesis, positionInfo)) { | 
| 1177 |         suggestJSStatementCompletion(itemAtPosition: positionInfo.itemAtPosition, result); | 
| 1178 |         return; | 
| 1179 |     } | 
| 1180 | } | 
| 1181 |  | 
| 1182 | void QQmlLSCompletion::insideScriptLiteralCompletion(const DomItem ¤tItem, | 
| 1183 |                                                      const QQmlLSCompletionPosition &positionInfo, | 
| 1184 |                                                      BackInsertIterator result) const | 
| 1185 | { | 
| 1186 |     Q_UNUSED(currentItem); | 
| 1187 |     if (positionInfo.cursorPosition.base().isEmpty()) { | 
| 1188 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1189 |         return; | 
| 1190 |     } | 
| 1191 | } | 
| 1192 |  | 
| 1193 | void QQmlLSCompletion::insideCallExpression(const DomItem ¤tItem, | 
| 1194 |                                             const QQmlLSCompletionPosition &positionInfo, | 
| 1195 |                                             BackInsertIterator result) const | 
| 1196 | { | 
| 1197 |     const auto regions = FileLocations::treeOf(currentItem)->info().regions; | 
| 1198 |     const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion]; | 
| 1199 |     const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion]; | 
| 1200 |     if (beforeLocation(ctx: positionInfo, right: leftParenthesis)) { | 
| 1201 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1202 |         return; | 
| 1203 |     } | 
| 1204 |     if (betweenLocations(left: leftParenthesis, positionInfo, right: rightParenthesis)) { | 
| 1205 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1206 |         return; | 
| 1207 |     } | 
| 1208 | } | 
| 1209 |  | 
| 1210 | void QQmlLSCompletion::insideIfStatement(const DomItem ¤tItem, | 
| 1211 |                                          const QQmlLSCompletionPosition &positionInfo, | 
| 1212 |                                          BackInsertIterator result) const | 
| 1213 | { | 
| 1214 |     const auto regions = FileLocations::treeOf(currentItem)->info().regions; | 
| 1215 |     const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion]; | 
| 1216 |     const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion]; | 
| 1217 |     const QQmlJS::SourceLocation elseKeyword = regions[ElseKeywordRegion]; | 
| 1218 |  | 
| 1219 |     if (betweenLocations(left: leftParenthesis, positionInfo, right: rightParenthesis)) { | 
| 1220 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1221 |         return; | 
| 1222 |     } | 
| 1223 |     if (betweenLocations(left: rightParenthesis, positionInfo, right: elseKeyword)) { | 
| 1224 |         suggestJSStatementCompletion(itemAtPosition: positionInfo.itemAtPosition, result); | 
| 1225 |         return; | 
| 1226 |     } | 
| 1227 |     if (afterLocation(left: elseKeyword, positionInfo)) { | 
| 1228 |         suggestJSStatementCompletion(itemAtPosition: positionInfo.itemAtPosition, result); | 
| 1229 |         return; | 
| 1230 |     } | 
| 1231 | } | 
| 1232 |  | 
| 1233 | void QQmlLSCompletion::insideReturnStatement(const DomItem ¤tItem, | 
| 1234 |                                              const QQmlLSCompletionPosition &positionInfo, | 
| 1235 |                                              BackInsertIterator result) const | 
| 1236 | { | 
| 1237 |     const auto regions = FileLocations::treeOf(currentItem)->info().regions; | 
| 1238 |     const QQmlJS::SourceLocation returnKeyword = regions[ReturnKeywordRegion]; | 
| 1239 |  | 
| 1240 |     if (afterLocation(left: returnKeyword, positionInfo)) { | 
| 1241 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1242 |         return; | 
| 1243 |     } | 
| 1244 | } | 
| 1245 |  | 
| 1246 | void QQmlLSCompletion::insideWhileStatement(const DomItem ¤tItem, | 
| 1247 |                                             const QQmlLSCompletionPosition &positionInfo, | 
| 1248 |                                             BackInsertIterator result) const | 
| 1249 | { | 
| 1250 |     const auto regions = FileLocations::treeOf(currentItem)->info().regions; | 
| 1251 |     const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion]; | 
| 1252 |     const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion]; | 
| 1253 |  | 
| 1254 |     if (betweenLocations(left: leftParenthesis, positionInfo, right: rightParenthesis)) { | 
| 1255 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1256 |         return; | 
| 1257 |     } | 
| 1258 |     if (afterLocation(left: rightParenthesis, positionInfo)) { | 
| 1259 |         suggestJSStatementCompletion(itemAtPosition: positionInfo.itemAtPosition, result); | 
| 1260 |         return; | 
| 1261 |     } | 
| 1262 | } | 
| 1263 |  | 
| 1264 | void QQmlLSCompletion::insideDoWhileStatement(const DomItem &parentForContext, | 
| 1265 |                                               const QQmlLSCompletionPosition &positionInfo, | 
| 1266 |                                               BackInsertIterator result) const | 
| 1267 | { | 
| 1268 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1269 |     const QQmlJS::SourceLocation doKeyword = regions[DoKeywordRegion]; | 
| 1270 |     const QQmlJS::SourceLocation whileKeyword = regions[WhileKeywordRegion]; | 
| 1271 |     const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion]; | 
| 1272 |     const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion]; | 
| 1273 |  | 
| 1274 |     if (betweenLocations(left: doKeyword, positionInfo, right: whileKeyword)) { | 
| 1275 |         suggestJSStatementCompletion(itemAtPosition: positionInfo.itemAtPosition, result); | 
| 1276 |         return; | 
| 1277 |     } | 
| 1278 |     if (betweenLocations(left: leftParenthesis, positionInfo, right: rightParenthesis)) { | 
| 1279 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1280 |         return; | 
| 1281 |     } | 
| 1282 | } | 
| 1283 |  | 
| 1284 | void QQmlLSCompletion::insideForEachStatement(const DomItem &parentForContext, | 
| 1285 |                                               const QQmlLSCompletionPosition &positionInfo, | 
| 1286 |                                               BackInsertIterator result) const | 
| 1287 | { | 
| 1288 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1289 |  | 
| 1290 |     const QQmlJS::SourceLocation inOf = regions[InOfTokenRegion]; | 
| 1291 |     const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion]; | 
| 1292 |     const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion]; | 
| 1293 |  | 
| 1294 |     if (betweenLocations(left: leftParenthesis, positionInfo, right: inOf)) { | 
| 1295 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1296 |         suggestVariableDeclarationStatementCompletion(result); | 
| 1297 |         return; | 
| 1298 |     } | 
| 1299 |     if (betweenLocations(left: inOf, positionInfo, right: rightParenthesis)) { | 
| 1300 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1301 |         return; | 
| 1302 |     } | 
| 1303 |  | 
| 1304 |     if (afterLocation(left: rightParenthesis, positionInfo)) { | 
| 1305 |         suggestJSStatementCompletion(itemAtPosition: positionInfo.itemAtPosition, result); | 
| 1306 |         return; | 
| 1307 |     } | 
| 1308 | } | 
| 1309 |  | 
| 1310 | void QQmlLSCompletion::insideSwitchStatement(const DomItem &parentForContext, | 
| 1311 |                                              const QQmlLSCompletionPosition positionInfo, | 
| 1312 |                                              BackInsertIterator result) const | 
| 1313 | { | 
| 1314 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1315 |  | 
| 1316 |     const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion]; | 
| 1317 |     const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion]; | 
| 1318 |  | 
| 1319 |     if (betweenLocations(left: leftParenthesis, positionInfo, right: rightParenthesis)) { | 
| 1320 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1321 |         return; | 
| 1322 |     } | 
| 1323 | } | 
| 1324 |  | 
| 1325 | void QQmlLSCompletion::insideCaseClause(const DomItem &parentForContext, | 
| 1326 |                                         const QQmlLSCompletionPosition &positionInfo, | 
| 1327 |                                         BackInsertIterator result) const | 
| 1328 | { | 
| 1329 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1330 |  | 
| 1331 |     const QQmlJS::SourceLocation caseKeyword = regions[CaseKeywordRegion]; | 
| 1332 |     const QQmlJS::SourceLocation colonToken = regions[ColonTokenRegion]; | 
| 1333 |  | 
| 1334 |     if (betweenLocations(left: caseKeyword, positionInfo, right: colonToken)) { | 
| 1335 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1336 |         return; | 
| 1337 |     } | 
| 1338 |     if (afterLocation(left: colonToken, positionInfo)) { | 
| 1339 |         suggestJSStatementCompletion(itemAtPosition: positionInfo.itemAtPosition, result); | 
| 1340 |         return; | 
| 1341 |     } | 
| 1342 |  | 
| 1343 | } | 
| 1344 |  | 
| 1345 | /*! | 
| 1346 | \internal | 
| 1347 | Checks if a case or default clause does happen before ctx in the code. | 
| 1348 | */ | 
| 1349 | bool QQmlLSCompletion::isCaseOrDefaultBeforeCtx(const DomItem ¤tClause, | 
| 1350 |                                                 const QQmlLSCompletionPosition &positionInfo, | 
| 1351 |                                                 FileLocationRegion keywordRegion) const | 
| 1352 | { | 
| 1353 |     Q_ASSERT(keywordRegion == QQmlJS::Dom::CaseKeywordRegion | 
| 1354 |              || keywordRegion == QQmlJS::Dom::DefaultKeywordRegion); | 
| 1355 |  | 
| 1356 |     if (!currentClause) | 
| 1357 |         return false; | 
| 1358 |  | 
| 1359 |     const auto token = FileLocations::treeOf(currentClause)->info().regions[keywordRegion]; | 
| 1360 |     if (afterLocation(left: token, positionInfo)) | 
| 1361 |         return true; | 
| 1362 |  | 
| 1363 |     return false; | 
| 1364 | } | 
| 1365 |  | 
| 1366 | /*! | 
| 1367 | \internal | 
| 1368 |  | 
| 1369 | Search for a `case ...:` or a `default: ` clause happening before ctx, and return the | 
| 1370 | corresponding DomItem of type DomType::CaseClauses or DomType::DefaultClause. | 
| 1371 |  | 
| 1372 | Return an empty DomItem if neither case nor default was found. | 
| 1373 | */ | 
| 1374 | DomItem | 
| 1375 | QQmlLSCompletion::previousCaseOfCaseBlock(const DomItem &parentForContext, | 
| 1376 |                                           const QQmlLSCompletionPosition &positionInfo) const | 
| 1377 | { | 
| 1378 |     const DomItem caseClauses = parentForContext.field(name: Fields::caseClauses); | 
| 1379 |     for (int i = 0; i < caseClauses.indexes(); ++i) { | 
| 1380 |         const DomItem currentClause = caseClauses.index(i); | 
| 1381 |         if (isCaseOrDefaultBeforeCtx(currentClause, positionInfo, keywordRegion: QQmlJS::Dom::CaseKeywordRegion)) { | 
| 1382 |             return currentClause; | 
| 1383 |         } | 
| 1384 |     } | 
| 1385 |  | 
| 1386 |     const DomItem defaultClause = parentForContext.field(name: Fields::defaultClause); | 
| 1387 |     if (isCaseOrDefaultBeforeCtx(currentClause: defaultClause, positionInfo, keywordRegion: QQmlJS::Dom::DefaultKeywordRegion)) | 
| 1388 |         return parentForContext.field(name: Fields::defaultClause); | 
| 1389 |  | 
| 1390 |     const DomItem moreCaseClauses = parentForContext.field(name: Fields::moreCaseClauses); | 
| 1391 |     for (int i = 0; i < moreCaseClauses.indexes(); ++i) { | 
| 1392 |         const DomItem currentClause = moreCaseClauses.index(i); | 
| 1393 |         if (isCaseOrDefaultBeforeCtx(currentClause, positionInfo, keywordRegion: QQmlJS::Dom::CaseKeywordRegion)) { | 
| 1394 |             return currentClause; | 
| 1395 |         } | 
| 1396 |     } | 
| 1397 |  | 
| 1398 |     return {}; | 
| 1399 | } | 
| 1400 |  | 
| 1401 | void QQmlLSCompletion::insideCaseBlock(const DomItem &parentForContext, | 
| 1402 |                                        const QQmlLSCompletionPosition &positionInfo, | 
| 1403 |                                        BackInsertIterator result) const | 
| 1404 | { | 
| 1405 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1406 |  | 
| 1407 |     const QQmlJS::SourceLocation leftBrace = regions[LeftBraceRegion]; | 
| 1408 |     const QQmlJS::SourceLocation rightBrace = regions[RightBraceRegion]; | 
| 1409 |  | 
| 1410 |     if (!betweenLocations(left: leftBrace, positionInfo, right: rightBrace)) | 
| 1411 |         return; | 
| 1412 |  | 
| 1413 |     // TODO: looks fishy | 
| 1414 |     // if there is a previous case or default clause, you can still add statements to it | 
| 1415 |     if (const auto previousCase = previousCaseOfCaseBlock(parentForContext, positionInfo)) { | 
| 1416 |         suggestJSStatementCompletion(itemAtPosition: previousCase, result); | 
| 1417 |         return; | 
| 1418 |     } | 
| 1419 |  | 
| 1420 |     // otherwise, only complete case and default | 
| 1421 |     suggestCaseAndDefaultStatementCompletion(result); | 
| 1422 | } | 
| 1423 |  | 
| 1424 | void QQmlLSCompletion::insideDefaultClause(const DomItem &parentForContext, | 
| 1425 |                                            const QQmlLSCompletionPosition &positionInfo, | 
| 1426 |                                            BackInsertIterator result) const | 
| 1427 | { | 
| 1428 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1429 |  | 
| 1430 |     const QQmlJS::SourceLocation colonToken = regions[ColonTokenRegion]; | 
| 1431 |  | 
| 1432 |     if (afterLocation(left: colonToken, positionInfo)) { | 
| 1433 |         suggestJSStatementCompletion(itemAtPosition: positionInfo.itemAtPosition, result); | 
| 1434 |         return ; | 
| 1435 |     } | 
| 1436 | } | 
| 1437 |  | 
| 1438 | void QQmlLSCompletion::insideBinaryExpressionCompletion( | 
| 1439 |         const DomItem &parentForContext, const QQmlLSCompletionPosition &positionInfo, | 
| 1440 |         BackInsertIterator result) const | 
| 1441 | { | 
| 1442 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1443 |  | 
| 1444 |     const QQmlJS::SourceLocation operatorLocation = regions[OperatorTokenRegion]; | 
| 1445 |  | 
| 1446 |     if (beforeLocation(ctx: positionInfo, right: operatorLocation)) { | 
| 1447 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1448 |         return; | 
| 1449 |     } | 
| 1450 |     if (afterLocation(left: operatorLocation, positionInfo)) { | 
| 1451 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1452 |         return; | 
| 1453 |     } | 
| 1454 | } | 
| 1455 |  | 
| 1456 | /*! | 
| 1457 | \internal | 
| 1458 | Doing completion in variable declarations requires taking a look at all different cases: | 
| 1459 |  | 
| 1460 | \list | 
| 1461 |     \li Normal variable names, like \c{let helloWorld = 123;} | 
| 1462 |         In this case, only autocomplete scriptexpressionidentifiers after the '=' token. | 
| 1463 |         Do not propose existing names for the variable name, because the variable name needs to be | 
| 1464 |         an identifier that is not used anywhere (to avoid shadowing and confusing code), | 
| 1465 |  | 
| 1466 |     \li Deconstructed arrays, like \c{let [ helloWorld, ] = [ 123, ];} | 
| 1467 |         In this case, only autocomplete scriptexpressionidentifiers after the '=' token. | 
| 1468 |         Do not propose already existing identifiers inside the left hand side array. | 
| 1469 |  | 
| 1470 |     \li Deconstructed arrays with initializers, like \c{let [ helloWorld = someVar, ] = [ 123, ];} | 
| 1471 |         Note: this assigns the value of someVar to helloWorld if the right hand side's first element | 
| 1472 |         is undefined or does not exist. | 
| 1473 |  | 
| 1474 |         In this case, only autocomplete scriptexpressionidentifiers after the '=' tokens. | 
| 1475 |         Only propose already existing identifiers inside the left hand side array when behind a '=' | 
| 1476 |     token. | 
| 1477 |  | 
| 1478 |     \li Deconstructed Objects, like \c{let { helloWorld, } = { helloWorld: 123, };} | 
| 1479 |         In this case, only autocomplete scriptexpressionidentifiers after the '=' token. | 
| 1480 |         Do not propose already existing identifiers inside the left hand side object. | 
| 1481 |  | 
| 1482 |     \li Deconstructed Objects with initializers, like \c{let { helloWorld = someVar, } = {};} | 
| 1483 |         Note: this assigns the value of someVar to helloWorld if the right hand side's object does | 
| 1484 |         not have a property called 'helloWorld'. | 
| 1485 |  | 
| 1486 |         In this case, only autocomplete scriptexpressionidentifiers after the '=' token. | 
| 1487 |         Only propose already existing identifiers inside the left hand side object when behind a '=' | 
| 1488 |         token. | 
| 1489 |  | 
| 1490 |     \li Finally, you are allowed to nest and combine all above possibilities together for all your | 
| 1491 |         deconstruction needs, so the exact same completion needs to be done for | 
| 1492 |         DomType::ScriptPatternElement too. | 
| 1493 |  | 
| 1494 | \endlist | 
| 1495 | */ | 
| 1496 | void QQmlLSCompletion::insideScriptPattern(const DomItem &parentForContext, | 
| 1497 |                                            const QQmlLSCompletionPosition &positionInfo, | 
| 1498 |                                            BackInsertIterator result) const | 
| 1499 | { | 
| 1500 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1501 |  | 
| 1502 |     const QQmlJS::SourceLocation equal = regions[EqualTokenRegion]; | 
| 1503 |  | 
| 1504 |     if (!afterLocation(left: equal, positionInfo)) | 
| 1505 |         return; | 
| 1506 |  | 
| 1507 |     // otherwise, only complete case and default | 
| 1508 |     suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1509 | } | 
| 1510 |  | 
| 1511 | /*! | 
| 1512 | \internal | 
| 1513 | See comment on insideScriptPattern(). | 
| 1514 | */ | 
| 1515 | void QQmlLSCompletion::insideVariableDeclarationEntry(const DomItem &parentForContext, | 
| 1516 |                                                       const QQmlLSCompletionPosition &positionInfo, | 
| 1517 |                                                       BackInsertIterator result) const | 
| 1518 | { | 
| 1519 |     insideScriptPattern(parentForContext, positionInfo, result); | 
| 1520 | } | 
| 1521 |  | 
| 1522 | void QQmlLSCompletion::insideThrowStatement(const DomItem &parentForContext, | 
| 1523 |                                             const QQmlLSCompletionPosition &positionInfo, | 
| 1524 |                                             BackInsertIterator result) const | 
| 1525 | { | 
| 1526 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1527 |  | 
| 1528 |     const QQmlJS::SourceLocation throwKeyword = regions[ThrowKeywordRegion]; | 
| 1529 |  | 
| 1530 |     if (afterLocation(left: throwKeyword, positionInfo)) { | 
| 1531 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1532 |         return; | 
| 1533 |     } | 
| 1534 | } | 
| 1535 |  | 
| 1536 | void QQmlLSCompletion::insideLabelledStatement(const DomItem &parentForContext, | 
| 1537 |                                                const QQmlLSCompletionPosition &positionInfo, | 
| 1538 |                                                BackInsertIterator result) const | 
| 1539 | { | 
| 1540 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1541 |  | 
| 1542 |     const QQmlJS::SourceLocation colon = regions[ColonTokenRegion]; | 
| 1543 |  | 
| 1544 |     if (afterLocation(left: colon, positionInfo)) { | 
| 1545 |         suggestJSStatementCompletion(itemAtPosition: positionInfo.itemAtPosition, result); | 
| 1546 |         return; | 
| 1547 |     } | 
| 1548 |     // note: the case "beforeLocation(ctx, colon)" probably never happens: | 
| 1549 |     // this is because without the colon, the parser will probably not parse this as a | 
| 1550 |     // labelledstatement but as a normal expression statement. | 
| 1551 |     // So this case only happens when the colon already exists, and the user goes back to the | 
| 1552 |     // label name and requests completion for that label. | 
| 1553 | } | 
| 1554 |  | 
| 1555 | /*! | 
| 1556 | \internal | 
| 1557 | Collect the current set of labels that some DomItem can jump to. | 
| 1558 | */ | 
| 1559 | static void collectLabels(const DomItem &context, QQmlLSCompletion::BackInsertIterator result) | 
| 1560 | { | 
| 1561 |     for (DomItem current = context; current; current = current.directParent()) { | 
| 1562 |         if (current.internalKind() == DomType::ScriptLabelledStatement) { | 
| 1563 |             const QString label = current.field(name: Fields::label).value().toString(); | 
| 1564 |             if (label.isEmpty()) | 
| 1565 |                 continue; | 
| 1566 |             CompletionItem item; | 
| 1567 |             item.label = label.toUtf8(); | 
| 1568 |             item.kind = int(CompletionItemKind::Value); // variable? | 
| 1569 |             // TODO: more stuff here? | 
| 1570 |             result = item; | 
| 1571 |         } else if (current.internalKind() == DomType::ScriptExpression) { | 
| 1572 |             // quick exit when leaving the JS part | 
| 1573 |             return; | 
| 1574 |         } | 
| 1575 |     } | 
| 1576 |     return; | 
| 1577 | } | 
| 1578 |  | 
| 1579 | void QQmlLSCompletion::insideContinueStatement(const DomItem &parentForContext, | 
| 1580 |                                                const QQmlLSCompletionPosition &positionInfo, | 
| 1581 |                                                BackInsertIterator result) const | 
| 1582 | { | 
| 1583 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1584 |  | 
| 1585 |     const QQmlJS::SourceLocation continueKeyword = regions[ContinueKeywordRegion]; | 
| 1586 |  | 
| 1587 |     if (afterLocation(left: continueKeyword, positionInfo)) { | 
| 1588 |         collectLabels(context: parentForContext, result); | 
| 1589 |         return; | 
| 1590 |     } | 
| 1591 | } | 
| 1592 |  | 
| 1593 | void QQmlLSCompletion::insideBreakStatement(const DomItem &parentForContext, | 
| 1594 |                                             const QQmlLSCompletionPosition &positionInfo, | 
| 1595 |                                             BackInsertIterator result) const | 
| 1596 | { | 
| 1597 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1598 |  | 
| 1599 |     const QQmlJS::SourceLocation breakKeyword = regions[BreakKeywordRegion]; | 
| 1600 |  | 
| 1601 |     if (afterLocation(left: breakKeyword, positionInfo)) { | 
| 1602 |         collectLabels(context: parentForContext, result); | 
| 1603 |         return; | 
| 1604 |     } | 
| 1605 | } | 
| 1606 |  | 
| 1607 | void QQmlLSCompletion::insideConditionalExpression(const DomItem &parentForContext, | 
| 1608 |                                                    const QQmlLSCompletionPosition &positionInfo, | 
| 1609 |                                                    BackInsertIterator result) const | 
| 1610 | { | 
| 1611 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1612 |  | 
| 1613 |     const QQmlJS::SourceLocation questionMark = regions[QuestionMarkTokenRegion]; | 
| 1614 |     const QQmlJS::SourceLocation colon = regions[ColonTokenRegion]; | 
| 1615 |  | 
| 1616 |     if (beforeLocation(ctx: positionInfo, right: questionMark)) { | 
| 1617 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1618 |         return; | 
| 1619 |     } | 
| 1620 |     if (betweenLocations(left: questionMark, positionInfo, right: colon)) { | 
| 1621 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1622 |         return; | 
| 1623 |     } | 
| 1624 |     if (afterLocation(left: colon, positionInfo)) { | 
| 1625 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1626 |         return; | 
| 1627 |     } | 
| 1628 | } | 
| 1629 |  | 
| 1630 | void QQmlLSCompletion::insideUnaryExpression(const DomItem &parentForContext, | 
| 1631 |                                              const QQmlLSCompletionPosition &positionInfo, | 
| 1632 |                                              BackInsertIterator result) const | 
| 1633 | { | 
| 1634 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1635 |  | 
| 1636 |     const QQmlJS::SourceLocation operatorToken = regions[OperatorTokenRegion]; | 
| 1637 |  | 
| 1638 |     if (afterLocation(left: operatorToken, positionInfo)) { | 
| 1639 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1640 |         return; | 
| 1641 |     } | 
| 1642 | } | 
| 1643 |  | 
| 1644 | void QQmlLSCompletion::insidePostExpression(const DomItem &parentForContext, | 
| 1645 |                                             const QQmlLSCompletionPosition &positionInfo, | 
| 1646 |                                             BackInsertIterator result) const | 
| 1647 | { | 
| 1648 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1649 |  | 
| 1650 |     const QQmlJS::SourceLocation operatorToken = regions[OperatorTokenRegion]; | 
| 1651 |  | 
| 1652 |     if (beforeLocation(ctx: positionInfo, right: operatorToken)) { | 
| 1653 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1654 |         return; | 
| 1655 |     } | 
| 1656 | } | 
| 1657 |  | 
| 1658 | void QQmlLSCompletion::insideParenthesizedExpression(const DomItem &parentForContext, | 
| 1659 |                                                      const QQmlLSCompletionPosition &positionInfo, | 
| 1660 |                                                      BackInsertIterator result) const | 
| 1661 | { | 
| 1662 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1663 |  | 
| 1664 |     const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion]; | 
| 1665 |     const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion]; | 
| 1666 |  | 
| 1667 |     if (betweenLocations(left: leftParenthesis, positionInfo, right: rightParenthesis)) { | 
| 1668 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1669 |         return; | 
| 1670 |     } | 
| 1671 | } | 
| 1672 |  | 
| 1673 | void QQmlLSCompletion::insideTemplateLiteral(const DomItem &parentForContext, | 
| 1674 |                                              const QQmlLSCompletionPosition &positionInfo, | 
| 1675 |                                              BackInsertIterator result) const | 
| 1676 | { | 
| 1677 |     Q_UNUSED(parentForContext); | 
| 1678 |     suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1679 | } | 
| 1680 |  | 
| 1681 | void QQmlLSCompletion::insideNewExpression(const DomItem &parentForContext, | 
| 1682 |                                            const QQmlLSCompletionPosition &positionInfo, | 
| 1683 |                                            BackInsertIterator result) const | 
| 1684 | { | 
| 1685 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1686 |     const QQmlJS::SourceLocation newKeyword = regions[NewKeywordRegion]; | 
| 1687 |  | 
| 1688 |     if (afterLocation(left: newKeyword, positionInfo)) { | 
| 1689 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1690 |         return; | 
| 1691 |     } | 
| 1692 | } | 
| 1693 |  | 
| 1694 | void QQmlLSCompletion::insideNewMemberExpression(const DomItem &parentForContext, | 
| 1695 |                                                  const QQmlLSCompletionPosition &positionInfo, | 
| 1696 |                                                  BackInsertIterator result) const | 
| 1697 | { | 
| 1698 |     const auto regions = FileLocations::treeOf(parentForContext)->info().regions; | 
| 1699 |     const QQmlJS::SourceLocation newKeyword = regions[NewKeywordRegion]; | 
| 1700 |     const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion]; | 
| 1701 |     const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion]; | 
| 1702 |  | 
| 1703 |     if (betweenLocations(left: newKeyword, positionInfo, right: leftParenthesis) | 
| 1704 |         || betweenLocations(left: leftParenthesis, positionInfo, right: rightParenthesis)) { | 
| 1705 |         suggestJSExpressionCompletion(scriptIdentifier: positionInfo.itemAtPosition, result); | 
| 1706 |         return; | 
| 1707 |     } | 
| 1708 | } | 
| 1709 |  | 
| 1710 | void QQmlLSCompletion::signalHandlerCompletion(const QQmlJSScope::ConstPtr &scope, | 
| 1711 |                                                QDuplicateTracker<QString> *usedNames, | 
| 1712 |                                                BackInsertIterator result) const | 
| 1713 | { | 
| 1714 |     const auto keyValues = scope->methods().asKeyValueRange(); | 
| 1715 |     for (const auto &[name, method] : keyValues) { | 
| 1716 |         if (method.access() != QQmlJSMetaMethod::Public | 
| 1717 |             || method.methodType() != QQmlJSMetaMethodType::Signal) { | 
| 1718 |             continue; | 
| 1719 |         } | 
| 1720 |         if (usedNames && usedNames->hasSeen(s: name)) { | 
| 1721 |             continue; | 
| 1722 |         } | 
| 1723 |  | 
| 1724 |         CompletionItem completion; | 
| 1725 |         completion.label = QQmlSignalNames::signalNameToHandlerName(signal: name).toUtf8(); | 
| 1726 |         completion.kind = int(CompletionItemKind::Method); | 
| 1727 |         result = completion; | 
| 1728 |     } | 
| 1729 | } | 
| 1730 |  | 
| 1731 | /*! | 
| 1732 | \internal | 
| 1733 | Decide which completions can be used at currentItem and compute them. | 
| 1734 | */ | 
| 1735 | QList<CompletionItem> | 
| 1736 | QQmlLSCompletion::completions(const DomItem ¤tItem, | 
| 1737 |                               const CompletionContextStrings &contextStrings) const | 
| 1738 | { | 
| 1739 |     QList<CompletionItem> result; | 
| 1740 |     collectCompletions(currentItem, ctx: contextStrings, result: std::back_inserter(x&: result)); | 
| 1741 |     return result; | 
| 1742 | } | 
| 1743 |  | 
| 1744 | void QQmlLSCompletion::collectCompletions(const DomItem ¤tItem, | 
| 1745 |                                           const CompletionContextStrings &contextStrings, | 
| 1746 |                                           BackInsertIterator result) const | 
| 1747 | { | 
| 1748 |     /*! | 
| 1749 |     Completion is not provided on a script identifier expression because script identifier | 
| 1750 |     expressions lack context information. Instead, find the first parent that has enough | 
| 1751 |     context information and provide completion for this one. | 
| 1752 |     For example, a script identifier expression \c{le} in | 
| 1753 |     \badcode | 
| 1754 |         for (;le;) { ... } | 
| 1755 |     \endcode | 
| 1756 |     will get completion for a property called \c{leProperty}, while the same script identifier | 
| 1757 |     expression in | 
| 1758 |     \badcode | 
| 1759 |         for (le;;) { ... } | 
| 1760 |     \endcode | 
| 1761 |     will, in addition to \c{leProperty}, also get completion for the \c{let} statement snippet. | 
| 1762 |     In this example, the parent used for the completion is the for-statement, of type | 
| 1763 |     DomType::ScriptForStatement. | 
| 1764 |  | 
| 1765 |     In addition of the parent for the context, use positionInfo to have exact information on where | 
| 1766 |     the cursor is (to compare with the SourceLocations of tokens) and which item is at this position | 
| 1767 |     (required to provide completion at the correct position, for example for attached properties). | 
| 1768 |     */ | 
| 1769 |     const QQmlLSCompletionPosition positionInfo{ .itemAtPosition: currentItem, .cursorPosition: contextStrings }; | 
| 1770 |     for (DomItem currentParent = currentItem; currentParent; | 
| 1771 |          currentParent = currentParent.directParent()) { | 
| 1772 |         const DomType currentType = currentParent.internalKind(); | 
| 1773 |  | 
| 1774 |         switch (currentType) { | 
| 1775 |         case DomType::Id: | 
| 1776 |             // suppress completions for ids | 
| 1777 |             return; | 
| 1778 |         case DomType::Pragma: | 
| 1779 |             insidePragmaCompletion(currentItem: currentParent, positionInfo, result); | 
| 1780 |             return; | 
| 1781 |         case DomType::ScriptType: { | 
| 1782 |             if (currentParent.directParent().internalKind() == DomType::QmlObject) { | 
| 1783 |                 insideQmlObjectCompletion(parentForContext: currentParent.directParent(), positionInfo, result); | 
| 1784 |                 return; | 
| 1785 |             } | 
| 1786 |  | 
| 1787 |             LocalSymbolsTypes options; | 
| 1788 |             options.setFlag(flag: LocalSymbolsType::ObjectType); | 
| 1789 |             options.setFlag(flag: LocalSymbolsType::ValueType); | 
| 1790 |             suggestReachableTypes(el: currentItem, options, kind: CompletionItemKind::Class, it: result); | 
| 1791 |             return; | 
| 1792 |         } | 
| 1793 |         case DomType::ScriptFormalParameter: | 
| 1794 |             // no autocompletion inside of function parameter definition | 
| 1795 |             return; | 
| 1796 |         case DomType::Binding: | 
| 1797 |             insideBindingCompletion(currentItem: currentParent, positionInfo, result); | 
| 1798 |             return; | 
| 1799 |         case DomType::Import: | 
| 1800 |             insideImportCompletion(currentItem: currentParent, positionInfo, result); | 
| 1801 |             return; | 
| 1802 |         case DomType::ScriptForStatement: | 
| 1803 |             insideForStatementCompletion(parentForContext: currentParent, positionInfo, result); | 
| 1804 |             return; | 
| 1805 |         case DomType::ScriptBlockStatement: | 
| 1806 |             suggestJSStatementCompletion(itemAtPosition: positionInfo.itemAtPosition, result); | 
| 1807 |             return; | 
| 1808 |         case DomType::QmlFile: | 
| 1809 |             insideQmlFileCompletion(currentItem: currentParent, positionInfo, result); | 
| 1810 |             return; | 
| 1811 |         case DomType::QmlObject: | 
| 1812 |             insideQmlObjectCompletion(parentForContext: currentParent, positionInfo, result); | 
| 1813 |             return; | 
| 1814 |         case DomType::MethodInfo: | 
| 1815 |             // suppress completions | 
| 1816 |             return; | 
| 1817 |         case DomType::PropertyDefinition: | 
| 1818 |             insidePropertyDefinitionCompletion(currentItem: currentParent, positionInfo, result); | 
| 1819 |             return; | 
| 1820 |         case DomType::ScriptBinaryExpression: | 
| 1821 |             // ignore field member expressions: these need additional context from its parents | 
| 1822 |             if (QQmlLSUtils::isFieldMemberExpression(item: currentParent)) | 
| 1823 |                 continue; | 
| 1824 |             insideBinaryExpressionCompletion(parentForContext: currentParent, positionInfo, result); | 
| 1825 |             return; | 
| 1826 |         case DomType::ScriptLiteral: | 
| 1827 |             insideScriptLiteralCompletion(currentItem: currentParent, positionInfo, result); | 
| 1828 |             return; | 
| 1829 |         case DomType::ScriptRegExpLiteral: | 
| 1830 |             // no completion inside of regexp literals | 
| 1831 |             return; | 
| 1832 |         case DomType::ScriptCallExpression: | 
| 1833 |             insideCallExpression(currentItem: currentParent, positionInfo, result); | 
| 1834 |             return; | 
| 1835 |         case DomType::ScriptIfStatement: | 
| 1836 |             insideIfStatement(currentItem: currentParent, positionInfo, result); | 
| 1837 |             return; | 
| 1838 |         case DomType::ScriptReturnStatement: | 
| 1839 |             insideReturnStatement(currentItem: currentParent, positionInfo, result); | 
| 1840 |             return; | 
| 1841 |         case DomType::ScriptWhileStatement: | 
| 1842 |             insideWhileStatement(currentItem: currentParent, positionInfo, result); | 
| 1843 |             return; | 
| 1844 |         case DomType::ScriptDoWhileStatement: | 
| 1845 |             insideDoWhileStatement(parentForContext: currentParent, positionInfo, result); | 
| 1846 |             return; | 
| 1847 |         case DomType::ScriptForEachStatement: | 
| 1848 |             insideForEachStatement(parentForContext: currentParent, positionInfo, result); | 
| 1849 |             return; | 
| 1850 |         case DomType::ScriptTryCatchStatement: | 
| 1851 |             /*! | 
| 1852 |             \internal | 
| 1853 |             The Ecmascript standard specifies that there can only be a block statement between \c | 
| 1854 |             try and \c catch(...), \c try and \c finally and \c catch(...) and \c finally, so all of | 
| 1855 |             these completions are already handled by the DomType::ScriptBlockStatement completion. | 
| 1856 |             The only place in the try statement where there is no BlockStatement and therefore needs | 
| 1857 |             its own completion is inside the catch parameter, but that is | 
| 1858 |             \quotation | 
| 1859 |             An optional identifier or pattern to hold the caught exception for the associated catch | 
| 1860 |             block. | 
| 1861 |             \endquotation | 
| 1862 |             citing | 
| 1863 |             https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch?retiredLocale=de#exceptionvar. | 
| 1864 |             This means that no completion is needed inside a catch-expression, as it should contain | 
| 1865 |             an identifier that is not yet used anywhere. | 
| 1866 |             Therefore, no completion is required at all when inside a try-statement but outside a | 
| 1867 |             block-statement. | 
| 1868 |             */ | 
| 1869 |             return; | 
| 1870 |         case DomType::ScriptSwitchStatement: | 
| 1871 |             insideSwitchStatement(parentForContext: currentParent, positionInfo, result); | 
| 1872 |             return; | 
| 1873 |         case DomType::ScriptCaseClause: | 
| 1874 |             insideCaseClause(parentForContext: currentParent, positionInfo, result); | 
| 1875 |             return; | 
| 1876 |         case DomType::ScriptDefaultClause: | 
| 1877 |             if (ctxBeforeStatement(positionInfo, parentForContext: currentParent, firstRegion: QQmlJS::Dom::DefaultKeywordRegion)) | 
| 1878 |                 continue; | 
| 1879 |             insideDefaultClause(parentForContext: currentParent, positionInfo, result); | 
| 1880 |             return; | 
| 1881 |         case DomType::ScriptCaseBlock: | 
| 1882 |             insideCaseBlock(parentForContext: currentParent, positionInfo, result); | 
| 1883 |             return; | 
| 1884 |         case DomType::ScriptVariableDeclaration: | 
| 1885 |             // not needed: thats a list of ScriptVariableDeclarationEntry, and those entries cannot | 
| 1886 |             // be suggested because they all start with `{`, `[` or an identifier that should not be | 
| 1887 |             // in use yet. | 
| 1888 |             return; | 
| 1889 |         case DomType::ScriptVariableDeclarationEntry: | 
| 1890 |             insideVariableDeclarationEntry(parentForContext: currentParent, positionInfo, result); | 
| 1891 |             return; | 
| 1892 |         case DomType::ScriptProperty: | 
| 1893 |             // fallthrough: a ScriptProperty is a ScriptPattern but inside a JS Object. It gets the | 
| 1894 |             // same completions as a ScriptPattern. | 
| 1895 |         case DomType::ScriptPattern: | 
| 1896 |             insideScriptPattern(parentForContext: currentParent, positionInfo, result); | 
| 1897 |             return; | 
| 1898 |         case DomType::ScriptThrowStatement: | 
| 1899 |             insideThrowStatement(parentForContext: currentParent, positionInfo, result); | 
| 1900 |             return; | 
| 1901 |         case DomType::ScriptLabelledStatement: | 
| 1902 |             insideLabelledStatement(parentForContext: currentParent, positionInfo, result); | 
| 1903 |             return; | 
| 1904 |         case DomType::ScriptContinueStatement: | 
| 1905 |             insideContinueStatement(parentForContext: currentParent, positionInfo, result); | 
| 1906 |             return; | 
| 1907 |         case DomType::ScriptBreakStatement: | 
| 1908 |             insideBreakStatement(parentForContext: currentParent, positionInfo, result); | 
| 1909 |             return; | 
| 1910 |         case DomType::ScriptConditionalExpression: | 
| 1911 |             insideConditionalExpression(parentForContext: currentParent, positionInfo, result); | 
| 1912 |             return; | 
| 1913 |         case DomType::ScriptUnaryExpression: | 
| 1914 |             insideUnaryExpression(parentForContext: currentParent, positionInfo, result); | 
| 1915 |             return; | 
| 1916 |         case DomType::ScriptPostExpression: | 
| 1917 |             insidePostExpression(parentForContext: currentParent, positionInfo, result); | 
| 1918 |             return; | 
| 1919 |         case DomType::ScriptParenthesizedExpression: | 
| 1920 |             insideParenthesizedExpression(parentForContext: currentParent, positionInfo, result); | 
| 1921 |             return; | 
| 1922 |         case DomType::ScriptTemplateLiteral: | 
| 1923 |             insideTemplateLiteral(parentForContext: currentParent, positionInfo, result); | 
| 1924 |             return; | 
| 1925 |         case DomType::ScriptTemplateStringPart: | 
| 1926 |             // no completion inside of the non-expression parts of template strings | 
| 1927 |             return; | 
| 1928 |         case DomType::ScriptNewExpression: | 
| 1929 |             insideNewExpression(parentForContext: currentParent, positionInfo, result); | 
| 1930 |             return; | 
| 1931 |         case DomType::ScriptNewMemberExpression: | 
| 1932 |             insideNewMemberExpression(parentForContext: currentParent, positionInfo, result); | 
| 1933 |             return; | 
| 1934 |         case DomType::ScriptThisExpression: | 
| 1935 |             // suppress completions on `this` | 
| 1936 |             return; | 
| 1937 |         case DomType::ScriptSuperLiteral: | 
| 1938 |             // suppress completions on `super` | 
| 1939 |             return; | 
| 1940 |         case DomType::Comment: | 
| 1941 |             // no completion inside of comments | 
| 1942 |             return; | 
| 1943 |  | 
| 1944 |         // TODO: Implement those statements. | 
| 1945 |         // In the meanwhile, suppress completions to avoid weird behaviors. | 
| 1946 |         case DomType::ScriptArray: | 
| 1947 |         case DomType::ScriptObject: | 
| 1948 |         case DomType::ScriptElision: | 
| 1949 |         case DomType::ScriptArrayEntry: | 
| 1950 |             return; | 
| 1951 |  | 
| 1952 |         default: | 
| 1953 |             continue; | 
| 1954 |         } | 
| 1955 |         Q_UNREACHABLE(); | 
| 1956 |     } | 
| 1957 |  | 
| 1958 |     // no completion could be found | 
| 1959 |     qCDebug(QQmlLSUtilsLog) << "No completion was found for current request." ; | 
| 1960 |     return; | 
| 1961 | } | 
| 1962 |  | 
| 1963 | QQmlLSCompletion::QQmlLSCompletion(const QFactoryLoader &pluginLoader) | 
| 1964 | { | 
| 1965 |     const auto keys = pluginLoader.metaDataKeys(); | 
| 1966 |     for (qsizetype i = 0; i < keys.size(); ++i) { | 
| 1967 |         auto instance = std::unique_ptr<QQmlLSPlugin>( | 
| 1968 |                 qobject_cast<QQmlLSPlugin *>(object: pluginLoader.instance(index: i))); | 
| 1969 |         if (!instance) | 
| 1970 |             continue; | 
| 1971 |         if (auto completionInstance = instance->createCompletionPlugin()) | 
| 1972 |             m_plugins.push_back(x: std::move(completionInstance)); | 
| 1973 |     } | 
| 1974 | } | 
| 1975 |  | 
| 1976 | /*! | 
| 1977 | \internal | 
| 1978 | Helper method to call a method on all loaded plugins. | 
| 1979 | */ | 
| 1980 | void QQmlLSCompletion::collectFromPlugins(qxp::function_ref<CompletionFromPluginFunction> f, | 
| 1981 |                                           BackInsertIterator result) const | 
| 1982 | { | 
| 1983 |     for (const auto &plugin : m_plugins) { | 
| 1984 |         Q_ASSERT(plugin); | 
| 1985 |         f(plugin.get(), result); | 
| 1986 |     } | 
| 1987 | } | 
| 1988 |  | 
| 1989 | void QQmlLSCompletion::suggestSnippetsForLeftHandSideOfBinding(const DomItem &itemAtPosition, | 
| 1990 |                                                                BackInsertIterator result) const | 
| 1991 | { | 
| 1992 |     collectFromPlugins( | 
| 1993 |             f: [&itemAtPosition](QQmlLSCompletionPlugin *p, BackInsertIterator result) { | 
| 1994 |                 p->suggestSnippetsForLeftHandSideOfBinding(items: itemAtPosition, result); | 
| 1995 |             }, | 
| 1996 |             result); | 
| 1997 | } | 
| 1998 |  | 
| 1999 | void QQmlLSCompletion::suggestSnippetsForRightHandSideOfBinding(const DomItem &itemAtPosition, | 
| 2000 |                                                                 BackInsertIterator result) const | 
| 2001 | { | 
| 2002 |     collectFromPlugins( | 
| 2003 |             f: [&itemAtPosition](QQmlLSCompletionPlugin *p, BackInsertIterator result) { | 
| 2004 |                 p->suggestSnippetsForRightHandSideOfBinding(items: itemAtPosition, result); | 
| 2005 |             }, | 
| 2006 |             result); | 
| 2007 | } | 
| 2008 |  | 
| 2009 | QT_END_NAMESPACE | 
| 2010 |  |