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