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