aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/parser/qqmljs.g21
-rw-r--r--src/qmldom/qqmldomastcreator.cpp15
-rw-r--r--src/qmldom/qqmldomastcreator_p.h3
-rw-r--r--src/qmldom/qqmldomconstants_p.h2
-rw-r--r--src/qmldom/qqmldomexternalitems.cpp4
-rw-r--r--src/qmldom/qqmldomoutwriter.cpp3
-rw-r--r--src/qmlls/qqmllsutils.cpp17
-rw-r--r--tests/auto/qmlls/utils/tst_qmlls_utils.cpp6
-rw-r--r--tests/auto/quick/qquickloader/tst_qquickloader.cpp12
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()