forked from OSchip/llvm-project
[clangd] Implement textDocument/hover
Summary: Implemention of textDocument/hover as described in LSP definition. This patch adds a basic Hover implementation. When hovering a variable, function, method or namespace, clangd will return a text containing the declaration's scope, as well as the declaration of the hovered entity. For example, for a variable: Declared in class Foo::Bar int hello = 2 For macros, the macro definition is returned. This patch doesn't include: - markdown support (the client I use doesn't support it yet) - range support (optional in the Hover response) - comments associated to variables/functions/classes They are kept as future work to keep this patch simpler. I added tests in XRefsTests.cpp. hover.test contains one simple smoketest to make sure the feature works from a black box perspective. Reviewers: malaperle, krasimir, bkramer, ilya-biryukov Subscribers: sammccall, mgrang, klimek, rwols, ilya-biryukov, arphaman, cfe-commits Differential Revision: https://reviews.llvm.org/D35894 Signed-off-by: Simon Marchi <simon.marchi@ericsson.com> Signed-off-by: William Enright <william.enright@polymtl.ca> llvm-svn: 325395
This commit is contained in:
parent
27b9ac2372
commit
3e618ed8f0
|
@ -118,6 +118,7 @@ void ClangdLSPServer::onInitialize(InitializeParams &Params) {
|
|||
}},
|
||||
{"definitionProvider", true},
|
||||
{"documentHighlightProvider", true},
|
||||
{"hoverProvider", true},
|
||||
{"renameProvider", true},
|
||||
{"executeCommandProvider",
|
||||
json::obj{
|
||||
|
@ -355,6 +356,19 @@ void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) {
|
|||
});
|
||||
}
|
||||
|
||||
void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) {
|
||||
Server.findHover(Params.textDocument.uri.file(), Params.position,
|
||||
[](llvm::Expected<Tagged<Hover>> H) {
|
||||
if (!H) {
|
||||
replyError(ErrorCode::InternalError,
|
||||
llvm::toString(H.takeError()));
|
||||
return;
|
||||
}
|
||||
|
||||
reply(H->Value);
|
||||
});
|
||||
}
|
||||
|
||||
ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
|
||||
bool StorePreamblesInMemory,
|
||||
const clangd::CodeCompleteOptions &CCOpts,
|
||||
|
|
|
@ -75,6 +75,7 @@ private:
|
|||
void onFileEvent(DidChangeWatchedFilesParams &Params) override;
|
||||
void onCommand(ExecuteCommandParams &Params) override;
|
||||
void onRename(RenameParams &Parames) override;
|
||||
void onHover(TextDocumentPositionParams &Params) override;
|
||||
|
||||
std::vector<TextEdit> getFixIts(StringRef File, const clangd::Diagnostic &D);
|
||||
|
||||
|
|
|
@ -491,6 +491,29 @@ void ClangdServer::findDocumentHighlights(
|
|||
WorkScheduler.runWithAST(File, BindWithForward(Action, std::move(Callback)));
|
||||
}
|
||||
|
||||
void ClangdServer::findHover(
|
||||
PathRef File, Position Pos,
|
||||
UniqueFunction<void(llvm::Expected<Tagged<Hover>>)> Callback) {
|
||||
Hover FinalHover;
|
||||
auto FileContents = DraftMgr.getDraft(File);
|
||||
if (!FileContents.Draft)
|
||||
return Callback(llvm::make_error<llvm::StringError>(
|
||||
"findHover called on non-added file", llvm::errc::invalid_argument));
|
||||
|
||||
auto TaggedFS = FSProvider.getTaggedFileSystem(File);
|
||||
|
||||
auto Action = [Pos, TaggedFS](decltype(Callback) Callback,
|
||||
llvm::Expected<InputsAndAST> InpAST) {
|
||||
if (!InpAST)
|
||||
return Callback(InpAST.takeError());
|
||||
|
||||
Hover Result = clangd::getHover(InpAST->AST, Pos);
|
||||
Callback(make_tagged(std::move(Result), TaggedFS.Tag));
|
||||
};
|
||||
|
||||
WorkScheduler.runWithAST(File, BindWithForward(Action, std::move(Callback)));
|
||||
}
|
||||
|
||||
void ClangdServer::scheduleReparseAndDiags(
|
||||
PathRef File, VersionedDraft Contents,
|
||||
Tagged<IntrusiveRefCntPtr<vfs::FileSystem>> TaggedFS) {
|
||||
|
|
|
@ -212,6 +212,10 @@ public:
|
|||
void(llvm::Expected<Tagged<std::vector<DocumentHighlight>>>)>
|
||||
Callback);
|
||||
|
||||
/// Get code hover for a given position.
|
||||
void findHover(PathRef File, Position Pos,
|
||||
UniqueFunction<void(llvm::Expected<Tagged<Hover>>)> Callback);
|
||||
|
||||
/// Run formatting for \p Rng inside \p File with content \p Code.
|
||||
llvm::Expected<tooling::Replacements> formatRange(StringRef Code,
|
||||
PathRef File, Range Rng);
|
||||
|
|
|
@ -386,6 +386,35 @@ bool fromJSON(const json::Expr &Params, TextDocumentPositionParams &R) {
|
|||
O.map("position", R.position);
|
||||
}
|
||||
|
||||
static StringRef toTextKind(MarkupKind Kind) {
|
||||
switch (Kind) {
|
||||
case MarkupKind::PlainText:
|
||||
return "plaintext";
|
||||
case MarkupKind::Markdown:
|
||||
return "markdown";
|
||||
}
|
||||
llvm_unreachable("Invalid MarkupKind");
|
||||
}
|
||||
|
||||
json::Expr toJSON(const MarkupContent &MC) {
|
||||
if (MC.Value.empty())
|
||||
return nullptr;
|
||||
|
||||
return json::obj{
|
||||
{"kind", toTextKind(MC.Kind)},
|
||||
{"value", MC.Value},
|
||||
};
|
||||
}
|
||||
|
||||
json::Expr toJSON(const Hover &H) {
|
||||
json::obj Result{{"contents", toJSON(H.Contents)}};
|
||||
|
||||
if (H.Range.hasValue())
|
||||
Result["range"] = toJSON(*H.Range);
|
||||
|
||||
return std::move(Result);
|
||||
}
|
||||
|
||||
json::Expr toJSON(const CompletionItem &CI) {
|
||||
assert(!CI.label.empty() && "completion item label is required");
|
||||
json::obj Result{{"label", CI.label}};
|
||||
|
|
|
@ -480,6 +480,27 @@ struct TextDocumentPositionParams {
|
|||
};
|
||||
bool fromJSON(const json::Expr &, TextDocumentPositionParams &);
|
||||
|
||||
enum class MarkupKind {
|
||||
PlainText,
|
||||
Markdown,
|
||||
};
|
||||
|
||||
struct MarkupContent {
|
||||
MarkupKind Kind = MarkupKind::PlainText;
|
||||
std::string Value;
|
||||
};
|
||||
json::Expr toJSON(const MarkupContent &MC);
|
||||
|
||||
struct Hover {
|
||||
/// The hover's content
|
||||
MarkupContent Contents;
|
||||
|
||||
/// An optional range is a range inside a text document
|
||||
/// that is used to visualize a hover, e.g. by changing the background color.
|
||||
llvm::Optional<Range> Range;
|
||||
};
|
||||
json::Expr toJSON(const Hover &H);
|
||||
|
||||
/// The kind of a completion entry.
|
||||
enum class CompletionItemKind {
|
||||
Missing = 0,
|
||||
|
|
|
@ -67,6 +67,7 @@ void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher,
|
|||
Register("textDocument/switchSourceHeader",
|
||||
&ProtocolCallbacks::onSwitchSourceHeader);
|
||||
Register("textDocument/rename", &ProtocolCallbacks::onRename);
|
||||
Register("textDocument/hover", &ProtocolCallbacks::onHover);
|
||||
Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent);
|
||||
Register("workspace/executeCommand", &ProtocolCallbacks::onCommand);
|
||||
Register("textDocument/documentHighlight",
|
||||
|
|
|
@ -51,6 +51,7 @@ public:
|
|||
virtual void onCommand(ExecuteCommandParams &Params) = 0;
|
||||
virtual void onRename(RenameParams &Parames) = 0;
|
||||
virtual void onDocumentHighlight(TextDocumentPositionParams &Params) = 0;
|
||||
virtual void onHover(TextDocumentPositionParams &Params) = 0;
|
||||
};
|
||||
|
||||
void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out,
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "XRefs.h"
|
||||
#include "Logger.h"
|
||||
#include "URI.h"
|
||||
#include "clang/AST/DeclTemplate.h"
|
||||
#include "clang/Index/IndexDataConsumer.h"
|
||||
#include "clang/Index/IndexingAction.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
|
@ -31,10 +32,15 @@ const Decl* GetDefinition(const Decl* D) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
struct MacroDecl {
|
||||
StringRef Name;
|
||||
const MacroInfo *Info;
|
||||
};
|
||||
|
||||
/// Finds declarations locations that a given source location refers to.
|
||||
class DeclarationAndMacrosFinder : public index::IndexDataConsumer {
|
||||
std::vector<const Decl *> Decls;
|
||||
std::vector<const MacroInfo *> MacroInfos;
|
||||
std::vector<MacroDecl> MacroInfos;
|
||||
const SourceLocation &SearchedLocation;
|
||||
const ASTContext &AST;
|
||||
Preprocessor &PP;
|
||||
|
@ -54,10 +60,17 @@ public:
|
|||
return std::move(Decls);
|
||||
}
|
||||
|
||||
std::vector<const MacroInfo *> takeMacroInfos() {
|
||||
std::vector<MacroDecl> takeMacroInfos() {
|
||||
// Don't keep the same Macro info multiple times.
|
||||
std::sort(MacroInfos.begin(), MacroInfos.end());
|
||||
auto Last = std::unique(MacroInfos.begin(), MacroInfos.end());
|
||||
std::sort(MacroInfos.begin(), MacroInfos.end(),
|
||||
[](const MacroDecl &Left, const MacroDecl &Right) {
|
||||
return Left.Info < Right.Info;
|
||||
});
|
||||
|
||||
auto Last = std::unique(MacroInfos.begin(), MacroInfos.end(),
|
||||
[](const MacroDecl &Left, const MacroDecl &Right) {
|
||||
return Left.Info == Right.Info;
|
||||
});
|
||||
MacroInfos.erase(Last, MacroInfos.end());
|
||||
return std::move(MacroInfos);
|
||||
}
|
||||
|
@ -111,7 +124,7 @@ private:
|
|||
PP.getMacroDefinitionAtLoc(IdentifierInfo, BeforeSearchedLocation);
|
||||
MacroInfo *MacroInf = MacroDef.getMacroInfo();
|
||||
if (MacroInf) {
|
||||
MacroInfos.push_back(MacroInf);
|
||||
MacroInfos.push_back(MacroDecl{IdentifierInfo->getName(), MacroInf});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -176,8 +189,7 @@ std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos) {
|
|||
DeclMacrosFinder, IndexOpts);
|
||||
|
||||
std::vector<const Decl *> Decls = DeclMacrosFinder->takeDecls();
|
||||
std::vector<const MacroInfo *> MacroInfos =
|
||||
DeclMacrosFinder->takeMacroInfos();
|
||||
std::vector<MacroDecl> MacroInfos = DeclMacrosFinder->takeMacroInfos();
|
||||
std::vector<Location> Result;
|
||||
|
||||
for (auto Item : Decls) {
|
||||
|
@ -187,7 +199,8 @@ std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos) {
|
|||
}
|
||||
|
||||
for (auto Item : MacroInfos) {
|
||||
SourceRange SR(Item->getDefinitionLoc(), Item->getDefinitionEndLoc());
|
||||
SourceRange SR(Item.Info->getDefinitionLoc(),
|
||||
Item.Info->getDefinitionEndLoc());
|
||||
auto L = getDeclarationLocation(AST, SR);
|
||||
if (L)
|
||||
Result.push_back(*L);
|
||||
|
@ -299,5 +312,138 @@ std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
|
|||
return DocHighlightsFinder->takeHighlights();
|
||||
}
|
||||
|
||||
static PrintingPolicy PrintingPolicyForDecls(PrintingPolicy Base) {
|
||||
PrintingPolicy Policy(Base);
|
||||
|
||||
Policy.AnonymousTagLocations = false;
|
||||
Policy.TerseOutput = true;
|
||||
Policy.PolishForDeclaration = true;
|
||||
Policy.ConstantsAsWritten = true;
|
||||
Policy.SuppressTagKeyword = false;
|
||||
|
||||
return Policy;
|
||||
}
|
||||
|
||||
/// Return a string representation (e.g. "class MyNamespace::MyClass") of
|
||||
/// the type declaration \p TD.
|
||||
static std::string TypeDeclToString(const TypeDecl *TD) {
|
||||
QualType Type = TD->getASTContext().getTypeDeclType(TD);
|
||||
|
||||
PrintingPolicy Policy =
|
||||
PrintingPolicyForDecls(TD->getASTContext().getPrintingPolicy());
|
||||
|
||||
std::string Name;
|
||||
llvm::raw_string_ostream Stream(Name);
|
||||
Type.print(Stream, Policy);
|
||||
|
||||
return Stream.str();
|
||||
}
|
||||
|
||||
/// Return a string representation (e.g. "namespace ns1::ns2") of
|
||||
/// the named declaration \p ND.
|
||||
static std::string NamedDeclQualifiedName(const NamedDecl *ND,
|
||||
StringRef Prefix) {
|
||||
PrintingPolicy Policy =
|
||||
PrintingPolicyForDecls(ND->getASTContext().getPrintingPolicy());
|
||||
|
||||
std::string Name;
|
||||
llvm::raw_string_ostream Stream(Name);
|
||||
Stream << Prefix << ' ';
|
||||
ND->printQualifiedName(Stream, Policy);
|
||||
|
||||
return Stream.str();
|
||||
}
|
||||
|
||||
/// Given a declaration \p D, return a human-readable string representing the
|
||||
/// scope in which it is declared. If the declaration is in the global scope,
|
||||
/// return the string "global namespace".
|
||||
static llvm::Optional<std::string> getScopeName(const Decl *D) {
|
||||
const DeclContext *DC = D->getDeclContext();
|
||||
|
||||
if (const TranslationUnitDecl *TUD = dyn_cast<TranslationUnitDecl>(DC))
|
||||
return std::string("global namespace");
|
||||
|
||||
if (const TypeDecl *TD = dyn_cast<TypeDecl>(DC))
|
||||
return TypeDeclToString(TD);
|
||||
else if (const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC))
|
||||
return NamedDeclQualifiedName(ND, "namespace");
|
||||
else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(DC))
|
||||
return NamedDeclQualifiedName(FD, "function");
|
||||
|
||||
return llvm::None;
|
||||
}
|
||||
|
||||
/// Generate a \p Hover object given the declaration \p D.
|
||||
static Hover getHoverContents(const Decl *D) {
|
||||
Hover H;
|
||||
llvm::Optional<std::string> NamedScope = getScopeName(D);
|
||||
|
||||
// Generate the "Declared in" section.
|
||||
if (NamedScope) {
|
||||
assert(!NamedScope->empty());
|
||||
|
||||
H.Contents.Value += "Declared in ";
|
||||
H.Contents.Value += *NamedScope;
|
||||
H.Contents.Value += "\n\n";
|
||||
}
|
||||
|
||||
// We want to include the template in the Hover.
|
||||
if (TemplateDecl *TD = D->getDescribedTemplate())
|
||||
D = TD;
|
||||
|
||||
std::string DeclText;
|
||||
llvm::raw_string_ostream OS(DeclText);
|
||||
|
||||
PrintingPolicy Policy =
|
||||
PrintingPolicyForDecls(D->getASTContext().getPrintingPolicy());
|
||||
|
||||
D->print(OS, Policy);
|
||||
|
||||
OS.flush();
|
||||
|
||||
H.Contents.Value += DeclText;
|
||||
return H;
|
||||
}
|
||||
|
||||
/// Generate a \p Hover object given the macro \p MacroInf.
|
||||
static Hover getHoverContents(StringRef MacroName) {
|
||||
Hover H;
|
||||
|
||||
H.Contents.Value = "#define ";
|
||||
H.Contents.Value += MacroName;
|
||||
|
||||
return H;
|
||||
}
|
||||
|
||||
Hover getHover(ParsedAST &AST, Position Pos) {
|
||||
const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
|
||||
const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID());
|
||||
if (FE == nullptr)
|
||||
return Hover();
|
||||
|
||||
SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, FE);
|
||||
auto DeclMacrosFinder = std::make_shared<DeclarationAndMacrosFinder>(
|
||||
llvm::errs(), SourceLocationBeg, AST.getASTContext(),
|
||||
AST.getPreprocessor());
|
||||
|
||||
index::IndexingOptions IndexOpts;
|
||||
IndexOpts.SystemSymbolFilter =
|
||||
index::IndexingOptions::SystemSymbolFilterKind::All;
|
||||
IndexOpts.IndexFunctionLocals = true;
|
||||
|
||||
indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(),
|
||||
DeclMacrosFinder, IndexOpts);
|
||||
|
||||
std::vector<MacroDecl> Macros = DeclMacrosFinder->takeMacroInfos();
|
||||
if (!Macros.empty())
|
||||
return getHoverContents(Macros[0].Name);
|
||||
|
||||
std::vector<const Decl *> Decls = DeclMacrosFinder->takeDecls();
|
||||
if (!Decls.empty())
|
||||
return getHoverContents(Decls[0]);
|
||||
|
||||
return Hover();
|
||||
}
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
|
|
@ -27,6 +27,9 @@ std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos);
|
|||
std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
|
||||
Position Pos);
|
||||
|
||||
/// Get the hover information when hovering at \p Pos.
|
||||
Hover getHover(ParsedAST &AST, Position Pos);
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# RUN: clangd -lit-test < %s | FileCheck %s
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"void foo(); int main() { foo(); }\n"}}}
|
||||
---
|
||||
{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":0,"character":27}}}
|
||||
# CHECK: "id": 1,
|
||||
# CHECK-NEXT: "jsonrpc": "2.0",
|
||||
# CHECK-NEXT: "result": {
|
||||
# CHECK-NEXT: "contents": {
|
||||
# CHECK-NEXT: "kind": "plaintext",
|
||||
# CHECK-NEXT: "value": "Declared in global namespace\n\nvoid foo()"
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT:}
|
||||
---
|
||||
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"exit"}
|
|
@ -28,6 +28,7 @@
|
|||
# CHECK-NEXT: "clangd.insertInclude"
|
||||
# CHECK-NEXT: ]
|
||||
# CHECK-NEXT: },
|
||||
# CHECK-NEXT: "hoverProvider": true,
|
||||
# CHECK-NEXT: "renameProvider": true,
|
||||
# CHECK-NEXT: "signatureHelpProvider": {
|
||||
# CHECK-NEXT: "triggerCharacters": [
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
# CHECK-NEXT: "clangd.insertInclude"
|
||||
# CHECK-NEXT: ]
|
||||
# CHECK-NEXT: },
|
||||
# CHECK-NEXT: "hoverProvider": true,
|
||||
# CHECK-NEXT: "renameProvider": true,
|
||||
# CHECK-NEXT: "signatureHelpProvider": {
|
||||
# CHECK-NEXT: "triggerCharacters": [
|
||||
|
|
|
@ -258,6 +258,311 @@ int baz = f^oo;
|
|||
ElementsAre(Location{URIForFile{FooCpp}, SourceAnnotations.range()}));
|
||||
}
|
||||
|
||||
TEST(Hover, All) {
|
||||
struct OneTest {
|
||||
StringRef Input;
|
||||
StringRef ExpectedHover;
|
||||
};
|
||||
|
||||
OneTest Tests[] = {
|
||||
{
|
||||
R"cpp(// Local variable
|
||||
int main() {
|
||||
int bonjour;
|
||||
^bonjour = 2;
|
||||
int test1 = bonjour;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in function main\n\nint bonjour",
|
||||
},
|
||||
{
|
||||
R"cpp(// Local variable in method
|
||||
struct s {
|
||||
void method() {
|
||||
int bonjour;
|
||||
^bonjour = 2;
|
||||
}
|
||||
};
|
||||
)cpp",
|
||||
"Declared in function s::method\n\nint bonjour",
|
||||
},
|
||||
{
|
||||
R"cpp(// Struct
|
||||
namespace ns1 {
|
||||
struct MyClass {};
|
||||
} // namespace ns1
|
||||
int main() {
|
||||
ns1::My^Class* Params;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in namespace ns1\n\nstruct MyClass {}",
|
||||
},
|
||||
{
|
||||
R"cpp(// Class
|
||||
namespace ns1 {
|
||||
class MyClass {};
|
||||
} // namespace ns1
|
||||
int main() {
|
||||
ns1::My^Class* Params;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in namespace ns1\n\nclass MyClass {}",
|
||||
},
|
||||
{
|
||||
R"cpp(// Union
|
||||
namespace ns1 {
|
||||
union MyUnion { int x; int y; };
|
||||
} // namespace ns1
|
||||
int main() {
|
||||
ns1::My^Union Params;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in namespace ns1\n\nunion MyUnion {}",
|
||||
},
|
||||
{
|
||||
R"cpp(// Function definition via pointer
|
||||
int foo(int) {}
|
||||
int main() {
|
||||
auto *X = &^foo;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in global namespace\n\nint foo(int)",
|
||||
},
|
||||
{
|
||||
R"cpp(// Function declaration via call
|
||||
int foo(int);
|
||||
int main() {
|
||||
return ^foo(42);
|
||||
}
|
||||
)cpp",
|
||||
"Declared in global namespace\n\nint foo(int)",
|
||||
},
|
||||
{
|
||||
R"cpp(// Field
|
||||
struct Foo { int x; };
|
||||
int main() {
|
||||
Foo bar;
|
||||
bar.^x;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in struct Foo\n\nint x",
|
||||
},
|
||||
{
|
||||
R"cpp(// Field with initialization
|
||||
struct Foo { int x = 5; };
|
||||
int main() {
|
||||
Foo bar;
|
||||
bar.^x;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in struct Foo\n\nint x = 5",
|
||||
},
|
||||
{
|
||||
R"cpp(// Static field
|
||||
struct Foo { static int x; };
|
||||
int main() {
|
||||
Foo::^x;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in struct Foo\n\nstatic int x",
|
||||
},
|
||||
{
|
||||
R"cpp(// Field, member initializer
|
||||
struct Foo {
|
||||
int x;
|
||||
Foo() : ^x(0) {}
|
||||
};
|
||||
)cpp",
|
||||
"Declared in struct Foo\n\nint x",
|
||||
},
|
||||
{
|
||||
R"cpp(// Field, GNU old-style field designator
|
||||
struct Foo { int x; };
|
||||
int main() {
|
||||
Foo bar = { ^x : 1 };
|
||||
}
|
||||
)cpp",
|
||||
"Declared in struct Foo\n\nint x",
|
||||
},
|
||||
{
|
||||
R"cpp(// Field, field designator
|
||||
struct Foo { int x; };
|
||||
int main() {
|
||||
Foo bar = { .^x = 2 };
|
||||
}
|
||||
)cpp",
|
||||
"Declared in struct Foo\n\nint x",
|
||||
},
|
||||
{
|
||||
R"cpp(// Method call
|
||||
struct Foo { int x(); };
|
||||
int main() {
|
||||
Foo bar;
|
||||
bar.^x();
|
||||
}
|
||||
)cpp",
|
||||
"Declared in struct Foo\n\nint x()",
|
||||
},
|
||||
{
|
||||
R"cpp(// Static method call
|
||||
struct Foo { static int x(); };
|
||||
int main() {
|
||||
Foo::^x();
|
||||
}
|
||||
)cpp",
|
||||
"Declared in struct Foo\n\nstatic int x()",
|
||||
},
|
||||
{
|
||||
R"cpp(// Typedef
|
||||
typedef int Foo;
|
||||
int main() {
|
||||
^Foo bar;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in global namespace\n\ntypedef int Foo",
|
||||
},
|
||||
{
|
||||
R"cpp(// Namespace
|
||||
namespace ns {
|
||||
struct Foo { static void bar(); }
|
||||
} // namespace ns
|
||||
int main() { ^ns::Foo::bar(); }
|
||||
)cpp",
|
||||
"Declared in global namespace\n\nnamespace ns {\n}",
|
||||
},
|
||||
{
|
||||
R"cpp(// Anonymous namespace
|
||||
namespace ns {
|
||||
namespace {
|
||||
int foo;
|
||||
} // anonymous namespace
|
||||
} // namespace ns
|
||||
int main() { ns::f^oo++; }
|
||||
)cpp",
|
||||
"Declared in namespace ns::(anonymous)\n\nint foo",
|
||||
},
|
||||
{
|
||||
R"cpp(// Macro
|
||||
#define MACRO 0
|
||||
#define MACRO 1
|
||||
int main() { return ^MACRO; }
|
||||
#define MACRO 2
|
||||
#undef macro
|
||||
)cpp",
|
||||
"#define MACRO",
|
||||
},
|
||||
{
|
||||
R"cpp(// Forward class declaration
|
||||
class Foo;
|
||||
class Foo {};
|
||||
F^oo* foo();
|
||||
)cpp",
|
||||
"Declared in global namespace\n\nclass Foo {}",
|
||||
},
|
||||
{
|
||||
R"cpp(// Function declaration
|
||||
void foo();
|
||||
void g() { f^oo(); }
|
||||
void foo() {}
|
||||
)cpp",
|
||||
"Declared in global namespace\n\nvoid foo()",
|
||||
},
|
||||
{
|
||||
R"cpp(// Enum declaration
|
||||
enum Hello {
|
||||
ONE, TWO, THREE,
|
||||
};
|
||||
void foo() {
|
||||
Hel^lo hello = ONE;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in global namespace\n\nenum Hello {\n}",
|
||||
},
|
||||
{
|
||||
R"cpp(// Enumerator
|
||||
enum Hello {
|
||||
ONE, TWO, THREE,
|
||||
};
|
||||
void foo() {
|
||||
Hello hello = O^NE;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in enum Hello\n\nONE",
|
||||
},
|
||||
{
|
||||
R"cpp(// Enumerator in anonymous enum
|
||||
enum {
|
||||
ONE, TWO, THREE,
|
||||
};
|
||||
void foo() {
|
||||
int hello = O^NE;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in enum (anonymous)\n\nONE",
|
||||
},
|
||||
{
|
||||
R"cpp(// Global variable
|
||||
static int hey = 10;
|
||||
void foo() {
|
||||
he^y++;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in global namespace\n\nstatic int hey = 10",
|
||||
},
|
||||
{
|
||||
R"cpp(// Global variable in namespace
|
||||
namespace ns1 {
|
||||
static int hey = 10;
|
||||
}
|
||||
void foo() {
|
||||
ns1::he^y++;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in namespace ns1\n\nstatic int hey = 10",
|
||||
},
|
||||
{
|
||||
R"cpp(// Field in anonymous struct
|
||||
static struct {
|
||||
int hello;
|
||||
} s;
|
||||
void foo() {
|
||||
s.he^llo++;
|
||||
}
|
||||
)cpp",
|
||||
"Declared in struct (anonymous)\n\nint hello",
|
||||
},
|
||||
{
|
||||
R"cpp(// Templated function
|
||||
template <typename T>
|
||||
T foo() {
|
||||
return 17;
|
||||
}
|
||||
void g() { auto x = f^oo<int>(); }
|
||||
)cpp",
|
||||
"Declared in global namespace\n\ntemplate <typename T> T foo()",
|
||||
},
|
||||
{
|
||||
R"cpp(// Anonymous union
|
||||
struct outer {
|
||||
union {
|
||||
int abc, def;
|
||||
} v;
|
||||
};
|
||||
void g() { struct outer o; o.v.d^ef++; }
|
||||
)cpp",
|
||||
"Declared in union outer::(anonymous)\n\nint def",
|
||||
},
|
||||
};
|
||||
|
||||
for (const OneTest &Test : Tests) {
|
||||
Annotations T(Test.Input);
|
||||
auto AST = build(T.code());
|
||||
Hover H = getHover(AST, T.point());
|
||||
|
||||
EXPECT_EQ(H.Contents.Value, Test.ExpectedHover) << Test.Input;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
|
Loading…
Reference in New Issue