From ef635e7bcf292e2525c9ed90d1a8370fa8188361 Mon Sep 17 00:00:00 2001 From: Sami Shalayel Date: Tue, 19 Dec 2023 12:05:42 +0100 Subject: qmldom: make type a FieldMemberExpression This commit prepares the Dom to be able to provide correct completions on binding scripts ending with a `.`. In addition to saving qualified types as a string, like `QQ.Item` for example, save them also as a FieldMemberExpression. Do this for QmlObjects, like `QQ.Item {}` for example, and type annotations, like `function f(): QQ.Item {}` for example. This will allow qmlls to suggest completions after `root.` in cases like ``` x: root. SomeQualifiedModule.Item {} ``` for example, or after `QQ.` in cases like ``` (x as QQ.Item) ``` for example. The latter magically makes the asCompletion test work (which was previously QEXPECT_FAIL'd). Prior to this commit, the Dom did not contain enough information to know where exactly the completion gets requested inside of `root.SomeQualifiedModule.Item` and therefore could not suggest any meaningful suggestion. Basically reuses a44f21f19462cc79f82080404515c4322b7728ee to model the nameIdentifiers, a FieldMemberExpression that contains the current type of the QmlObject. Adapt existing tests propertyDefinitionBinding and ignoreNonRelatedTypesForpropertyDefinitionBinding to the changes in the Dom: the qml object type in a binding, for example `Item` in `myBinding: Item {}`, is now part of the QmlObject. It was part of the Binding prior to this commit. Adapt the qmllsutils method to the change in structure. Pick-to: 6.7 Task-number: QTBUG-119839 Change-Id: Ie7bc7692f731a01467392dc1dffdf7e67c4d7c46 Reviewed-by: Ulf Hermann --- src/qmlls/qqmllsutils.cpp | 57 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 8 deletions(-) (limited to 'src/qmlls') 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 @@ -73,6 +73,42 @@ static bool isFieldMemberAccess(const DomItem &item) return item == rightHandSide; } +/*! + \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". @@ -352,10 +388,7 @@ QList QQmlLSUtils::itemsFromTextLocation(const DomItem DomItem QQmlLSUtils::baseObject(const DomItem &object) { - if (!object.as()) - 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 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 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 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) -- cgit v1.2.3