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:
parent
7e4b5d75e6
commit
7ea3235f5c
|
@ -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));
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue