[clangd] Add support for additional symbol tags proposed for LSP 3.18 (#167536)
Implements support for symbol tags proposed for LSP 3.18 in https://github.com/microsoft/language-server-protocol/pull/2003, in the `documentSymbols` and `workspace/symbols` requests. Fixes https://github.com/clangd/clangd/issues/2123. --------- Co-authored-by: chouzz <zhouhua258@outlook.com> Co-authored-by: Dimitri Ratz <dimitri.ratz@thinkdigital.cc>
This commit is contained in:
parent
700b5f1387
commit
c961174b2d
@ -23,13 +23,211 @@
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
#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<CXXMethodDecl>(D))
|
||||
return CMD->isStatic();
|
||||
if (const VarDecl *VD = llvm::dyn_cast<VarDecl>(D))
|
||||
return VD->isStaticDataMember() || VD->isStaticLocal();
|
||||
if (const auto *OPD = llvm::dyn_cast<ObjCPropertyDecl>(D))
|
||||
return OPD->isClassProperty();
|
||||
if (const auto *OMD = llvm::dyn_cast<ObjCMethodDecl>(D))
|
||||
return OMD->isClassMethod();
|
||||
if (const auto *FD = llvm::dyn_cast<FunctionDecl>(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<EnumConstantDecl>(D) || llvm::isa<NonTypeTemplateParmDecl>(D))
|
||||
return true;
|
||||
if (llvm::isa<FieldDecl>(D) || llvm::isa<VarDecl>(D) ||
|
||||
llvm::isa<MSPropertyDecl>(D) || llvm::isa<BindingDecl>(D)) {
|
||||
if (isConst(llvm::cast<ValueDecl>(D)->getType()))
|
||||
return true;
|
||||
}
|
||||
if (const auto *OCPD = llvm::dyn_cast<ObjCPropertyDecl>(D)) {
|
||||
if (OCPD->isReadOnly())
|
||||
return true;
|
||||
}
|
||||
if (const auto *MPD = llvm::dyn_cast<MSPropertyDecl>(D)) {
|
||||
if (!MPD->hasSetter())
|
||||
return true;
|
||||
}
|
||||
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D)) {
|
||||
if (CMD->isConst())
|
||||
return true;
|
||||
}
|
||||
if (const auto *FD = llvm::dyn_cast<FunctionDecl>(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<CXXMethodDecl>(D))
|
||||
return CMD->isPureVirtual();
|
||||
if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(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<CXXMethodDecl>(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<CXXMethodDecl>(D))
|
||||
return CRD->hasAttr<FinalAttr>();
|
||||
|
||||
if (const auto *CRD = dyn_cast<CXXRecordDecl>(D))
|
||||
return CRD->hasAttr<FinalAttr>();
|
||||
|
||||
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<FunctionDecl>(Decl))
|
||||
return Func->isThisDeclarationADefinition();
|
||||
if (auto *Klass = dyn_cast<CXXRecordDecl>(Decl))
|
||||
return Klass->isThisDeclarationADefinition();
|
||||
if (auto *Iface = dyn_cast<ObjCInterfaceDecl>(Decl))
|
||||
return Iface->isThisDeclarationADefinition();
|
||||
if (auto *Proto = dyn_cast<ObjCProtocolDecl>(Decl))
|
||||
return Proto->isThisDeclarationADefinition();
|
||||
if (auto *Var = dyn_cast<VarDecl>(Decl))
|
||||
return Var->isThisDeclarationADefinition();
|
||||
return isa<TemplateTypeParmDecl>(Decl) ||
|
||||
isa<NonTypeTemplateParmDecl>(Decl) ||
|
||||
isa<TemplateTemplateParmDecl>(Decl) || isa<ObjCCategoryDecl>(Decl) ||
|
||||
isa<ObjCImplDecl>(Decl);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
|
||||
return (1 << static_cast<unsigned>(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<UnresolvedUsingValueDecl>(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<SymbolTag> getSymbolTags(const NamedDecl &ND) {
|
||||
const auto symbolTags = computeSymbolTags(ND);
|
||||
std::vector<SymbolTag> 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<unsigned>(SymbolTag::FirstTag);
|
||||
constexpr unsigned MaxTag = static_cast<unsigned>(SymbolTag::LastTag);
|
||||
for (unsigned I = MinTag; I <= MaxTag; ++I) {
|
||||
auto ST = static_cast<SymbolTag>(I);
|
||||
if (symbolTags & toSymbolTagBitmask(ST))
|
||||
Tags.push_back(ST);
|
||||
}
|
||||
return Tags;
|
||||
}
|
||||
|
||||
namespace {
|
||||
using ScoredSymbolInfo = std::pair<float, SymbolInformation>;
|
||||
struct ScoredSymbolGreater {
|
||||
@ -242,6 +440,7 @@ std::optional<DocumentSymbol> 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;
|
||||
|
||||
@ -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<unsigned>(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<Location> indexToLSPLocation(const SymbolLocation &Loc,
|
||||
llvm::StringRef TUPath);
|
||||
@ -47,6 +57,18 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit,
|
||||
/// same order that they appear.
|
||||
llvm::Expected<std::vector<DocumentSymbol>> 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<SymbolTag> getSymbolTags(const NamedDecl &ND);
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<TextEdit> 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<std::vector<TextDocumentEdit>> documentChanges;
|
||||
|
||||
|
||||
/// A map of change annotations that can be referenced in
|
||||
/// AnnotatedTextEdit.
|
||||
/// AnnotatedTextEdit.
|
||||
std::map<std::string, ChangeAnnotation> 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<SymbolTag> 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<SymbolTag> 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 {};
|
||||
|
||||
|
||||
@ -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 <algorithm>
|
||||
#include <optional>
|
||||
|
||||
@ -77,23 +78,6 @@ bool canHighlightName(DeclarationName Name) {
|
||||
llvm_unreachable("invalid name kind");
|
||||
}
|
||||
|
||||
bool isUniqueDefinition(const NamedDecl *Decl) {
|
||||
if (auto *Func = dyn_cast<FunctionDecl>(Decl))
|
||||
return Func->isThisDeclarationADefinition();
|
||||
if (auto *Klass = dyn_cast<CXXRecordDecl>(Decl))
|
||||
return Klass->isThisDeclarationADefinition();
|
||||
if (auto *Iface = dyn_cast<ObjCInterfaceDecl>(Decl))
|
||||
return Iface->isThisDeclarationADefinition();
|
||||
if (auto *Proto = dyn_cast<ObjCProtocolDecl>(Decl))
|
||||
return Proto->isThisDeclarationADefinition();
|
||||
if (auto *Var = dyn_cast<VarDecl>(Decl))
|
||||
return Var->isThisDeclarationADefinition();
|
||||
return isa<TemplateTypeParmDecl>(Decl) ||
|
||||
isa<NonTypeTemplateParmDecl>(Decl) ||
|
||||
isa<TemplateTemplateParmDecl>(Decl) || isa<ObjCCategoryDecl>(Decl) ||
|
||||
isa<ObjCImplDecl>(Decl);
|
||||
}
|
||||
|
||||
std::optional<HighlightingKind> kindForType(const Type *TP,
|
||||
const HeuristicResolver *Resolver);
|
||||
std::optional<HighlightingKind> kindForDecl(const NamedDecl *D,
|
||||
@ -147,10 +131,9 @@ std::optional<HighlightingKind> kindForDecl(const NamedDecl *D,
|
||||
if (auto *VD = dyn_cast<VarDecl>(D)) {
|
||||
if (isa<ImplicitParamDecl>(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<BindingDecl>(D))
|
||||
return BD->getDeclContext()->isFunctionOrMethod()
|
||||
@ -192,91 +175,6 @@ std::optional<HighlightingKind> 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<EnumConstantDecl>(D) || llvm::isa<NonTypeTemplateParmDecl>(D))
|
||||
return true;
|
||||
if (llvm::isa<FieldDecl>(D) || llvm::isa<VarDecl>(D) ||
|
||||
llvm::isa<MSPropertyDecl>(D) || llvm::isa<BindingDecl>(D)) {
|
||||
if (isConst(llvm::cast<ValueDecl>(D)->getType()))
|
||||
return true;
|
||||
}
|
||||
if (const auto *OCPD = llvm::dyn_cast<ObjCPropertyDecl>(D)) {
|
||||
if (OCPD->isReadOnly())
|
||||
return true;
|
||||
}
|
||||
if (const auto *MPD = llvm::dyn_cast<MSPropertyDecl>(D)) {
|
||||
if (!MPD->hasSetter())
|
||||
return true;
|
||||
}
|
||||
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(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<CXXMethodDecl>(D))
|
||||
return CMD->isStatic();
|
||||
if (const VarDecl *VD = llvm::dyn_cast<VarDecl>(D))
|
||||
return VD->isStaticDataMember() || VD->isStaticLocal();
|
||||
if (const auto *OPD = llvm::dyn_cast<ObjCPropertyDecl>(D))
|
||||
return OPD->isClassProperty();
|
||||
if (const auto *OMD = llvm::dyn_cast<ObjCMethodDecl>(D))
|
||||
return OMD->isClassMethod();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isAbstract(const Decl *D) {
|
||||
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
|
||||
return CMD->isPureVirtual();
|
||||
if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(D))
|
||||
return CRD->hasDefinition() && CRD->isAbstract();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isVirtual(const Decl *D) {
|
||||
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
|
||||
return CMD->isVirtual();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isDependent(const Decl *D) {
|
||||
if (isa<UnresolvedUsingValueDecl>(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);
|
||||
if (isDependent(Decl))
|
||||
Tok.addModifier(HighlightingModifier::DependentName);
|
||||
if (isDefaultLibrary(Decl))
|
||||
Tok.addModifier(HighlightingModifier::DefaultLibrary);
|
||||
if (Decl->isDeprecated())
|
||||
Tok.addModifier(HighlightingModifier::Deprecated);
|
||||
if (isa<CXXConstructorDecl>(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<UnresolvedUsingValueDecl>(Decl))
|
||||
Tok.addModifier(HighlightingModifier::Declaration);
|
||||
if (isUniqueDefinition(Decl))
|
||||
|
||||
const auto SymbolTags = computeSymbolTags(*Decl);
|
||||
|
||||
static const thread_local llvm::DenseMap<SymbolTag,
|
||||
HighlightingModifier>
|
||||
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 (isa<CXXConstructorDecl>(Decl))
|
||||
Tok.addModifier(HighlightingModifier::ConstructorOrDestructor);
|
||||
}
|
||||
},
|
||||
AST.getHeuristicResolver());
|
||||
@ -1501,9 +1409,8 @@ llvm::StringRef toSemanticTokenModifier(HighlightingModifier Modifier) {
|
||||
llvm_unreachable("unhandled HighlightingModifier");
|
||||
}
|
||||
|
||||
std::vector<SemanticTokensEdit>
|
||||
diffTokens(llvm::ArrayRef<SemanticToken> Old,
|
||||
llvm::ArrayRef<SemanticToken> New) {
|
||||
std::vector<SemanticTokensEdit> diffTokens(llvm::ArrayRef<SemanticToken> Old,
|
||||
llvm::ArrayRef<SemanticToken> New) {
|
||||
// For now, just replace everything from the first-last modification.
|
||||
// FIXME: use a real diff instead, this is bad with include-insertion.
|
||||
|
||||
|
||||
85
clang-tools-extra/clangd/test/symbol-tags.test
Normal file
85
clang-tools-extra/clangd/test/symbol-tags.test
Normal file
@ -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"}
|
||||
@ -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:}
|
||||
|
||||
@ -43,6 +43,12 @@ template <class... ChildMatchers>
|
||||
return Field(&DocumentSymbol::children, UnorderedElementsAre(ChildrenM...));
|
||||
}
|
||||
|
||||
template <typename... Tags>
|
||||
::testing::Matcher<DocumentSymbol> withSymbolTags(Tags... tags) {
|
||||
// Matches the tags vector ignoring element order.
|
||||
return Field(&DocumentSymbol::tags, UnorderedElementsAre(tags...));
|
||||
}
|
||||
|
||||
std::vector<SymbolInformation> 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user