diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp index 7655a39d5ba1f..243746056aed0 100644 --- a/clang-tools-extra/clangd/FindSymbols.cpp +++ b/clang-tools-extra/clangd/FindSymbols.cpp @@ -23,13 +23,211 @@ #include "llvm/ADT/StringRef.h" #include #include -#include #define DEBUG_TYPE "FindSymbols" namespace clang { namespace clangd { +namespace { + +// "Static" means many things in C++, only some get the "static" modifier. +// +// Meanings that do: +// - Members associated with the class rather than the instance. +// This is what 'static' most often means across languages. +// - static local variables +// These are similarly "detached from their context" by the static keyword. +// In practice, these are rarely used inside classes, reducing confusion. +// +// Meanings that don't: +// - Namespace-scoped variables, which have static storage class. +// This is implicit, so the keyword "static" isn't so strongly associated. +// If we want a modifier for these, "global scope" is probably the concept. +// - Namespace-scoped variables/functions explicitly marked "static". +// There the keyword changes *linkage* , which is a totally different concept. +// If we want to model this, "file scope" would be a nice modifier. +// +// This is confusing, and maybe we should use another name, but because "static" +// is a standard LSP modifier, having one with that name has advantages. +bool isStatic(const Decl *D) { + if (const auto *CMD = llvm::dyn_cast(D)) + return CMD->isStatic(); + if (const VarDecl *VD = llvm::dyn_cast(D)) + return VD->isStaticDataMember() || VD->isStaticLocal(); + if (const auto *OPD = llvm::dyn_cast(D)) + return OPD->isClassProperty(); + if (const auto *OMD = llvm::dyn_cast(D)) + return OMD->isClassMethod(); + if (const auto *FD = llvm::dyn_cast(D)) + return FD->isStatic(); + return false; +} + +// Whether T is const in a loose sense - is a variable with this type readonly? +bool isConst(QualType T) { + if (T.isNull()) + return false; + T = T.getNonReferenceType(); + if (T.isConstQualified()) + return true; + if (const auto *AT = T->getAsArrayTypeUnsafe()) + return isConst(AT->getElementType()); + if (isConst(T->getPointeeType())) + return true; + return false; +} + +// Whether D is const in a loose sense (should it be highlighted as such?) +// FIXME: This is separate from whether *a particular usage* can mutate D. +// We may want V in V.size() to be readonly even if V is mutable. +bool isConst(const Decl *D) { + if (llvm::isa(D) || llvm::isa(D)) + return true; + if (llvm::isa(D) || llvm::isa(D) || + llvm::isa(D) || llvm::isa(D)) { + if (isConst(llvm::cast(D)->getType())) + return true; + } + if (const auto *OCPD = llvm::dyn_cast(D)) { + if (OCPD->isReadOnly()) + return true; + } + if (const auto *MPD = llvm::dyn_cast(D)) { + if (!MPD->hasSetter()) + return true; + } + if (const auto *CMD = llvm::dyn_cast(D)) { + if (CMD->isConst()) + return true; + } + if (const auto *FD = llvm::dyn_cast(D)) + return isConst(FD->getReturnType()); + return false; +} + +// Indicates whether declaration D is abstract in cases where D is a struct or a +// class. +bool isAbstract(const Decl *D) { + if (const auto *CMD = llvm::dyn_cast(D)) + return CMD->isPureVirtual(); + if (const auto *CRD = llvm::dyn_cast(D)) + return CRD->hasDefinition() && CRD->isAbstract(); + return false; +} + +// Indicates whether declaration D is virtual in cases where D is a method. +bool isVirtual(const Decl *D) { + if (const auto *CMD = llvm::dyn_cast(D)) + return CMD->isVirtual(); + return false; +} + +// Indicates whether declaration D is final in cases where D is a struct, class +// or method. +bool isFinal(const Decl *D) { + if (const auto *CRD = dyn_cast(D)) + return CRD->hasAttr(); + + if (const auto *CRD = dyn_cast(D)) + return CRD->hasAttr(); + + return false; +} + +// Indicates whether declaration D is a unique definition (as opposed to a +// declaration). +bool isUniqueDefinition(const NamedDecl *Decl) { + if (auto *Func = dyn_cast(Decl)) + return Func->isThisDeclarationADefinition(); + if (auto *Klass = dyn_cast(Decl)) + return Klass->isThisDeclarationADefinition(); + if (auto *Iface = dyn_cast(Decl)) + return Iface->isThisDeclarationADefinition(); + if (auto *Proto = dyn_cast(Decl)) + return Proto->isThisDeclarationADefinition(); + if (auto *Var = dyn_cast(Decl)) + return Var->isThisDeclarationADefinition(); + return isa(Decl) || + isa(Decl) || + isa(Decl) || isa(Decl) || + isa(Decl); +} +} // namespace + +SymbolTags toSymbolTagBitmask(const SymbolTag ST) { + return (1 << static_cast(ST)); +} + +SymbolTags computeSymbolTags(const NamedDecl &ND) { + SymbolTags Result = 0; + const auto IsDef = isUniqueDefinition(&ND); + + if (ND.isDeprecated()) + Result |= toSymbolTagBitmask(SymbolTag::Deprecated); + + if (isConst(&ND)) + Result |= toSymbolTagBitmask(SymbolTag::ReadOnly); + + if (isStatic(&ND)) + Result |= toSymbolTagBitmask(SymbolTag::Static); + + if (isVirtual(&ND)) + Result |= toSymbolTagBitmask(SymbolTag::Virtual); + + if (isAbstract(&ND)) + Result |= toSymbolTagBitmask(SymbolTag::Abstract); + + if (isFinal(&ND)) + Result |= toSymbolTagBitmask(SymbolTag::Final); + + if (not isa(ND)) { + // Do not treat an UnresolvedUsingValueDecl as a declaration. + // It's more common to think of it as a reference to the + // underlying declaration. + Result |= toSymbolTagBitmask(SymbolTag::Declaration); + + if (IsDef) + Result |= toSymbolTagBitmask(SymbolTag::Definition); + } + + switch (ND.getAccess()) { + case AS_public: + Result |= toSymbolTagBitmask(SymbolTag::Public); + break; + case AS_protected: + Result |= toSymbolTagBitmask(SymbolTag::Protected); + break; + case AS_private: + Result |= toSymbolTagBitmask(SymbolTag::Private); + break; + default: + break; + } + + return Result; +} + +std::vector getSymbolTags(const NamedDecl &ND) { + const auto symbolTags = computeSymbolTags(ND); + std::vector Tags; + + if (symbolTags == 0) + return Tags; + + // Iterate through SymbolTag enum values and collect any that are present in + // the bitmask. SymbolTag values are in the numeric range + // [FirstTag .. LastTag]. + constexpr unsigned MinTag = static_cast(SymbolTag::FirstTag); + constexpr unsigned MaxTag = static_cast(SymbolTag::LastTag); + for (unsigned I = MinTag; I <= MaxTag; ++I) { + auto ST = static_cast(I); + if (symbolTags & toSymbolTagBitmask(ST)) + Tags.push_back(ST); + } + return Tags; +} + namespace { using ScoredSymbolInfo = std::pair; struct ScoredSymbolGreater { @@ -242,6 +440,7 @@ std::optional declToSym(ASTContext &Ctx, const NamedDecl &ND) { SI.range = Range{sourceLocToPosition(SM, SymbolRange->getBegin()), sourceLocToPosition(SM, SymbolRange->getEnd())}; SI.detail = getSymbolDetail(Ctx, ND); + SI.tags = getSymbolTags(ND); SourceLocation NameLoc = ND.getLocation(); SourceLocation FallbackNameLoc; diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h index 5fb116b13d113..97b99af4f35e6 100644 --- a/clang-tools-extra/clangd/FindSymbols.h +++ b/clang-tools-extra/clangd/FindSymbols.h @@ -14,6 +14,7 @@ #include "Protocol.h" #include "index/Symbol.h" +#include "clang/AST/Decl.h" #include "llvm/ADT/StringRef.h" namespace clang { @@ -21,6 +22,15 @@ namespace clangd { class ParsedAST; class SymbolIndex; +/// A bitmask type representing symbol tags supported by LSP. +/// \see +/// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#symbolTag +using SymbolTags = uint32_t; +/// Ensure we have enough bits to represent all SymbolTag values. +static_assert(static_cast(SymbolTag::LastTag) <= 32, + "Too many SymbolTags to fit in uint32_t. Change to uint64_t if " + "we ever have more than 32 tags."); + /// Helper function for deriving an LSP Location from an index SymbolLocation. llvm::Expected indexToLSPLocation(const SymbolLocation &Loc, llvm::StringRef TUPath); @@ -47,6 +57,18 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit, /// same order that they appear. llvm::Expected> getDocumentSymbols(ParsedAST &AST); +/// Converts a single SymbolTag to a bitmask. +SymbolTags toSymbolTagBitmask(SymbolTag ST); + +/// Computes symbol tags for a given NamedDecl. +SymbolTags computeSymbolTags(const NamedDecl &ND); + +/// Returns the symbol tags for the given declaration. +/// This is a wrapper around computeSymbolTags() which unpacks +/// the tags into a vector. +/// \p ND The declaration to get tags for. +std::vector getSymbolTags(const NamedDecl &ND); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index 560b8e00ed377..9926f2dd63de5 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -964,6 +964,8 @@ llvm::json::Value toJSON(const DocumentSymbol &S) { Result["children"] = S.children; if (S.deprecated) Result["deprecated"] = true; + if (!S.tags.empty()) + Result["tags"] = S.tags; // FIXME: workaround for older gcc/clang return std::move(Result); } diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index 2248572060431..a88c9a391f97a 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -281,7 +281,7 @@ struct TextDocumentEdit { /// The text document to change. VersionedTextDocumentIdentifier textDocument; - /// The edits to be applied. + /// The edits to be applied. /// FIXME: support the AnnotatedTextEdit variant. std::vector edits; }; @@ -560,7 +560,7 @@ struct ClientCapabilities { /// The client supports versioned document changes for WorkspaceEdit. bool DocumentChanges = false; - + /// The client supports change annotations on text edits, bool ChangeAnnotation = false; @@ -1027,12 +1027,12 @@ struct WorkspaceEdit { /// Versioned document edits. /// /// If a client neither supports `documentChanges` nor - /// `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s - /// using the `changes` property are supported. + /// `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s + /// using the `changes` property are supported. std::optional> documentChanges; - + /// A map of change annotations that can be referenced in - /// AnnotatedTextEdit. + /// AnnotatedTextEdit. std::map changeAnnotations; }; bool fromJSON(const llvm::json::Value &, WorkspaceEdit &, llvm::json::Path); @@ -1104,6 +1104,35 @@ struct CodeAction { }; llvm::json::Value toJSON(const CodeAction &); +/// Symbol tags are extra annotations that can be attached to a symbol. +/// \see https://github.com/microsoft/language-server-protocol/pull/2003 +enum class SymbolTag { + Deprecated = 1, + Private = 2, + Package = 3, + Protected = 4, + Public = 5, + Internal = 6, + File = 7, + Static = 8, + Abstract = 9, + Final = 10, + Sealed = 11, + Transient = 12, + Volatile = 13, + Synchronized = 14, + Virtual = 15, + Nullable = 16, + NonNull = 17, + Declaration = 18, + Definition = 19, + ReadOnly = 20, + + // Update as needed + FirstTag = Deprecated, + LastTag = ReadOnly +}; +llvm::json::Value toJSON(SymbolTag); /// Represents programming constructs like variables, classes, interfaces etc. /// that appear in a document. Document symbols can be hierarchical and they /// have two ranges: one that encloses its definition and one that points to its @@ -1121,6 +1150,9 @@ struct DocumentSymbol { /// Indicates if this symbol is deprecated. bool deprecated = false; + /// The tags for this symbol. + std::vector tags; + /// The range enclosing this symbol not including leading/trailing whitespace /// but everything else like comments. This information is typically used to /// determine if the clients cursor is inside the symbol to reveal in the @@ -1146,6 +1178,9 @@ struct SymbolInformation { /// The kind of this symbol. SymbolKind kind; + /// Tags for this symbol, e.g public, private, static, const etc. + std::vector tags; + /// The location of this symbol. Location location; @@ -1288,13 +1323,13 @@ enum class InsertTextFormat { /// Additional details for a completion item label. struct CompletionItemLabelDetails { /// An optional string which is rendered less prominently directly after label - /// without any spacing. Should be used for function signatures or type + /// without any spacing. Should be used for function signatures or type /// annotations. std::string detail; /// An optional string which is rendered less prominently after - /// CompletionItemLabelDetails.detail. Should be used for fully qualified - /// names or file path. + /// CompletionItemLabelDetails.detail. Should be used for fully qualified + /// names or file path. std::string description; }; llvm::json::Value toJSON(const CompletionItemLabelDetails &); @@ -1572,9 +1607,6 @@ struct ResolveTypeHierarchyItemParams { bool fromJSON(const llvm::json::Value &, ResolveTypeHierarchyItemParams &, llvm::json::Path); -enum class SymbolTag { Deprecated = 1 }; -llvm::json::Value toJSON(SymbolTag); - /// The parameter of a `textDocument/prepareCallHierarchy` request. struct CallHierarchyPrepareParams : public TextDocumentPositionParams {}; diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp index ab720ebe6b47f..79446a166d0d8 100644 --- a/clang-tools-extra/clangd/SemanticHighlighting.cpp +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp @@ -7,7 +7,9 @@ //===----------------------------------------------------------------------===// #include "SemanticHighlighting.h" +#include "AST.h" #include "Config.h" +#include "FindSymbols.h" #include "FindTarget.h" #include "ParsedAST.h" #include "Protocol.h" @@ -21,9 +23,7 @@ #include "clang/AST/DeclarationName.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/RecursiveASTVisitor.h" -#include "clang/AST/Type.h" #include "clang/AST/TypeLoc.h" -#include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Sema/HeuristicResolver.h" @@ -32,6 +32,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" + #include #include @@ -77,23 +78,6 @@ bool canHighlightName(DeclarationName Name) { llvm_unreachable("invalid name kind"); } -bool isUniqueDefinition(const NamedDecl *Decl) { - if (auto *Func = dyn_cast(Decl)) - return Func->isThisDeclarationADefinition(); - if (auto *Klass = dyn_cast(Decl)) - return Klass->isThisDeclarationADefinition(); - if (auto *Iface = dyn_cast(Decl)) - return Iface->isThisDeclarationADefinition(); - if (auto *Proto = dyn_cast(Decl)) - return Proto->isThisDeclarationADefinition(); - if (auto *Var = dyn_cast(Decl)) - return Var->isThisDeclarationADefinition(); - return isa(Decl) || - isa(Decl) || - isa(Decl) || isa(Decl) || - isa(Decl); -} - std::optional kindForType(const Type *TP, const HeuristicResolver *Resolver); std::optional kindForDecl(const NamedDecl *D, @@ -147,10 +131,9 @@ std::optional kindForDecl(const NamedDecl *D, if (auto *VD = dyn_cast(D)) { if (isa(VD)) // e.g. ObjC Self return std::nullopt; - return VD->isStaticDataMember() - ? HighlightingKind::StaticField - : VD->isLocalVarDecl() ? HighlightingKind::LocalVariable - : HighlightingKind::Variable; + return VD->isStaticDataMember() ? HighlightingKind::StaticField + : VD->isLocalVarDecl() ? HighlightingKind::LocalVariable + : HighlightingKind::Variable; } if (const auto *BD = dyn_cast(D)) return BD->getDeclContext()->isFunctionOrMethod() @@ -192,91 +175,6 @@ std::optional kindForType(const Type *TP, return std::nullopt; } -// Whether T is const in a loose sense - is a variable with this type readonly? -bool isConst(QualType T) { - if (T.isNull()) - return false; - T = T.getNonReferenceType(); - if (T.isConstQualified()) - return true; - if (const auto *AT = T->getAsArrayTypeUnsafe()) - return isConst(AT->getElementType()); - if (isConst(T->getPointeeType())) - return true; - return false; -} - -// Whether D is const in a loose sense (should it be highlighted as such?) -// FIXME: This is separate from whether *a particular usage* can mutate D. -// We may want V in V.size() to be readonly even if V is mutable. -bool isConst(const Decl *D) { - if (llvm::isa(D) || llvm::isa(D)) - return true; - if (llvm::isa(D) || llvm::isa(D) || - llvm::isa(D) || llvm::isa(D)) { - if (isConst(llvm::cast(D)->getType())) - return true; - } - if (const auto *OCPD = llvm::dyn_cast(D)) { - if (OCPD->isReadOnly()) - return true; - } - if (const auto *MPD = llvm::dyn_cast(D)) { - if (!MPD->hasSetter()) - return true; - } - if (const auto *CMD = llvm::dyn_cast(D)) { - if (CMD->isConst()) - return true; - } - return false; -} - -// "Static" means many things in C++, only some get the "static" modifier. -// -// Meanings that do: -// - Members associated with the class rather than the instance. -// This is what 'static' most often means across languages. -// - static local variables -// These are similarly "detached from their context" by the static keyword. -// In practice, these are rarely used inside classes, reducing confusion. -// -// Meanings that don't: -// - Namespace-scoped variables, which have static storage class. -// This is implicit, so the keyword "static" isn't so strongly associated. -// If we want a modifier for these, "global scope" is probably the concept. -// - Namespace-scoped variables/functions explicitly marked "static". -// There the keyword changes *linkage* , which is a totally different concept. -// If we want to model this, "file scope" would be a nice modifier. -// -// This is confusing, and maybe we should use another name, but because "static" -// is a standard LSP modifier, having one with that name has advantages. -bool isStatic(const Decl *D) { - if (const auto *CMD = llvm::dyn_cast(D)) - return CMD->isStatic(); - if (const VarDecl *VD = llvm::dyn_cast(D)) - return VD->isStaticDataMember() || VD->isStaticLocal(); - if (const auto *OPD = llvm::dyn_cast(D)) - return OPD->isClassProperty(); - if (const auto *OMD = llvm::dyn_cast(D)) - return OMD->isClassMethod(); - return false; -} - -bool isAbstract(const Decl *D) { - if (const auto *CMD = llvm::dyn_cast(D)) - return CMD->isPureVirtual(); - if (const auto *CRD = llvm::dyn_cast(D)) - return CRD->hasDefinition() && CRD->isAbstract(); - return false; -} - -bool isVirtual(const Decl *D) { - if (const auto *CMD = llvm::dyn_cast(D)) - return CMD->isVirtual(); - return false; -} - bool isDependent(const Decl *D) { if (isa(D)) return true; @@ -1157,31 +1055,41 @@ getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens) { } if (auto Mod = scopeModifier(Decl)) Tok.addModifier(*Mod); - if (isConst(Decl)) - Tok.addModifier(HighlightingModifier::Readonly); - if (isStatic(Decl)) - Tok.addModifier(HighlightingModifier::Static); - if (isAbstract(Decl)) - Tok.addModifier(HighlightingModifier::Abstract); - if (isVirtual(Decl)) - Tok.addModifier(HighlightingModifier::Virtual); + + const auto SymbolTags = computeSymbolTags(*Decl); + + static const thread_local llvm::DenseMap + TagModifierMap = { + {SymbolTag::Deprecated, HighlightingModifier::Deprecated}, + {SymbolTag::ReadOnly, HighlightingModifier::Readonly}, + {SymbolTag::Static, HighlightingModifier::Static}, + {SymbolTag::Virtual, HighlightingModifier::Virtual}, + {SymbolTag::Abstract, HighlightingModifier::Abstract}, + // Declaration and Definition are handled separately below. + }; + + for (const auto &[Tag, Modifier] : TagModifierMap) { + if (SymbolTags & toSymbolTagBitmask(Tag)) + Tok.addModifier(Modifier); + } + + if (R.IsDecl && + (SymbolTags & toSymbolTagBitmask(SymbolTag::Declaration))) { + Tok.addModifier(HighlightingModifier::Declaration); + + if (SymbolTags & toSymbolTagBitmask(SymbolTag::Definition)) + Tok.addModifier(HighlightingModifier::Definition); + } + if (isDependent(Decl)) Tok.addModifier(HighlightingModifier::DependentName); + if (isDefaultLibrary(Decl)) Tok.addModifier(HighlightingModifier::DefaultLibrary); - if (Decl->isDeprecated()) - Tok.addModifier(HighlightingModifier::Deprecated); + if (isa(Decl)) Tok.addModifier(HighlightingModifier::ConstructorOrDestructor); - if (R.IsDecl) { - // Do not treat an UnresolvedUsingValueDecl as a declaration. - // It's more common to think of it as a reference to the - // underlying declaration. - if (!isa(Decl)) - Tok.addModifier(HighlightingModifier::Declaration); - if (isUniqueDefinition(Decl)) - Tok.addModifier(HighlightingModifier::Definition); - } } }, AST.getHeuristicResolver()); @@ -1501,9 +1409,8 @@ llvm::StringRef toSemanticTokenModifier(HighlightingModifier Modifier) { llvm_unreachable("unhandled HighlightingModifier"); } -std::vector -diffTokens(llvm::ArrayRef Old, - llvm::ArrayRef New) { +std::vector diffTokens(llvm::ArrayRef Old, + llvm::ArrayRef New) { // For now, just replace everything from the first-last modification. // FIXME: use a real diff instead, this is bad with include-insertion. diff --git a/clang-tools-extra/clangd/test/symbol-tags.test b/clang-tools-extra/clangd/test/symbol-tags.test new file mode 100644 index 0000000000000..6b17dc994d029 --- /dev/null +++ b/clang-tools-extra/clangd/test/symbol-tags.test @@ -0,0 +1,85 @@ +# COM: Checks the extraction of symbol tags. + +# RUN: clangd -lit-test < %s | FileCheck %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"documentSymbol":{"hierarchicalDocumentSymbolSupport":true}},"workspace":{"symbol":{"symbolKind":{"valueSet": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]}}}},"trace":"off"}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1, + "text": + "class A {\n virtual void f() const = 0;\n};" +}}} +--- +{"jsonrpc":"2.0","id":2,"method":"textDocument/documentSymbol","params":{"textDocument":{"uri":"test:///main.cpp"}}} +# CHECK: "id": 2 +# CHECK: "jsonrpc": "2.0", +# CHECK: "result": [ +# CHECK: { +# CHECK: "children": [ +# CHECK: { +# CHECK: "detail": "void () const", +# CHECK: "kind": 6, +# CHECK: "name": "f", +# CHECK: "range": { +# CHECK: "end": { +# CHECK: "character": 27, +# CHECK: "line": 1 +# CHECK: }, +# CHECK: "start": { +# CHECK: "character": 1, +# CHECK: "line": 1 +# CHECK: } +# CHECK: }, +# CHECK: "selectionRange": { +# CHECK: "end": { +# CHECK: "character": 15, +# CHECK: "line": 1 +# CHECK: }, +# CHECK: "start": { +# CHECK: "character": 14, +# CHECK: "line": 1 +# CHECK: } +# CHECK: }, +# CHECK: "tags": [ +# CHECK: 2, +# CHECK: 9, +# CHECK: 15, +# CHECK: 18, +# CHECK: 20 +# CHECK: ] +# CHECK: } +# CHECK: ], +# CHECK: "detail": "class", +# CHECK: "kind": 5, +# CHECK: "name": "A", +# CHECK: "range": { +# CHECK: "end": { +# CHECK: "character": 1, +# CHECK: "line": 2 +# CHECK: }, +# CHECK: "start": { +# CHECK: "character": 0, +# CHECK: "line": 0 +# CHECK: } +# CHECK: }, +# CHECK: "selectionRange": { +# CHECK: "end": { +# CHECK: "character": 7, +# CHECK: "line": 0 +# CHECK: }, +# CHECK: "start": { +# CHECK: "character": 6, +# CHECK: "line": 0 +# CHECK: } +# CHECK: }, +# CHECK: "tags": [ +# CHECK: 9, +# CHECK: 18, +# CHECK: 19 +# CHECK: ] +# CHECK: } +# CHECK: ] +# CHECK: } + +--- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} diff --git a/clang-tools-extra/clangd/test/symbols.test b/clang-tools-extra/clangd/test/symbols.test index af5d74123630e..a16a226e48c05 100644 --- a/clang-tools-extra/clangd/test/symbols.test +++ b/clang-tools-extra/clangd/test/symbols.test @@ -56,7 +56,10 @@ # CHECK-NEXT: "character": {{.*}}, # CHECK-NEXT: "line": {{.*}} # CHECK-NEXT: } -# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "tags": [ +# CHECK-NEXT: 18 +# CHECK-NEXT: ] # CHECK-NEXT: }, # CHECK-NEXT: { # CHECK-NEXT: "detail": "int ()", @@ -81,7 +84,11 @@ # CHECK-NEXT: "character": {{.*}}, # CHECK-NEXT: "line": {{.*}} # CHECK-NEXT: } -# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "tags": [ +# CHECK-NEXT: 18, +# CHECK-NEXT: 19 +# CHECK-NEXT: ] # CHECK-NEXT: } # CHECK-NEXT: ] # CHECK-NEXT:} diff --git a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp index d6e94bf752452..d0dd5d0f8f434 100644 --- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp @@ -43,6 +43,12 @@ ::testing::Matcher children(ChildMatchers... ChildrenM) { return Field(&DocumentSymbol::children, UnorderedElementsAre(ChildrenM...)); } +template +::testing::Matcher withSymbolTags(Tags... tags) { + // Matches the tags vector ignoring element order. + return Field(&DocumentSymbol::tags, UnorderedElementsAre(tags...)); +} + std::vector getSymbols(TestTU &TU, llvm::StringRef Query, int Limit = 0) { auto SymbolInfos = getWorkspaceSymbols(Query, Limit, TU.index().get(), @@ -1132,6 +1138,69 @@ TEST(DocumentSymbolsTest, PragmaMarkGroupsNoNesting) { withName("Core"), withName("coreMethod"))); } +TEST(DocumentSymbolsTest, SymbolTags) { + TestTU TU; + Annotations Main(R"cpp( + class AbstractClass { + public: + virtual ~AbstractClass() = default; + virtual void f1() = 0; + void f2() const; + protected: + void f3(){} + private: + static void f4(){} + }; + + void AbstractClass::f2() const {} + + class ImplClass final: public AbstractClass { + public: + void f1() final {} + }; + )cpp"); + + TU.Code = Main.code().str(); + auto Symbols = getSymbols(TU.build()); + EXPECT_THAT( + Symbols, + UnorderedElementsAre( + AllOf( + withName("AbstractClass"), + withSymbolTags(SymbolTag::Abstract, SymbolTag::Declaration, + SymbolTag::Definition), + children( + AllOf(withName("~AbstractClass"), + withSymbolTags(SymbolTag::Public, SymbolTag::Virtual, + SymbolTag::Declaration, + SymbolTag::Definition)), + AllOf(withName("f1"), + withSymbolTags(SymbolTag::Public, SymbolTag::Abstract, + SymbolTag::Virtual, + SymbolTag::Declaration)), + AllOf(withName("f2"), withSymbolTags(SymbolTag::Public, + SymbolTag::Declaration, + SymbolTag::ReadOnly)), + AllOf(withName("f3"), withSymbolTags(SymbolTag::Protected, + SymbolTag::Declaration, + SymbolTag::Definition)), + AllOf(withName("f4"), + withSymbolTags(SymbolTag::Private, SymbolTag::Static, + SymbolTag::Declaration, + SymbolTag::Definition)))), + AllOf(withName("AbstractClass::f2"), + withSymbolTags(SymbolTag::Public, SymbolTag::Declaration, + SymbolTag::Definition, SymbolTag::ReadOnly)), + AllOf(withName("ImplClass"), + withSymbolTags(SymbolTag::Final, SymbolTag::Declaration, + SymbolTag::Definition), + children(AllOf( + withName("f1"), + withSymbolTags(SymbolTag::Public, SymbolTag::Final, + SymbolTag::Virtual, SymbolTag::Declaration, + SymbolTag::Definition)))))); +} + } // namespace } // namespace clangd } // namespace clang