1 //===--- DumpAST.cpp ---------------------------------------------*- C++-*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 // Defines a few tweaks that expose AST and related information.
9 // Some of these are fairly clang-specific and hidden (e.g. textual AST dumps).
10 // Others are more generally useful (class layout) and are exposed by default.
11 //===----------------------------------------------------------------------===//
12 #include "XRefs.h"
13 #include "refactor/Tweak.h"
14 #include "clang/AST/ASTTypeTraits.h"
15 #include "clang/AST/Type.h"
16 #include "llvm/Support/FormatVariadic.h"
17 #include "llvm/Support/ScopedPrinter.h"
18 #include "llvm/Support/raw_ostream.h"
19
20 namespace clang {
21 namespace clangd {
22 namespace {
23
24 /// Dumps the AST of the selected node.
25 /// Input:
26 /// fcall("foo");
27 /// ^^^^^
28 /// Message:
29 /// CallExpr
30 /// |-DeclRefExpr fcall
31 /// `-StringLiteral "foo"
32 class DumpAST : public Tweak {
33 public:
34 const char *id() const override final;
35
prepare(const Selection & Inputs)36 bool prepare(const Selection &Inputs) override {
37 for (auto N = Inputs.ASTSelection.commonAncestor(); N && !Node;
38 N = N->Parent)
39 if (dumpable(N->ASTNode))
40 Node = N->ASTNode;
41 return Node.hasValue();
42 }
43 Expected<Effect> apply(const Selection &Inputs) override;
title() const44 std::string title() const override {
45 return std::string(
46 llvm::formatv("Dump {0} AST", Node->getNodeKind().asStringRef()));
47 }
kind() const48 llvm::StringLiteral kind() const override { return CodeAction::INFO_KIND; }
hidden() const49 bool hidden() const override { return true; }
50
51 private:
dumpable(const ast_type_traits::DynTypedNode & N)52 static bool dumpable(const ast_type_traits::DynTypedNode &N) {
53 // Sadly not all node types can be dumped, and there's no API to check.
54 // See DynTypedNode::dump().
55 return N.get<Decl>() || N.get<Stmt>() || N.get<Type>();
56 }
57
58 llvm::Optional<ast_type_traits::DynTypedNode> Node;
59 };
REGISTER_TWEAK(DumpAST)60 REGISTER_TWEAK(DumpAST)
61
62 llvm::Expected<Tweak::Effect> DumpAST::apply(const Selection &Inputs) {
63 std::string Str;
64 llvm::raw_string_ostream OS(Str);
65 Node->dump(OS, Inputs.AST->getASTContext());
66 return Effect::showMessage(std::move(OS.str()));
67 }
68
69 /// Dumps the SelectionTree.
70 /// Input:
71 /// int fcall(int);
72 /// void foo() {
73 /// fcall(2 + 2);
74 /// ^^^^^
75 /// }
76 /// Message:
77 /// TranslationUnitDecl
78 /// FunctionDecl void foo()
79 /// CompoundStmt {}
80 /// .CallExpr fcall(2 + 2)
81 /// ImplicitCastExpr fcall
82 /// .DeclRefExpr fcall
83 /// BinaryOperator 2 + 2
84 /// *IntegerLiteral 2
85 class ShowSelectionTree : public Tweak {
86 public:
87 const char *id() const override final;
88
prepare(const Selection & Inputs)89 bool prepare(const Selection &Inputs) override { return true; }
apply(const Selection & Inputs)90 Expected<Effect> apply(const Selection &Inputs) override {
91 return Effect::showMessage(llvm::to_string(Inputs.ASTSelection));
92 }
title() const93 std::string title() const override { return "Show selection tree"; }
kind() const94 llvm::StringLiteral kind() const override { return CodeAction::INFO_KIND; }
hidden() const95 bool hidden() const override { return true; }
96 };
97 REGISTER_TWEAK(ShowSelectionTree)
98
99 /// Dumps the symbol under the cursor.
100 /// Inputs:
101 /// void foo();
102 /// ^^^
103 /// Message:
104 /// foo -
105 /// {"containerName":null,"id":"CA2EBE44A1D76D2A","name":"foo","usr":"c:@F@foo#"}
106 class DumpSymbol : public Tweak {
107 const char *id() const override final;
prepare(const Selection & Inputs)108 bool prepare(const Selection &Inputs) override { return true; }
apply(const Selection & Inputs)109 Expected<Effect> apply(const Selection &Inputs) override {
110 std::string Storage;
111 llvm::raw_string_ostream Out(Storage);
112
113 for (auto &Sym : getSymbolInfo(
114 *Inputs.AST, sourceLocToPosition(Inputs.AST->getSourceManager(),
115 Inputs.Cursor)))
116 Out << Sym;
117 return Effect::showMessage(Out.str());
118 }
title() const119 std::string title() const override { return "Dump symbol under the cursor"; }
kind() const120 llvm::StringLiteral kind() const override { return CodeAction::INFO_KIND; }
hidden() const121 bool hidden() const override { return true; }
122 };
123 REGISTER_TWEAK(DumpSymbol)
124
125 /// Shows the layout of the RecordDecl under the cursor.
126 /// Input:
127 /// struct X { int foo; };
128 /// ^^^^^^^^
129 /// Message:
130 /// 0 | struct S
131 /// 0 | int foo
132 /// | [sizeof=4, dsize=4, align=4,
133 /// | nvsize=4, nvalign=4]
134 class DumpRecordLayout : public Tweak {
135 public:
136 const char *id() const override final;
137
prepare(const Selection & Inputs)138 bool prepare(const Selection &Inputs) override {
139 if (auto *Node = Inputs.ASTSelection.commonAncestor())
140 if (auto *D = Node->ASTNode.get<Decl>())
141 Record = dyn_cast<RecordDecl>(D);
142 return Record && Record->isThisDeclarationADefinition() &&
143 !Record->isDependentType();
144 }
apply(const Selection & Inputs)145 Expected<Effect> apply(const Selection &Inputs) override {
146 std::string Str;
147 llvm::raw_string_ostream OS(Str);
148 Inputs.AST->getASTContext().DumpRecordLayout(Record, OS);
149 return Effect::showMessage(std::move(OS.str()));
150 }
title() const151 std::string title() const override {
152 return std::string(llvm::formatv(
153 "Show {0} layout",
154 TypeWithKeyword::getTagTypeKindName(Record->getTagKind())));
155 }
kind() const156 llvm::StringLiteral kind() const override { return CodeAction::INFO_KIND; }
157 // FIXME: this is interesting to most users. However:
158 // - triggering is too broad (e.g. triggers on comments within a class)
159 // - showMessage has inconsistent UX (e.g. newlines are stripped in VSCode)
160 // - the output itself is a bit hard to decipher.
hidden() const161 bool hidden() const override { return true; }
162
163 private:
164 const RecordDecl *Record = nullptr;
165 };
166 REGISTER_TWEAK(DumpRecordLayout)
167
168 } // namespace
169 } // namespace clangd
170 } // namespace clang
171