DocumentSymbol add Detail deriving functionality
This commit introduces support for detail field of DocumentSymbol. For Dom::Id it's the .name() For EnumMember is implicit or explicit .value() For QmlObject it's - .idStr if it exists - "root" if .idStr doesnt's exist + object is indeed root - null otherwise For MethodInfo it's a signature, however, because this field is absent from the MethodInfo struct, it needs to be derived here. Dom lacks unified support for querying signatures, esp. of "Singal"-s, hence it's manually handled and a bit messy. As for Binding, detail is constructed using BindingValue Task-number: QTBUG-120002 Change-Id: I96ed605d96caaa778e09a156bed51ac4a4f09b50 Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
This commit is contained in:
parent
751beaff29
commit
86a3a0fbf7
|
@ -745,6 +745,10 @@ public:
|
|||
TypeAnnotationStyle typeAnnotationStyle = TypeAnnotationStyle::Suffix;
|
||||
};
|
||||
|
||||
// TODO (QTBUG-128423)
|
||||
// Refactor to differentiate between Signals and Methods easily,
|
||||
// considering their distinct handling and formatting needs.
|
||||
// Explore separating Signal functionality or unifying shared methods.
|
||||
class QMLDOM_EXPORT MethodInfo : public AttributeInfo
|
||||
{
|
||||
Q_GADGET
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "documentsymbolutils_p.h"
|
||||
#include <QtLanguageServer/private/qlanguageserverspectypes_p.h>
|
||||
#include <QtQmlDom/private/qqmldomitem_p.h>
|
||||
#include <QtQmlDom/private/qqmldomoutwriter_p.h>
|
||||
#include <stack>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
@ -22,11 +23,11 @@ struct TypeSymbolRelation
|
|||
constexpr static std::array<TypeSymbolRelation, 9> s_TypeSymbolRelations = { {
|
||||
{ DomType::Binding, SymbolKind::Variable },
|
||||
{ DomType::PropertyDefinition, SymbolKind::Property },
|
||||
{ DomType::MethodInfo, SymbolKind::Method }, // BEWARE.
|
||||
// Even though it's just a relation MethodInfo -> Method for the sake of simplicity,
|
||||
// atm SymbolKind for MethodInfo requires special handling:
|
||||
// in cases when MethodInfo is Signal, the SymbolKind will be Event,
|
||||
// this is explicitly handled in the symbolKindOf() helper
|
||||
// Although MethodInfo simply relates to Method, SymbolKind requires special handling:
|
||||
// When MethodInfo represents a Signal, its SymbolKind is set to Event.
|
||||
// This distinction is explicitly managed in the symbolKindOf() helper function.
|
||||
// see also QTBUG-128423
|
||||
{ DomType::MethodInfo, SymbolKind::Method },
|
||||
{ DomType::Id, SymbolKind::Key },
|
||||
{ DomType::QmlObject, SymbolKind::Object },
|
||||
{ DomType::EnumDecl, SymbolKind::Enum },
|
||||
|
@ -51,6 +52,69 @@ constexpr static inline bool documentSymbolNotSupportedFor(const DomType &type)
|
|||
return symbolKindFor(type) == SymbolKind::Null;
|
||||
}
|
||||
|
||||
static std::optional<QByteArray> tryGetQmlObjectDetail(const DomItem &qmlObj)
|
||||
{
|
||||
using namespace QQmlJS::Dom;
|
||||
Q_ASSERT(qmlObj.internalKind() == DomType::QmlObject);
|
||||
bool hasId = !qmlObj.idStr().isEmpty();
|
||||
if (hasId) {
|
||||
return qmlObj.idStr().toUtf8();
|
||||
}
|
||||
const bool isRootObject = qmlObj.component().field(Fields::objects).index(0) == qmlObj;
|
||||
if (isRootObject) {
|
||||
return "root";
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<QByteArray> tryGetBindingDetail(const DomItem &bItem)
|
||||
{
|
||||
const auto *bindingPtr = bItem.as<Binding>();
|
||||
Q_ASSERT(bindingPtr);
|
||||
switch (bindingPtr->valueKind()) {
|
||||
case BindingValueKind::ScriptExpression: {
|
||||
auto exprCode = bindingPtr->scriptExpressionValue()->code();
|
||||
if (exprCode.length() > 25) {
|
||||
return QStringView(exprCode).first(22).toUtf8().append("...");
|
||||
}
|
||||
if (exprCode.endsWith(QStringLiteral(";"))) {
|
||||
exprCode.chop(1);
|
||||
}
|
||||
return exprCode.toUtf8();
|
||||
}
|
||||
default:
|
||||
// Value is QmlObject or QList<QmlObject> => no detail
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
static inline QByteArray getMethodDetail(const DomItem &mItem)
|
||||
{
|
||||
const auto *methodInfoPtr = mItem.as<MethodInfo>();
|
||||
Q_ASSERT(methodInfoPtr);
|
||||
return methodInfoPtr->signature(mItem).toUtf8();
|
||||
}
|
||||
|
||||
std::optional<QByteArray> tryGetDetailOf(const DomItem &item)
|
||||
{
|
||||
switch (item.internalKind()) {
|
||||
case DomType::Id: {
|
||||
const auto name = item.name();
|
||||
return name.isEmpty() ? std::nullopt : std::make_optional(name.toUtf8());
|
||||
}
|
||||
case DomType::EnumItem:
|
||||
return QByteArray::number(item.as<EnumItem>()->value());
|
||||
case DomType::QmlObject:
|
||||
return tryGetQmlObjectDetail(item);
|
||||
case DomType::MethodInfo:
|
||||
return getMethodDetail(item);
|
||||
case DomType::Binding:
|
||||
return tryGetBindingDetail(item);
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
* Constructs a \c DocumentSymbol for an \c Item with the provided \c children.
|
||||
* Returns \c children if the current \c Item should not be represented via a \c DocumentSymbol.
|
||||
|
@ -65,6 +129,7 @@ SymbolsList buildSymbolOrReturnChildren(const DomItem &item, SymbolsList &&child
|
|||
QLspSpecification::DocumentSymbol symbol;
|
||||
symbol.kind = symbolKindOf(item);
|
||||
symbol.name = symbolNameOf(item);
|
||||
symbol.detail = tryGetDetailOf(item);
|
||||
std::tie(symbol.range, symbol.selectionRange) = symbolRangesOf(item);
|
||||
if (!children.empty()) {
|
||||
symbol.children.emplace(std::move(children));
|
||||
|
|
|
@ -42,6 +42,8 @@ symbolRangesOf(const DomItem &item);
|
|||
|
||||
[[nodiscard]] QLspSpecification::SymbolKind symbolKindOf(const DomItem &item);
|
||||
|
||||
[[nodiscard]] std::optional<QByteArray> tryGetDetailOf(const DomItem &item);
|
||||
|
||||
[[nodiscard]] SymbolsList
|
||||
assembleSymbolsForQmlFile(const DomItem &item,
|
||||
const AssemblingFunction af = buildSymbolOrReturnChildren);
|
||||
|
|
|
@ -18,20 +18,21 @@ QT_BEGIN_NAMESPACE
|
|||
template <typename DomClass>
|
||||
static inline DomItem domItem(DomClass &&c)
|
||||
{
|
||||
DomItem domEnv(DomEnvironment::create({}));
|
||||
constexpr auto kind = std::decay<DomClass>::type::kindValue;
|
||||
if constexpr (domTypeIsDomElement(kind)) {
|
||||
c.updatePathFromOwner(
|
||||
Path::Root()); // necessary for querying fields, a.k.a. init DomElement
|
||||
Path::Current()); // necessary for querying fields, a.k.a. init DomElement
|
||||
// For the "DomElement"-s owners are required,
|
||||
// hence creating Env as an owner and then call copy on it
|
||||
DomItem domEnv(DomEnvironment::create({}));
|
||||
return domEnv.copy(&c);
|
||||
}
|
||||
if constexpr (kind == DomType::QmlFile) {
|
||||
DomItem domEnv(DomEnvironment::create({}));
|
||||
return domEnv.copy(&c);
|
||||
}
|
||||
return DomItem().wrap(QQmlJS::Dom::PathEls::PathComponent(), c);
|
||||
// it's helpful to use wrap on DomEnv instead of just DomItem() to make
|
||||
// .canonicalPath() usable
|
||||
return domEnv.wrap(QQmlJS::Dom::PathEls::PathComponent(), c);
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
|
@ -291,4 +292,112 @@ void tst_document_symbol_utils::symbolKindOf()
|
|||
QCOMPARE(DocumentSymbolUtils::symbolKindOf(DomItem()), QLspSpecification::SymbolKind::Null);
|
||||
}
|
||||
|
||||
void tst_document_symbol_utils::tryGetDetailOf()
|
||||
{
|
||||
{ // Id
|
||||
Id id;
|
||||
auto detail = DocumentSymbolUtils::tryGetDetailOf(domItem(id));
|
||||
QVERIFY(!detail.has_value());
|
||||
|
||||
const QString name("name");
|
||||
id.name = name;
|
||||
const auto expectedDetail = name.toUtf8();
|
||||
detail = DocumentSymbolUtils::tryGetDetailOf(domItem(id));
|
||||
QVERIFY(detail.has_value());
|
||||
QCOMPARE(detail, expectedDetail);
|
||||
}
|
||||
{ // EnumItem
|
||||
const int value = 4;
|
||||
const auto expectedDetail = QByteArray::number(value);
|
||||
EnumItem enumItem("a", value);
|
||||
const auto detail = DocumentSymbolUtils::tryGetDetailOf(domItem(enumItem));
|
||||
QCOMPARE(detail, expectedDetail);
|
||||
}
|
||||
{ // Binding
|
||||
Binding b;
|
||||
auto detail = DocumentSymbolUtils::tryGetDetailOf(domItem(b));
|
||||
QCOMPARE(detail, std::nullopt);
|
||||
|
||||
b.setValue(std::make_unique<BindingValue>());
|
||||
detail = DocumentSymbolUtils::tryGetDetailOf(domItem(b));
|
||||
QCOMPARE(detail, std::nullopt);
|
||||
|
||||
b.setValue(std::make_unique<BindingValue>(QmlObject()));
|
||||
detail = DocumentSymbolUtils::tryGetDetailOf(domItem(b));
|
||||
QCOMPARE(detail, std::nullopt);
|
||||
|
||||
b.setValue(std::make_unique<BindingValue>(QList<QmlObject>()));
|
||||
detail = DocumentSymbolUtils::tryGetDetailOf(domItem(b));
|
||||
QCOMPARE(detail, std::nullopt);
|
||||
|
||||
{
|
||||
const QString bindingValue("4");
|
||||
const auto expectedDetail = bindingValue.toUtf8();
|
||||
const QString bindingExpr = bindingValue + ";";
|
||||
const auto exprPtr = std::make_shared<ScriptExpression>(
|
||||
bindingExpr, ScriptExpression::ExpressionType::BindingExpression);
|
||||
b.setValue(std::make_unique<BindingValue>(exprPtr));
|
||||
detail = DocumentSymbolUtils::tryGetDetailOf(domItem(b));
|
||||
QCOMPARE(detail, expectedDetail);
|
||||
}
|
||||
{
|
||||
const QString bindingExpr("12345678901234567890123456"); // 26 symbols
|
||||
const auto expectedDetail = QString("1234567890123456789012...").toUtf8();
|
||||
const auto exprPtr = std::make_shared<ScriptExpression>(
|
||||
bindingExpr, ScriptExpression::ExpressionType::BindingExpression);
|
||||
b.setValue(std::make_unique<BindingValue>(exprPtr));
|
||||
detail = DocumentSymbolUtils::tryGetDetailOf(domItem(b));
|
||||
QCOMPARE(detail, expectedDetail);
|
||||
}
|
||||
}
|
||||
{ // MethodInfo
|
||||
{ // Method
|
||||
MethodParameter intA;
|
||||
intA.name = "a";
|
||||
intA.typeName = "int";
|
||||
MethodParameter stringB;
|
||||
stringB.name = "b";
|
||||
stringB.typeName = "string";
|
||||
MethodInfo method;
|
||||
method.parameters = { intA, stringB };
|
||||
method.typeName = "bool";
|
||||
|
||||
const auto expectedDetail = QString("(a: int, b: string): bool").toUtf8();
|
||||
const auto detail = DocumentSymbolUtils::tryGetDetailOf(domItem(method));
|
||||
QVERIFY(detail.has_value());
|
||||
QCOMPARE(detail.value(), expectedDetail);
|
||||
}
|
||||
{ // Signal
|
||||
MethodParameter intA;
|
||||
intA.name = "a";
|
||||
intA.typeName = "int";
|
||||
MethodInfo method;
|
||||
method.methodType = MethodInfo::MethodType::Signal;
|
||||
method.parameters = { intA };
|
||||
|
||||
const auto expectedDetail = QString("(a: int)").toUtf8();
|
||||
const auto detail = DocumentSymbolUtils::tryGetDetailOf(domItem(method));
|
||||
QVERIFY(detail.has_value());
|
||||
QCOMPARE(detail.value(), expectedDetail);
|
||||
}
|
||||
}
|
||||
{ // QmlObject
|
||||
QmlObject obj;
|
||||
auto detail = DocumentSymbolUtils::tryGetDetailOf(domItem(obj));
|
||||
QCOMPARE(detail, std::nullopt);
|
||||
|
||||
const QString objId("objId");
|
||||
obj.setIdStr(objId);
|
||||
detail = DocumentSymbolUtils::tryGetDetailOf(domItem(obj));
|
||||
QCOMPARE(detail, objId.toUtf8());
|
||||
|
||||
/*
|
||||
* Unfortunately because of the way DomItem::component() and filterUp() are working
|
||||
* (using full path from the root(env/top) and non-trivially trimming it)
|
||||
* I can't properly construct a fake hierarchy of components and objects
|
||||
* to verify the "root" case.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_document_symbol_utils)
|
||||
|
|
|
@ -27,4 +27,5 @@ private slots:
|
|||
void assembleSymbolsForQmlFile();
|
||||
void symbolNameOf();
|
||||
void symbolKindOf();
|
||||
void tryGetDetailOf();
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue