diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -53,6 +53,7 @@ ConfigYAML.cpp Diagnostics.cpp DraftStore.cpp + DumpAST.cpp ExpectedTypes.cpp FindSymbols.cpp FindTarget.cpp diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -92,6 +92,7 @@ void onDocumentDidChange(const DidChangeTextDocumentParams &); void onDocumentDidClose(const DidCloseTextDocumentParams &); void onDocumentDidSave(const DidSaveTextDocumentParams &); + void onAST(const ASTParams &, Callback>); void onDocumentOnTypeFormatting(const DocumentOnTypeFormattingParams &, Callback>); void onDocumentRangeFormatting(const DocumentRangeFormattingParams &, diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -11,6 +11,7 @@ #include "CodeComplete.h" #include "Diagnostics.h" #include "DraftStore.h" +#include "DumpAST.h" #include "GlobalCompilationDatabase.h" #include "Protocol.h" #include "SemanticHighlighting.h" @@ -21,6 +22,7 @@ #include "support/Context.h" #include "support/MemoryTree.h" #include "support/Trace.h" +#include "clang/AST/ASTContext.h" #include "clang/Basic/Version.h" #include "clang/Tooling/Core/Replacement.h" #include "llvm/ADT/ArrayRef.h" @@ -610,6 +612,7 @@ {"documentSymbolProvider", true}, {"workspaceSymbolProvider", true}, {"referencesProvider", true}, + {"astProvider", true}, {"executeCommandProvider", llvm::json::Object{ {"commands", @@ -1383,6 +1386,11 @@ }); } +void ClangdLSPServer::onAST(const ASTParams &Params, + Callback> CB) { + Server->getAST(Params.textDocument.uri.file(), Params.range, std::move(CB)); +} + ClangdLSPServer::ClangdLSPServer(class Transport &Transp, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts) @@ -1412,6 +1420,7 @@ MsgHandler->bind("workspace/executeCommand", &ClangdLSPServer::onCommand); MsgHandler->bind("textDocument/documentHighlight", &ClangdLSPServer::onDocumentHighlight); MsgHandler->bind("workspace/symbol", &ClangdLSPServer::onWorkspaceSymbol); + MsgHandler->bind("textDocument/ast", &ClangdLSPServer::onAST); MsgHandler->bind("textDocument/didOpen", &ClangdLSPServer::onDocumentDidOpen); MsgHandler->bind("textDocument/didClose", &ClangdLSPServer::onDocumentDidClose); MsgHandler->bind("textDocument/didChange", &ClangdLSPServer::onDocumentDidChange); diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -319,6 +319,9 @@ void semanticHighlights(PathRef File, Callback>); + /// Describe the AST subtree for a piece of code. + void getAST(PathRef File, Range, Callback>); + /// Runs an arbitrary action that has access to the AST of the specified file. /// The action will execute on one of ClangdServer's internal threads. /// The AST is only valid for the duration of the callback. diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -9,6 +9,7 @@ #include "ClangdServer.h" #include "CodeComplete.h" #include "Config.h" +#include "DumpAST.h" #include "FindSymbols.h" #include "Format.h" #include "HeaderSourceSwitch.h" @@ -784,6 +785,38 @@ TUScheduler::InvalidateOnUpdate); } +void ClangdServer::getAST(PathRef File, Range R, + Callback> CB) { + auto Action = + [R, CB(std::move(CB))](llvm::Expected Inputs) mutable { + if (!Inputs) + return CB(Inputs.takeError()); + unsigned Start, End; + if (auto O = positionToOffset(Inputs->Inputs.Contents, R.start)) + Start = *O; + else + return CB(O.takeError()); + if (auto O = positionToOffset(Inputs->Inputs.Contents, R.end)) + End = *O; + else + return CB(O.takeError()); + + bool Success = SelectionTree::createEach( + Inputs->AST.getASTContext(), Inputs->AST.getTokens(), Start, End, + [&](SelectionTree T) { + if (const SelectionTree::Node *N = T.commonAncestor()) { + CB(dumpAST(N->ASTNode, Inputs->AST.getTokens(), + Inputs->AST.getASTContext())); + return true; + } + return false; + }); + if (!Success) + CB(llvm::None); + }; + WorkScheduler.runWithAST("GetAST", File, std::move(Action)); +} + void ClangdServer::customAction(PathRef File, llvm::StringRef Name, Callback Action) { WorkScheduler.runWithAST(Name, File, std::move(Action)); diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -1576,6 +1576,45 @@ }; llvm::json::Value toJSON(const FoldingRange &Range); +/// Payload for textDocument/ast request. +/// This request is a clangd extension. +struct ASTParams { + /// The text document. + TextDocumentIdentifier textDocument; + + /// The position of the node to be dumped. + /// The highest-level node that entirely contains the range will be returned. + Range range; +}; +bool fromJSON(const llvm::json::Value &, ASTParams &, llvm::json::Path); + +/// Simplified description of a clang AST node. +/// This is clangd's internal representation of C++ code. +struct ASTNode { + /// The general kind of node, such as "expression" + /// Corresponds to the base AST node type such as Expr. + std::string role; + /// The specific kind of node this is, such as "BinaryOperator". + /// This is usually a concrete node class (with Expr etc suffix dropped). + /// When there's no hierarchy (e.g. TemplateName), the variant (NameKind). + std::string kind; + /// Brief additional information, such as "||" for the particular operator. + /// The information included depends on the node kind, and may be empty. + std::string detail; + /// A one-line dump of detailed information about the node. + /// This includes role/kind/description information, but is rather cryptic. + /// It is similar to the output from `clang -Xclang -ast-dump`. + /// May be empty for certain types of nodes. + std::string arcana; + /// The range of the original source file covered by this node. + /// May be missing for implicit nodes, or those created by macro expansion. + llvm::Optional range; + /// Nodes nested within this one, such as the operands of a BinaryOperator. + std::vector children; +}; +llvm::json::Value toJSON(const ASTNode &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const ASTNode &); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -1300,5 +1300,41 @@ return Result; } +bool fromJSON(const llvm::json::Value &Params, ASTParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + return O && O.map("textDocument", R.textDocument) && O.map("range", R.range); +} + +llvm::json::Value toJSON(const ASTNode &N) { + llvm::json::Object Result{ + {"role", N.role}, + {"kind", N.kind}, + }; + if (!N.children.empty()) + Result["children"] = N.children; + if (!N.detail.empty()) + Result["detail"] = N.detail; + if (!N.arcana.empty()) + Result["arcana"] = N.arcana; + if (N.range) + Result["range"] = *N.range; + return Result; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const ASTNode &Root) { + std::function Print = [&](const ASTNode &N, + unsigned Level) { + OS.indent(2 * Level) << N.role << ": " << N.kind; + if (!N.detail.empty()) + OS << " - " << N.detail; + OS << "\n"; + for (const ASTNode &C : N.children) + Print(C, Level + 1); + }; + Print(Root, 0); + return OS; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test --- a/clang-tools-extra/clangd/test/initialize-params.test +++ b/clang-tools-extra/clangd/test/initialize-params.test @@ -5,6 +5,7 @@ # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": { # CHECK-NEXT: "capabilities": { +# CHECK-NEXT: "astProvider": true, # CHECK-NEXT: "codeActionProvider": true, # CHECK-NEXT: "completionProvider": { # CHECK-NEXT: "allCommitCharacters": [ diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -51,6 +51,7 @@ DexTests.cpp DiagnosticsTests.cpp DraftStoreTests.cpp + DumpASTTests.cpp ExpectedTypeTest.cpp FileDistanceTests.cpp FileIndexTests.cpp