From 7ea3235f5c8d00d86584bf620ac5033374e64742 Mon Sep 17 00:00:00 2001 From: Sami Shalayel Date: Fri, 8 Aug 2025 08:19:11 +0200 Subject: [PATCH] qmlls: find C++ & QML definitions of enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement go to definition for enums (values and keys) defined from C++ or QML. This allows user to jump to their own C++ files, and does not jump to headers outside the project folder, like private/qquickitem_p.h for example, as it might get confusing for the user to open a file in the editor that they are not supposed to edit. Task-number: QTBUG-128393 Change-Id: I0410ecf4cf810e6c6072038bffc4564eb787c7fc Reviewed-by: Olivier De Cannière --- src/qmlcompiler/qqmljsimportvisitor.cpp | 1 + src/qmlls/qqmllsutils.cpp | 66 +++++++++++++++++-- .../data/ModuleWithComponent/plugin.qmltypes | 12 ++++ .../data/findDefinition/UseMyCppComponent.qml | 6 ++ tests/auto/qmlls/utils/tst_qmlls_utils.cpp | 22 +++++++ 5 files changed, 103 insertions(+), 4 deletions(-) diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 2edbe61307..91c88081c3 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -2652,6 +2652,7 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied) { QQmlJSMetaEnum qmlEnum(uied->name.toString()); qmlEnum.setIsQml(true); + qmlEnum.setLineNumber(uied->enumToken.startLine); for (const auto *member = uied->members; member; member = member->next) { qmlEnum.addKey(member->member.toString()); qmlEnum.addValue(int(member->value)); diff --git a/src/qmlls/qqmllsutils.cpp b/src/qmlls/qqmllsutils.cpp index 40e619752e..3f39074034 100644 --- a/src/qmlls/qqmllsutils.cpp +++ b/src/qmlls/qqmllsutils.cpp @@ -1361,9 +1361,11 @@ static std::optional resolveFieldMemberExpressionType(const DomI // Enumerations should live under the root element scope of the file that defines the enum, // therefore use the DomItem to find the root element of the qml file instead of directly // using owner->semanticScope. - if (const auto scope = item.goToFile(owner->semanticScope->filePath()) - .rootQmlObject(GoTo::MostLikely) - .semanticScope()) { + if (const auto scope = owner->semanticScope->isComposite() + ? item.goToFile(owner->semanticScope->filePath()) + .rootQmlObject(GoTo::MostLikely) + .semanticScope() + : owner->semanticScope) { if (scope->hasEnumerationKey(name)) { return ExpressionType{ name, scope, EnumeratorValueIdentifier }; } @@ -1908,6 +1910,43 @@ DomItem sourceLocationToDomItem(const DomItem &file, const QQmlJS::SourceLocatio return {}; } +static std::optional +findEnumDefinitionOf(const DomItem &file, QQmlJS::SourceLocation location, const QString &name) +{ + const DomItem enumeration = [&file, &location, &name]() -> DomItem { + const DomItem enumerations = QQmlLSUtils::sourceLocationToDomItem(file, location) + .qmlObject() + .component() + .field(Fields::enumerations); + const QSet enumerationNames = enumerations.keys(); + for (const QString &enumName : enumerationNames) { + const DomItem currentKey = enumerations.key(enumName).index(0); + if (enumName == name) + return currentKey; + const DomItem values = currentKey.field(Fields::values); + for (int i = 0, end = values.size(); i < end; ++i) { + const DomItem currentValue = values.index(i); + if (currentValue.field(Fields::name).value().toStringView() == name) + return currentValue; + } + } + return {}; + }(); + + auto fileLocation = FileLocations::treeOf(enumeration); + + if (!fileLocation) + return {}; + + auto regions = fileLocation->info().regions; + + if (auto it = regions.constFind(IdentifierRegion); it != regions.constEnd()) { + return Location::tryFrom(enumeration.canonicalFilePath(), *it, file); + } + + return {}; +} + static std::optional findMethodDefinitionOf(const DomItem &file, QQmlJS::SourceLocation location, const QString &name) { @@ -2072,7 +2111,26 @@ std::optional findDefinitionOf(const DomItem &item, const QStringList return {}; } case EnumeratorIdentifier: - case EnumeratorValueIdentifier: + case EnumeratorValueIdentifier: { + if (!resolvedExpression->semanticScope->isComposite()) { + const auto enumerations = resolvedExpression->semanticScope->enumerations(); + for (const auto &enumeration : enumerations) { + if (enumeration.hasKey(*resolvedExpression->name) + || enumeration.name() == *resolvedExpression->name) { + return createCppTypeLocation( + resolvedExpression->semanticScope, headerDirectories, + sourceLocationOrDefault(QQmlJS::SourceLocation::fromQSizeType( + 0, 0, enumeration.lineNumber(), 1))); + } + } + return {}; + } + const DomItem ownerFile = item.goToFile(resolvedExpression->semanticScope->filePath()); + const QQmlJS::SourceLocation ownerLocation = + resolvedExpression->semanticScope->sourceLocation(); + return findEnumDefinitionOf(ownerFile, ownerLocation, *resolvedExpression->name); + } + case LambdaMethodIdentifier: case NotAnIdentifier: qCDebug(QQmlLSUtilsLog) << "QQmlLSUtils::findDefinitionOf was not implemented for type" diff --git a/tests/auto/qmlls/utils/data/ModuleWithComponent/plugin.qmltypes b/tests/auto/qmlls/utils/data/ModuleWithComponent/plugin.qmltypes index fabf36e086..7f06e11a4f 100644 --- a/tests/auto/qmlls/utils/data/ModuleWithComponent/plugin.qmltypes +++ b/tests/auto/qmlls/utils/data/ModuleWithComponent/plugin.qmltypes @@ -37,6 +37,18 @@ Module { Method { name: "mySlot"; type: "int"; lineNumber: 29 } Method { name: "myInvokable"; type: "int"; lineNumber: 23 } + Enum { + name: "MyEnum" + lineNumber: 150 + values: ["HelloWorld", "HelloEnum", "HelloSun"] + } + Enum { + name: "MyFlags" + alias: "MyFlag" + isFlag: true + lineNumber: 555 + values: ["HelloFlag", "RedFlag", "GreenFlag"] + } } Component { file: "mycomponentfromcppheader.h" diff --git a/tests/auto/qmlls/utils/data/findDefinition/UseMyCppComponent.qml b/tests/auto/qmlls/utils/data/findDefinition/UseMyCppComponent.qml index e7dcead435..d344a97e15 100644 --- a/tests/auto/qmlls/utils/data/findDefinition/UseMyCppComponent.qml +++ b/tests/auto/qmlls/utils/data/findDefinition/UseMyCppComponent.qml @@ -7,4 +7,10 @@ MyComponentFromCpp { function callSignals() { someSignal(); mySlot(); myInvokable(); } gadget.myProperty: 34 gadget { myProperty2: 35 } + property int someEnum: MyComponentFromCpp.HelloEnum + property int someScopedEnum: MyComponentFromCpp.MyEnum.HelloEnum + property int someFlag: MyComponentFromCpp.HelloFlag + enum EnumFromQml { HelloEnumFromQml, ByeEnumFromQml } + property int someEnum2: UseMyCppComponent.ByeEnumFromQml + property int someScopedEnum2: UseMyCppComponent.EnumFromQml.ByeEnumFromQml } diff --git a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp index 16d242a344..cc499a3053 100644 --- a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp +++ b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp @@ -2012,6 +2012,28 @@ void tst_qmlls_utils::findDefinitionFromLocation_data() << testFile("findDefinition/UseMyCppComponent.qml"_L1) << 9 << 18 << componentFromCppHeaderPath << 38 << 1 << strlen("") << QStringList{ testFile("findDefinition"_L1) }; + QTest::addRow("enumFromCpp") << testFile("findDefinition/UseMyCppComponent.qml"_L1) << 10 << 52 + << componentFromCppHeaderPath << 150 << 1 << strlen("") + << QStringList{ testFile("findDefinition"_L1) }; + QTest::addRow("enumFromCpp2") << testFile("findDefinition/UseMyCppComponent.qml"_L1) << 11 << 60 + << componentFromCppHeaderPath << 150 << 1 << strlen("") + << QStringList{ testFile("findDefinition"_L1) }; + QTest::addRow("scopedEnumFromCpp") << testFile("findDefinition/UseMyCppComponent.qml"_L1) << 11 + << 56 << componentFromCppHeaderPath << 150 << 1 << strlen("") + << QStringList{ testFile("findDefinition"_L1) }; + QTest::addRow("enumFromQml") << testFile("findDefinition/UseMyCppComponent.qml"_L1) << 14 << 51 + << "UseMyCppComponent.qml" << 13 << 42 << strlen("ByeEnumFromQml") + << QStringList{ testFile("findDefinition"_L1) }; + QTest::addRow("enumFromQml2") << testFile("findDefinition/UseMyCppComponent.qml"_L1) << 15 << 68 + << "UseMyCppComponent.qml" << 13 << 42 << strlen("ByeEnumFromQml") + << QStringList{ testFile("findDefinition"_L1) }; + QTest::addRow("scopedEnumFromQml") + << testFile("findDefinition/UseMyCppComponent.qml"_L1) << 15 << 59 + << "UseMyCppComponent.qml" << 13 << 10 << strlen("EnumFromQml") + << QStringList{ testFile("findDefinition"_L1) }; + QTest::addRow("flagFromCpp") << testFile("findDefinition/UseMyCppComponent.qml"_L1) << 12 << 53 + << componentFromCppHeaderPath << 555 << 1 << strlen("") + << QStringList{ testFile("findDefinition"_L1) }; const QString attachedHeaderPath = testFile("findDefinition/SomeIncludeFolder/attached.h"_L1); QTest::addRow("attachedTypeFromCpp")