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 @@ -344,7 +344,8 @@ Callback>); /// Describe the AST subtree for a piece of code. - void getAST(PathRef File, Range R, Callback> CB); + void getAST(PathRef File, llvm::Optional R, + Callback> CB); /// 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. 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 @@ -884,22 +884,29 @@ Transient); } -void ClangdServer::getAST(PathRef File, Range R, +void ClangdServer::getAST(PathRef File, llvm::Optional R, Callback> CB) { auto Action = [R, CB(std::move(CB))](llvm::Expected Inputs) mutable { if (!Inputs) return CB(Inputs.takeError()); + if (!R) { + // It's safe to pass in the TU, as dumpAST() does not + // deserialize the preamble. + auto Node = DynTypedNode::create( + *Inputs->AST.getASTContext().getTranslationUnitDecl()); + return CB(dumpAST(Node, Inputs->AST.getTokens(), + Inputs->AST.getASTContext())); + } unsigned Start, End; - if (auto Offset = positionToOffset(Inputs->Inputs.Contents, R.start)) + if (auto Offset = positionToOffset(Inputs->Inputs.Contents, R->start)) Start = *Offset; else return CB(Offset.takeError()); - if (auto Offset = positionToOffset(Inputs->Inputs.Contents, R.end)) + if (auto Offset = positionToOffset(Inputs->Inputs.Contents, R->end)) End = *Offset; else return CB(Offset.takeError()); - bool Success = SelectionTree::createEach( Inputs->AST.getASTContext(), Inputs->AST.getTokens(), Start, End, [&](SelectionTree T) { diff --git a/clang-tools-extra/clangd/DumpAST.h b/clang-tools-extra/clangd/DumpAST.h --- a/clang-tools-extra/clangd/DumpAST.h +++ b/clang-tools-extra/clangd/DumpAST.h @@ -39,6 +39,8 @@ } // namespace syntax namespace clangd { +// Note: It's safe for the node to be a TranslationUnitDecl, as this function +// does not deserialize the preamble. ASTNode dumpAST(const DynTypedNode &, const syntax::TokenBuffer &Tokens, const ASTContext &); diff --git a/clang-tools-extra/clangd/DumpAST.cpp b/clang-tools-extra/clangd/DumpAST.cpp --- a/clang-tools-extra/clangd/DumpAST.cpp +++ b/clang-tools-extra/clangd/DumpAST.cpp @@ -335,7 +335,13 @@ // Override traversal to record the nodes we care about. // Generally, these are nodes with position information (TypeLoc, not Type). + bool TraverseDecl(Decl *D) { + if (const auto *TU = dyn_cast(D)) { + return traverseNode("declaration", TU, [&] { + Base::TraverseAST(const_cast(Ctx)); + }); + } return !D || isInjectedClassName(D) || traverseNode("declaration", D, [&] { Base::TraverseDecl(D); }); } 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 @@ -1725,7 +1725,8 @@ /// The position of the node to be dumped. /// The highest-level node that entirely contains the range will be returned. - Range range; + /// If no range is given, the root translation unit node will be returned. + llvm::Optional range; }; bool fromJSON(const llvm::json::Value &, ASTParams &, llvm::json::Path); diff --git a/clang-tools-extra/clangd/test/ast-no-range.test b/clang-tools-extra/clangd/test/ast-no-range.test new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/test/ast-no-range.test @@ -0,0 +1,53 @@ +# 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":"test:///simple.cpp","languageId":"cpp","version":1,"text":"int x;"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/ast","params":{"textDocument":{"uri":"test:///simple.cpp"}}} +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": { +# CHECK-NEXT: "arcana": "{{TranslationUnitDecl.*}}" +# CHECK-NEXT: "children": [ +# CHECK-NEXT: { +# CHECK: "arcana": "VarDecl {{.*}} x 'int'", +# CHECK-NEXT: "children": [ +# CHECK-NEXT: { +# CHECK-NEXT: "arcana": "QualType {{.*}} 'int' ", +# CHECK-NEXT: "detail": "int", +# CHECK-NEXT: "kind": "Builtin", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 3, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "role": "type" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "detail": "x", +# CHECK-NEXT: "kind": "Var", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 5, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "role": "declaration" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "kind": "TranslationUnit", +# CHECK-NEXT: "role": "declaration" +# CHECK-NEXT: } +--- +{"jsonrpc":"2.0","id":2,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} diff --git a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp --- a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp @@ -16,8 +16,12 @@ namespace clang { namespace clangd { namespace { +using testing::Contains; +using testing::Not; using testing::SizeIs; +MATCHER_P(WithDetail, str, "") { return arg.detail == str; } + TEST(DumpASTTests, BasicInfo) { std::pair Cases[] = { {R"cpp( @@ -157,6 +161,20 @@ EXPECT_EQ(Node.children.front().range, Case.range("type")); } +TEST(DumpASTTests, NoRange) { + auto TU = TestTU::withHeaderCode("void funcFromHeader();"); + TU.Code = "int varFromSource;"; + ParsedAST AST = TU.build(); + auto Node = dumpAST( + DynTypedNode::create(*AST.getASTContext().getTranslationUnitDecl()), + AST.getTokens(), AST.getASTContext()); + ASSERT_THAT(Node.children, Contains(WithDetail("varFromSource"))); + ASSERT_THAT(Node.children, Not(Contains(WithDetail("funcFromHeader")))); + EXPECT_THAT(Node.arcana, testing::StartsWith("TranslationUnitDecl ")); + ASSERT_FALSE(Node.range.hasValue()) + << "Expected no range for translation unit"; +} + TEST(DumpASTTests, Arcana) { ParsedAST AST = TestTU::withCode("int x;").build(); auto Node = dumpAST(DynTypedNode::create(findDecl(AST, "x")), AST.getTokens(),