aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qmldom/qqmldomastcreator.cpp96
-rw-r--r--src/qmldom/qqmldomelements.cpp10
-rw-r--r--src/qmldom/qqmldomelements_p.h4
-rw-r--r--src/qmldom/qqmldompath_p.h2
-rw-r--r--src/qmlls/qqmllsutils.cpp57
-rw-r--r--tests/auto/qmldom/domdata/domitem/objectBindings.qml13
-rw-r--r--tests/auto/qmldom/domitem/tst_qmldomitem.h61
-rw-r--r--tests/auto/qmlls/utils/tst_qmlls_utils.cpp31
8 files changed, 216 insertions, 58 deletions
diff --git a/src/qmldom/qqmldomastcreator.cpp b/src/qmldom/qqmldomastcreator.cpp
index a748038671..76c9acc5f9 100644
--- a/src/qmldom/qqmldomastcreator.cpp
+++ b/src/qmldom/qqmldomastcreator.cpp
@@ -119,6 +119,46 @@ SourceLocation combineLocations(Node *n)
return combineLocations(n->firstSourceLocation(), n->lastSourceLocation());
}
+static ScriptElementVariant wrapIntoFieldMemberExpression(const ScriptElementVariant &left,
+ const SourceLocation &dotToken,
+ const ScriptElementVariant &right)
+{
+ SourceLocation s1, s2;
+ left.visitConst([&s1](auto &&el) { s1 = el->mainRegionLocation(); });
+ right.visitConst([&s2](auto &&el) { s2 = el->mainRegionLocation(); });
+
+ auto result = std::make_shared<ScriptElements::BinaryExpression>(s1, s2);
+ result->addLocation(OperatorTokenRegion, dotToken);
+ result->setOp(ScriptElements::BinaryExpression::FieldMemberAccess);
+ result->setLeft(left);
+ result->setRight(right);
+ return ScriptElementVariant::fromElement(result);
+};
+
+/*!
+ \internal
+ Creates a FieldMemberExpression if the qualified id has dots.
+*/
+static ScriptElementVariant fieldMemberExpressionForQualifiedId(AST::UiQualifiedId *qualifiedId)
+{
+ ScriptElementVariant bindable;
+ bool first = true;
+ for (auto exp = qualifiedId; exp; exp = exp->next) {
+ const SourceLocation identifierLoc = exp->identifierToken;
+ auto id = std::make_shared<ScriptElements::IdentifierExpression>(identifierLoc);
+ id->setName(exp->name);
+ if (first) {
+ first = false;
+ bindable = ScriptElementVariant::fromElement(id);
+ continue;
+ }
+ bindable = wrapIntoFieldMemberExpression(bindable, exp->dotToken,
+ ScriptElementVariant::fromElement(id));
+ }
+
+ return bindable;
+}
+
QQmlDomAstCreator::QmlStackElement &QQmlDomAstCreator::currentQmlObjectOrComponentEl(int idx)
{
Q_ASSERT_X(idx < nodeStack.size() && idx >= 0, "currentQmlObjectOrComponentEl",
@@ -751,6 +791,15 @@ bool QQmlDomAstCreator::visit(AST::UiObjectDefinition *el)
el->qualifiedTypeNameId->identifierToken);
}
Q_ASSERT_X(sPtr, className, "could not recover new scope");
+
+ if (m_enableScriptExpressions) {
+ auto qmlObjectType = makeGenericScriptElement(el->qualifiedTypeNameId, DomType::ScriptType);
+ qmlObjectType->insertChild(Fields::typeName,
+ fieldMemberExpressionForQualifiedId(el->qualifiedTypeNameId));
+ sPtr->setNameIdentifiers(
+ finalizeScriptExpression(ScriptElementVariant::fromElement(qmlObjectType),
+ sPathFromOwner.field(Fields::nameIdentifiers), rootMap));
+ }
pushEl(sPathFromOwner, *sPtr, el);
loadAnnotations(el);
return true;
@@ -814,6 +863,16 @@ bool QQmlDomAstCreator::visit(AST::UiObjectBinding *el)
QmlObject *objValue = bPtr->objectValue();
Q_ASSERT_X(objValue, className, "could not recover objectValue");
objValue->setName(toString(el->qualifiedTypeNameId));
+
+ if (m_enableScriptExpressions) {
+ auto qmlObjectType = makeGenericScriptElement(el->qualifiedTypeNameId, DomType::ScriptType);
+ qmlObjectType->insertChild(Fields::typeName,
+ fieldMemberExpressionForQualifiedId(el->qualifiedTypeNameId));
+ objValue->setNameIdentifiers(finalizeScriptExpression(
+ ScriptElementVariant::fromElement(qmlObjectType),
+ bPathFromOwner.field(Fields::value).field(Fields::nameIdentifiers), rootMap));
+ }
+
objValue->addPrototypePath(Paths::lookupTypePath(objValue->name()));
pushEl(bPathFromOwner.field(Fields::value), *objValue, el->initializer);
return true;
@@ -889,22 +948,6 @@ bool QQmlDomAstCreator::visit(AST::UiScriptBinding *el)
.withPath(pathFromOwner)));
}
} else {
- // Create FieldExpression if the bindable element has dots
- const auto reparentExp = [](const auto &left, const SourceLocation &dotToken,
- const auto &right) {
- SourceLocation s1, s2;
- left.visitConst([&s1](auto &&el) { s1 = el->mainRegionLocation(); });
-
- right.visitConst([&s2](auto &&el) { s2 = el->mainRegionLocation(); });
-
- auto result = std::make_shared<ScriptElements::BinaryExpression>(s1, s2);
- result->addLocation(OperatorTokenRegion, dotToken);
- result->setOp(ScriptElements::BinaryExpression::FieldMemberAccess);
- result->setLeft(left);
- result->setRight(right);
- return ScriptElementVariant::fromElement(result);
- };
-
pathFromOwner =
current<QmlObject>().addBinding(bindingV, AddOption::KeepExisting, &bindingPtr);
QmlStackElement &containingObjectEl = currentEl<QmlObject>();
@@ -916,19 +959,7 @@ bool QQmlDomAstCreator::visit(AST::UiScriptBinding *el)
el->qualifiedId->identifierToken);
FileLocations::addRegion(bindingFileLocation, ColonTokenRegion, el->colonToken);
- ScriptElementVariant bindable;
- bool first = true;
- for (auto exp = el->qualifiedId; exp; exp = exp->next) {
- const SourceLocation identifierLoc = exp->identifierToken;
- auto id = std::make_shared<ScriptElements::IdentifierExpression>(identifierLoc);
- id->setName(exp->name);
- if (first) {
- first = false;
- bindable = ScriptElementVariant::fromElement(id);
- continue;
- }
- bindable = reparentExp(bindable, exp->dotToken, ScriptElementVariant::fromElement(id));
- }
+ ScriptElementVariant bindable = fieldMemberExpressionForQualifiedId(el->qualifiedId);
bindingPtr->setBindingIdentifiers(finalizeScriptExpression(
bindable, pathFromOwner.field(Fields::bindingIdentifiers), rootMap));
@@ -1938,13 +1969,12 @@ void QQmlDomAstCreator::endVisit(AST::Type *exp)
auto current = makeGenericScriptElement(exp, DomType::ScriptType);
if (exp->typeArgument) {
- auto currentChild = scriptElementForQualifiedId(exp->typeArgument);
- current->insertChild(Fields::typeArgument, currentChild);
+ current->insertChild(Fields::typeArgumentName,
+ fieldMemberExpressionForQualifiedId(exp->typeArgument));
}
if (exp->typeId) {
- auto currentChild = scriptElementForQualifiedId(exp->typeId);
- current->insertChild(Fields::typeName, currentChild);
+ current->insertChild(Fields::typeName, fieldMemberExpressionForQualifiedId(exp->typeId));
}
pushScriptElement(current);
diff --git a/src/qmldom/qqmldomelements.cpp b/src/qmldom/qqmldomelements.cpp
index fe25087763..dd1cd0be7b 100644
--- a/src/qmldom/qqmldomelements.cpp
+++ b/src/qmldom/qqmldomelements.cpp
@@ -412,6 +412,11 @@ bool QmlObject::iterateBaseDirectSubpaths(const DomItem &self, DirectVisitor vis
[&self](const DomItem &) { return self.propertyInfoNames(); },
QLatin1String("PropertyInfo")));
});
+ if (m_nameIdentifiers) {
+ cont = cont && self.dvItemField(visitor, Fields::nameIdentifiers, [this, &self]() {
+ return self.subScriptElementWrapperItem(m_nameIdentifiers);
+ });
+ }
return cont;
}
@@ -505,6 +510,11 @@ DomItem QmlObject::field(const DomItem &self, QStringView name) const
[copiedSelf = self](const DomItem &) { return copiedSelf.propertyInfoNames(); },
QLatin1String("PropertyInfo")));
break;
+ case 15:
+ if (name == Fields::nameIdentifiers && m_nameIdentifiers) {
+ return self.subScriptElementWrapperItem(m_nameIdentifiers);
+ }
+ break;
case 19:
if (name == Fields::defaultPropertyName)
return self.subDataItem(PathEls::Field(Fields::defaultPropertyName),
diff --git a/src/qmldom/qqmldomelements_p.h b/src/qmldom/qqmldomelements_p.h
index e1afa0cff3..d71f97851c 100644
--- a/src/qmldom/qqmldomelements_p.h
+++ b/src/qmldom/qqmldomelements_p.h
@@ -914,6 +914,9 @@ public:
QQmlJSScope::ConstPtr semanticScope() const { return m_scope; }
void setSemanticScope(const QQmlJSScope::ConstPtr &scope) { m_scope = scope; }
+ ScriptElementVariant nameIdentifiers() const { return m_nameIdentifiers; }
+ void setNameIdentifiers(const ScriptElementVariant &name) { m_nameIdentifiers = name; }
+
private:
friend class QQmlDomAstCreator;
QString m_idStr;
@@ -927,6 +930,7 @@ private:
QList<QmlObject> m_children;
QList<QmlObject> m_annotations;
QQmlJSScope::ConstPtr m_scope;
+ ScriptElementVariant m_nameIdentifiers;
};
class Export
diff --git a/src/qmldom/qqmldompath_p.h b/src/qmldom/qqmldompath_p.h
index be85ebd408..32ee25122f 100644
--- a/src/qmldom/qqmldompath_p.h
+++ b/src/qmldom/qqmldompath_p.h
@@ -502,6 +502,7 @@ QMLDOM_FIELD(nCallbacks);
QMLDOM_FIELD(nLoaded);
QMLDOM_FIELD(nNotdone);
QMLDOM_FIELD(name);
+QMLDOM_FIELD(nameIdentifiers);
QMLDOM_FIELD(newlinesBefore);
QMLDOM_FIELD(nextComponent);
QMLDOM_FIELD(nextScope);
@@ -568,6 +569,7 @@ QMLDOM_FIELD(targetPropertyName);
QMLDOM_FIELD(text);
QMLDOM_FIELD(type);
QMLDOM_FIELD(typeArgument);
+QMLDOM_FIELD(typeArgumentName);
QMLDOM_FIELD(typeName);
QMLDOM_FIELD(types);
QMLDOM_FIELD(universe);
diff --git a/src/qmlls/qqmllsutils.cpp b/src/qmlls/qqmllsutils.cpp
index 99357e172c..8b636082e4 100644
--- a/src/qmlls/qqmllsutils.cpp
+++ b/src/qmlls/qqmllsutils.cpp
@@ -75,6 +75,42 @@ static bool isFieldMemberAccess(const DomItem &item)
/*!
\internal
+ Get the bits of a field member expression, like \c{a}, \c{b} and \c{c} for \c{a.b.c}.
+
+ stopAtChild can either be an FieldMemberExpression, a ScriptIdentifierExpression or a default
+ constructed DomItem: This exits early before processing Field::right of an
+ FieldMemberExpression stopAtChild, or before processing a ScriptIdentifierExpression stopAtChild.
+ No early exits if stopAtChild is default constructed.
+*/
+static QStringList fieldMemberExpressionBits(const DomItem &item, const DomItem &stopAtChild = {})
+{
+ const bool isAccess = isFieldMemberAccess(item);
+ const bool isExpression = isFieldMemberExpression(item);
+
+ // assume it is a non-qualified name
+ if (!isAccess && !isExpression)
+ return { item.value().toString() };
+
+ const DomItem stopMarker =
+ isFieldMemberExpression(stopAtChild) ? stopAtChild : stopAtChild.directParent();
+
+ QStringList result;
+ DomItem current =
+ isAccess ? item.directParent() : (isFieldMemberExpression(item) ? item : DomItem{});
+
+ for (; isFieldMemberExpression(current); current = current.field(Fields::right)) {
+ result << current.field(Fields::left).value().toString();
+
+ if (current == stopMarker)
+ return result;
+ }
+ result << current.value().toString();
+
+ return result;
+}
+
+/*!
+ \internal
The language server protocol calls "URI" what QML calls "URL".
According to RFC 3986, a URL is a special case of URI that not only
identifies a resource but also shows how to access it.
@@ -352,10 +388,7 @@ QList<QQmlLSUtilsItemLocation> QQmlLSUtils::itemsFromTextLocation(const DomItem
DomItem QQmlLSUtils::baseObject(const DomItem &object)
{
- if (!object.as<QmlObject>())
- return {};
-
- auto prototypes = object.field(QQmlJS::Dom::Fields::prototypes);
+ auto prototypes = object.qmlObject().field(QQmlJS::Dom::Fields::prototypes);
switch (prototypes.indexes()) {
case 0:
return {};
@@ -449,8 +482,15 @@ std::optional<QQmlLSUtilsLocation> QQmlLSUtils::findTypeDefinitionOf(const DomIt
[](DomType k, const DomItem &) { return k == DomType::ScriptType; },
FilterUpOptions::ReturnOuter)) {
- const QString name = type.field(Fields::typeName).value().toString();
- typeDefinition = object.path(Paths::lookupTypePath(name));
+ const QString name = fieldMemberExpressionBits(type.field(Fields::typeName)).join(u'.');
+ if (type.directParent().internalKind() == DomType::QmlObject) {
+ // is the type name of a QmlObject, like Item in `Item {...}`
+ typeDefinition = baseObject(type.directParent());
+ } else {
+ // is a type annotation, like Item in `function f(x: Item) { ... }`
+ typeDefinition = object.path(Paths::lookupTypePath(name));
+ }
+
break;
}
if (DomItem id = object.filterUp(
@@ -1358,7 +1398,7 @@ DomItem QQmlLSUtils::sourceLocationToDomItem(const DomItem &file,
static std::optional<QQmlLSUtilsLocation>
findMethodDefinitionOf(const DomItem &file, QQmlJS::SourceLocation location, const QString &name)
{
- DomItem owner = QQmlLSUtils::sourceLocationToDomItem(file, location);
+ DomItem owner = QQmlLSUtils::sourceLocationToDomItem(file, location).qmlObject();
DomItem method = owner.field(Fields::methods).key(name).index(0);
auto fileLocation = FileLocations::treeOf(method);
if (!fileLocation)
@@ -1380,7 +1420,8 @@ static std::optional<QQmlLSUtilsLocation>
findPropertyDefinitionOf(const DomItem &file, QQmlJS::SourceLocation propertyDefinitionLocation,
const QString &name)
{
- DomItem propertyOwner = QQmlLSUtils::sourceLocationToDomItem(file, propertyDefinitionLocation);
+ DomItem propertyOwner =
+ QQmlLSUtils::sourceLocationToDomItem(file, propertyDefinitionLocation).qmlObject();
DomItem propertyDefinition = propertyOwner.field(Fields::propertyDefs).key(name).index(0);
auto fileLocation = FileLocations::treeOf(propertyDefinition);
if (!fileLocation)
diff --git a/tests/auto/qmldom/domdata/domitem/objectBindings.qml b/tests/auto/qmldom/domdata/domitem/objectBindings.qml
new file mode 100644
index 0000000000..d47d60e5ea
--- /dev/null
+++ b/tests/auto/qmldom/domdata/domitem/objectBindings.qml
@@ -0,0 +1,13 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import QtQuick
+import QtQuick as QQ
+
+Item {
+ id: root
+ Item {}
+ QQ.Item {}
+ property var x: root.
+ QQ.Drag {}
+}
diff --git a/tests/auto/qmldom/domitem/tst_qmldomitem.h b/tests/auto/qmldom/domitem/tst_qmldomitem.h
index 3fbe32c920..ead1db101f 100644
--- a/tests/auto/qmldom/domitem/tst_qmldomitem.h
+++ b/tests/auto/qmldom/domitem/tst_qmldomitem.h
@@ -2962,6 +2962,67 @@ private slots:
u"a"_s);
}
+ void objectBindings()
+ {
+ using namespace Qt::StringLiterals;
+ const QString testFile = baseDir + u"/objectBindings.qml"_s;
+ const DomItem rootQmlObject = rootQmlObjectFromFile(testFile, qmltypeDirs);
+
+ const DomItem xBinding = rootQmlObject.path(".bindings[\"x\"][0].value");
+ QCOMPARE(xBinding.field(Fields::name).value().toString(), u"root.QQ.Drag");
+ QCOMPARE(xBinding.field(Fields::nameIdentifiers).internalKind(),
+ DomType::ScriptType);
+ QCOMPARE(xBinding.field(Fields::nameIdentifiers).field(Fields::typeName).internalKind(),
+ DomType::ScriptBinaryExpression);
+ QCOMPARE(xBinding.field(Fields::nameIdentifiers)
+ .field(Fields::typeName)
+ .field(Fields::operation)
+ .value()
+ .toInteger(-1),
+ ScriptElements::BinaryExpression::FieldMemberAccess);
+
+ QCOMPARE(xBinding.field(Fields::nameIdentifiers).field(Fields::typeName).field(Fields::left).internalKind(),
+ DomType::ScriptBinaryExpression);
+ QCOMPARE(xBinding.field(Fields::nameIdentifiers)
+ .field(Fields::typeName)
+ .field(Fields::left)
+ .field(Fields::right)
+ .value()
+ .toString(),
+ u"QQ");
+ QCOMPARE(xBinding.field(Fields::nameIdentifiers)
+ .field(Fields::typeName)
+ .field(Fields::left)
+ .field(Fields::left)
+ .value()
+ .toString(),
+ u"root");
+
+ const DomItem item = rootQmlObject.path(".children[0]");
+ QCOMPARE(item.field(Fields::nameIdentifiers).field(Fields::typeName).value().toString(),
+ u"Item");
+
+ const DomItem qqItem = rootQmlObject.path(".children[1]");
+ QCOMPARE(qqItem.field(Fields::nameIdentifiers)
+ .field(Fields::typeName)
+ .field(Fields::operation)
+ .value()
+ .toInteger(-1),
+ ScriptElements::BinaryExpression::FieldMemberAccess);
+ QCOMPARE(qqItem.field(Fields::nameIdentifiers)
+ .field(Fields::typeName)
+ .field(Fields::right)
+ .value()
+ .toString(),
+ u"Item");
+ QCOMPARE(qqItem.field(Fields::nameIdentifiers)
+ .field(Fields::typeName)
+ .field(Fields::left)
+ .value()
+ .toString(),
+ u"QQ");
+ }
+
private:
QString baseDir;
QStringList qmltypeDirs;
diff --git a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp
index c70638bfa5..519bbafa83 100644
--- a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp
+++ b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp
@@ -189,7 +189,8 @@ void tst_qmlls_utils::findItemFromLocation_data()
<< QQmlJS::Dom::DomType::PropertyDefinition << -1 << 26;
QTest::addRow("onCChild") << file1Qml << 16 << positionAfterOneIndent << firstResult << outOfOne
- << QQmlJS::Dom::DomType::QmlObject << -1 << positionAfterOneIndent;
+ << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1
+ << positionAfterOneIndent;
// check for off-by-one/overlapping items
QTest::addRow("closingBraceOfC")
@@ -201,11 +202,12 @@ void tst_qmlls_utils::findItemFromLocation_data()
QTest::addRow("firstBetweenCandD")
<< file1Qml << 16 << 20 << secondResult << outOfTwo << QQmlJS::Dom::DomType::QmlObject
<< -1 << positionAfterOneIndent;
- QTest::addRow("secondBetweenCandD") << file1Qml << 16 << 20 << firstResult << outOfTwo
- << QQmlJS::Dom::DomType::QmlObject << -1 << -1;
+ QTest::addRow("secondBetweenCandD")
+ << file1Qml << 16 << 20 << firstResult << outOfTwo
+ << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 << -1;
QTest::addRow("afterD") << file1Qml << 16 << 21 << firstResult << outOfOne
- << QQmlJS::Dom::DomType::QmlObject << -1 << 20;
+ << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 << 20;
// check what happens between items (it should not crash)
@@ -226,7 +228,7 @@ void tst_qmlls_utils::findItemFromLocation_data()
QTest::addRow("ic") << file1Qml << 15 << 15 << firstResult << outOfOne
<< QQmlJS::Dom::DomType::QmlComponent << -1 << 5;
QTest::addRow("ic2") << file1Qml << 15 << 20 << firstResult << outOfOne
- << QQmlJS::Dom::DomType::QmlObject << -1 << 18;
+ << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 << 18;
QTest::addRow("ic3") << file1Qml << 15 << 33 << firstResult << outOfOne
<< QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 << 29;
@@ -245,8 +247,9 @@ void tst_qmlls_utils::findItemFromLocation_data()
<< positionAfterOneIndent;
// check rectangle property
- QTest::addRow("rectangle-property") << file1Qml << 44 << 31 << firstResult << outOfOne
- << QQmlJS::Dom::DomType::Binding << -1 << 24;
+ QTest::addRow("rectangle-property")
+ << file1Qml << 44 << 31 << firstResult << outOfOne
+ << QQmlJS::Dom::DomType::ScriptIdentifierExpression << -1 << 29;
}
void tst_qmlls_utils::findItemFromLocation()
@@ -1846,9 +1849,9 @@ void tst_qmlls_utils::completions_data()
QTest::newRow("asCompletions")
<< file << 26 << 9
<< ExpectedCompletions({
- { u"Rectangle"_s, CompletionItemKind::Field },
+ { u"Rectangle"_s, CompletionItemKind::Class },
})
- << QStringList({ u"foo"_s, u"import"_s, u"lala()"_s, u"width"_s });
+ << QStringList({ u"foo"_s, u"import"_s, u"lala"_s, u"width"_s });
QTest::newRow("parameterCompletion")
<< file << 36 << 24
@@ -1906,7 +1909,7 @@ void tst_qmlls_utils::completions_data()
};
QTest::newRow("propertyDefinitionBinding")
- << file << 90 << 28
+ << file << 90 << 27
<< (ExpectedCompletions({
{ u"lala"_s, CompletionItemKind::Method },
{ u"createRectangle"_s, CompletionItemKind::Method },
@@ -1921,7 +1924,7 @@ void tst_qmlls_utils::completions_data()
};
QTest::newRow("ignoreNonRelatedTypesForPropertyDefinitionBinding")
- << file << 16 << 29
+ << file << 16 << 28
<< (ExpectedCompletions({
{ u"createRectangle"_s, CompletionItemKind::Method },
{ u"createItem"_s, CompletionItemKind::Method },
@@ -3276,12 +3279,6 @@ void tst_qmlls_utils::completions()
const QString labelsForPrinting = sortedLabels.join(u", "_s);
for (const ExpectedCompletion &exp : expected) {
- QEXPECT_FAIL(
- "asCompletions",
- "Cannot complete after 'QQ.': either there is already a type behind and then "
- "there is nothing to complete, or there is nothing behind 'QQ.' and the parser "
- "fails because of the unexpected '.'",
- Abort);
QEXPECT_FAIL("letStatementAfterEqual", "Completion not implemented yet!", Abort);
QEXPECT_FAIL("binaryExpressionMissingRHSWithDefaultProperty",
"Current parser cannot recover from this error yet!", Abort);