diff options
-rw-r--r-- | src/qml/parser/qqmljs.g | 1 | ||||
-rw-r--r-- | src/qml/parser/qqmljsast_p.h | 1 | ||||
-rw-r--r-- | src/qmldom/qqmldomastcreator.cpp | 1 | ||||
-rw-r--r-- | src/qmlls/qqmlgototypedefinitionsupport.cpp | 22 | ||||
-rw-r--r-- | src/qmlls/qqmllsutils.cpp | 81 | ||||
-rw-r--r-- | src/qmlls/qqmllsutils_p.h | 2 | ||||
-rw-r--r-- | tests/auto/qmlls/utils/tst_qmlls_utils.cpp | 149 |
7 files changed, 122 insertions, 135 deletions
diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index 9a10015cbc..902e8290b4 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -1494,6 +1494,7 @@ UiObjectMember: T_COMPONENT T_IDENTIFIER T_COLON UiObjectDefinition; } auto inlineComponent = new (pool) AST::UiInlineComponent(stringRef(2), sym(4).UiObjectDefinition); inlineComponent->componentToken = loc(1); + inlineComponent->identifierToken = loc(2); sym(1).Node = inlineComponent; } break; ./ diff --git a/src/qml/parser/qqmljsast_p.h b/src/qml/parser/qqmljsast_p.h index 5f718c4c62..494fca9e28 100644 --- a/src/qml/parser/qqmljsast_p.h +++ b/src/qml/parser/qqmljsast_p.h @@ -3538,6 +3538,7 @@ public: QStringView name; UiObjectDefinition* component; SourceLocation componentToken; + SourceLocation identifierToken; }; class QML_PARSER_EXPORT UiSourceElement: public UiObjectMember diff --git a/src/qmldom/qqmldomastcreator.cpp b/src/qmldom/qqmldomastcreator.cpp index 54b72e4d1f..06f380b63f 100644 --- a/src/qmldom/qqmldomastcreator.cpp +++ b/src/qmldom/qqmldomastcreator.cpp @@ -1111,6 +1111,7 @@ bool QQmlDomAstCreator::visit(AST::UiInlineComponent *el) Path p = qmlFilePtr->addComponent(QmlComponent(cName), AddOption::KeepExisting, &compPtr); pushEl(p, *compPtr, el); FileLocations::addRegion(nodeStack.last().fileLocations, u"component", el->componentToken); + FileLocations::addRegion(nodeStack.last().fileLocations, u"identifier", el->identifierToken); loadAnnotations(el); return true; } diff --git a/src/qmlls/qqmlgototypedefinitionsupport.cpp b/src/qmlls/qqmlgototypedefinitionsupport.cpp index 7e2a123af2..d8a0277a62 100644 --- a/src/qmlls/qqmlgototypedefinitionsupport.cpp +++ b/src/qmlls/qqmlgototypedefinitionsupport.cpp @@ -48,28 +48,14 @@ void QmlGoToTypeDefinitionSupport::process(RequestPointerArgument request) QQmlLSUtilsItemLocation &front = std::get<QList<QQmlLSUtilsItemLocation>>(itemsFound).front(); - QQmlJS::Dom::DomItem base = QQmlLSUtils::findTypeDefinitionOf(front.domItem); - if (base.domKind() == QQmlJS::Dom::DomKind::Empty) { - qWarning() << u"Could not obtain the type definition, was the type correctly resolved?"_s - << u"\n Obtained type was:\n"_s << base.toString() - << u"\nbut selected item was:\n" - << front.domItem.toString(); - return; - } + auto base = QQmlLSUtils::findTypeDefinitionOf(front.domItem); - if (base.domKind() == QQmlJS::Dom::DomKind::Empty) { + if (!base) { qDebug() << u"Could not obtain the base from the item"_s; return; } - auto locationInfo = QQmlJS::Dom::FileLocations::fileLocationsOf(base); - if (!locationInfo) { - qDebug() - << u"Could not obtain the text location from the base item, was it correctly resolved?\nBase was "_s - << base.toString(); - return; - } - QQmlJS::Dom::DomItem fileOfBase = base.containingFile(); + QQmlJS::Dom::DomItem fileOfBase = front.domItem.goToFile(base->filename); auto fileOfBasePtr = fileOfBase.ownerAs<QQmlJS::Dom::QmlFile>(); if (!fileOfBasePtr) { qDebug() << u"Could not obtain the file of the base."_s; @@ -80,7 +66,7 @@ void QmlGoToTypeDefinitionSupport::process(RequestPointerArgument request) l.uri = QUrl::fromLocalFile(fileOfBasePtr->canonicalFilePath()).toEncoded(); const QString qmlCode = fileOfBasePtr->code(); - l.range = QQmlLSUtils::qmlLocationToLspLocation(qmlCode, locationInfo->fullRegion); + l.range = QQmlLSUtils::qmlLocationToLspLocation(qmlCode, base->sourceLocation); results.append(l); } diff --git a/src/qmlls/qqmllsutils.cpp b/src/qmlls/qqmllsutils.cpp index 6bb3ceeb7b..2a93000bd4 100644 --- a/src/qmlls/qqmllsutils.cpp +++ b/src/qmlls/qqmllsutils.cpp @@ -289,29 +289,48 @@ DomItem QQmlLSUtils::baseObject(DomItem object) return base; } +static std::optional<QQmlLSUtilsLocation> locationFromDomItem(DomItem &item, + const QString ®ionName = QString()) +{ + QQmlLSUtilsLocation location; + location.filename = item.canonicalFilePath(); + + auto tree = FileLocations::treeOf(item); + // tree is null for C++ defined types, for example + if (!tree) + return {}; + + auto info = tree->info(); + if (!regionName.isEmpty() && info.regions.contains(regionName)) { + location.sourceLocation = info.regions[regionName]; + } else { + location.sourceLocation = info.fullRegion; + } + return location; +} + /*! \internal - \brief Extracts the QML object type of an \l DomItem. - - For a \c PropertyDefinition, return the type of the property. - For a \c Binding, return the bound item's type if an QmlObject is bound, and otherwise the type - of the property. - For a \c QmlObject, do nothing and return it. - For an \c Id, return the object to - which the id resolves. - For a \c Methodparameter, return the type of the parameter. = - Otherwise, return an empty item. + \brief Returns the location of the type definition pointed by object. + + For a \c PropertyDefinition, return the location of the type of the property. + For a \c Binding, return the bound item's type location if an QmlObject is bound, and otherwise + the type of the property. + For a \c QmlObject, return the location of the QmlObject's base. + For an \c Id, return the location of the object to which the id resolves. + For a \c Methodparameter, return the location of the type of the parameter. + Otherwise, return std::nullopt. */ -DomItem QQmlLSUtils::findTypeDefinitionOf(DomItem object) +std::optional<QQmlLSUtilsLocation> QQmlLSUtils::findTypeDefinitionOf(DomItem object) { - DomItem result; + DomItem typeDefinition; switch (object.internalKind()) { case QQmlJS::Dom::DomType::QmlComponent: - result = object.field(Fields::objects).index(0); + typeDefinition = object.field(Fields::objects).index(0); break; case QQmlJS::Dom::DomType::QmlObject: - result = baseObject(object); + typeDefinition = baseObject(object); break; case QQmlJS::Dom::DomType::Binding: { auto binding = object.as<Binding>(); @@ -319,7 +338,8 @@ DomItem QQmlLSUtils::findTypeDefinitionOf(DomItem object) // try to grab the type from the bound object if (binding->valueKind() == BindingValueKind::Object) { - result = baseObject(object.field(Fields::value)); + typeDefinition = baseObject(object.field(Fields::value)); + break; } else { // use the type of the property it is bound on for scriptexpression etc. DomItem propertyDefinition; @@ -334,17 +354,18 @@ DomItem QQmlLSUtils::findTypeDefinitionOf(DomItem object) return true; }, LookupType::PropertyDef); - result = propertyDefinition.field(Fields::type).proceedToScope(); + typeDefinition = propertyDefinition.field(Fields::type).proceedToScope(); + break; } - break; + Q_UNREACHABLE(); } case QQmlJS::Dom::DomType::Id: - result = object.field(Fields::referredObject).proceedToScope(); + typeDefinition = object.field(Fields::referredObject).proceedToScope(); break; case QQmlJS::Dom::DomType::PropertyDefinition: case QQmlJS::Dom::DomType::MethodParameter: case QQmlJS::Dom::DomType::MethodInfo: - result = object.field(Fields::type).proceedToScope(); + typeDefinition = object.field(Fields::type).proceedToScope(); break; case QQmlJS::Dom::DomType::ScriptIdentifierExpression: { if (object.directParent().internalKind() == DomType::ScriptType) { @@ -353,26 +374,34 @@ DomItem QQmlLSUtils::findTypeDefinitionOf(DomItem object) FilterUpOptions::ReturnOuter); const QString name = type.field(Fields::typeName).value().toString(); - result = object.path(Paths::lookupTypePath(name)); + typeDefinition = object.path(Paths::lookupTypePath(name)); break; } + auto scope = QQmlLSUtils::resolveExpressionType( object, QQmlLSUtilsResolveOptions::ResolveActualTypeForFieldMemberExpression); if (!scope) return {}; - return QQmlLSUtils::sourceLocationToDomItem(object.containingFile(), - scope->semanticScope->sourceLocation()); + typeDefinition = QQmlLSUtils::sourceLocationToDomItem( + object.containingFile(), scope->semanticScope->sourceLocation()); + + switch (scope->type) { + case QmlObjectIdIdentifier: + break; + default: + typeDefinition = typeDefinition.component(); + } + + return locationFromDomItem(typeDefinition, u"identifier"_s); } - case QQmlJS::Dom::DomType::Empty: - break; default: qDebug() << "QQmlLSUtils::findTypeDefinitionOf: Found unimplemented Type" << object.internalKindStr(); - result = {}; + return {}; } - return result; + return locationFromDomItem(typeDefinition); } static DomItem findJSIdentifierDefinition(DomItem item, const QString &name) diff --git a/src/qmlls/qqmllsutils_p.h b/src/qmlls/qqmllsutils_p.h index e74021c23d..c83e889870 100644 --- a/src/qmlls/qqmllsutils_p.h +++ b/src/qmlls/qqmllsutils_p.h @@ -126,7 +126,7 @@ public: static QLspSpecification::Range qmlLocationToLspLocation(const QString &code, QQmlJS::SourceLocation qmlLocation); static QQmlJS::Dom::DomItem baseObject(QQmlJS::Dom::DomItem qmlObject); - static QQmlJS::Dom::DomItem findTypeDefinitionOf(QQmlJS::Dom::DomItem item); + static std::optional<QQmlLSUtilsLocation> findTypeDefinitionOf(QQmlJS::Dom::DomItem item); static std::optional<QQmlLSUtilsLocation> findDefinitionOf(QQmlJS::Dom::DomItem item); static QList<QQmlLSUtilsLocation> findUsagesOf(QQmlJS::Dom::DomItem item); diff --git a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp index 6eecda6a11..166689e2b5 100644 --- a/tests/auto/qmlls/utils/tst_qmlls_utils.cpp +++ b/tests/auto/qmlls/utils/tst_qmlls_utils.cpp @@ -299,7 +299,6 @@ void tst_qmlls_utils::findTypeDefinitionFromLocation_data() // item to be checked against QTest::addColumn<int>("resultIndex"); QTest::addColumn<int>("expectedItemsCount"); - QTest::addColumn<QString>("expectedTypeName"); QTest::addColumn<QString>("expectedFilePath"); // set to -1 when unchanged from above line and character. 0-based. QTest::addColumn<int>("expectedLine"); @@ -309,80 +308,74 @@ void tst_qmlls_utils::findTypeDefinitionFromLocation_data() const QString TypeQml = testFile(u"Type.qml"_s); // pass this as file when no result is expected, e.g. for type definition of "var". - QTest::addRow("onCProperty") << file1Qml << 11 << 16 << firstResult << outOfOne << "file1.C" - << file1Qml << 7 << positionAfterOneIndent; + QTest::addRow("onCProperty") << file1Qml << 11 << 16 << firstResult << outOfOne << file1Qml << 7 + << positionAfterOneIndent; - QTest::addRow("onCProperty2") << file1Qml << 28 << 37 << firstResult << outOfOne << "file1.C" - << file1Qml << 7 << positionAfterOneIndent; + QTest::addRow("onCProperty2") << file1Qml << 28 << 37 << firstResult << outOfOne << file1Qml + << 7 << positionAfterOneIndent; - QTest::addRow("onCProperty3") << file1Qml << 28 << 35 << firstResult << outOfOne << "file1.C" - << file1Qml << 7 << positionAfterOneIndent; + QTest::addRow("onCProperty3") << file1Qml << 28 << 35 << firstResult << outOfOne << file1Qml + << 7 << positionAfterOneIndent; - QTest::addRow("onCBinding") << file1Qml << 46 << 8 << firstResult << outOfOne << "file1.C" - << file1Qml << 7 << positionAfterOneIndent; + QTest::addRow("onCBinding") << file1Qml << 46 << 8 << firstResult << outOfOne << file1Qml << 7 + << positionAfterOneIndent; - QTest::addRow("onDefaultBinding") - << file1Qml << 16 << positionAfterOneIndent << firstResult << outOfOne << "file1.C" - << file1Qml << 7 << positionAfterOneIndent; + QTest::addRow("onDefaultBinding") << file1Qml << 16 << positionAfterOneIndent << firstResult + << outOfOne << file1Qml << 7 << positionAfterOneIndent; QTest::addRow("onDefaultBindingId") - << file1Qml << 16 << 28 << firstResult << outOfOne << "firstD" << file1Qml << 16 << 20; + << file1Qml << 16 << 28 << firstResult << outOfOne << file1Qml << 16 << 20; - QTest::addRow("findIntProperty") << file1Qml << 9 << 18 << firstResult << outOfOne << u"int"_s - << file1Qml << -1 << positionAfterOneIndent; - - QTest::addRow("colorBinding") << file1Qml << 39 << 8 << firstResult << outOfOne << u"QColor"_s - << file1Qml << -1 << positionAfterOneIndent; + QTest::addRow("findIntProperty") << file1Qml << 9 << 18 << firstResult << outOfOne << file1Qml + << -1 << positionAfterOneIndent; + QTest::addRow("colorBinding") << file1Qml << 39 << 8 << firstResult << outOfOne << file1Qml + << -1 << positionAfterOneIndent; // check what happens between items (it should not crash) - QTest::addRow("onWhitespaceBeforeC") << file1Qml << 16 << 1 << firstResult << outOfOne - << noResultExpected << noResultExpected << -1 << -1; + QTest::addRow("onWhitespaceBeforeC") + << file1Qml << 16 << 1 << firstResult << outOfOne << noResultExpected << -1 << -1; - QTest::addRow("onWhitespaceAfterC") << file1Qml << 17 << 23 << firstResult << outOfOne - << noResultExpected << noResultExpected << -1 << -1; + QTest::addRow("onWhitespaceAfterC") + << file1Qml << 17 << 23 << firstResult << outOfOne << noResultExpected << -1 << -1; - QTest::addRow("onWhitespaceBetweenCAndD") << file1Qml << 17 << 24 << firstResult << outOfOne - << noResultExpected << noResultExpected << -1 << -1; + QTest::addRow("onWhitespaceBetweenCAndD") + << file1Qml << 17 << 24 << firstResult << outOfOne << noResultExpected << -1 << -1; - QTest::addRow("ic") << file1Qml << 15 << 15 << firstResult << outOfOne << u"icid"_s << file1Qml - << -1 << 18; - QTest::addRow("icBase") << file1Qml << 15 << 20 << firstResult << outOfOne << u"QQuickItem"_s + QTest::addRow("ic") << file1Qml << 15 << 15 << firstResult << outOfOne << file1Qml << -1 << 18; + QTest::addRow("icBase") << file1Qml << 15 << 20 << firstResult << outOfOne << u"TODO: file location for C++ defined types?"_s << -1 << -1; - QTest::addRow("ic3") << file1Qml << 15 << 33 << firstResult << outOfOne << u"icid"_s << file1Qml - << -1 << 18; + QTest::addRow("ic3") << file1Qml << 15 << 33 << firstResult << outOfOne << file1Qml << -1 << 18; // TODO: type definition of function = type definition of return type? // if not, this might need fixing: // currently, asking the type definition of the "function" keyword returns // the type definitin of the return type (when available). - QTest::addRow("function-keyword") << file1Qml << 33 << 5 << firstResult << outOfOne - << u"file1.C"_s << file1Qml << 7 << positionAfterOneIndent; + QTest::addRow("function-keyword") << file1Qml << 33 << 5 << firstResult << outOfOne << file1Qml + << 7 << positionAfterOneIndent; QTest::addRow("function-parameter-builtin") - << file1Qml << 33 << 20 << firstResult << outOfOne << "a" << file1Qml << -1 << -1; - QTest::addRow("function-parameter-item") - << file1Qml << 33 << 36 << firstResult << outOfOne << "file1.C" << file1Qml << 7 - << positionAfterOneIndent; + << file1Qml << 33 << 20 << firstResult << outOfOne << file1Qml << -1 << -1; + QTest::addRow("function-parameter-item") << file1Qml << 33 << 36 << firstResult << outOfOne + << file1Qml << 7 << positionAfterOneIndent; - QTest::addRow("function-return") << file1Qml << 33 << 41 << firstResult << outOfOne << "file1.C" - << file1Qml << 7 << positionAfterOneIndent; + QTest::addRow("function-return") << file1Qml << 33 << 41 << firstResult << outOfOne << file1Qml + << 7 << positionAfterOneIndent; - QTest::addRow("void-function") << file1Qml << 36 << 17 << firstResult << outOfOne << "void" - << noResultExpected << -1 << -1; + QTest::addRow("void-function") + << file1Qml << 36 << 17 << firstResult << outOfOne << noResultExpected << -1 << -1; - QTest::addRow("rectangle-property") - << file1Qml << 44 << 31 << firstResult << outOfOne << "QQuickRectangle" - << "TODO: c++ type location" << -1 << -1; + QTest::addRow("rectangle-property") << file1Qml << 44 << 31 << firstResult << outOfOne + << "TODO: c++ type location" << -1 << -1; QTest::addRow("functionParameterICUsage") - << file1Qml << 34 << 16 << firstResult << outOfOne << "file1.C" << file1Qml << 7 << 18; + << file1Qml << 34 << 16 << firstResult << outOfOne << file1Qml << 7 << 15; QTest::addRow("ICBindingUsage") - << file1Qml << 47 << 21 << firstResult << outOfOne << "file1.C" << file1Qml << 7 << 18; + << file1Qml << 47 << 21 << firstResult << outOfOne << file1Qml << 7 << 15; QTest::addRow("ICBindingUsage2") - << file1Qml << 49 << 11 << firstResult << outOfOne << "file1.C" << file1Qml << 7 << 18; + << file1Qml << 49 << 11 << firstResult << outOfOne << file1Qml << 7 << 15; QTest::addRow("ICBindingUsage3") - << file1Qml << 52 << 17 << firstResult << outOfOne << "file1.C" << file1Qml << 7 << 18; + << file1Qml << 52 << 17 << firstResult << outOfOne << file1Qml << 7 << 15; } void tst_qmlls_utils::findTypeDefinitionFromLocation() @@ -392,7 +385,6 @@ void tst_qmlls_utils::findTypeDefinitionFromLocation() QFETCH(int, character); QFETCH(int, resultIndex); QFETCH(int, expectedItemsCount); - QFETCH(QString, expectedTypeName); QFETCH(QString, expectedFilePath); QFETCH(int, expectedLine); QFETCH(int, expectedCharacter); @@ -419,67 +411,39 @@ void tst_qmlls_utils::findTypeDefinitionFromLocation() QCOMPARE(locations.size(), expectedItemsCount); - QQmlJS::Dom::DomItem type = QQmlLSUtils::findTypeDefinitionOf(locations[resultIndex].domItem); + auto base = QQmlLSUtils::findTypeDefinitionOf(locations[resultIndex].domItem); // if expectedFilePath is empty, we probably just want to make sure that it does // not crash if (expectedFilePath == noResultExpected) { - QCOMPARE(type.internalKind(), QQmlJS::Dom::DomType::Empty); + QVERIFY(!base); return; } - QVERIFY(type); - - QQmlJS::Dom::FileLocations::Tree typeLocationToTest = QQmlJS::Dom::FileLocations::treeOf(type); - QEXPECT_FAIL("findIntProperty", "Builtins not supported yet", Abort); QEXPECT_FAIL("function-parameter-builtin", "Base types defined in C++ are not supported yet", Abort); QEXPECT_FAIL("colorBinding", "Types from C++ bases not supported yet", Abort); QEXPECT_FAIL("rectangle-property", "Types from C++ bases not supported yet", Abort); QEXPECT_FAIL("icBase", "Base types defined in C++ are not supported yet", Abort); + QVERIFY(base); - auto fileObject = type.containingFile().as<QQmlJS::Dom::QmlFile>(); + auto fileObject = + locations[resultIndex].domItem.goToFile(base->filename).as<QQmlJS::Dom::QmlFile>(); // print some debug message when failing, instead of using QVERIFY2 // (printing the type every time takes a lot of time). if constexpr (enable_debug_output) { if (!fileObject) - qDebug() << "This has no file: " << type.containingFile() << " for type " - << type.field(QQmlJS::Dom::Fields::type).get().component(); + qDebug() << "Could not find the file" << base->filename << "in the Dom."; } QVERIFY(fileObject); + QCOMPARE(base->filename, expectedFilePath); QCOMPARE(fileObject->canonicalFilePath(), expectedFilePath); - QCOMPARE(typeLocationToTest->info().fullRegion.startLine, quint32(expectedLine)); - QCOMPARE(typeLocationToTest->info().fullRegion.startColumn, quint32(expectedCharacter)); - - if (auto object = type.as<QQmlJS::Dom::QmlObject>()) { - const std::vector<QString> except = { - "functionParameterICUsage", - "ICBindingUsage", - "ICBindingUsage2", - "ICBindingUsage3", - }; - for (const QString &functionName : except) { - QEXPECT_FAIL( - functionName.toStdString().c_str(), - "Types for JS-identifiers point to the type inside inline components instead " - "of the inline component itself", - Continue); - } - QCOMPARE(object->idStr(), expectedTypeName); - } else if (auto exported = type.as<QQmlJS::Dom::Export>()) { - QCOMPARE(exported->typeName, expectedTypeName); - } else { - if constexpr (enable_debug_output) { - if (type.name() != expectedTypeName) - qDebug() << " has not the unexpected name " << type.name() - << " instead of expected " << expectedTypeName << " in " << type; - } - QCOMPARE(type.name(), expectedTypeName); - } + QCOMPARE(base->sourceLocation.startLine, quint32(expectedLine)); + QCOMPARE(base->sourceLocation.startColumn, quint32(expectedCharacter)); } void tst_qmlls_utils::findLocationOfItem_data() @@ -624,6 +588,9 @@ void tst_qmlls_utils::findBaseObject_data() void tst_qmlls_utils::findBaseObject() { + const QByteArray failOnInlineComponentsMessage = + "The Dom cannot resolve inline components from the basetype yet."; + QFETCH(QString, filePath); QFETCH(int, line); QFETCH(int, character); @@ -650,15 +617,17 @@ void tst_qmlls_utils::findBaseObject() } QCOMPARE(locations.size(), 1); - auto type = QQmlLSUtils::findTypeDefinitionOf(locations.front().domItem); - auto base = QQmlLSUtils::baseObject(type); - QByteArray failOnInlineComponentsMessage = - "The Dom cannot resolve inline components from the basetype yet."; - + auto typeLocation = QQmlLSUtils::findTypeDefinitionOf(locations.front().domItem); QEXPECT_FAIL("inline-ic", failOnInlineComponentsMessage, Abort); - QEXPECT_FAIL("inline-ic-from-id", failOnInlineComponentsMessage, Abort); QEXPECT_FAIL("inline-ic2", failOnInlineComponentsMessage, Abort); QEXPECT_FAIL("inline-ic2-from-id", failOnInlineComponentsMessage, Abort); + QVERIFY(typeLocation); + QQmlJS::Dom::DomItem type = QQmlLSUtils::sourceLocationToDomItem( + locations.front().domItem.goToFile(typeLocation->filename), + typeLocation->sourceLocation); + auto base = QQmlLSUtils::baseObject(type); + + QEXPECT_FAIL("inline-ic-from-id", failOnInlineComponentsMessage, Abort); if constexpr (enable_debug_output) { if (!base) |