| 1 | // Copyright (C) 2024 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #include "qqmllsutils_p.h" |
| 5 | #include "documentsymbolutils_p.h" |
| 6 | #include <QtLanguageServer/private/qlanguageserverspectypes_p.h> |
| 7 | #include <QtQmlDom/private/qqmldomitem_p.h> |
| 8 | #include <QtQmlDom/private/qqmldomoutwriter_p.h> |
| 9 | #include <stack> |
| 10 | |
| 11 | QT_BEGIN_NAMESPACE |
| 12 | |
| 13 | namespace DocumentSymbolUtils { |
| 14 | using QLspSpecification::SymbolKind; |
| 15 | using namespace QQmlJS::Dom; |
| 16 | |
| 17 | struct TypeSymbolRelation |
| 18 | { |
| 19 | DomType domType; |
| 20 | SymbolKind symbolKind; |
| 21 | }; |
| 22 | |
| 23 | constexpr static std::array<TypeSymbolRelation, 9> s_TypeSymbolRelations = { ._M_elems: { |
| 24 | { .domType: DomType::Binding, .symbolKind: SymbolKind::Variable }, |
| 25 | { .domType: DomType::PropertyDefinition, .symbolKind: SymbolKind::Property }, |
| 26 | // Although MethodInfo simply relates to Method, SymbolKind requires special handling: |
| 27 | // When MethodInfo represents a Signal, its SymbolKind is set to Event. |
| 28 | // This distinction is explicitly managed in the symbolKindOf() helper function. |
| 29 | // see also QTBUG-128423 |
| 30 | { .domType: DomType::MethodInfo, .symbolKind: SymbolKind::Method }, |
| 31 | { .domType: DomType::Id, .symbolKind: SymbolKind::Key }, |
| 32 | { .domType: DomType::QmlObject, .symbolKind: SymbolKind::Object }, |
| 33 | { .domType: DomType::EnumDecl, .symbolKind: SymbolKind::Enum }, |
| 34 | { .domType: DomType::EnumItem, .symbolKind: SymbolKind::EnumMember }, |
| 35 | { .domType: DomType::QmlComponent, .symbolKind: SymbolKind::Module }, |
| 36 | { .domType: DomType::QmlFile, .symbolKind: SymbolKind::File }, |
| 37 | } }; |
| 38 | |
| 39 | [[nodiscard]] constexpr static inline SymbolKind symbolKindFor(const DomType &type) |
| 40 | { |
| 41 | // constexpr std::find_if is only from c++20 |
| 42 | for (const auto &mapping : s_TypeSymbolRelations) { |
| 43 | if (mapping.domType == type) { |
| 44 | return mapping.symbolKind; |
| 45 | } |
| 46 | } |
| 47 | return SymbolKind::Null; |
| 48 | } |
| 49 | |
| 50 | constexpr static inline bool documentSymbolNotSupportedFor(const DomType &type) |
| 51 | { |
| 52 | return symbolKindFor(type) == SymbolKind::Null; |
| 53 | } |
| 54 | |
| 55 | static bool propertyBoundAtDefinitionLine(const DomItem &propertyDefinition) |
| 56 | { |
| 57 | Q_ASSERT(propertyDefinition.internalKind() == DomType::PropertyDefinition); |
| 58 | return FileLocations::treeOf(propertyDefinition)->info().regions[ColonTokenRegion].isValid(); |
| 59 | } |
| 60 | |
| 61 | static inline bool shouldFilterOut(const DomItem &item) |
| 62 | { |
| 63 | const auto itemType = item.internalKind(); |
| 64 | if (documentSymbolNotSupportedFor(type: itemType)) { |
| 65 | return true; |
| 66 | } |
| 67 | if (itemType == DomType::PropertyDefinition && propertyBoundAtDefinitionLine(propertyDefinition: item)) { |
| 68 | // without this check there is a "duplication" of symbols. |
| 69 | // one representing PropertyDefinition another one - Binding |
| 70 | return true; |
| 71 | } |
| 72 | return false; |
| 73 | } |
| 74 | |
| 75 | static std::optional<QByteArray> tryGetQmlObjectDetail(const DomItem &qmlObj) |
| 76 | { |
| 77 | using namespace QQmlJS::Dom; |
| 78 | Q_ASSERT(qmlObj.internalKind() == DomType::QmlObject); |
| 79 | bool hasId = !qmlObj.idStr().isEmpty(); |
| 80 | if (hasId) { |
| 81 | return qmlObj.idStr().toUtf8(); |
| 82 | } |
| 83 | const bool isRootObject = qmlObj.component().field(name: Fields::objects).index(0) == qmlObj; |
| 84 | if (isRootObject) { |
| 85 | return "root" ; |
| 86 | } |
| 87 | return std::nullopt; |
| 88 | } |
| 89 | |
| 90 | static std::optional<QByteArray> tryGetBindingDetail(const DomItem &bItem) |
| 91 | { |
| 92 | const auto *bindingPtr = bItem.as<Binding>(); |
| 93 | Q_ASSERT(bindingPtr); |
| 94 | switch (bindingPtr->valueKind()) { |
| 95 | case BindingValueKind::ScriptExpression: { |
| 96 | auto exprCode = bindingPtr->scriptExpressionValue()->code(); |
| 97 | if (exprCode.length() > 25) { |
| 98 | return QStringView(exprCode).first(n: 22).toUtf8().append(s: "..." ); |
| 99 | } |
| 100 | if (exprCode.endsWith(QStringLiteral(";" ))) { |
| 101 | exprCode.chop(n: 1); |
| 102 | } |
| 103 | return exprCode.toUtf8(); |
| 104 | } |
| 105 | default: |
| 106 | // Value is QmlObject or QList<QmlObject> => no detail |
| 107 | return std::nullopt; |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | static inline QByteArray getMethodDetail(const DomItem &mItem) |
| 112 | { |
| 113 | const auto *methodInfoPtr = mItem.as<MethodInfo>(); |
| 114 | Q_ASSERT(methodInfoPtr); |
| 115 | return methodInfoPtr->signature(self: mItem).toUtf8(); |
| 116 | } |
| 117 | |
| 118 | std::optional<QByteArray> tryGetDetailOf(const DomItem &item) |
| 119 | { |
| 120 | switch (item.internalKind()) { |
| 121 | case DomType::Id: { |
| 122 | const auto name = item.name(); |
| 123 | return name.isEmpty() ? std::nullopt : std::make_optional(t: name.toUtf8()); |
| 124 | } |
| 125 | case DomType::EnumItem: |
| 126 | return QByteArray::number(item.as<EnumItem>()->value()); |
| 127 | case DomType::QmlObject: |
| 128 | return tryGetQmlObjectDetail(qmlObj: item); |
| 129 | case DomType::MethodInfo: |
| 130 | return getMethodDetail(mItem: item); |
| 131 | case DomType::Binding: |
| 132 | return tryGetBindingDetail(bItem: item); |
| 133 | default: |
| 134 | return std::nullopt; |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | // TODO move to qmllsUtils? |
| 139 | static inline bool isSubRange(const QLspSpecification::Range &potentialSubRange, |
| 140 | const QLspSpecification::Range &range) |
| 141 | { |
| 142 | // Check if the start of a is greater than or equal to the start of b |
| 143 | bool startContained = (potentialSubRange.start.line > range.start.line |
| 144 | || (potentialSubRange.start.line == range.start.line |
| 145 | && potentialSubRange.start.character >= range.start.character)); |
| 146 | |
| 147 | // Check if the end of a is less than or equal to the end of b |
| 148 | bool endContained = (potentialSubRange.end.line < range.end.line |
| 149 | || (potentialSubRange.end.line == range.end.line |
| 150 | && potentialSubRange.end.character <= range.end.character)); |
| 151 | |
| 152 | return startContained && endContained; |
| 153 | } |
| 154 | |
| 155 | using MutableRefToDocumentSymbol = QLspSpecification::DocumentSymbol &; |
| 156 | [[nodiscard]] static MutableRefToDocumentSymbol |
| 157 | findDirectParentFor(const QLspSpecification::DocumentSymbol &child, |
| 158 | MutableRefToDocumentSymbol currentParent) |
| 159 | { |
| 160 | const auto containsChildRange = |
| 161 | [&range = child.range](const QLspSpecification::DocumentSymbol &symbol) { |
| 162 | return isSubRange(potentialSubRange: range, range: symbol.range); |
| 163 | }; |
| 164 | // Parent's Range covers children's Ranges |
| 165 | // all children, grand-children, grand-grand-children and so forth |
| 166 | // are not supposed to have overlapping Ranges, hence it's just "gready" approach |
| 167 | // 1. find a Symbol among children, containing a Range |
| 168 | // 2. set it as currentCandidate |
| 169 | // 3. repeat |
| 170 | std::reference_wrapper<QLspSpecification::DocumentSymbol> currentCandidate(currentParent); |
| 171 | while (containsChildRange(currentCandidate) && currentCandidate.get().children.has_value()) { |
| 172 | auto newCandidate = |
| 173 | std::find_if(first: currentCandidate.get().children->begin(), |
| 174 | last: currentCandidate.get().children->end(), pred: containsChildRange); |
| 175 | if (newCandidate == currentCandidate.get().children->end()) { |
| 176 | break; |
| 177 | } |
| 178 | currentCandidate = std::ref(t&: *newCandidate); |
| 179 | } |
| 180 | return currentCandidate; |
| 181 | } |
| 182 | |
| 183 | using DocumentSymbolPredicate = |
| 184 | qxp::function_ref<bool(const QLspSpecification::DocumentSymbol &) const>; |
| 185 | [[nodiscard]] static SymbolsList (const DocumentSymbolPredicate shouldBeReadopted, |
| 186 | MutableRefToDocumentSymbol currentParent) |
| 187 | { |
| 188 | if (!currentParent.children.has_value()) { |
| 189 | return {}; |
| 190 | } |
| 191 | auto &parentsChildren = currentParent.children.value(); |
| 192 | SymbolsList ; |
| 193 | extractedChildren.reserve(asize: parentsChildren.size()); |
| 194 | auto [_, toBeRemoved] = std::partition_copy(first: parentsChildren.cbegin(), last: parentsChildren.cend(), |
| 195 | out_true: std::back_inserter(x&: extractedChildren), |
| 196 | out_false: parentsChildren.begin(), pred: shouldBeReadopted); |
| 197 | parentsChildren.erase(abegin: toBeRemoved, aend: parentsChildren.end()); |
| 198 | return extractedChildren; |
| 199 | } |
| 200 | |
| 201 | static inline void adopt(QLspSpecification::DocumentSymbol &&child, |
| 202 | MutableRefToDocumentSymbol parent) |
| 203 | { |
| 204 | if (!parent.children.has_value()) { |
| 205 | parent.children.emplace(il: { std::move(child) }); |
| 206 | return; |
| 207 | } |
| 208 | parent.children->emplace_back(args: std::move(child)); |
| 209 | } |
| 210 | |
| 211 | static void readoptChildrenIf(const DocumentSymbolPredicate unaryPred, |
| 212 | MutableRefToDocumentSymbol currentParent) |
| 213 | { |
| 214 | auto childrenToBeReadopted = extractChildrenIf(shouldBeReadopted: unaryPred, currentParent); |
| 215 | for (auto &&child : childrenToBeReadopted) { |
| 216 | auto &newParentRef = findDirectParentFor(child, currentParent); |
| 217 | adopt(child: std::move(child), parent&: newParentRef); |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | // Readopts all Enum-s and Id-s |
| 222 | static void reorganizeQmlComponentSymbol(MutableRefToDocumentSymbol qmlCompSymbol) |
| 223 | { |
| 224 | Q_ASSERT(qmlCompSymbol.kind == symbolKindFor(DomType::QmlComponent)); |
| 225 | if (!qmlCompSymbol.children.has_value()) { |
| 226 | // nothing to reorganize |
| 227 | return; |
| 228 | } |
| 229 | |
| 230 | constexpr auto enumDeclSymbolKind = symbolKindFor(type: DomType::EnumDecl); |
| 231 | const auto symbolIsEnumDecl = [](const QLspSpecification::DocumentSymbol &symbol) -> bool { |
| 232 | return symbol.kind == enumDeclSymbolKind; |
| 233 | }; |
| 234 | readoptChildrenIf(unaryPred: symbolIsEnumDecl, currentParent&: qmlCompSymbol); |
| 235 | } |
| 236 | |
| 237 | /*! \internal |
| 238 | * This function reorganizes \c qmlFileSymbols (result of assembleSymbolsForQmlFile) |
| 239 | * in the following way: |
| 240 | * 1. Moves Symbol-s representing Enum-s and inline QmlComponent-s |
| 241 | * to their respective range-containing parents , a.k.a. direct structural parents. |
| 242 | * 2. Reassignes head to the DocumentSymbol representing root QmlObject of the main |
| 243 | * QmlComponent |
| 244 | */ |
| 245 | void reorganizeForOutlineView(SymbolsList &qmlFileSymbols) |
| 246 | { |
| 247 | Q_ASSERT(qmlFileSymbols.at(0).kind == symbolKindFor(DomType::QmlFile) |
| 248 | && qmlFileSymbols.at(0).children.has_value()); |
| 249 | |
| 250 | auto &qmlFileSymbol = qmlFileSymbols[0]; |
| 251 | constexpr auto qmlCompSymbolKind = symbolKindFor(type: DomType::QmlComponent); |
| 252 | for (auto &childSymbol : qmlFileSymbol.children.value()) { |
| 253 | if (childSymbol.kind == qmlCompSymbolKind) { |
| 254 | reorganizeQmlComponentSymbol(qmlCompSymbol&: childSymbol); |
| 255 | } |
| 256 | } |
| 257 | |
| 258 | const auto symbolIsInlineComp = [](const QLspSpecification::DocumentSymbol &symbol) -> bool { |
| 259 | return symbol.kind == qmlCompSymbolKind && symbol.name.contains(bv: "." ); |
| 260 | }; |
| 261 | readoptChildrenIf(unaryPred: symbolIsInlineComp, currentParent&: qmlFileSymbol); |
| 262 | |
| 263 | // move pointer from the documentSymbol representing QmlFile |
| 264 | // to the documentSymbols representing children of main QmlComponent |
| 265 | // a.k.a. ignore / not to show QmlFile and mainComponent symbols |
| 266 | qmlFileSymbols = qmlFileSymbol.children->at(i: 0).children.value(); |
| 267 | } |
| 268 | |
| 269 | /*! \internal |
| 270 | * Constructs a \c DocumentSymbol for an \c Item with the provided \c children. |
| 271 | * Returns \c children if the current \c Item should not be represented via a \c DocumentSymbol. |
| 272 | */ |
| 273 | SymbolsList buildSymbolOrReturnChildren(const DomItem &item, SymbolsList &&children) |
| 274 | { |
| 275 | if (shouldFilterOut(item)) { |
| 276 | // nothing to build, just returning children |
| 277 | return std::move(children); |
| 278 | } |
| 279 | |
| 280 | const auto buildPartialSymbol = [](const DomItem &item) { |
| 281 | QLspSpecification::DocumentSymbol symbol; |
| 282 | symbol.kind = symbolKindOf(item); |
| 283 | symbol.name = symbolNameOf(item); |
| 284 | symbol.detail = tryGetDetailOf(item); |
| 285 | std::tie(args&: symbol.range, args&: symbol.selectionRange) = symbolRangesOf(item); |
| 286 | return symbol; |
| 287 | }; |
| 288 | |
| 289 | auto symbol = buildPartialSymbol(item); |
| 290 | if (!children.empty()) { |
| 291 | symbol.children.emplace(args: std::move(children)); |
| 292 | } |
| 293 | /* |
| 294 | To avoid pushing down Id items through the DocumentSymbol tree, |
| 295 | as part of rearrangement step, it was decided to handle them here explicitly. |
| 296 | That Id issue atm only affects objects |
| 297 | If / when Id is moving from component level to Object level this should be reflected |
| 298 | also in the visiting logic. |
| 299 | TODO(QTBUG-128274) |
| 300 | */ |
| 301 | if (const auto objPtr = item.as<QmlObject>()) { |
| 302 | if (const auto idItem = item.component().field(name: Fields::ids).key(name: objPtr->idStr()).index(0)) { |
| 303 | auto idSymbol = buildPartialSymbol(idItem); |
| 304 | adopt(child: std::move(idSymbol), parent&: symbol); |
| 305 | } |
| 306 | } |
| 307 | return SymbolsList{ std::move(symbol) }; |
| 308 | } |
| 309 | |
| 310 | std::pair<QLspSpecification::Range, QLspSpecification::Range> symbolRangesOf(const DomItem &item) |
| 311 | { |
| 312 | const auto &fLoc = FileLocations::treeOf(item)->info(); |
| 313 | const auto fullRangeSourceloc = fLoc.fullRegion; |
| 314 | const auto selectionRangeSourceLoc = fLoc.regions[IdentifierRegion].isValid() |
| 315 | ? fLoc.regions[IdentifierRegion] |
| 316 | : fullRangeSourceloc; |
| 317 | |
| 318 | auto fItem = item.containingFile(); |
| 319 | Q_ASSERT(fItem); |
| 320 | const QString &code = fItem.ownerAs<QmlFile>()->code(); |
| 321 | return { QQmlLSUtils::qmlLocationToLspLocation( |
| 322 | qmlLocation: QQmlLSUtils::Location::from(fileName: {}, sourceLocation: fullRangeSourceloc, code)), |
| 323 | QQmlLSUtils::qmlLocationToLspLocation( |
| 324 | qmlLocation: QQmlLSUtils::Location::from(fileName: {}, sourceLocation: selectionRangeSourceLoc, code)) }; |
| 325 | } |
| 326 | |
| 327 | QByteArray symbolNameOf(const DomItem &item) |
| 328 | { |
| 329 | if (item.internalKind() == DomType::Id) { |
| 330 | return "id" ; |
| 331 | } |
| 332 | return (item.name().isEmpty() ? item.internalKindStr() : item.name()).toUtf8(); |
| 333 | } |
| 334 | |
| 335 | QLspSpecification::SymbolKind symbolKindOf(const DomItem &item) |
| 336 | { |
| 337 | if (item.internalKind() == DomType::MethodInfo) { |
| 338 | const auto *methodInfoPtr = item.as<MethodInfo>(); |
| 339 | Q_ASSERT(methodInfoPtr); |
| 340 | return methodInfoPtr->methodType == MethodInfo::MethodType::Signal |
| 341 | ? SymbolKind::Event |
| 342 | : symbolKindFor(type: DomType::MethodInfo); |
| 343 | } |
| 344 | return symbolKindFor(type: item.internalKind()); |
| 345 | } |
| 346 | |
| 347 | /*! \internal |
| 348 | * Design decisions behind this class are the following: |
| 349 | * 1. It is an implementation detail of the free \c assembleSymbolsForQmlFile function |
| 350 | * 2. It can only be initialized and used once per \c Item. |
| 351 | * This is enforced by its \c refToRootItem reference member. |
| 352 | * 3. It is tested via the public \c assembleSymbolsForQmlFile function. |
| 353 | */ |
| 354 | class DocumentSymbolVisitor |
| 355 | { |
| 356 | public: |
| 357 | DocumentSymbolVisitor(const DomItem &item, const AssemblingFunction af) |
| 358 | : m_assemble(af), m_refToRootItem(item) {}; |
| 359 | |
| 360 | static const FieldFilter &fieldsFilter(); |
| 361 | |
| 362 | [[nodiscard]] SymbolsList assembleSymbols(); |
| 363 | |
| 364 | private: |
| 365 | [[nodiscard]] SymbolsList popAndAssembleSymbolsFor(const DomItem &item); |
| 366 | |
| 367 | void appendToTop(const SymbolsList &symbols); |
| 368 | |
| 369 | private: |
| 370 | const AssemblingFunction m_assemble; |
| 371 | const DomItem &m_refToRootItem; |
| 372 | std::stack<SymbolsList> m_stackOfChildrenSymbols; |
| 373 | }; |
| 374 | |
| 375 | const FieldFilter &DocumentSymbolVisitor::fieldsFilter() |
| 376 | { |
| 377 | // TODO(QTBUG-128118) add only fields to be visited and not the ones |
| 378 | // to be removed. |
| 379 | static const FieldFilter ff{ |
| 380 | {}, // to add |
| 381 | { |
| 382 | // to remove |
| 383 | { QString(), Fields::code.toString() }, |
| 384 | { QString(), Fields::postCode.toString() }, |
| 385 | { QString(), Fields::preCode.toString() }, |
| 386 | { QString(), Fields::importScope.toString() }, |
| 387 | { QString(), Fields::fileLocationsTree.toString() }, |
| 388 | { QString(), Fields::astComments.toString() }, |
| 389 | { QString(), Fields::comments.toString() }, |
| 390 | { QString(), Fields::exports.toString() }, |
| 391 | { QString(), Fields::propertyInfos.toString() }, |
| 392 | { QLatin1String("FileLocationsNode" ), Fields::parent.toString() }, |
| 393 | //^^^ FieldFilter::default |
| 394 | { QString(), Fields::errors.toString() }, |
| 395 | { QString(), Fields::imports.toString() }, |
| 396 | { QString(), Fields::prototypes.toString() }, |
| 397 | { QString(), Fields::annotations.toString() }, |
| 398 | { QString(), Fields::attachedType.toString() }, |
| 399 | { QString(), Fields::canonicalFilePath.toString() }, |
| 400 | { QString(), Fields::isValid.toString() }, |
| 401 | { QString(), Fields::isSingleton.toString() }, |
| 402 | { QString(), Fields::isCreatable.toString() }, |
| 403 | { QString(), Fields::isComposite.toString() }, |
| 404 | { QString(), Fields::attachedTypeName.toString() }, |
| 405 | { QString(), Fields::pragmas.toString() }, |
| 406 | { QString(), Fields::defaultPropertyName.toString() }, |
| 407 | { QString(), Fields::name.toString() }, |
| 408 | { QString(), Fields::nameIdentifiers.toString() }, |
| 409 | { QString(), Fields::prototypes.toString() }, |
| 410 | { QString(), Fields::nextScope.toString() }, |
| 411 | { QString(), Fields::parameters.toString() }, |
| 412 | { QString(), Fields::methodType.toString() }, |
| 413 | { QString(), Fields::type.toString() }, |
| 414 | { QString(), Fields::isConstructor.toString() }, |
| 415 | { QString(), Fields::returnType.toString() }, |
| 416 | { QString(), Fields::body.toString() }, |
| 417 | { QString(), Fields::access.toString() }, |
| 418 | { QString(), Fields::typeName.toString() }, |
| 419 | { QString(), Fields::isReadonly.toString() }, |
| 420 | { QString(), Fields::isList.toString() }, |
| 421 | { QString(), Fields::bindingIdentifiers.toString() }, |
| 422 | { QString(), Fields::bindingType.toString() }, |
| 423 | { QString(), Fields::isSignalHandler.toString() }, |
| 424 | // prop def? |
| 425 | { QString(), Fields::isPointer.toString() }, |
| 426 | { QString(), Fields::isFinal.toString() }, |
| 427 | { QString(), Fields::isAlias.toString() }, |
| 428 | { QString(), Fields::isDefaultMember.toString() }, |
| 429 | { QString(), Fields::isRequired.toString() }, |
| 430 | { QString(), Fields::read.toString() }, |
| 431 | { QString(), Fields::write.toString() }, |
| 432 | { QString(), Fields::bindable.toString() }, |
| 433 | { QString(), Fields::notify.toString() }, |
| 434 | { QString(), Fields::type.toString() }, |
| 435 | // scriptExpr |
| 436 | { QString(), Fields::scriptElement.toString() }, |
| 437 | { QString(), Fields::localOffset.toString() }, |
| 438 | { QString(), Fields::astRelocatableDump.toString() }, |
| 439 | { QString(), Fields::expressionType.toString() }, |
| 440 | // components |
| 441 | { QString(), Fields::subComponents.toString() }, |
| 442 | // BEWARE |
| 443 | // Ids and IdStr are filtered out during the visit, because |
| 444 | // documentSymbol-s for them will be explicitly handled as part of the |
| 445 | // creation of symbol for QmlObject |
| 446 | { QString(), Fields::ids.toString() }, |
| 447 | { QString(), Fields::idStr.toString() }, |
| 448 | |
| 449 | // id |
| 450 | { QString(), Fields::referredObject.toString() }, |
| 451 | // enum item |
| 452 | { QLatin1String("EnumItem" ), Fields::value.toString() }, |
| 453 | } |
| 454 | }; |
| 455 | return ff; |
| 456 | } |
| 457 | |
| 458 | SymbolsList DocumentSymbolVisitor::assembleSymbols() |
| 459 | { |
| 460 | using namespace QQmlJS::Dom; |
| 461 | auto openingVisitor = [this](const Path &, const DomItem &, bool) -> bool { |
| 462 | m_stackOfChildrenSymbols.emplace(); |
| 463 | return true; |
| 464 | }; |
| 465 | auto closingVisitor = [this](const Path &, const DomItem &item, bool) -> bool { |
| 466 | // it's closing Visitor, openingVisitor must have pushed something |
| 467 | Q_ASSERT(!m_stackOfChildrenSymbols.empty()); |
| 468 | if (m_stackOfChildrenSymbols.size() == 1) { |
| 469 | // reached children of root, nothing to do |
| 470 | return false; |
| 471 | } |
| 472 | auto symbols = popAndAssembleSymbolsFor(item); |
| 473 | appendToTop(symbols); |
| 474 | return true; |
| 475 | }; |
| 476 | m_refToRootItem.visitTree(basePath: Path(), visitor: emptyChildrenVisitor, options: VisitOption::Default, openingVisitor, |
| 477 | closingVisitor, filter: fieldsFilter()); |
| 478 | return popAndAssembleSymbolsFor(item: m_refToRootItem); |
| 479 | } |
| 480 | |
| 481 | SymbolsList DocumentSymbolVisitor::popAndAssembleSymbolsFor(const DomItem &item) |
| 482 | { |
| 483 | Q_ASSERT(!m_stackOfChildrenSymbols.empty()); |
| 484 | auto atEnd = qScopeGuard(f: [this]() { m_stackOfChildrenSymbols.pop(); }); |
| 485 | return m_assemble(item, std::move(m_stackOfChildrenSymbols.top())); |
| 486 | } |
| 487 | |
| 488 | void DocumentSymbolVisitor::appendToTop(const SymbolsList &symbols) |
| 489 | { |
| 490 | Q_ASSERT(!m_stackOfChildrenSymbols.empty()); |
| 491 | m_stackOfChildrenSymbols.top().append(l: symbols); |
| 492 | } |
| 493 | |
| 494 | SymbolsList assembleSymbolsForQmlFile(const DomItem &item, const AssemblingFunction af) |
| 495 | { |
| 496 | Q_ASSERT(item.internalKind() == DomType::QmlFile); |
| 497 | DocumentSymbolVisitor visitor(item, af); |
| 498 | return visitor.assembleSymbols(); |
| 499 | } |
| 500 | } // namespace DocumentSymbolUtils |
| 501 | |
| 502 | QT_END_NAMESPACE |
| 503 | |