Revision control

++ // Copyright (C) 2024 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qqmllscompletion_p.h"
++
++ QT_BEGIN_NAMESPACE
++
++ Q_LOGGING_CATEGORY(QQmlLSCompletionLog, "qt.languageserver.completions")
++
++ using namespace QLspSpecification;
++ using namespace QQmlJS::Dom;
++ using namespace Qt::StringLiterals;
++
++ /*!
++ \class QQmlLSCompletion
++ \internal
++ \brief QQmlLSCompletion provides completions for all kinds of QML and JS constructs.
++
++ Use the \l{completions} method to obtain completions at a certain DomItem.
++
++ All the other methods in this class are helper methods: some compute completions for specific QML
++ and JS constructs and some are shared between multiple QML or JS constructs to avoid code
++ duplication. Most of the helper methods add their completion items via a BackInsertIterator.
++
++ Some helper methods are called "suggest*" and will try to suggest code that does not exist yet. For
++ example, any JS statement can be expected inside a Blockstatement so suggestJSStatementCompletion()
++ is used to suggest JS statements inside of BlockStatements. Another example might be
++ suggestReachableTypes() that will suggest Types for type annotations, attached types or Qml Object
++ hierarchies, or suggestCaseAndDefaultStatementCompletion() that will only suggest "case" and
++ "default" clauses for switch statements.
++
++ Some helper methods are called "inside*" and will try to suggest code inside an existing structure.
++ For example, insideForStatementCompletion() will try to suggest completion for the different code
++ pieces initializer, condition, increment and statement that exist inside of:
++ \badcode
++ for(initializer; condition; increment)
++ statement
++ \endcode
++ */
++
++ CompletionItem QQmlLSCompletion::makeSnippet(QUtf8StringView qualifier, QUtf8StringView label,
++ QUtf8StringView insertText)
++ {
++ CompletionItem res;
++ if (!qualifier.isEmpty()) {
++ res.label = qualifier.data();
++ res.label += '.';
++ }
++ res.label += label.data();
++ res.insertTextFormat = InsertTextFormat::Snippet;
++ if (!qualifier.isEmpty()) {
++ res.insertText = qualifier.data();
++ *res.insertText += '.';
++ *res.insertText += insertText.data();
++ } else {
++ res.insertText = insertText.data();
++ }
++ res.kind = CompletionItemKind::Snippet;
++ res.insertTextMode = InsertTextMode::AdjustIndentation;
++ return res;
++ }
++
++ CompletionItem QQmlLSCompletion::makeSnippet(QUtf8StringView label, QUtf8StringView insertText)
++ {
++ return makeSnippet(QByteArray(), label, insertText);
++ }
++
++ /*!
++ \internal
++ \brief Compare left and right locations to the position denoted by ctx, see special cases below.
++
++ Statements and expressions need to provide different completions depending on where the cursor is.
++ For example, lets take following for-statement:
++ \badcode
++ for (let i = 0; <here> ; ++i) {}
++ \endcode
++ We want to provide script expression completion (method names, property names, available JS
++ variables names, QML objects ids, and so on) at the place denoted by \c{<here>}.
++ The question is: how do we know that the cursor is really at \c{<here>}? In the case of the
++ for-loop, we can compare the position of the cursor with the first and the second semicolon of the
++ for loop.
++
++ If the first semicolon does not exist, it has an invalid sourcelocation and the cursor is
++ definitively \e{not} at \c{<here>}. Therefore, return false when \c{left} is invalid.
++
++ If the second semicolon does not exist, then just ignore it: it might not have been written yet.
++ */
++ bool QQmlLSCompletion::betweenLocations(QQmlJS::SourceLocation left,
++ const QQmlLSCompletionPosition &positionInfo,
++ QQmlJS::SourceLocation right) const
++ {
++ if (!left.isValid())
++ return false;
++ // note: left.end() == ctx.offset() means that the cursor lies exactly after left
++ if (!(left.end() <= positionInfo.offset()))
++ return false;
++ if (!right.isValid())
++ return true;
++
++ // note: ctx.offset() == right.begin() means that the cursor lies exactly before right
++ return positionInfo.offset() <= right.begin();
++ }
++
++ /*!
++ \internal
++ Returns true if ctx denotes an offset lying behind left.end(), and false otherwise.
++ */
++ bool QQmlLSCompletion::afterLocation(QQmlJS::SourceLocation left,
++ const QQmlLSCompletionPosition &positionInfo) const
++ {
++ return betweenLocations(left, positionInfo, QQmlJS::SourceLocation{});
++ }
++
++ /*!
++ \internal
++ Returns true if ctx denotes an offset lying before right.begin(), and false otherwise.
++ */
++ bool QQmlLSCompletion::beforeLocation(const QQmlLSCompletionPosition &ctx,
++ QQmlJS::SourceLocation right) const
++ {
++ if (!right.isValid())
++ return true;
++
++ // note: ctx.offset() == right.begin() means that the cursor lies exactly before right
++ if (ctx.offset() <= right.begin())
++ return true;
++
++ return false;
++ }
++
++ bool QQmlLSCompletion::ctxBeforeStatement(const QQmlLSCompletionPosition &positionInfo,
++ const DomItem &parentForContext,
++ FileLocationRegion firstRegion) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++ const bool result = beforeLocation(positionInfo, regions[firstRegion]);
++ return result;
++ }
++
++ void
++ QQmlLSCompletion::suggestBindingCompletion(const DomItem &itemAtPosition, BackInsertIterator it) const
++ {
++ suggestReachableTypes(itemAtPosition, LocalSymbolsType::AttachedType, CompletionItemKind::Class,
++ it);
++
++ const auto scope = [&]() -> QQmlJSScope::ConstPtr {
++ const DomItem owner = ownerOfQualifiedExpression(itemAtPosition);
++ if (!owner)
++ return itemAtPosition.qmlObject().semanticScope();
++
++ const auto expressionType = QQmlLSUtils::resolveExpressionType(
++ owner, QQmlLSUtils::ResolveActualTypeForFieldMemberExpression);
++
++ // no properties nor signal handlers inside a qualified import
++ if (!expressionType || expressionType->type == QQmlLSUtils::QualifiedModuleIdentifier)
++ return {};
++
++ return expressionType->semanticScope;
++ }();
++
++ if (!scope)
++ return;
++
++ propertyCompletion(scope, nullptr, it);
++ signalHandlerCompletion(scope, nullptr, it);
++ }
++
++ void QQmlLSCompletion::insideImportCompletionHelper(const DomItem &file,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator it) const
++ {
++ // returns completions for import statements, ctx is supposed to be in an import statement
++ const CompletionContextStrings &ctx = positionInfo.cursorPosition;
++ ImportCompletionType importCompletionType = ImportCompletionType::None;
++ QRegularExpression spaceRe(uR"(\s+)"_s);
++ QList<QStringView> linePieces = ctx.preLine().split(spaceRe, Qt::SkipEmptyParts);
++ qsizetype effectiveLength = linePieces.size()
++ + ((!ctx.preLine().isEmpty() && ctx.preLine().last().isSpace()) ? 1 : 0);
++ if (effectiveLength < 2) {
++ CompletionItem comp;
++ comp.label = "import";
++ comp.kind = CompletionItemKind::Keyword;
++ it = comp;
++ }
++ if (linePieces.isEmpty() || linePieces.first() != u"import")
++ return;
++ if (effectiveLength == 2) {
++ // the cursor is after the import, possibly in a partial module name
++ importCompletionType = ImportCompletionType::Module;
++ } else if (effectiveLength == 3) {
++ if (linePieces.last() != u"as") {
++ // the cursor is after the module, possibly in a partial version token (or partial as)
++ CompletionItem comp;
++ comp.label = "as";
++ comp.kind = CompletionItemKind::Keyword;
++ it = comp;
++ importCompletionType = ImportCompletionType::Version;
++ }
++ }
++ DomItem env = file.environment();
++ if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
++ switch (importCompletionType) {
++ case ImportCompletionType::None:
++ break;
++ case ImportCompletionType::Module: {
++ break;
++ }
++ case ImportCompletionType::Version:
++ break;
++ }
++ }
++ }
++
++ void QQmlLSCompletion::idsCompletions(const DomItem &component, BackInsertIterator it) const
++ {
++ qCDebug(QQmlLSCompletionLog) << "adding ids completions";
++ for (const QString &k : component.field(Fields::ids).keys()) {
++ CompletionItem comp;
++ comp.label = k.toUtf8();
++ comp.kind = CompletionItemKind::Value;
++ it = comp;
++ }
++ }
++
++ static bool testScopeSymbol(const QQmlJSScope::ConstPtr &scope, LocalSymbolsTypes options,
++ CompletionItemKind kind)
++ {
++ const bool currentIsSingleton = scope->isSingleton();
++ const bool currentIsAttached = !scope->attachedType().isNull();
++ if ((options & LocalSymbolsType::Singleton) && currentIsSingleton) {
++ return true;
++ }
++ if ((options & LocalSymbolsType::AttachedType) && currentIsAttached) {
++ return true;
++ }
++ const bool isObjectType = scope->isReferenceType();
++ if (options & LocalSymbolsType::ObjectType && !currentIsSingleton && isObjectType) {
++ return kind != CompletionItemKind::Constructor || scope->isCreatable();
++ }
++ if (options & LocalSymbolsType::ValueType && !currentIsSingleton && !isObjectType) {
++ return true;
++ }
++ return false;
++ }
++
++ /*!
++ \internal
++ Obtain the types reachable from \c{el} as a CompletionItems.
++ */
++ void QQmlLSCompletion::suggestReachableTypes(const DomItem &el, LocalSymbolsTypes options,
++ CompletionItemKind kind, BackInsertIterator it) const
++ {
++ auto file = el.containingFile().as<QmlFile>();
++ if (!file)
++ return;
++ auto resolver = file->typeResolver();
++ if (!resolver)
++ return;
++
++ const QString requiredQualifiers = QQmlLSUtils::qualifiersFrom(el);
++ const auto keyValueRange = resolver->importedTypes().asKeyValueRange();
++ for (const auto &type : keyValueRange) {
++ // ignore special QQmlJSImporterMarkers
++ const bool isMarkerType = type.first.contains(u"$internal$.")
++ || type.first.contains(u"$anonymous$.") || type.first.contains(u"$module$.");
++ if (isMarkerType || !type.first.startsWith(requiredQualifiers))
++ continue;
++
++ auto &scope = type.second.scope;
++ if (!scope)
++ continue;
++
++ if (!testScopeSymbol(scope, options, kind))
++ continue;
++
++ CompletionItem completion;
++ completion.label = QStringView(type.first).sliced(requiredQualifiers.size()).toUtf8();
++ completion.kind = kind;
++ it = completion;
++ }
++ }
++
++ void QQmlLSCompletion::jsIdentifierCompletion(const QQmlJSScope::ConstPtr &scope,
++ QDuplicateTracker<QString> *usedNames,
++ BackInsertIterator it) const
++ {
++ for (const auto &[name, jsIdentifier] : scope->ownJSIdentifiers().asKeyValueRange()) {
++ CompletionItem completion;
++ if (usedNames && usedNames->hasSeen(name)) {
++ continue;
++ }
++ completion.label = name.toUtf8();
++ completion.kind = CompletionItemKind::Variable;
++ QString detail = u"has type "_s;
++ if (jsIdentifier.typeName) {
++ if (jsIdentifier.isConst) {
++ detail.append(u"const ");
++ }
++ detail.append(*jsIdentifier.typeName);
++ } else {
++ detail.append(jsIdentifier.isConst ? u"const"_s : u"var"_s);
++ }
++ completion.detail = detail.toUtf8();
++ it = completion;
++ }
++ }
++
++ void QQmlLSCompletion::methodCompletion(const QQmlJSScope::ConstPtr &scope,
++ QDuplicateTracker<QString> *usedNames,
++ BackInsertIterator it) const
++ {
++ // JS functions in current and base scopes
++ for (const auto &[name, method] : scope->methods().asKeyValueRange()) {
++ if (method.access() != QQmlJSMetaMethod::Public)
++ continue;
++ if (usedNames && usedNames->hasSeen(name)) {
++ continue;
++ }
++ CompletionItem completion;
++ completion.label = name.toUtf8();
++ completion.kind = CompletionItemKind::Method;
++ it = completion;
++ // TODO: QQmlLSUtils::reachableSymbols seems to be able to do documentation and detail
++ // and co, it should also be done here if possible.
++ }
++ }
++
++ void QQmlLSCompletion::propertyCompletion(const QQmlJSScope::ConstPtr &scope,
++ QDuplicateTracker<QString> *usedNames,
++ BackInsertIterator it) const
++ {
++ for (const auto &[name, property] : scope->properties().asKeyValueRange()) {
++ if (usedNames && usedNames->hasSeen(name)) {
++ continue;
++ }
++ CompletionItem completion;
++ completion.label = name.toUtf8();
++ completion.kind = CompletionItemKind::Property;
++ QString detail{ u"has type "_s };
++ if (!property.isWritable())
++ detail.append(u"readonly "_s);
++ detail.append(property.typeName().isEmpty() ? u"var"_s : property.typeName());
++ completion.detail = detail.toUtf8();
++ it = completion;
++ }
++ }
++
++ void QQmlLSCompletion::enumerationCompletion(const QQmlJSScope::ConstPtr &scope,
++ QDuplicateTracker<QString> *usedNames,
++ BackInsertIterator it) const
++ {
++ for (const QQmlJSMetaEnum &enumerator : scope->enumerations()) {
++ if (usedNames && usedNames->hasSeen(enumerator.name())) {
++ continue;
++ }
++ CompletionItem completion;
++ completion.label = enumerator.name().toUtf8();
++ completion.kind = CompletionItemKind::Enum;
++ it = completion;
++ }
++ }
++
++ void QQmlLSCompletion::enumerationValueCompletionHelper(const QStringList &enumeratorKeys,
++ BackInsertIterator it) const
++ {
++ for (const QString &enumeratorKey : enumeratorKeys) {
++ CompletionItem completion;
++ completion.label = enumeratorKey.toUtf8();
++ completion.kind = CompletionItemKind::EnumMember;
++ it = completion;
++ }
++ }
++
++ /*!
++ \internal
++ Creates completion items for enumerationvalues.
++ If enumeratorName is a valid enumerator then only do completion for the requested enumerator, and
++ otherwise do completion for \b{all other possible} enumerators.
++
++ For example:
++ ```
++ id: someItem
++ enum Hello { World }
++ enum MyEnum { ValueOne, ValueTwo }
++
++ // Hello does refer to a enumerator:
++ property var a: Hello.<complete only World here>
++
++ // someItem does not refer to a enumerator:
++ property var b: someItem.<complete World, ValueOne and ValueTwo here>
++ ```
++ */
++
++ void QQmlLSCompletion::enumerationValueCompletion(const QQmlJSScope::ConstPtr &scope,
++ const QString &enumeratorName,
++ BackInsertIterator result) const
++ {
++ auto enumerator = scope->enumeration(enumeratorName);
++ if (enumerator.isValid()) {
++ enumerationValueCompletionHelper(enumerator.keys(), result);
++ return;
++ }
++
++ for (const QQmlJSMetaEnum &enumerator : scope->enumerations()) {
++ enumerationValueCompletionHelper(enumerator.keys(), result);
++ }
++ }
++
++ /*!
++ \internal
++ Calls F on all JavaScript-parents of scope. For example, you can use this method to
++ collect all the JavaScript Identifiers from following code:
++ ```
++ { // this block statement contains only 'x'
++ let x = 3;
++ { // this block statement contains only 'y', and 'x' has to be retrieved via its parent.
++ let y = 4;
++ }
++ }
++ ```
++ */
++ template<typename F>
++ void collectFromAllJavaScriptParents(const F &&f, const QQmlJSScope::ConstPtr &scope)
++ {
++ for (QQmlJSScope::ConstPtr current = scope; current; current = current->parentScope()) {
++ f(current);
++ if (current->scopeType() == QQmlSA::ScopeType::QMLScope)
++ return;
++ }
++ }
++
++ /*!
++ \internal
++ Suggest enumerations (if applicable) and enumeration values from \c scope, for example \c
++ Asynchronous from the \c CompilationMode enum:
++
++ \qml
++ property var xxx: Component.Asynchronous // Component contains the \c CompilationMode enum
++ property var xxx2: CompilationMode.Asynchronous
++ \endqml
++ */
++ void QQmlLSCompletion::suggestEnumerationsAndEnumerationValues(
++ const QQmlJSScope::ConstPtr &scope, const QString &enumName,
++ QDuplicateTracker<QString> &usedNames, BackInsertIterator result) const
++ {
++ enumerationValueCompletion(scope, enumName, result);
++
++ // skip enumeration types if already inside an enumeration type
++ if (auto enumerator = scope->enumeration(enumName); !enumerator.isValid()) {
++ enumerationCompletion(scope, &usedNames, result);
++ }
++ }
++
++ /*!
++ \internal
++
++ Returns the owner of a qualified expression for further resolving, for example:
++ 1. \c owner from the \c member ScriptExpression in \c {owner.member}. This happens when completion
++ is requested on \c member.
++ 2. \c owner from the ScriptBinaryExpression \c {owner.member}. This happens when completion is
++ requested on the dot between \c owner and \c member.
++ 3. An empty DomItem otherwise.
++ */
++ DomItem QQmlLSCompletion::ownerOfQualifiedExpression(const DomItem &qualifiedExpression) const
++ {
++ // note: there is an edge case, where the user asks for completion right after the dot
++ // of some qualified expression like `root.hello`. In this case, scriptIdentifier is actually
++ // the BinaryExpression instead of the left-hand-side that has not be written down yet.
++ const bool askForCompletionOnDot = QQmlLSUtils::isFieldMemberExpression(qualifiedExpression);
++ const bool hasQualifier =
++ QQmlLSUtils::isFieldMemberAccess(qualifiedExpression) || askForCompletionOnDot;
++
++ if (!hasQualifier)
++ return {};
++
++ const DomItem owner =
++ (askForCompletionOnDot ? qualifiedExpression : qualifiedExpression.directParent())
++ .field(Fields::left);
++ return owner;
++ }
++
++ /*!
++ \internal
++ Generate autocompletions for JS expressions, suggest possible properties, methods, etc.
++
++ If scriptIdentifier is inside a Field Member Expression, like \c{onCompleted} in
++ \c{Component.onCompleted} for example, then this method will only suggest properties, methods, etc
++ from the correct type. For the previous example that would be properties, methods, etc. from the
++ Component attached type.
++ */
++ void QQmlLSCompletion::suggestJSExpressionCompletion(const DomItem &scriptIdentifier,
++ BackInsertIterator result) const
++ {
++ QDuplicateTracker<QString> usedNames;
++ QQmlJSScope::ConstPtr nearestScope;
++
++ const DomItem owner = ownerOfQualifiedExpression(scriptIdentifier);
++
++ if (!owner) {
++ for (QUtf8StringView view : std::array<QUtf8StringView, 3>{ "null", "false", "true" }) {
++ CompletionItem completion;
++ completion.label = view.data();
++ completion.kind = CompletionItemKind::Value;
++ result = completion;
++ }
++ idsCompletions(scriptIdentifier.component(), result);
++ suggestReachableTypes(scriptIdentifier,
++ LocalSymbolsType::Singleton | LocalSymbolsType::AttachedType,
++ CompletionItemKind::Class, result);
++
++ auto scope = scriptIdentifier.nearestSemanticScope();
++ if (!scope)
++ return;
++ nearestScope = scope;
++
++ enumerationCompletion(nearestScope, &usedNames, result);
++ } else {
++ auto ownerExpressionType = QQmlLSUtils::resolveExpressionType(
++ owner, QQmlLSUtils::ResolveActualTypeForFieldMemberExpression);
++ if (!ownerExpressionType || !ownerExpressionType->semanticScope)
++ return;
++ nearestScope = ownerExpressionType->semanticScope;
++
++ switch (ownerExpressionType->type) {
++ case QQmlLSUtils::EnumeratorValueIdentifier:
++ return;
++ case QQmlLSUtils::EnumeratorIdentifier:
++ suggestEnumerationsAndEnumerationValues(nearestScope, *ownerExpressionType->name,
++ usedNames, result);
++ return;
++ case QQmlLSUtils::QmlComponentIdentifier:
++ // Suggest members of the attached type, for example suggest `progress` in
++ // `property real p: Component.progress`.
++ if (QQmlJSScope::ConstPtr attachedType =
++ ownerExpressionType->semanticScope->attachedType()) {
++ methodCompletion(attachedType, &usedNames, result);
++ propertyCompletion(attachedType, &usedNames, result);
++ suggestEnumerationsAndEnumerationValues(
++ attachedType, *ownerExpressionType->name, usedNames, result);
++ }
++ Q_FALLTHROUGH();
++ case QQmlLSUtils::SingletonIdentifier:
++ if (ownerExpressionType->name)
++ suggestEnumerationsAndEnumerationValues(nearestScope, *ownerExpressionType->name,
++ usedNames, result);
++ break;
++ default:
++ break;
++ }
++ }
++
++ Q_ASSERT(nearestScope);
++
++ methodCompletion(nearestScope, &usedNames, result);
++ propertyCompletion(nearestScope, &usedNames, result);
++
++ if (!owner) {
++ // collect all of the stuff from parents
++ collectFromAllJavaScriptParents(
++ [this, &usedNames, result](const QQmlJSScope::ConstPtr &scope) {
++ jsIdentifierCompletion(scope, &usedNames, result);
++ },
++ nearestScope);
++ collectFromAllJavaScriptParents(
++ [this, &usedNames, result](const QQmlJSScope::ConstPtr &scope) {
++ methodCompletion(scope, &usedNames, result);
++ },
++ nearestScope);
++ collectFromAllJavaScriptParents(
++ [this, &usedNames, result](const QQmlJSScope::ConstPtr &scope) {
++ propertyCompletion(scope, &usedNames, result);
++ },
++ nearestScope);
++
++ auto file = scriptIdentifier.containingFile().as<QmlFile>();
++ if (!file)
++ return;
++ auto resolver = file->typeResolver();
++ if (!resolver)
++ return;
++
++ const auto globals = resolver->jsGlobalObject();
++ methodCompletion(globals, &usedNames, result);
++ propertyCompletion(globals, &usedNames, result);
++ }
++ }
++
++ static const QQmlJSScope *resolve(const QQmlJSScope *current, const QStringList &names)
++ {
++ for (const QString &name : names) {
++ if (auto property = current->property(name); property.isValid()) {
++ if (auto propertyType = property.type().get()) {
++ current = propertyType;
++ continue;
++ }
++ }
++ return {};
++ }
++ return current;
++ }
++
++ bool QQmlLSCompletion::cursorInFrontOfItem(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo)
++ {
++ auto fileLocations = FileLocations::treeOf(parentForContext)->info().fullRegion;
++ return positionInfo.offset() <= fileLocations.begin();
++ }
++
++ bool QQmlLSCompletion::cursorAfterColon(const DomItem &currentItem,
++ const QQmlLSCompletionPosition &positionInfo)
++ {
++ auto location = FileLocations::treeOf(currentItem)->info();
++ auto region = location.regions.constFind(ColonTokenRegion);
++
++ if (region == location.regions.constEnd())
++ return false;
++
++ if (region.value().isValid() && region.value().begin() < positionInfo.offset()) {
++ return true;
++ }
++ return false;
++ }
++
++ /*!
++ \internal
++ \brief Mapping from pragma names to allowed pragma values.
++
++ This mapping of pragma names to pragma values is not complete. In fact, it only contains the
++ pragma names and values that one should see autocompletion for.
++ Some pragmas like FunctionSignatureBehavior or Strict or the Reference/Value of ValueTypeBehavior,
++ for example, should currently not be proposed as completion items by qmlls.
++
++ An empty QList-value in the QMap means that the pragma does not accept pragma values.
++ */
++ static const QMap<QString, QList<QString>> valuesForPragmas{
++ { u"ComponentBehavior"_s, { u"Unbound"_s, u"Bound"_s } },
++ { u"NativeMethodBehavior"_s, { u"AcceptThisObject"_s, u"RejectThisObject"_s } },
++ { u"ListPropertyAssignBehavior"_s, { u"Append"_s, u"Replace"_s, u"ReplaceIfNotDefault"_s } },
++ { u"Singleton"_s, {} },
++ { u"ValueTypeBehavior"_s, { u"Addressable"_s, u"Inaddressable"_s } },
++ };
++
++ void QQmlLSCompletion::insidePragmaCompletion(QQmlJS::Dom::DomItem currentItem,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ if (cursorAfterColon(currentItem, positionInfo)) {
++ const QString name = currentItem.field(Fields::name).value().toString();
++ auto values = valuesForPragmas.constFind(name);
++ if (values == valuesForPragmas.constEnd())
++ return;
++
++ for (const auto &value : *values) {
++ CompletionItem comp;
++ comp.label = value.toUtf8();
++ comp.kind = CompletionItemKind::Value;
++ result = comp;
++ }
++ return;
++ }
++
++ for (const auto &pragma : valuesForPragmas.asKeyValueRange()) {
++ CompletionItem comp;
++ comp.label = pragma.first.toUtf8();
++ if (!pragma.second.isEmpty()) {
++ comp.insertText = QString(pragma.first).append(u": ").toUtf8();
++ }
++ comp.kind = CompletionItemKind::Value;
++ result = comp;
++ }
++ }
++
++ void QQmlLSCompletion::insideQmlObjectCompletion(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation leftBrace = regions[LeftBraceRegion];
++ const QQmlJS::SourceLocation rightBrace = regions[RightBraceRegion];
++
++ if (beforeLocation(positionInfo, leftBrace)) {
++ LocalSymbolsTypes options;
++ options.setFlag(LocalSymbolsType::ObjectType);
++ suggestReachableTypes(positionInfo.itemAtPosition, options, CompletionItemKind::Constructor,
++ result);
++ if (parentForContext.directParent().internalKind() == DomType::Binding)
++ suggestSnippetsForRightHandSideOfBinding(positionInfo.itemAtPosition, result);
++ else
++ suggestSnippetsForLeftHandSideOfBinding(positionInfo.itemAtPosition, result);
++
++ if (QQmlLSUtils::isFieldMemberExpression(positionInfo.itemAtPosition)) {
++ /*!
++ \internal
++ In the case that a missing identifier is followed by an assignment to the default
++ property, the parser will create a QmlObject out of both binding and default
++ binding. For example, in \code property int x: root. Item {} \endcode the parser will
++ create one binding containing one QmlObject of type `root.Item`, instead of two
++ bindings (one for `x` and one for the default property). For this special case, if
++ completion is requested inside `root.Item`, then try to also suggest JS expressions.
++
++ Note: suggestJSExpressionCompletion() will suggest nothing if the
++ fieldMemberExpression starts with the name of a qualified module or a filename, so
++ this only adds invalid suggestions in the case that there is something shadowing the
++ qualified module name or filename, like a property name for example.
++
++ Note 2: This does not happen for field member accesses. For example, in
++ \code
++ property int x: root.x
++ Item {}
++ \endcode
++ The parser will create both bindings correctly.
++ */
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ }
++ return;
++ }
++
++ if (betweenLocations(leftBrace, positionInfo, rightBrace)) {
++ // TODO(QTBUG-138020)
++
++ // property attributes completion
++ constexpr static std::array completions{ ""_L1,
++ "default "_L1,
++ "final "_L1,
++ "virtual "_L1,
++ "override "_L1,
++ "required "_L1,
++ "readonly "_L1,
++ "default final "_L1,
++ "default virtual "_L1,
++ "default override "_L1,
++ "default required "_L1,
++ "final required "_L1,
++ "final readonly "_L1,
++ "virtual required "_L1,
++ "virtual readonly "_L1,
++ "override required "_L1,
++ "override readonly "_L1,
++ "default final required "_L1,
++ "default virtual required "_L1,
++ "default override required "_L1 };
++ for (QLatin1StringView completion : completions) {
++ // By default we should suggest both declarations: with and without an initializer
++ // However readonly properties are required to have an initializer,
++ // and required ones, on the contrary, should not have an initializer
++
++ if (!completion.contains(QLatin1StringView("readonly"))) {
++ result = makeSnippet(
++ QByteArray(completion.data()).append("property type name;"),
++ QByteArray(completion.data()).append("property ${1:type} ${0:name};"));
++ }
++
++ if (!completion.contains(QLatin1StringView("required"))) {
++ result = makeSnippet(
++ QByteArray(completion.data()).append("property type name: value;"),
++ QByteArray(completion.data())
++ .append("property ${1:type} ${2:name}: ${0:value};"));
++ }
++ }
++
++ // signal
++ result = makeSnippet("signal name(arg1:type1, ...)", "signal ${1:name}($0)");
++
++ // signal without parameters
++ result = makeSnippet("signal name;", "signal ${0:name};");
++
++ // make already existing property required
++ result = makeSnippet("required name;", "required ${0:name};");
++
++ // function
++ result = makeSnippet("function name(args...): returnType { statements...}",
++ "function ${1:name}($2): ${3:returnType} {\n\t$0\n}");
++
++ // enum
++ result = makeSnippet("enum name { Values...}", "enum ${1:name} {\n\t${0:values}\n}");
++
++ // inline component
++ result = makeSnippet("component Name: BaseType { ... }",
++ "component ${1:name}: ${2:baseType} {\n\t$0\n}");
++
++ suggestBindingCompletion(positionInfo.itemAtPosition, result);
++
++ // add Qml Types for default binding
++ const DomItem containingFile = parentForContext.containingFile();
++ suggestReachableTypes(containingFile, LocalSymbolsType::ObjectType,
++ CompletionItemKind::Constructor, result);
++ suggestSnippetsForLeftHandSideOfBinding(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ // TODO(QTBUG-138020)
++ void QQmlLSCompletion::insidePropertyDefinitionCompletion(
++ const DomItem &currentItem, const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ auto info = FileLocations::treeOf(currentItem)->info();
++ const QQmlJS::SourceLocation propertyKeyword = info.regions[PropertyKeywordRegion];
++
++ // do completions for the keywords
++ if (positionInfo.offset() < propertyKeyword.end()) {
++ auto addCompletionKeyword = [&result](QUtf8StringView view) {
++ CompletionItem item;
++ item.label = view.data();
++ item.kind = CompletionItemKind::Keyword;
++ result = item;
++ };
++
++ const auto alreadyTyped = [&info,
++ &positionInfo](const FileLocationRegion &keywordRegion) -> bool {
++ const auto &keywordSLoc = info.regions[keywordRegion];
++ return keywordSLoc.isValid() && keywordSLoc.begin() < positionInfo.offset();
++ };
++
++ const bool alreadyTypedReadonlyOrRequired =
++ alreadyTyped(ReadonlyKeywordRegion) || alreadyTyped(RequiredKeywordRegion);
++ if (!alreadyTypedReadonlyOrRequired) {
++ // Suggestions for `readonly` and `required` are mutually exclusive.
++ // If one is already typed, we suggest neither.
++ // If neither is present, both are valid suggestions.
++
++ addCompletionKeyword(u8"readonly");
++ addCompletionKeyword(u8"required");
++ }
++
++ if (!alreadyTyped(DefaultKeywordRegion)) {
++ addCompletionKeyword(u8"default");
++ }
++
++ const bool alreadyTypedVirtualOrOverrideOrFinal = alreadyTyped(VirtualKeywordRegion)
++ || alreadyTyped(OverrideKeywordRegion) || alreadyTyped(FinalKeywordRegion);
++ if (!alreadyTypedVirtualOrOverrideOrFinal) {
++ // `virtual`, `override`, and `final` cannot be combined.
++ // If one is typed, suggest nothing.
++ // If none are typed, suggest all three.
++
++ addCompletionKeyword(u8"final");
++ addCompletionKeyword(u8"virtual");
++ addCompletionKeyword(u8"override");
++ }
++
++ addCompletionKeyword(u8"property");
++
++ return;
++ }
++
++ const QQmlJS::SourceLocation propertyIdentifier = info.regions[IdentifierRegion];
++ if (propertyKeyword.end() <= positionInfo.offset()
++ && positionInfo.offset() < propertyIdentifier.begin()) {
++ suggestReachableTypes(currentItem,
++ LocalSymbolsType::ObjectType | LocalSymbolsType::ValueType,
++ CompletionItemKind::Class, result);
++ }
++ // do not autocomplete the rest
++ return;
++ }
++
++ void QQmlLSCompletion::insideBindingCompletion(const DomItem &currentItem,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const DomItem containingBinding = currentItem.filterUp(
++ [](DomType type, const QQmlJS::Dom::DomItem &) { return type == DomType::Binding; },
++ FilterUpOptions::ReturnOuter);
++
++ // do scriptidentifiercompletion after the ':' of a binding
++ if (cursorAfterColon(containingBinding, positionInfo)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++
++ if (auto type = QQmlLSUtils::resolveExpressionType(currentItem,
++ QQmlLSUtils::ResolveOwnerType)) {
++ const QStringList names = currentItem.field(Fields::name).toString().split(u'.');
++ const QQmlJSScope *current = resolve(type->semanticScope.get(), names);
++ // add type names when binding to an object type or a property with var type
++ if (!current || current->accessSemantics() == QQmlSA::AccessSemantics::Reference) {
++ LocalSymbolsTypes options;
++ options.setFlag(LocalSymbolsType::ObjectType);
++ suggestReachableTypes(positionInfo.itemAtPosition, options,
++ CompletionItemKind::Constructor, result);
++ suggestSnippetsForRightHandSideOfBinding(positionInfo.itemAtPosition, result);
++ }
++ }
++ return;
++ }
++
++ // ignore the binding if asking for completion in front of the binding
++ if (cursorInFrontOfItem(containingBinding, positionInfo)) {
++ insideQmlObjectCompletion(currentItem.containingObject(), positionInfo, result);
++ return;
++ }
++
++ const DomItem containingObject = currentItem.qmlObject();
++
++ suggestBindingCompletion(positionInfo.itemAtPosition, result);
++
++ // add Qml Types for default binding
++ suggestReachableTypes(positionInfo.itemAtPosition, LocalSymbolsType::ObjectType,
++ CompletionItemKind::Constructor, result);
++ suggestSnippetsForLeftHandSideOfBinding(positionInfo.itemAtPosition, result);
++ }
++
++ void QQmlLSCompletion::insideImportCompletion(const DomItem &currentItem,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const DomItem containingFile = currentItem.containingFile();
++ insideImportCompletionHelper(containingFile, positionInfo, result);
++
++ // when in front of the import statement: propose types for root Qml Object completion
++ if (cursorInFrontOfItem(currentItem, positionInfo)) {
++ suggestReachableTypes(containingFile, LocalSymbolsType::ObjectType,
++ CompletionItemKind::Constructor, result);
++ }
++ }
++
++ void QQmlLSCompletion::insideQmlFileCompletion(const DomItem &currentItem,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const DomItem containingFile = currentItem.containingFile();
++ // completions for code outside the root Qml Object
++ // global completions
++ if (positionInfo.cursorPosition.atLineStart()) {
++ if (positionInfo.cursorPosition.base().isEmpty()) {
++ for (const QStringView &s : std::array<QStringView, 2>({ u"pragma", u"import" })) {
++ CompletionItem comp;
++ comp.label = s.toUtf8();
++ comp.kind = CompletionItemKind::Keyword;
++ result = comp;
++ }
++ }
++ }
++ // Types for root Qml Object completion
++ suggestReachableTypes(containingFile, LocalSymbolsType::ObjectType,
++ CompletionItemKind::Constructor, result);
++ }
++
++ /*!
++ \internal
++ Generate the snippets for let, var and const variable declarations.
++ */
++ void QQmlLSCompletion::suggestVariableDeclarationStatementCompletion(
++ BackInsertIterator result, AppendOption option) const
++ {
++ // let/var/const statement
++ for (auto view : std::array<QUtf8StringView, 3>{ "let", "var", "const" }) {
++ auto snippet = makeSnippet(QByteArray(view.data()).append(" variable = value"),
++ QByteArray(view.data()).append(" ${1:variable} = $0"));
++ if (option == AppendSemicolon) {
++ snippet.insertText->append(";");
++ snippet.label.append(";");
++ }
++ result = snippet;
++ }
++ }
++
++ /*!
++ \internal
++ Generate the snippets for case and default statements.
++ */
++ void QQmlLSCompletion::suggestCaseAndDefaultStatementCompletion(BackInsertIterator result) const
++ {
++ // case snippet
++ result = makeSnippet("case value: statements...", "case ${1:value}:\n\t$0");
++ // case + brackets snippet
++ result = makeSnippet("case value: { statements... }", "case ${1:value}: {\n\t$0\n}");
++
++ // default snippet
++ result = makeSnippet("default: statements...", "default:\n\t$0");
++ // default + brackets snippet
++ result = makeSnippet("default: { statements... }", "default: {\n\t$0\n}");
++ }
++
++ /*!
++ \internal
++ Break and continue can be inserted only in following situations:
++ \list
++ \li Break and continue inside a loop.
++ \li Break inside a (nested) LabelledStatement
++ \li Break inside a (nested) SwitchStatement
++ \endlist
++ */
++ void QQmlLSCompletion::suggestContinueAndBreakStatementIfNeeded(const DomItem &itemAtPosition,
++ BackInsertIterator result) const
++ {
++ bool alreadyInLabel = false;
++ bool alreadyInSwitch = false;
++ for (DomItem current = itemAtPosition; current; current = current.directParent()) {
++ switch (current.internalKind()) {
++ case DomType::ScriptExpression:
++ // reached end of script expression
++ return;
++
++ case DomType::ScriptForStatement:
++ case DomType::ScriptForEachStatement:
++ case DomType::ScriptWhileStatement:
++ case DomType::ScriptDoWhileStatement: {
++ CompletionItem continueKeyword;
++ continueKeyword.label = "continue";
++ continueKeyword.kind = CompletionItemKind::Keyword;
++ result = continueKeyword;
++
++ // do not add break twice
++ if (!alreadyInSwitch && !alreadyInLabel) {
++ CompletionItem breakKeyword;
++ breakKeyword.label = "break";
++ breakKeyword.kind = CompletionItemKind::Keyword;
++ result = breakKeyword;
++ }
++ // early exit: cannot suggest more completions
++ return;
++ }
++ case DomType::ScriptSwitchStatement: {
++ // check if break was already inserted
++ if (alreadyInSwitch || alreadyInLabel)
++ break;
++ alreadyInSwitch = true;
++
++ CompletionItem breakKeyword;
++ breakKeyword.label = "break";
++ breakKeyword.kind = CompletionItemKind::Keyword;
++ result = breakKeyword;
++ break;
++ }
++ case DomType::ScriptLabelledStatement: {
++ // check if break was already inserted because of switch or loop
++ if (alreadyInSwitch || alreadyInLabel)
++ break;
++ alreadyInLabel = true;
++
++ CompletionItem breakKeyword;
++ breakKeyword.label = "break";
++ breakKeyword.kind = CompletionItemKind::Keyword;
++ result = breakKeyword;
++ break;
++ }
++ default:
++ break;
++ }
++ }
++ }
++
++ /*!
++ \internal
++ Generates snippets or keywords for all possible JS statements where it makes sense. To use whenever
++ any JS statement can be expected, but when no JS statement is there yet.
++
++ Only generates JS expression completions when itemAtPosition is a qualified name.
++
++ Here is a list of statements that do \e{not} get any snippets:
++ \list
++ \li BlockStatement does not need a code snippet, editors automatically include the closing
++ bracket anyway. \li EmptyStatement completion would only generate a single \c{;} \li
++ ExpressionStatement completion cannot generate any snippet, only identifiers \li WithStatement
++ completion is not recommended: qmllint will warn about usage of with statements \li
++ LabelledStatement completion might need to propose labels (TODO?) \li DebuggerStatement completion
++ does not strike as being very useful \endlist
++ */
++ void QQmlLSCompletion::suggestJSStatementCompletion(const DomItem &itemAtPosition,
++ BackInsertIterator result) const
++ {
++ suggestJSExpressionCompletion(itemAtPosition, result);
++
++ if (QQmlLSUtils::isFieldMemberAccess(itemAtPosition)
++ || QQmlLSUtils::isFieldMemberExpression(itemAtPosition))
++ return;
++
++ // expression statements
++ suggestVariableDeclarationStatementCompletion(result);
++ // block statement
++ result = makeSnippet("{ statements... }", "{\n\t$0\n}");
++
++ // if + brackets statement
++ result = makeSnippet("if (condition) { statements }", "if ($1) {\n\t$0\n}");
++
++ // do statement
++ result = makeSnippet("do { statements } while (condition);", "do {\n\t$1\n} while ($0);");
++
++ // while + brackets statement
++ result = makeSnippet("while (condition) { statements...}", "while ($1) {\n\t$0\n}");
++
++ // for + brackets loop statement
++ result = makeSnippet("for (initializer; condition; increment) { statements... }",
++ "for ($1;$2;$3) {\n\t$0\n}");
++
++ // for ... in + brackets loop statement
++ result = makeSnippet("for (property in object) { statements... }", "for ($1 in $2) {\n\t$0\n}");
++
++ // for ... of + brackets loop statement
++ result = makeSnippet("for (element of array) { statements... }", "for ($1 of $2) {\n\t$0\n}");
++
++ // try + catch statement
++ result = makeSnippet("try { statements... } catch(error) { statements... }",
++ "try {\n\t$1\n} catch($2) {\n\t$0\n}");
++
++ // try + finally statement
++ result = makeSnippet("try { statements... } finally { statements... }",
++ "try {\n\t$1\n} finally {\n\t$0\n}");
++
++ // try + catch + finally statement
++ result = makeSnippet(
++ "try { statements... } catch(error) { statements... } finally { statements... }",
++ "try {\n\t$1\n} catch($2) {\n\t$3\n} finally {\n\t$0\n}");
++
++ // one can always assume that JS code in QML is inside a function, so always propose `return`
++ for (const QByteArray &view : { "return"_ba, "throw"_ba }) {
++ CompletionItem item;
++ item.label = view;
++ item.kind = CompletionItemKind::Keyword;
++ result = item;
++ }
++
++ // rules for case+default statements:
++ // 1) when inside a CaseBlock, or
++ // 2) inside a CaseClause, as an (non-nested) element of the CaseClause statementlist.
++ // 3) inside a DefaultClause, as an (non-nested) element of the DefaultClause statementlist,
++ //
++ // switch (x) {
++ // // (1)
++ // case 1:
++ // myProperty = 5;
++ // // (2) -> could be another statement of current case, but also a new case or default!
++ // default:
++ // myProperty = 5;
++ // // (3) -> could be another statement of current default, but also a new case or default!
++ // }
++ const DomType currentKind = itemAtPosition.internalKind();
++ const DomType parentKind = itemAtPosition.directParent().internalKind();
++ if (currentKind == DomType::ScriptCaseBlock || currentKind == DomType::ScriptCaseClause
++ || currentKind == DomType::ScriptDefaultClause
++ || (currentKind == DomType::List
++ && (parentKind == DomType::ScriptCaseClause
++ || parentKind == DomType::ScriptDefaultClause))) {
++ suggestCaseAndDefaultStatementCompletion(result);
++ }
++ suggestContinueAndBreakStatementIfNeeded(itemAtPosition, result);
++ }
++
++ void QQmlLSCompletion::insideForStatementCompletion(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
++ const QQmlJS::SourceLocation firstSemicolon = regions[FirstSemicolonTokenRegion];
++ const QQmlJS::SourceLocation secondSemicolon = regions[SecondSemicolonRegion];
++ const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
++
++ if (betweenLocations(leftParenthesis, positionInfo, firstSemicolon)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ suggestVariableDeclarationStatementCompletion(result,
++ AppendOption::AppendNothing);
++ return;
++ }
++ if (betweenLocations(firstSemicolon, positionInfo, secondSemicolon)
++ || betweenLocations(secondSemicolon, positionInfo, rightParenthesis)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++
++ if (afterLocation(rightParenthesis, positionInfo)) {
++ suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideScriptLiteralCompletion(const DomItem &currentItem,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ Q_UNUSED(currentItem);
++ if (positionInfo.cursorPosition.base().isEmpty()) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideCallExpression(const DomItem &currentItem,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(currentItem)->info().regions;
++ const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
++ const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
++ if (beforeLocation(positionInfo, leftParenthesis)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ if (betweenLocations(leftParenthesis, positionInfo, rightParenthesis)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideIfStatement(const DomItem &currentItem,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(currentItem)->info().regions;
++ const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
++ const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
++ const QQmlJS::SourceLocation elseKeyword = regions[ElseKeywordRegion];
++
++ if (betweenLocations(leftParenthesis, positionInfo, rightParenthesis)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ if (betweenLocations(rightParenthesis, positionInfo, elseKeyword)) {
++ suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ if (afterLocation(elseKeyword, positionInfo)) {
++ suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideReturnStatement(const DomItem &currentItem,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(currentItem)->info().regions;
++ const QQmlJS::SourceLocation returnKeyword = regions[ReturnKeywordRegion];
++
++ if (afterLocation(returnKeyword, positionInfo)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideWhileStatement(const DomItem &currentItem,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(currentItem)->info().regions;
++ const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
++ const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
++
++ if (betweenLocations(leftParenthesis, positionInfo, rightParenthesis)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ if (afterLocation(rightParenthesis, positionInfo)) {
++ suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideDoWhileStatement(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++ const QQmlJS::SourceLocation doKeyword = regions[DoKeywordRegion];
++ const QQmlJS::SourceLocation whileKeyword = regions[WhileKeywordRegion];
++ const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
++ const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
++
++ if (betweenLocations(doKeyword, positionInfo, whileKeyword)) {
++ suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ if (betweenLocations(leftParenthesis, positionInfo, rightParenthesis)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideForEachStatement(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation inOf = regions[InOfTokenRegion];
++ const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
++ const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
++
++ if (betweenLocations(leftParenthesis, positionInfo, inOf)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ suggestVariableDeclarationStatementCompletion(result);
++ return;
++ }
++ if (betweenLocations(inOf, positionInfo, rightParenthesis)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++
++ if (afterLocation(rightParenthesis, positionInfo)) {
++ suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideSwitchStatement(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
++ const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
++
++ if (betweenLocations(leftParenthesis, positionInfo, rightParenthesis)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideCaseClause(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation caseKeyword = regions[CaseKeywordRegion];
++ const QQmlJS::SourceLocation colonToken = regions[ColonTokenRegion];
++
++ if (betweenLocations(caseKeyword, positionInfo, colonToken)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ if (afterLocation(colonToken, positionInfo)) {
++ suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++
++ }
++
++ /*!
++ \internal
++ Checks if a case or default clause does happen before ctx in the code.
++ */
++ bool QQmlLSCompletion::isCaseOrDefaultBeforeCtx(const DomItem &currentClause,
++ const QQmlLSCompletionPosition &positionInfo,
++ FileLocationRegion keywordRegion) const
++ {
++ Q_ASSERT(keywordRegion == QQmlJS::Dom::CaseKeywordRegion
++ || keywordRegion == QQmlJS::Dom::DefaultKeywordRegion);
++
++ if (!currentClause)
++ return false;
++
++ const auto token = FileLocations::treeOf(currentClause)->info().regions[keywordRegion];
++ if (afterLocation(token, positionInfo))
++ return true;
++
++ return false;
++ }
++
++ /*!
++ \internal
++
++ Search for a `case ...:` or a `default: ` clause happening before ctx, and return the
++ corresponding DomItem of type DomType::CaseClauses or DomType::DefaultClause.
++
++ Return an empty DomItem if neither case nor default was found.
++ */
++ DomItem
++ QQmlLSCompletion::previousCaseOfCaseBlock(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo) const
++ {
++ const DomItem caseClauses = parentForContext.field(Fields::caseClauses);
++ for (int i = 0; i < caseClauses.indexes(); ++i) {
++ const DomItem currentClause = caseClauses.index(i);
++ if (isCaseOrDefaultBeforeCtx(currentClause, positionInfo, QQmlJS::Dom::CaseKeywordRegion)) {
++ return currentClause;
++ }
++ }
++
++ const DomItem defaultClause = parentForContext.field(Fields::defaultClause);
++ if (isCaseOrDefaultBeforeCtx(defaultClause, positionInfo, QQmlJS::Dom::DefaultKeywordRegion))
++ return parentForContext.field(Fields::defaultClause);
++
++ const DomItem moreCaseClauses = parentForContext.field(Fields::moreCaseClauses);
++ for (int i = 0; i < moreCaseClauses.indexes(); ++i) {
++ const DomItem currentClause = moreCaseClauses.index(i);
++ if (isCaseOrDefaultBeforeCtx(currentClause, positionInfo, QQmlJS::Dom::CaseKeywordRegion)) {
++ return currentClause;
++ }
++ }
++
++ return {};
++ }
++
++ void QQmlLSCompletion::insideCaseBlock(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation leftBrace = regions[LeftBraceRegion];
++ const QQmlJS::SourceLocation rightBrace = regions[RightBraceRegion];
++
++ if (!betweenLocations(leftBrace, positionInfo, rightBrace))
++ return;
++
++ // TODO: looks fishy
++ // if there is a previous case or default clause, you can still add statements to it
++ if (const auto previousCase = previousCaseOfCaseBlock(parentForContext, positionInfo)) {
++ suggestJSStatementCompletion(previousCase, result);
++ return;
++ }
++
++ // otherwise, only complete case and default
++ suggestCaseAndDefaultStatementCompletion(result);
++ }
++
++ void QQmlLSCompletion::insideDefaultClause(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation colonToken = regions[ColonTokenRegion];
++
++ if (afterLocation(colonToken, positionInfo)) {
++ suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
++ return ;
++ }
++ }
++
++ void QQmlLSCompletion::insideBinaryExpressionCompletion(
++ const DomItem &parentForContext, const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation operatorLocation = regions[OperatorTokenRegion];
++
++ if (beforeLocation(positionInfo, operatorLocation)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ if (afterLocation(operatorLocation, positionInfo)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ /*!
++ \internal
++ Doing completion in variable declarations requires taking a look at all different cases:
++
++ \list
++ \li Normal variable names, like \c{let helloWorld = 123;}
++ In this case, only autocomplete scriptexpressionidentifiers after the '=' token.
++ Do not propose existing names for the variable name, because the variable name needs to be
++ an identifier that is not used anywhere (to avoid shadowing and confusing code),
++
++ \li Deconstructed arrays, like \c{let [ helloWorld, ] = [ 123, ];}
++ In this case, only autocomplete scriptexpressionidentifiers after the '=' token.
++ Do not propose already existing identifiers inside the left hand side array.
++
++ \li Deconstructed arrays with initializers, like \c{let [ helloWorld = someVar, ] = [ 123, ];}
++ Note: this assigns the value of someVar to helloWorld if the right hand side's first element
++ is undefined or does not exist.
++
++ In this case, only autocomplete scriptexpressionidentifiers after the '=' tokens.
++ Only propose already existing identifiers inside the left hand side array when behind a '='
++ token.
++
++ \li Deconstructed Objects, like \c{let { helloWorld, } = { helloWorld: 123, };}
++ In this case, only autocomplete scriptexpressionidentifiers after the '=' token.
++ Do not propose already existing identifiers inside the left hand side object.
++
++ \li Deconstructed Objects with initializers, like \c{let { helloWorld = someVar, } = {};}
++ Note: this assigns the value of someVar to helloWorld if the right hand side's object does
++ not have a property called 'helloWorld'.
++
++ In this case, only autocomplete scriptexpressionidentifiers after the '=' token.
++ Only propose already existing identifiers inside the left hand side object when behind a '='
++ token.
++
++ \li Finally, you are allowed to nest and combine all above possibilities together for all your
++ deconstruction needs, so the exact same completion needs to be done for
++ DomType::ScriptPatternElement too.
++
++ \endlist
++ */
++ void QQmlLSCompletion::insideScriptPattern(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation equal = regions[EqualTokenRegion];
++
++ if (!afterLocation(equal, positionInfo))
++ return;
++
++ // otherwise, only complete case and default
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ }
++
++ /*!
++ \internal
++ See comment on insideScriptPattern().
++ */
++ void QQmlLSCompletion::insideVariableDeclarationEntry(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ insideScriptPattern(parentForContext, positionInfo, result);
++ }
++
++ void QQmlLSCompletion::insideThrowStatement(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation throwKeyword = regions[ThrowKeywordRegion];
++
++ if (afterLocation(throwKeyword, positionInfo)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideLabelledStatement(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation colon = regions[ColonTokenRegion];
++
++ if (afterLocation(colon, positionInfo)) {
++ suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ // note: the case "beforeLocation(ctx, colon)" probably never happens:
++ // this is because without the colon, the parser will probably not parse this as a
++ // labelledstatement but as a normal expression statement.
++ // So this case only happens when the colon already exists, and the user goes back to the
++ // label name and requests completion for that label.
++ }
++
++ /*!
++ \internal
++ Collect the current set of labels that some DomItem can jump to.
++ */
++ static void collectLabels(const DomItem &context, QQmlLSCompletion::BackInsertIterator result)
++ {
++ for (DomItem current = context; current; current = current.directParent()) {
++ if (current.internalKind() == DomType::ScriptLabelledStatement) {
++ const QString label = current.field(Fields::label).value().toString();
++ if (label.isEmpty())
++ continue;
++ CompletionItem item;
++ item.label = label.toUtf8();
++ item.kind = CompletionItemKind::Value; // variable?
++ // TODO: more stuff here?
++ result = item;
++ } else if (current.internalKind() == DomType::ScriptExpression) {
++ // quick exit when leaving the JS part
++ return;
++ }
++ }
++ return;
++ }
++
++ void QQmlLSCompletion::insideContinueStatement(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation continueKeyword = regions[ContinueKeywordRegion];
++
++ if (afterLocation(continueKeyword, positionInfo)) {
++ collectLabels(parentForContext, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideBreakStatement(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation breakKeyword = regions[BreakKeywordRegion];
++
++ if (afterLocation(breakKeyword, positionInfo)) {
++ collectLabels(parentForContext, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideConditionalExpression(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation questionMark = regions[QuestionMarkTokenRegion];
++ const QQmlJS::SourceLocation colon = regions[ColonTokenRegion];
++
++ if (beforeLocation(positionInfo, questionMark)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ if (betweenLocations(questionMark, positionInfo, colon)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ if (afterLocation(colon, positionInfo)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideUnaryExpression(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation operatorToken = regions[OperatorTokenRegion];
++
++ if (afterLocation(operatorToken, positionInfo)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insidePostExpression(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation operatorToken = regions[OperatorTokenRegion];
++
++ if (beforeLocation(positionInfo, operatorToken)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideParenthesizedExpression(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++
++ const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
++ const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
++
++ if (betweenLocations(leftParenthesis, positionInfo, rightParenthesis)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideTemplateLiteral(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ Q_UNUSED(parentForContext);
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ }
++
++ void QQmlLSCompletion::insideNewExpression(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++ const QQmlJS::SourceLocation newKeyword = regions[NewKeywordRegion];
++
++ if (afterLocation(newKeyword, positionInfo)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::insideNewMemberExpression(const DomItem &parentForContext,
++ const QQmlLSCompletionPosition &positionInfo,
++ BackInsertIterator result) const
++ {
++ const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
++ const QQmlJS::SourceLocation newKeyword = regions[NewKeywordRegion];
++ const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
++ const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
++
++ if (betweenLocations(newKeyword, positionInfo, leftParenthesis)
++ || betweenLocations(leftParenthesis, positionInfo, rightParenthesis)) {
++ suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
++ return;
++ }
++ }
++
++ void QQmlLSCompletion::signalHandlerCompletion(const QQmlJSScope::ConstPtr &scope,
++ QDuplicateTracker<QString> *usedNames,
++ BackInsertIterator result) const
++ {
++ const auto keyValues = scope->methods().asKeyValueRange();
++ for (const auto &[name, method] : keyValues) {
++ if (method.access() != QQmlJSMetaMethod::Public
++ || method.methodType() != QQmlJSMetaMethodType::Signal) {
++ continue;
++ }
++ if (usedNames && usedNames->hasSeen(name)) {
++ continue;
++ }
++
++ CompletionItem completion;
++ completion.label = QQmlSignalNames::signalNameToHandlerName(name).toUtf8();
++ completion.kind = CompletionItemKind::Method;
++ result = completion;
++ }
++ }
++
++ /*!
++ \internal
++ Decide which completions can be used at currentItem and compute them.
++ */
++ QList<CompletionItem>
++ QQmlLSCompletion::completions(const DomItem &currentItem,
++ const CompletionContextStrings &contextStrings) const
++ {
++ QList<CompletionItem> result;
++ collectCompletions(currentItem, contextStrings, std::back_inserter(result));
++ return result;
++ }
++
++ void QQmlLSCompletion::collectCompletions(const DomItem &currentItem,
++ const CompletionContextStrings &contextStrings,
++ BackInsertIterator result) const
++ {
++ /*!
++ Completion is not provided on a script identifier expression because script identifier
++ expressions lack context information. Instead, find the first parent that has enough
++ context information and provide completion for this one.
++ For example, a script identifier expression \c{le} in
++ \badcode
++ for (;le;) { ... }
++ \endcode
++ will get completion for a property called \c{leProperty}, while the same script identifier
++ expression in
++ \badcode
++ for (le;;) { ... }
++ \endcode
++ will, in addition to \c{leProperty}, also get completion for the \c{let} statement snippet.
++ In this example, the parent used for the completion is the for-statement, of type
++ DomType::ScriptForStatement.
++
++ In addition of the parent for the context, use positionInfo to have exact information on where
++ the cursor is (to compare with the SourceLocations of tokens) and which item is at this position
++ (required to provide completion at the correct position, for example for attached properties).
++ */
++ const QQmlLSCompletionPosition positionInfo{ currentItem, contextStrings };
++ for (DomItem currentParent = currentItem; currentParent;
++ currentParent = currentParent.directParent()) {
++ const DomType currentType = currentParent.internalKind();
++
++ switch (currentType) {
++ case DomType::Id:
++ // suppress completions for ids
++ return;
++ case DomType::Pragma:
++ insidePragmaCompletion(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptType: {
++ if (currentParent.directParent().internalKind() == DomType::QmlObject) {
++ insideQmlObjectCompletion(currentParent.directParent(), positionInfo, result);
++ return;
++ }
++
++ LocalSymbolsTypes options;
++ options.setFlag(LocalSymbolsType::ObjectType);
++ options.setFlag(LocalSymbolsType::ValueType);
++ suggestReachableTypes(currentItem, options, CompletionItemKind::Class, result);
++ return;
++ }
++ case DomType::ScriptFormalParameter:
++ // no autocompletion inside of function parameter definition
++ return;
++ case DomType::Binding:
++ insideBindingCompletion(currentParent, positionInfo, result);
++ return;
++ case DomType::Import:
++ insideImportCompletion(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptForStatement:
++ insideForStatementCompletion(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptBlockStatement:
++ suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
++ return;
++ case DomType::QmlFile:
++ insideQmlFileCompletion(currentParent, positionInfo, result);
++ return;
++ case DomType::QmlObject:
++ insideQmlObjectCompletion(currentParent, positionInfo, result);
++ return;
++ case DomType::MethodInfo:
++ // suppress completions
++ return;
++ case DomType::PropertyDefinition:
++ insidePropertyDefinitionCompletion(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptBinaryExpression:
++ // ignore field member expressions: these need additional context from its parents
++ if (QQmlLSUtils::isFieldMemberExpression(currentParent))
++ continue;
++ insideBinaryExpressionCompletion(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptLiteral:
++ insideScriptLiteralCompletion(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptRegExpLiteral:
++ // no completion inside of regexp literals
++ return;
++ case DomType::ScriptCallExpression:
++ insideCallExpression(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptIfStatement:
++ insideIfStatement(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptReturnStatement:
++ insideReturnStatement(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptWhileStatement:
++ insideWhileStatement(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptDoWhileStatement:
++ insideDoWhileStatement(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptForEachStatement:
++ insideForEachStatement(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptTryCatchStatement:
++ /*!
++ \internal
++ The Ecmascript standard specifies that there can only be a block statement between \c
++ try and \c catch(...), \c try and \c finally and \c catch(...) and \c finally, so all of
++ these completions are already handled by the DomType::ScriptBlockStatement completion.
++ The only place in the try statement where there is no BlockStatement and therefore needs
++ its own completion is inside the catch parameter, but that is
++ \quotation
++ An optional identifier or pattern to hold the caught exception for the associated catch
++ block.
++ \endquotation
++ citing
++ This means that no completion is needed inside a catch-expression, as it should contain
++ an identifier that is not yet used anywhere.
++ Therefore, no completion is required at all when inside a try-statement but outside a
++ block-statement.
++ */
++ return;
++ case DomType::ScriptSwitchStatement:
++ insideSwitchStatement(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptCaseClause:
++ insideCaseClause(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptDefaultClause:
++ if (ctxBeforeStatement(positionInfo, currentParent, QQmlJS::Dom::DefaultKeywordRegion))
++ continue;
++ insideDefaultClause(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptCaseBlock:
++ insideCaseBlock(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptVariableDeclaration:
++ // not needed: thats a list of ScriptVariableDeclarationEntry, and those entries cannot
++ // be suggested because they all start with `{`, `[` or an identifier that should not be
++ // in use yet.
++ return;
++ case DomType::ScriptVariableDeclarationEntry:
++ insideVariableDeclarationEntry(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptProperty:
++ // fallthrough: a ScriptProperty is a ScriptPattern but inside a JS Object. It gets the
++ // same completions as a ScriptPattern.
++ case DomType::ScriptPattern:
++ insideScriptPattern(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptThrowStatement:
++ insideThrowStatement(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptLabelledStatement:
++ insideLabelledStatement(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptContinueStatement:
++ insideContinueStatement(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptBreakStatement:
++ insideBreakStatement(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptConditionalExpression:
++ insideConditionalExpression(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptUnaryExpression:
++ insideUnaryExpression(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptPostExpression:
++ insidePostExpression(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptParenthesizedExpression:
++ insideParenthesizedExpression(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptTemplateLiteral:
++ insideTemplateLiteral(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptTemplateStringPart:
++ // no completion inside of the non-expression parts of template strings
++ return;
++ case DomType::ScriptNewExpression:
++ insideNewExpression(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptNewMemberExpression:
++ insideNewMemberExpression(currentParent, positionInfo, result);
++ return;
++ case DomType::ScriptThisExpression:
++ // suppress completions on `this`
++ return;
++ case DomType::ScriptSuperLiteral:
++ // suppress completions on `super`
++ return;
++ case DomType::Comment:
++ // no completion inside of comments
++ return;
++
++ // TODO: Implement those statements.
++ // In the meanwhile, suppress completions to avoid weird behaviors.
++ case DomType::ScriptArray:
++ case DomType::ScriptObject:
++ case DomType::ScriptElision:
++ case DomType::ScriptArrayEntry:
++ return;
++
++ default:
++ continue;
++ }
++ Q_UNREACHABLE();
++ }
++
++ // no completion could be found
++ qCDebug(QQmlLSUtilsLog) << "No completion was found for current request.";
++ return;
++ }
++
++ QQmlLSCompletion::QQmlLSCompletion(const QFactoryLoader &pluginLoader)
++ {
++ const auto keys = pluginLoader.metaDataKeys();
++ for (qsizetype i = 0; i < keys.size(); ++i) {
++ auto instance = std::unique_ptr<QQmlLSPlugin>(
++ qobject_cast<QQmlLSPlugin *>(pluginLoader.instance(i)));
++ if (!instance)
++ continue;
++ if (auto completionInstance = instance->createCompletionPlugin())
++ m_plugins.push_back(std::move(completionInstance));
++ }
++ }
++
++ /*!
++ \internal
++ Helper method to call a method on all loaded plugins.
++ */
++ void QQmlLSCompletion::collectFromPlugins(qxp::function_ref<CompletionFromPluginFunction> f,
++ BackInsertIterator result) const
++ {
++ for (const auto &plugin : m_plugins) {
++ Q_ASSERT(plugin);
++ f(plugin.get(), result);
++ }
++ }
++
++ void QQmlLSCompletion::suggestSnippetsForLeftHandSideOfBinding(const DomItem &itemAtPosition,
++ BackInsertIterator result) const
++ {
++ collectFromPlugins(
++ [&itemAtPosition](QQmlLSCompletionPlugin *p, BackInsertIterator result) {
++ p->suggestSnippetsForLeftHandSideOfBinding(itemAtPosition, result);
++ },
++ result);
++ }
++
++ void QQmlLSCompletion::suggestSnippetsForRightHandSideOfBinding(const DomItem &itemAtPosition,
++ BackInsertIterator result) const
++ {
++ collectFromPlugins(
++ [&itemAtPosition](QQmlLSCompletionPlugin *p, BackInsertIterator result) {
++ p->suggestSnippetsForRightHandSideOfBinding(itemAtPosition, result);
++ },
++ result);
++ }
++
++ QT_END_NAMESPACE