From aada41a2f114ed95400b575e5d84f545cbb115b9 Mon Sep 17 00:00:00 2001 From: Sami Shalayel Date: Fri, 3 Nov 2023 17:15:10 +0100 Subject: [PATCH] qmlls: auto completion for inside for each statements Save the source location of the parenthesis and the `in` or `of` token. With these locations, provide support for for..in and for..of statements. Task-number: QTBUG-117445 Change-Id: I642e252caa5e8fc9152bcd233d14ce4680e41e32 Reviewed-by: Fabian Kosmale Reviewed-by: Qt CI Bot --- src/qmldom/qqmldomastcreator.cpp | 3 + src/qmlls/qqmllsutils.cpp | 30 ++++++++- tests/auto/qmlls/utils/data/Yyy.qml | 4 ++ tests/auto/qmlls/utils/tst_qmlls_utils.cpp | 78 ++++++++++++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) diff --git a/src/qmldom/qqmldomastcreator.cpp b/src/qmldom/qqmldomastcreator.cpp index 3336a736fe..e7ee513fe0 100644 --- a/src/qmldom/qqmldomastcreator.cpp +++ b/src/qmldom/qqmldomastcreator.cpp @@ -2157,6 +2157,9 @@ void QQmlDomAstCreator::endVisit(AST::ForEachStatement *exp) return; auto current = makeGenericScriptElement(exp, DomType::ScriptForEachStatement); + current->addLocation(FileLocationRegion::InOfTokenRegion, exp->inOfToken); + current->addLocation(FileLocationRegion::LeftParenthesisRegion, exp->lparenToken); + current->addLocation(FileLocationRegion::RightParenthesisRegion, exp->rparenToken); if (exp->statement) { Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty()); diff --git a/src/qmlls/qqmllsutils.cpp b/src/qmlls/qqmllsutils.cpp index 4661a5bace..11fa457c88 100644 --- a/src/qmlls/qqmllsutils.cpp +++ b/src/qmlls/qqmllsutils.cpp @@ -2757,6 +2757,33 @@ static QList insideDoWhileStatement(const DomItem ¤tItem, return {}; } +static QList insideForEachStatement(const DomItem ¤tItem, + const CompletionContextStrings &ctx) +{ + const auto regions = FileLocations::treeOf(currentItem)->info().regions; + + const QQmlJS::SourceLocation inOf = regions[InOfTokenRegion]; + const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion]; + const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion]; + + if (betweenLocations(leftParenthesis, ctx, inOf)) { + QList res; + res << QQmlLSUtils::scriptIdentifierCompletion(currentItem, ctx) + << QQmlLSUtils::suggestVariableDeclarationStatementCompletion(); + return res; + } + if (betweenLocations(inOf, ctx, rightParenthesis)) { + const QList res = QQmlLSUtils::scriptIdentifierCompletion(currentItem, ctx); + return res; + } + + if (afterLocation(rightParenthesis, ctx)) { + return QQmlLSUtils::suggestJSStatementCompletion(currentItem); + } + + return {}; +} + QList QQmlLSUtils::completions(const DomItem ¤tItem, const CompletionContextStrings &ctx) { @@ -2828,6 +2855,8 @@ QList QQmlLSUtils::completions(const DomItem ¤tItem, return insideWhileStatement(currentParent, ctx); case DomType::ScriptDoWhileStatement: return insideDoWhileStatement(currentParent, ctx); + case DomType::ScriptForEachStatement: + return insideForEachStatement(currentParent, ctx); // TODO: Implement those statements. // In the meanwhile, suppress completions to avoid weird behaviors. @@ -2844,7 +2873,6 @@ QList QQmlLSUtils::completions(const DomItem ¤tItem, case DomType::ScriptCaseClauses: case DomType::ScriptCaseClause: case DomType::ScriptDefaultClause: - case DomType::ScriptForEachStatement: return {}; default: diff --git a/tests/auto/qmlls/utils/data/Yyy.qml b/tests/auto/qmlls/utils/data/Yyy.qml index 49306aa07d..92133e0c78 100644 --- a/tests/auto/qmlls/utils/data/Yyy.qml +++ b/tests/auto/qmlls/utils/data/Yyy.qml @@ -130,4 +130,8 @@ Zzz { function helloDoWhileStatement(hello) { do --hello; while (hello); } + function helloForEachStatement(hello) { + for(variable in hello) ++hello; + for(element of hello) ++hello; + } } diff --git a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp index aea8defa7c..87d9554b0f 100644 --- a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp +++ b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp @@ -2440,6 +2440,84 @@ void tst_qmlls_utils::completions_data() << ExpectedCompletions{ { u"hello"_s, CompletionItemKind::Variable }, { letStatementCompletion, CompletionItemKind::Snippet } } << QStringList{ propertyCompletion } << None; + + QTest::newRow("forInStatementLet") + << file << 134 << 13 + << ExpectedCompletions{ + { letStatementCompletion, CompletionItemKind::Snippet }, + { constStatementCompletion, CompletionItemKind::Snippet }, + { varStatementCompletion, CompletionItemKind::Snippet }, + { u"helloJSStatements"_s, CompletionItemKind::Method } + } + << QStringList{ + u"property"_s, + u"readonly"_s, + u"required"_s, + forStatementCompletion, + ifStatementCompletion, + } << None; + + QTest::newRow("forOfStatementLet") + << file << 135 << 13 + << ExpectedCompletions{ + { letStatementCompletion, CompletionItemKind::Snippet }, + { constStatementCompletion, CompletionItemKind::Snippet }, + { varStatementCompletion, CompletionItemKind::Snippet }, + { u"helloJSStatements"_s, CompletionItemKind::Method } + } + << QStringList{ + u"property"_s, + u"readonly"_s, + u"required"_s, + forStatementCompletion, + ifStatementCompletion, + } << None; + + QTest::newRow("forInStatementTarget") + << file << 134 << 25 + << ExpectedCompletions{ + { u"helloJSStatements"_s, CompletionItemKind::Method }, + { u"hello"_s, CompletionItemKind::Variable }, + } + << QStringList{ u"property"_s, u"readonly"_s, u"required"_s, + forStatementCompletion, ifStatementCompletion, varStatementCompletion, + letStatementCompletion, constStatementCompletion, } + << None; + + QTest::newRow("forOfStatementTarget") + << file << 135 << 24 + << ExpectedCompletions{ + { u"helloJSStatements"_s, CompletionItemKind::Method }, + { u"hello"_s, CompletionItemKind::Variable }, + } + << QStringList{ u"property"_s, u"readonly"_s, u"required"_s, + forStatementCompletion, ifStatementCompletion, varStatementCompletion, + letStatementCompletion, constStatementCompletion, } + << None; + + QTest::newRow("forInStatementConsequence") + << file << 134 << 31 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { constStatementCompletion, CompletionItemKind:: Snippet }, + { varStatementCompletion, CompletionItemKind::Snippet }, + { u"helloJSStatements"_s, CompletionItemKind::Method }, + { u"hello"_s, CompletionItemKind::Variable }, + { forStatementCompletion, CompletionItemKind::Snippet } + } + << QStringList{ propertyCompletion } + << None; + + QTest::newRow("forOfStatementConsequence") + << file << 135 << 33 + << ExpectedCompletions{ { letStatementCompletion, CompletionItemKind::Snippet }, + { constStatementCompletion, CompletionItemKind:: Snippet }, + { varStatementCompletion, CompletionItemKind::Snippet }, + { u"helloJSStatements"_s, CompletionItemKind::Method }, + { u"hello"_s, CompletionItemKind::Variable }, + { forStatementCompletion, CompletionItemKind::Snippet } + } + << QStringList{ propertyCompletion } + << None; } void tst_qmlls_utils::completions()