diff options
author | Sami Shalayel <sami.shalayel@qt.io> | 2024-01-17 16:08:12 +0100 |
---|---|---|
committer | Sami Shalayel <sami.shalayel@qt.io> | 2024-02-23 09:24:23 +0100 |
commit | 4de69daf82a278bea54dd62fd073449b0d8937f9 (patch) | |
tree | 10460603b20709f0e7c4eedd857fab99032d31ee | |
parent | 49165e9643ee6ad8684b975a2168b8e13cd80e43 (diff) |
qmlls: add Qt Quick completion snippets
Add snippets for Qt Quick types, for feature parity with the Qt Creator
code model.
Do not propose them when QtQuick is not imported, and add the qualifier
to the snippets in case QtQuick is imported with a qualified name, like
`import QtQuick as QQ` for example.
TODO: move the snippets to their own plugin.
Task-number: QTBUG-119969
Change-Id: I874989e28c27d65fcde90b0ccaa4c447c44a5b30
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Reviewed-by: Dmitrii Akshintsev <dmitrii.akshintsev@qt.io>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r-- | src/qmlls/qqmllsutils.cpp | 225 | ||||
-rw-r--r-- | src/qmlls/qqmllsutils_p.h | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmlformat/tst_qmlformat.cpp | 1 | ||||
-rw-r--r-- | tests/auto/qmlls/utils/data/Yyy.qml | 3 | ||||
-rw-r--r-- | tests/auto/qmlls/utils/tst_qmlls_utils.cpp | 293 |
5 files changed, 457 insertions, 66 deletions
diff --git a/src/qmlls/qqmllsutils.cpp b/src/qmlls/qqmllsutils.cpp index b71fcb1349..17cf113290 100644 --- a/src/qmlls/qqmllsutils.cpp +++ b/src/qmlls/qqmllsutils.cpp @@ -171,6 +171,33 @@ static QStringList fieldMemberExpressionBits(const DomItem &item, const DomItem return result; } +static CompletionItem makeSnippet(QUtf8StringView qualifier, QUtf8StringView label, + QUtf8StringView insertText) +{ + CompletionItem res; + if (!qualifier.isEmpty()) { + res.label = qualifier.data(); + res.label += '.'; + } + res.label += label.data(); + res.insertTextFormat = InsertTextFormat::Snippet; + if (!qualifier.isEmpty()) { + res.insertText = qualifier.data(); + *res.insertText += '.'; + *res.insertText += insertText.data(); + } else { + res.insertText = insertText.data(); + } + res.kind = int(CompletionItemKind::Snippet); + res.insertTextMode = InsertTextMode::AdjustIndentation; + return res; +} + +static CompletionItem makeSnippet(QUtf8StringView label, QUtf8StringView insertText) +{ + return makeSnippet(QByteArray(), label, insertText); +} + /*! \internal The language server protocol calls "URI" what QML calls "URL". @@ -1927,6 +1954,146 @@ static QList<CompletionItem> signalHandlerCompletion(const QQmlJSScope::ConstPtr return res; } +static QList<CompletionItem> suggestQuickSnippetsCompletion(const DomItem &itemAtPosition) +{ + QList<CompletionItem> res; + auto file = itemAtPosition.containingFile().as<QmlFile>(); + if (!file) + return {}; + + // check if QtQuick has been imported + const auto &imports = file->imports(); + auto it = std::find_if(imports.constBegin(), imports.constEnd(), [](const Import &import) { + return import.uri.moduleUri() == u"QtQuick"; + }); + if (it == imports.constEnd()) { + return res; + } + + // check if the user already typed some qualifier, remove its dot and compare it to QtQuick's + // qualified name + const QString userTypedQualifier = QQmlLSUtils::qualifiersFrom(itemAtPosition); + if (!userTypedQualifier.isEmpty() + && !it->importId.startsWith(QStringView(userTypedQualifier).chopped(1))) { + return res; + } + + const QByteArray prefixForSnippet = + userTypedQualifier.isEmpty() ? it->importId.toUtf8() : QByteArray(); + const QByteArray prefixWithDotForSnippet = + prefixForSnippet.isEmpty() ? QByteArray() : QByteArray(prefixForSnippet).append(u'.'); + + // Quick completions from Qt Creator's code model + res << makeSnippet(prefixForSnippet, "BorderImage snippet", + "BorderImage {\n" + "\tid: ${1:name}\n" + "\tsource: \"${2:file}\"\n" + "\twidth: ${3:100}; height: ${4:100}\n" + "\tborder.left: ${5: 5}; border.top: ${5}\n" + "\tborder.right: ${5}; border.bottom: ${5}\n" + "}"); + res << makeSnippet(prefixForSnippet, "ColorAnimation snippet", + "ColorAnimation {\n" + "\tfrom: \"${1:white}\"\n" + "\tto: \"${2:black}\"\n" + "\tduration: ${3:200}\n" + "}"); + res << makeSnippet(prefixForSnippet, "Image snippet", + "Image {\n" + "\tid: ${1:name}\n" + "\tsource: \"${2:file}\"\n" + "}"); + res << makeSnippet(prefixForSnippet, "Item snippet", + "Item {\n" + "\tid: ${1:name}\n" + "}"); + res << makeSnippet(prefixForSnippet, "NumberAnimation snippet", + "NumberAnimation {\n" + "\ttarget: ${1:object}\n" + "\tproperty: \"${2:name}\"\n" + "\tduration: ${3:200}\n" + "\teasing.type: "_ba.append(prefixWithDotForSnippet) + .append("Easing.${4:InOutQuad}\n" + "}")); + res << makeSnippet(prefixForSnippet, "NumberAnimation with targets snippet", + "NumberAnimation {\n" + "\ttargets: [${1:object}]\n" + "\tproperties: \"${2:name}\"\n" + "\tduration: ${3:200}\n" + "}"); + res << makeSnippet(prefixForSnippet, "PauseAnimation snippet", + "PauseAnimation {\n" + "\tduration: ${1:200}\n" + "}"); + res << makeSnippet(prefixForSnippet, "PropertyAction snippet", + "PropertyAction {\n" + "\ttarget: ${1:object}\n" + "\tproperty: \"${2:name}\"\n" + "}"); + res << makeSnippet(prefixForSnippet, "PropertyAction with targets snippet", + "PropertyAction {\n" + "\ttargets: [${1:object}]\n" + "\tproperties: \"${2:name}\"\n" + "}"); + res << makeSnippet(prefixForSnippet, "PropertyChanges snippet", + "PropertyChanges {\n" + "\ttarget: ${1:object}\n" + "}"); + res << makeSnippet(prefixForSnippet, "State snippet", + "State {\n" + "\tname: ${1:name}\n" + "\t"_ba.append(prefixWithDotForSnippet) + .append("PropertyChanges {\n" + "\t\ttarget: ${2:object}\n" + "\t}\n" + "}")); + res << makeSnippet(prefixForSnippet, "Text snippet", + "Text {\n" + "\tid: ${1:name}\n" + "\ttext: qsTr(\"${2:text}\")\n" + "}"); + res << makeSnippet(prefixForSnippet, "Transition snippet", + "Transition {\n" + "\tfrom: \"${1:fromState}\"\n" + "\tto: \"${2:toState}\"\n" + "}"); + + if (!userTypedQualifier.isEmpty()) + return res; + + auto resolver = file->typeResolver(); + if (!resolver) + return res; + const auto qquickItemScope = resolver->typeForName(prefixWithDotForSnippet + u"Item"_s); + const QQmlJSScope::ConstPtr ownerScope = itemAtPosition.qmlObject().semanticScope(); + if (!ownerScope || !qquickItemScope) + return res; + + if (ownerScope->inherits(qquickItemScope)) { + res << makeSnippet("states binding with PropertyChanges in State", + "states: [\n" + "\t"_ba.append(prefixWithDotForSnippet) + .append("State {\n" + "\t\tname: \"${1:name}\"\n" + "\t\t"_ba.append(prefixWithDotForSnippet) + .append("PropertyChanges {\n" + "\t\t\ttarget: ${2:object}\n" + "\t\t}\n" + "\t}\n" + "]"))); + res << makeSnippet("transitions binding with Transition", + "transitions: [\n" + "\t"_ba.append(prefixWithDotForSnippet) + .append("Transition {\n" + "\t\tfrom: \"${1:fromState}\"\n" + "\t\tto: \"${2:fromState}\"\n" + "\t}\n" + "]")); + } + + return res; +} + static QList<CompletionItem> suggestBindingCompletion(const DomItem &itemAtPosition) { QList<CompletionItem> res; @@ -2075,6 +2242,29 @@ static bool testScopeSymbol(const QQmlJSScope::ConstPtr &scope, LocalSymbolsType return false; } +QString QQmlLSUtils::qualifiersFrom(const DomItem &el) +{ + const bool isAccess = isFieldMemberAccess(el); + if (!isAccess && !isFieldMemberExpression(el)) + return {}; + + const DomItem fieldMemberExpressionBeginning = + el.filterUp([](DomType, const DomItem &item) { return !isFieldMemberAccess(item); }, + FilterUpOptions::ReturnOuter); + QStringList qualifiers = fieldMemberExpressionBits(fieldMemberExpressionBeginning, el); + + QString result; + for (const QString &qualifier : qualifiers) + result.append(qualifier).append(QChar(u'.')); + return result; +} + +/*! +\internal +Obtain the types reachable from \c{el}. + +The parameter \c{qualifiers} is re-computed from \c{el} when empty. +*/ QList<CompletionItem> QQmlLSUtils::reachableTypes(const DomItem &el, LocalSymbolsTypes options, CompletionItemKind kind) { @@ -2085,22 +2275,7 @@ QList<CompletionItem> QQmlLSUtils::reachableTypes(const DomItem &el, LocalSymbol if (!resolver) return {}; - const QString requiredQualifiers = [&el]() -> QString { - const bool isAccess = isFieldMemberAccess(el); - if (!isAccess && !isFieldMemberExpression(el)) - return {}; - - const DomItem fieldMemberExpressionBeginning = - el.filterUp([](DomType, const DomItem &item) { return !isFieldMemberAccess(item); }, - FilterUpOptions::ReturnOuter); - QStringList qualifiers = fieldMemberExpressionBits(fieldMemberExpressionBeginning, el); - - QString result; - for (const QString &qualifier : qualifiers) - result.append(qualifier).append(u'.'); - return result; - }(); - + const QString requiredQualifiers = qualifiersFrom(el); QList<CompletionItem> res; const auto keyValueRange = resolver->importedTypes().asKeyValueRange(); for (const auto &type : keyValueRange) { @@ -2459,17 +2634,6 @@ static QList<CompletionItem> insidePragmaCompletion(QQmlJS::Dom::DomItem current return res; } -static CompletionItem makeSnippet(QUtf8StringView label, QUtf8StringView insertText) -{ - CompletionItem res; - res.label = label.data(); - res.insertTextFormat = InsertTextFormat::Snippet; - res.insertText = insertText.data(); - res.kind = int(CompletionItemKind::Snippet); - res.insertTextMode = InsertTextMode::AdjustIndentation; - return res; -} - static QList<CompletionItem> insideQmlObjectCompletion(const DomItem &parentForContext, const QQmlLSCompletionPosition &positionInfo) { @@ -2485,6 +2649,7 @@ static QList<CompletionItem> insideQmlObjectCompletion(const DomItem &parentForC options.setFlag(LocalSymbolsType::ObjectType); res << QQmlLSUtils::reachableTypes(positionInfo.itemAtPosition, options, CompletionItemKind::Constructor); + res << suggestQuickSnippetsCompletion(positionInfo.itemAtPosition); if (isFieldMemberExpression(positionInfo.itemAtPosition)) { /*! @@ -2564,6 +2729,7 @@ static QList<CompletionItem> insideQmlObjectCompletion(const DomItem &parentForC const DomItem containingFile = parentForContext.containingFile(); res += QQmlLSUtils::reachableTypes(containingFile, LocalSymbolsType::ObjectType, CompletionItemKind::Constructor); + res << suggestQuickSnippetsCompletion(positionInfo.itemAtPosition); return res; } return {}; @@ -2653,6 +2819,7 @@ static QList<CompletionItem> insideBindingCompletion(const DomItem ¤tItem, options.setFlag(LocalSymbolsType::ObjectType); res << QQmlLSUtils::reachableTypes(positionInfo.itemAtPosition, options, CompletionItemKind::Constructor); + res << suggestQuickSnippetsCompletion(positionInfo.itemAtPosition); } } return res; @@ -2671,6 +2838,7 @@ static QList<CompletionItem> insideBindingCompletion(const DomItem ¤tItem, // add Qml Types for default binding res += QQmlLSUtils::reachableTypes(positionInfo.itemAtPosition, LocalSymbolsType::ObjectType, CompletionItemKind::Constructor); + res << suggestQuickSnippetsCompletion(positionInfo.itemAtPosition); return res; } @@ -2682,9 +2850,10 @@ static QList<CompletionItem> insideImportCompletion(const DomItem ¤tItem, res += insideImportCompletionHelper(containingFile, positionInfo); // when in front of the import statement: propose types for root Qml Object completion - if (cursorInFrontOfItem(currentItem, positionInfo)) + if (cursorInFrontOfItem(currentItem, positionInfo)) { res += QQmlLSUtils::reachableTypes(containingFile, LocalSymbolsType::ObjectType, CompletionItemKind::Constructor); + } return res; } diff --git a/src/qmlls/qqmllsutils_p.h b/src/qmlls/qqmllsutils_p.h index a50256ac8b..1c5ad7598e 100644 --- a/src/qmlls/qqmllsutils_p.h +++ b/src/qmlls/qqmllsutils_p.h @@ -162,6 +162,7 @@ public: using CompletionItem = QLspSpecification::CompletionItem; static QList<CompletionItem> idsCompletions(const DomItem& component); + static QString qualifiersFrom(const DomItem &el); static QList<CompletionItem> reachableTypes(const DomItem &context, QQmlJS::Dom::LocalSymbolsTypes typeCompletionType, QLspSpecification::CompletionItemKind kind); diff --git a/tests/auto/qml/qmlformat/tst_qmlformat.cpp b/tests/auto/qml/qmlformat/tst_qmlformat.cpp index 9d85fec982..c30e62536f 100644 --- a/tests/auto/qml/qmlformat/tst_qmlformat.cpp +++ b/tests/auto/qml/qmlformat/tst_qmlformat.cpp @@ -144,6 +144,7 @@ void TestQmlformat::initTestCase() m_invalidFiles << "tests/auto/qmlls/utils/data/completions/afterDots.qml"; m_invalidFiles << "tests/auto/qmlls/modules/data/completions/bindingAfterDot.qml"; m_invalidFiles << "tests/auto/qmlls/modules/data/completions/defaultBindingAfterDot.qml"; + m_invalidFiles << "tests/auto/qmlls/utils/data/qualifiedModule.qml"; // Files that get changed: // rewrite of import "bla/bla/.." to import "bla" diff --git a/tests/auto/qmlls/utils/data/Yyy.qml b/tests/auto/qmlls/utils/data/Yyy.qml index 80d2812505..6c3886c0a4 100644 --- a/tests/auto/qmlls/utils/data/Yyy.qml +++ b/tests/auto/qmlls/utils/data/Yyy.qml @@ -137,4 +137,7 @@ Zzz { function qualifiedScriptIdentifiers() { console.l() } + QtObject { + + } } diff --git a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp index 8b602a301d..ea6b31e8ef 100644 --- a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp +++ b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp @@ -1664,48 +1664,265 @@ void tst_qmlls_utils::completions_data() const QString propertyCompletion = u"property type name: value;"_s; const QString functionCompletion = u"function name(args...): returnType { statements...}"_s; + + const ExpectedCompletions quickSnippetsWithQualifier{ + { u"QQ.BorderImage snippet"_s, CompletionItemKind::Snippet, + u"QQ.BorderImage {\n" + u"\tid: ${1:name}\n" + u"\tsource: \"${2:file}\"\n" + u"\twidth: ${3:100}; height: ${4:100}\n" + u"\tborder.left: ${5: 5}; border.top: ${5}\n" + u"\tborder.right: ${5}; border.bottom: ${5}\n" + u"}"_s }, + { u"QQ.ColorAnimation snippet"_s, CompletionItemKind::Snippet, + u"QQ.ColorAnimation {\n" + u"\tfrom: \"${1:white}\"\n" + u"\tto: \"${2:black}\"\n" + u"\tduration: ${3:200}\n" + u"}"_s }, + { u"QQ.Image snippet"_s, CompletionItemKind::Snippet, + u"QQ.Image {\n" + u"\tid: ${1:name}\n" + u"\tsource: \"${2:file}\"\n" + u"}"_s }, + { u"QQ.Item snippet"_s, CompletionItemKind::Snippet, + u"QQ.Item {\n" + u"\tid: ${1:name}\n" + u"}"_s }, + { u"QQ.NumberAnimation snippet"_s, CompletionItemKind::Snippet, + u"QQ.NumberAnimation {\n" + u"\ttarget: ${1:object}\n" + u"\tproperty: \"${2:name}\"\n" + u"\tduration: ${3:200}\n" + u"\teasing.type: QQ.Easing.${4:InOutQuad}\n" + u"}"_s }, + { u"QQ.NumberAnimation with targets snippet"_s, CompletionItemKind::Snippet, + u"QQ.NumberAnimation {\n" + u"\ttargets: [${1:object}]\n" + u"\tproperties: \"${2:name}\"\n" + u"\tduration: ${3:200}\n" + u"}"_s }, + { u"QQ.PauseAnimation snippet"_s, CompletionItemKind::Snippet, + u"QQ.PauseAnimation {\n" + u"\tduration: ${1:200}\n" + u"}"_s }, + { u"QQ.PropertyAction snippet"_s, CompletionItemKind::Snippet, + u"QQ.PropertyAction {\n" + u"\ttarget: ${1:object}\n" + u"\tproperty: \"${2:name}\"\n" + "}"_s }, + { u"QQ.PropertyAction with targets snippet"_s, CompletionItemKind::Snippet, + u"QQ.PropertyAction {\n" + u"\ttargets: [${1:object}]\n" + u"\tproperties: \"${2:name}\"\n" + u"}"_s }, + { u"QQ.PropertyChanges snippet"_s, CompletionItemKind::Snippet, + u"QQ.PropertyChanges {\n" + u"\ttarget: ${1:object}\n" + u"}"_s }, + { u"QQ.State snippet"_s, CompletionItemKind::Snippet, + u"QQ.State {\n" + u"\tname: ${1:name}\n" + u"\tQQ.PropertyChanges {\n" + u"\t\ttarget: ${2:object}\n" + u"\t}\n" + u"}"_s }, + { u"QQ.Text snippet"_s, CompletionItemKind::Snippet, + u"QQ.Text {\n" + u"\tid: ${1:name}\n" + u"\ttext: qsTr(\"${2:text}\")\n" + u"}"_s }, + { u"QQ.Transition snippet"_s, CompletionItemKind::Snippet, + u"QQ.Transition {\n" + u"\tfrom: \"${1:fromState}\"\n" + u"\tto: \"${2:toState}\"\n" + u"}"_s }, + { u"states binding with PropertyChanges in State"_s, CompletionItemKind::Snippet, + u"states: [\n" + u"\tQQ.State {\n" + u"\t\tname: \"${1:name}\"\n" + u"\t\tQQ.PropertyChanges {\n" + u"\t\t\ttarget: ${2:object}\n" + u"\t\t}\n" + u"\t}\n" + u"]"_s }, + { u"transitions binding with Transition"_s, CompletionItemKind::Snippet, + u"transitions: [\n" + u"\tQQ.Transition {\n" + u"\t\tfrom: \"${1:fromState}\"\n" + u"\t\tto: \"${2:fromState}\"\n" + u"\t}\n" + u"]"_s } + }; + const ExpectedCompletions quickSnippetsWithoutQualifier{ + { { u"BorderImage snippet"_s, CompletionItemKind::Snippet, + u"BorderImage {\n" + u"\tid: ${1:name}\n" + u"\tsource: \"${2:file}\"\n" + u"\twidth: ${3:100}; height: ${4:100}\n" + u"\tborder.left: ${5: 5}; border.top: ${5}\n" + u"\tborder.right: ${5}; border.bottom: ${5}\n" + u"}"_s }, + { u"ColorAnimation snippet"_s, CompletionItemKind::Snippet, + u"ColorAnimation {\n" + u"\tfrom: \"${1:white}\"\n" + u"\tto: \"${2:black}\"\n" + u"\tduration: ${3:200}\n" + u"}"_s }, + { u"Image snippet"_s, CompletionItemKind::Snippet, + u"Image {\n" + u"\tid: ${1:name}\n" + u"\tsource: \"${2:file}\"\n" + u"}"_s }, + { u"Item snippet"_s, CompletionItemKind::Snippet, + u"Item {\n" + u"\tid: ${1:name}\n" + u"}"_s }, + { u"NumberAnimation snippet"_s, CompletionItemKind::Snippet, + u"NumberAnimation {\n" + u"\ttarget: ${1:object}\n" + u"\tproperty: \"${2:name}\"\n" + u"\tduration: ${3:200}\n" + u"\teasing.type: Easing.${4:InOutQuad}\n" + u"}"_s }, + { u"NumberAnimation with targets snippet"_s, CompletionItemKind::Snippet, + u"NumberAnimation {\n" + u"\ttargets: [${1:object}]\n" + u"\tproperties: \"${2:name}\"\n" + u"\tduration: ${3:200}\n" + u"}"_s }, + { u"PauseAnimation snippet"_s, CompletionItemKind::Snippet, + u"PauseAnimation {\n" + u"\tduration: ${1:200}\n" + u"}"_s }, + { u"PropertyAction snippet"_s, CompletionItemKind::Snippet, + u"PropertyAction {\n" + u"\ttarget: ${1:object}\n" + u"\tproperty: \"${2:name}\"\n" + "}"_s }, + { u"PropertyAction with targets snippet"_s, CompletionItemKind::Snippet, + u"PropertyAction {\n" + u"\ttargets: [${1:object}]\n" + u"\tproperties: \"${2:name}\"\n" + u"}"_s }, + { u"PropertyChanges snippet"_s, CompletionItemKind::Snippet, + u"PropertyChanges {\n" + u"\ttarget: ${1:object}\n" + u"}"_s }, + { u"State snippet"_s, CompletionItemKind::Snippet, + u"State {\n" + u"\tname: ${1:name}\n" + u"\tPropertyChanges {\n" + u"\t\ttarget: ${2:object}\n" + u"\t}\n" + u"}"_s }, + { u"Text snippet"_s, CompletionItemKind::Snippet, + u"Text {\n" + u"\tid: ${1:name}\n" + u"\ttext: qsTr(\"${2:text}\")\n" + u"}"_s }, + { u"Transition snippet"_s, CompletionItemKind::Snippet, + u"Transition {\n" + u"\tfrom: \"${1:fromState}\"\n" + u"\tto: \"${2:toState}\"\n" + u"}"_s } } + }; + const ExpectedCompletions quickSnippetsWithoutQualifierWithBindings = ExpectedCompletions{ + { { u"states binding with PropertyChanges in State"_s, CompletionItemKind::Snippet, + u"states: [\n" + u"\tState {\n" + u"\t\tname: \"${1:name}\"\n" + u"\t\tPropertyChanges {\n" + u"\t\t\ttarget: ${2:object}\n" + u"\t\t}\n" + u"\t}\n" + u"]"_s }, + { u"transitions binding with Transition"_s, CompletionItemKind::Snippet, + u"transitions: [\n" + u"\tTransition {\n" + u"\t\tfrom: \"${1:fromState}\"\n" + u"\t\tto: \"${2:fromState}\"\n" + u"\t}\n" + u"]"_s } } + } += quickSnippetsWithoutQualifier; QTest::newRow("objEmptyLineSnippets") << file << 9 << 1 - << ExpectedCompletions({ - { propertyCompletion, CompletionItemKind::Snippet, - u"property ${1:type} ${2:name}: ${0:value};"_s }, - { u"readonly property type name: value;"_s, CompletionItemKind::Snippet, - u"readonly property ${1:type} ${2:name}: ${0:value};"_s }, - { u"default property type name: value;"_s, CompletionItemKind::Snippet, - u"default property ${1:type} ${2:name}: ${0:value};"_s }, - { u"default required property type name: value;"_s, - CompletionItemKind::Snippet, - u"default required property ${1:type} ${2:name}: ${0:value};"_s }, - { u"required default property type name: value;"_s, - CompletionItemKind::Snippet, - u"required default property ${1:type} ${2:name}: ${0:value};"_s }, - { u"required property type name: value;"_s, CompletionItemKind::Snippet, - u"required property ${1:type} ${2:name}: ${0:value};"_s }, - { u"property type name;"_s, CompletionItemKind::Snippet, - u"property ${1:type} ${0:name};"_s }, - { u"required property type name;"_s, CompletionItemKind::Snippet, - u"required property ${1:type} ${0:name};"_s }, - { u"default property type name;"_s, CompletionItemKind::Snippet, - u"default property ${1:type} ${0:name};"_s }, - { u"default required property type name;"_s, CompletionItemKind::Snippet, - u"default required property ${1:type} ${0:name};"_s }, - { u"required default property type name;"_s, CompletionItemKind::Snippet, - u"required default property ${1:type} ${0:name};"_s }, - { u"signal name(arg1:type1, ...)"_s, CompletionItemKind::Snippet, - u"signal ${1:name}($0)"_s }, - { u"signal name;"_s, CompletionItemKind::Snippet, u"signal ${0:name};"_s }, - { u"required name;"_s, CompletionItemKind::Snippet, - u"required ${0:name};"_s }, - { functionCompletion, CompletionItemKind::Snippet, - u"function ${1:name}($2): ${3:returnType} {\n\t$0\n}"_s }, - { u"enum name { Values...}"_s, CompletionItemKind::Snippet, - u"enum ${1:name} {\n\t${0:values}\n}"_s }, - { u"component Name: BaseType { ... }"_s, CompletionItemKind::Snippet, - u"component ${1:name}: ${2:baseType} {\n\t$0\n}"_s }, - }) + << (ExpectedCompletions({ + { propertyCompletion, CompletionItemKind::Snippet, + u"property ${1:type} ${2:name}: ${0:value};"_s }, + { u"readonly property type name: value;"_s, CompletionItemKind::Snippet, + u"readonly property ${1:type} ${2:name}: ${0:value};"_s }, + { u"default property type name: value;"_s, CompletionItemKind::Snippet, + u"default property ${1:type} ${2:name}: ${0:value};"_s }, + { u"default required property type name: value;"_s, + CompletionItemKind::Snippet, + u"default required property ${1:type} ${2:name}: ${0:value};"_s }, + { u"required default property type name: value;"_s, + CompletionItemKind::Snippet, + u"required default property ${1:type} ${2:name}: ${0:value};"_s }, + { u"required property type name: value;"_s, CompletionItemKind::Snippet, + u"required property ${1:type} ${2:name}: ${0:value};"_s }, + { u"property type name;"_s, CompletionItemKind::Snippet, + u"property ${1:type} ${0:name};"_s }, + { u"required property type name;"_s, CompletionItemKind::Snippet, + u"required property ${1:type} ${0:name};"_s }, + { u"default property type name;"_s, CompletionItemKind::Snippet, + u"default property ${1:type} ${0:name};"_s }, + { u"default required property type name;"_s, CompletionItemKind::Snippet, + u"default required property ${1:type} ${0:name};"_s }, + { u"required default property type name;"_s, CompletionItemKind::Snippet, + u"required default property ${1:type} ${0:name};"_s }, + { u"signal name(arg1:type1, ...)"_s, CompletionItemKind::Snippet, + u"signal ${1:name}($0)"_s }, + { u"signal name;"_s, CompletionItemKind::Snippet, u"signal ${0:name};"_s }, + { u"required name;"_s, CompletionItemKind::Snippet, + u"required ${0:name};"_s }, + { functionCompletion, CompletionItemKind::Snippet, + u"function ${1:name}($2): ${3:returnType} {\n\t$0\n}"_s }, + { u"enum name { Values...}"_s, CompletionItemKind::Snippet, + u"enum ${1:name} {\n\t${0:values}\n}"_s }, + { u"component Name: BaseType { ... }"_s, CompletionItemKind::Snippet, + u"component ${1:name}: ${2:baseType} {\n\t$0\n}"_s }, + }) += quickSnippetsWithoutQualifierWithBindings) // not allowed because required properties need an initializer << QStringList({ u"readonly property type name;"_s }); + QTest::newRow("quickSnippetsForQualifiedQuickImport") + << testFile("qualifiedModule.qml") << 5 << 1 + << quickSnippetsWithQualifier + // not allowed because required properties need an initializer + << QStringList({ u"readonly property type name;"_s }); + + QTest::newRow("quickSnippetsForQualifiedQuickImportBeforeDot") + << testFile("qualifiedModule.qml") << 5 << 7 + << quickSnippetsWithQualifier + // not allowed because required properties need an initializer + << QStringList({ u"readonly property type name;"_s }); + + QTest::newRow("quickSnippetsForQualifiedQuickImportAfterDot") + << testFile("qualifiedModule.qml") << 5 << 8 + << quickSnippetsWithoutQualifier + // not allowed because required properties need an initializer + << QStringList({ u"readonly property type name;"_s, + u"states binding with PropertyChanges in State"_s, + u"transitions binding with Transition"_s }); + + QTest::newRow("quickSnippetsForQualifiedQuickImportBeforeDotInBinding") + << testFile("qualifiedModule.qml") << 4 << 33 << quickSnippetsWithQualifier + << QStringList(); + + QTest::newRow("quickSnippetsForQualifiedQuickImportAfterDotInBinding") + << testFile("qualifiedModule.qml") << 4 << 34 << quickSnippetsWithoutQualifier + << QStringList({ u"states binding with PropertyChanges in State"_s, + u"transitions binding with Transition"_s }); + + // forbid transitions and states because QtObject does not inherit from Item + QTest::newRow("qtObjectEmptyLineSnippets") + << file << 141 << 8 + << ExpectedCompletions{ { u"Item"_s, CompletionItemKind::Constructor } } + << QStringList({ u"transitions binding with Transition"_s, + u"states binding with PropertyChanges in State"_s }); + QTest::newRow("handlers") << file << 5 << 1 << ExpectedCompletions{ { { u"onHandleMe"_s, CompletionItemKind::Method }, @@ -1849,7 +2066,7 @@ void tst_qmlls_utils::completions_data() << ExpectedCompletions({ { u"QQ.Rectangle"_s, CompletionItemKind::Constructor }, }) - << QStringList({ u"foo"_s, u"import"_s, u"lala"_s, u"width"_s }); + << QStringList({ u"foo"_s, u"import"_s, u"lala"_s, }); QTest::newRow("qualifiedTypeCompletionAfterDot") << testFile(u"qualifiedModule.qml"_s) << 4 << 35 |