qmlls: find C++ & QML definitions of enum

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 <olivier.decanniere@qt.io>
This commit is contained in:
Sami Shalayel 2025-08-08 08:19:11 +02:00
parent 7e4b5d75e6
commit 7ea3235f5c
5 changed files with 103 additions and 4 deletions

View File

@ -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));

View File

@ -1361,9 +1361,11 @@ static std::optional<ExpressionType> 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<Location>
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<QString> 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<Location>
findMethodDefinitionOf(const DomItem &file, QQmlJS::SourceLocation location, const QString &name)
{
@ -2072,7 +2111,26 @@ std::optional<Location> 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"

View File

@ -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"

View File

@ -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
}

View File

@ -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")