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