diff options
-rw-r--r-- | src/qml/parser/qqmljs.g | 21 | ||||
-rw-r--r-- | src/qmldom/qqmldomastcreator.cpp | 15 | ||||
-rw-r--r-- | src/qmldom/qqmldomastcreator_p.h | 3 | ||||
-rw-r--r-- | src/qmldom/qqmldomconstants_p.h | 2 | ||||
-rw-r--r-- | src/qmldom/qqmldomexternalitems.cpp | 4 | ||||
-rw-r--r-- | src/qmldom/qqmldomoutwriter.cpp | 3 | ||||
-rw-r--r-- | src/qmlls/qqmllsutils.cpp | 17 | ||||
-rw-r--r-- | tests/auto/qmlls/utils/tst_qmlls_utils.cpp | 6 | ||||
-rw-r--r-- | tests/auto/quick/qquickloader/tst_qquickloader.cpp | 12 |
9 files changed, 71 insertions, 12 deletions
diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index bc5955245a..bb27ccc96f 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -308,6 +308,12 @@ public: inline void enableIdentifierInsertion() { m_enableIdentifierInsertion = true; } + inline bool incompleteBindings() const + { return m_enableIncompleteBindings; } + + inline void enableIncompleteBindings() + { m_enableIncompleteBindings = true; } + protected: bool parse(int startToken); @@ -399,6 +405,7 @@ protected: QList<DiagnosticMessage> diagnostic_messages; bool m_enableIdentifierInsertion = false; + bool m_enableIncompleteBindings = false; }; } // end of namespace QQmlJS @@ -1142,6 +1149,20 @@ case $rule_number: } break; ./ +UiObjectMember: UiQualifiedId Semicolon; +/. + case $rule_number: { + if (!m_enableIncompleteBindings) { + diagnostic_messages.append(compileError(loc(1), QLatin1String("Incomplete binding, expected token `:` or `{`"))); + return false; + } + AST::EmptyStatement *statement = new (pool) AST::EmptyStatement; + statement->semicolonToken = loc(2); + AST::UiScriptBinding *node = new (pool) AST::UiScriptBinding(sym(1).UiQualifiedId, statement); + sym(1).Node = node; + } break; +./ + UiPropertyType: T_VAR; /. case $rule_number: Q_FALLTHROUGH(); ./ UiPropertyType: T_RESERVED_WORD; diff --git a/src/qmldom/qqmldomastcreator.cpp b/src/qmldom/qqmldomastcreator.cpp index a4f4a67789..a748038671 100644 --- a/src/qmldom/qqmldomastcreator.cpp +++ b/src/qmldom/qqmldomastcreator.cpp @@ -2658,6 +2658,21 @@ void QQmlDomAstCreator::endVisit(AST::PreIncrementExpression *statement) pushScriptElement(current); } +bool QQmlDomAstCreator::visit(AST::EmptyStatement *) +{ + return m_enableScriptExpressions; +} + +void QQmlDomAstCreator::endVisit(AST::EmptyStatement *statement) +{ + if (!m_enableScriptExpressions) + return; + + auto current = makeGenericScriptElement(statement, DomType::ScriptEmptyStatement); + current->addLocation(FileLocationRegion::SemicolonTokenRegion, statement->semicolonToken); + pushScriptElement(current); +} + bool QQmlDomAstCreator::visit(AST::PreDecrementExpression *) { return m_enableScriptExpressions; diff --git a/src/qmldom/qqmldomastcreator_p.h b/src/qmldom/qqmldomastcreator_p.h index 4ca4920514..b67687b5e7 100644 --- a/src/qmldom/qqmldomastcreator_p.h +++ b/src/qmldom/qqmldomastcreator_p.h @@ -480,6 +480,9 @@ public: bool visit(AST::PreIncrementExpression *) override; void endVisit(AST::PreIncrementExpression *) override; + bool visit(AST::EmptyStatement *) override; + void endVisit(AST::EmptyStatement *) override; + // lists of stuff whose children do not need a qqmljsscope: visitation order can be custom bool visit(AST::ArgumentList *) override; bool visit(AST::UiParameterList *) override; diff --git a/src/qmldom/qqmldomconstants_p.h b/src/qmldom/qqmldomconstants_p.h index 58aeba68e4..5797be9a63 100644 --- a/src/qmldom/qqmldomconstants_p.h +++ b/src/qmldom/qqmldomconstants_p.h @@ -225,6 +225,7 @@ enum class DomType { ScriptBreakStatement, ScriptContinueStatement, ScriptConditionalExpression, + ScriptEmptyStatement, ScriptElementStop, // marker to check if a DomType is a scriptelement or not }; @@ -390,6 +391,7 @@ enum FileLocationRegion : int { RightBracketRegion, RightParenthesisRegion, SecondSemicolonRegion, + SemicolonTokenRegion, SignalKeywordRegion, ThrowKeywordRegion, TryKeywordRegion, diff --git a/src/qmldom/qqmldomexternalitems.cpp b/src/qmldom/qqmldomexternalitems.cpp index 2860be4c93..1e90c8408b 100644 --- a/src/qmldom/qqmldomexternalitems.cpp +++ b/src/qmldom/qqmldomexternalitems.cpp @@ -445,8 +445,10 @@ QmlFile::QmlFile( QQmlJS::Lexer lexer(m_engine.get()); lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/true); QQmlJS::Parser parser(m_engine.get()); - if (option == EnableParserRecovery) + if (option == EnableParserRecovery) { parser.enableIdentifierInsertion(); + parser.enableIncompleteBindings(); + } m_isValid = parser.parse(); const auto diagnostics = parser.diagnosticMessages(); for (const DiagnosticMessage &msg : diagnostics) { diff --git a/src/qmldom/qqmldomoutwriter.cpp b/src/qmldom/qqmldomoutwriter.cpp index 9b2977252c..3828545d3b 100644 --- a/src/qmldom/qqmldomoutwriter.cpp +++ b/src/qmldom/qqmldomoutwriter.cpp @@ -238,6 +238,9 @@ OutWriter &OutWriter::writeRegion(FileLocationRegion region) case QuestionMarkTokenRegion: codeForRegion = u"?"_s; break; + case SemicolonTokenRegion: + codeForRegion = u";"_s; + break; // not keywords: case ImportUriRegion: diff --git a/src/qmlls/qqmllsutils.cpp b/src/qmlls/qqmllsutils.cpp index a1a7f13215..203f16cd7f 100644 --- a/src/qmlls/qqmllsutils.cpp +++ b/src/qmlls/qqmllsutils.cpp @@ -329,6 +329,12 @@ QList<QQmlLSUtilsItemLocation> QQmlLSUtils::itemsFromTextLocation(const DomItem "not follow the DomItem Structure."; continue; } + // the parser inserts empty Script Expressions for bindings that are not completely + // written out yet. Ignore them here. + if (subItem.domItem.internalKind() == DomType::ScriptExpression + && subLoc->info().fullRegion.length == 0) { + continue; + } subItem.fileLocation = subLoc; toDo.append(subItem); inParentButOutsideChildren = false; @@ -2186,7 +2192,12 @@ QList<CompletionItem> QQmlLSUtils::suggestJSExpressionCompletion(const DomItem & QList<CompletionItem> result; QDuplicateTracker<QString> usedNames; QQmlJSScope::ConstPtr nearestScope; - const bool hasQualifier = isFieldMemberAccess(scriptIdentifier); + + // note: there is an edge case, where the user asks for completion right after the dot + // of some qualified expression like `root.hello`. In this case, scriptIdentifier is actually + // the BinaryExpression instead of the left-hand-side that has not be written down yet. + const bool askForCompletionOnDot = isFieldMemberExpression(scriptIdentifier); + const bool hasQualifier = isFieldMemberAccess(scriptIdentifier) || askForCompletionOnDot; if (!hasQualifier) { result << idsCompletions(scriptIdentifier.component()) @@ -2201,7 +2212,9 @@ QList<CompletionItem> QQmlLSUtils::suggestJSExpressionCompletion(const DomItem & result << enumerationCompletion(nearestScope, &usedNames); } else { - const DomItem owner = scriptIdentifier.directParent().field(Fields::left); + const DomItem owner = + (askForCompletionOnDot ? scriptIdentifier : scriptIdentifier.directParent()) + .field(Fields::left); auto expressionType = QQmlLSUtils::resolveExpressionType( owner, ResolveActualTypeForFieldMemberExpression); if (!expressionType || !expressionType->semanticScope) diff --git a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp index 234ea14677..a1d41c2fb7 100644 --- a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp +++ b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp @@ -3201,7 +3201,7 @@ void tst_qmlls_utils::completions_data() u"bad"_s }; QTest::newRow("groupedPropertyAfterDotMissingRHS") - << testFile("completions/attachedPropertyMissingRHS.qml") << 7 << 11 + << testFile("completions/groupedPropertyMissingRHS.qml") << 7 << 11 << ExpectedCompletions({ { u"family"_s, CompletionItemKind::Property }, }) @@ -3231,10 +3231,6 @@ void tst_qmlls_utils::completions() "Current parser cannot recover from this error yet!", Abort); QEXPECT_FAIL("binaryExpressionMissingRHSWithStatement", "Current parser cannot recover from this error yet!", Abort); - QEXPECT_FAIL("attachedPropertyAfterDotMissingRHS", - "Current parser cannot recover from this error yet!", Abort); - QEXPECT_FAIL("groupedPropertyAfterDotMissingRHS", - "Current parser cannot recover from this error yet!", Abort); QCOMPARE(locations.size(), 1); QString code; diff --git a/tests/auto/quick/qquickloader/tst_qquickloader.cpp b/tests/auto/quick/qquickloader/tst_qquickloader.cpp index 709deddc91..d1093a10e4 100644 --- a/tests/auto/quick/qquickloader/tst_qquickloader.cpp +++ b/tests/auto/quick/qquickloader/tst_qquickloader.cpp @@ -752,8 +752,10 @@ void tst_QQuickLoader::initialPropertyValuesError_data() QTest::newRow("nonexistent source url") << testFileUrl("initialPropertyValues.error.2.qml") << (QStringList() << QString(testFileUrl("NonexistentSourceComponent.qml").toString() + ": No such file or directory")); - QTest::newRow("invalid source url") << testFileUrl("initialPropertyValues.error.3.qml") - << (QStringList() << QString(testFileUrl("InvalidSourceComponent.qml").toString() + ":5:1: Expected token `:'")); + QTest::newRow("invalid source url") + << testFileUrl("initialPropertyValues.error.3.qml") + << (QStringList() << QString(testFileUrl("InvalidSourceComponent.qml").toString() + + ":4:5: Incomplete binding, expected token `:` or `{`")); QTest::newRow("invalid initial property values object with invalid property access") << testFileUrl("initialPropertyValues.error.4.qml") << (QStringList() << QString(testFileUrl("initialPropertyValues.error.4.qml").toString() + ":7:5: QML Loader: setSource: value is not an object") @@ -898,8 +900,10 @@ void tst_QQuickLoader::asynchronous_data() QTest::newRow("Non-existent component") << testFileUrl("IDoNotExist.qml") << (QStringList() << QString(testFileUrl("IDoNotExist.qml").toString() + ": No such file or directory")); - QTest::newRow("Invalid component") << testFileUrl("InvalidSourceComponent.qml") - << (QStringList() << QString(testFileUrl("InvalidSourceComponent.qml").toString() + ":5:1: Expected token `:'")); + QTest::newRow("Invalid component") + << testFileUrl("InvalidSourceComponent.qml") + << (QStringList() << QString(testFileUrl("InvalidSourceComponent.qml").toString() + + ":4:5: Incomplete binding, expected token `:` or `{`")); } void tst_QQuickLoader::asynchronous() |