xref: /llvm-project/clang-tools-extra/clangd/refactor/tweaks/DumpAST.cpp (revision f71ffd3b735b4d6ae3c12be1806cdd6205b3b378)
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 #include <optional>
20 
21 namespace clang {
22 namespace clangd {
23 namespace {
24 
25 /// Dumps the AST of the selected node.
26 /// Input:
27 ///   fcall("foo");
28 ///   ^^^^^
29 /// Message:
30 ///   CallExpr
31 ///   |-DeclRefExpr fcall
32 ///   `-StringLiteral "foo"
33 class DumpAST : public Tweak {
34 public:
35   const char *id() const final;
36 
prepare(const Selection & Inputs)37   bool prepare(const Selection &Inputs) override {
38     for (auto *N = Inputs.ASTSelection.commonAncestor(); N && !Node;
39          N = N->Parent)
40       if (dumpable(N->ASTNode))
41         Node = N->ASTNode;
42     return Node.has_value();
43   }
44   Expected<Effect> apply(const Selection &Inputs) override;
title() const45   std::string title() const override {
46     return std::string(
47         llvm::formatv("Dump {0} AST", Node->getNodeKind().asStringRef()));
48   }
kind() const49   llvm::StringLiteral kind() const override { return CodeAction::INFO_KIND; }
hidden() const50   bool hidden() const override { return true; }
51 
52 private:
dumpable(const DynTypedNode & N)53   static bool dumpable(const DynTypedNode &N) {
54     // Sadly not all node types can be dumped, and there's no API to check.
55     // See DynTypedNode::dump().
56     return N.get<Decl>() || N.get<Stmt>() || N.get<Type>();
57   }
58 
59   std::optional<DynTypedNode> Node;
60 };
REGISTER_TWEAK(DumpAST)61 REGISTER_TWEAK(DumpAST)
62 
63 llvm::Expected<Tweak::Effect> DumpAST::apply(const Selection &Inputs) {
64   std::string Str;
65   llvm::raw_string_ostream OS(Str);
66   Node->dump(OS, Inputs.AST->getASTContext());
67   return Effect::showMessage(std::move(OS.str()));
68 }
69 
70 /// Dumps the SelectionTree.
71 /// Input:
72 /// int fcall(int);
73 /// void foo() {
74 ///   fcall(2 + 2);
75 ///     ^^^^^
76 /// }
77 /// Message:
78 /// TranslationUnitDecl
79 ///   FunctionDecl void foo()
80 ///     CompoundStmt {}
81 ///      .CallExpr fcall(2 + 2)
82 ///        ImplicitCastExpr fcall
83 ///         .DeclRefExpr fcall
84 ///        BinaryOperator 2 + 2
85 ///          *IntegerLiteral 2
86 class ShowSelectionTree : public Tweak {
87 public:
88   const char *id() const final;
89 
prepare(const Selection & Inputs)90   bool prepare(const Selection &Inputs) override { return true; }
apply(const Selection & Inputs)91   Expected<Effect> apply(const Selection &Inputs) override {
92     return Effect::showMessage(llvm::to_string(Inputs.ASTSelection));
93   }
title() const94   std::string title() const override { return "Show selection tree"; }
kind() const95   llvm::StringLiteral kind() const override { return CodeAction::INFO_KIND; }
hidden() const96   bool hidden() const override { return true; }
97 };
98 REGISTER_TWEAK(ShowSelectionTree)
99 
100 /// Dumps the symbol under the cursor.
101 /// Inputs:
102 /// void foo();
103 ///      ^^^
104 /// Message:
105 ///  foo -
106 ///  {"containerName":null,"id":"CA2EBE44A1D76D2A","name":"foo","usr":"c:@F@foo#"}
107 class DumpSymbol : public Tweak {
108   const char *id() const final;
prepare(const Selection & Inputs)109   bool prepare(const Selection &Inputs) override { return true; }
apply(const Selection & Inputs)110   Expected<Effect> apply(const Selection &Inputs) override {
111     std::string Storage;
112     llvm::raw_string_ostream Out(Storage);
113 
114     for (auto &Sym : getSymbolInfo(
115              *Inputs.AST, sourceLocToPosition(Inputs.AST->getSourceManager(),
116                                               Inputs.Cursor)))
117       Out << Sym;
118     return Effect::showMessage(Out.str());
119   }
title() const120   std::string title() const override { return "Dump symbol under the cursor"; }
kind() const121   llvm::StringLiteral kind() const override { return CodeAction::INFO_KIND; }
hidden() const122   bool hidden() const override { return true; }
123 };
124 REGISTER_TWEAK(DumpSymbol)
125 
126 /// Shows the layout of the RecordDecl under the cursor.
127 /// Input:
128 /// struct X { int foo; };
129 /// ^^^^^^^^
130 /// Message:
131 ///        0 | struct S
132 ///        0 |   int foo
133 ///          | [sizeof=4, dsize=4, align=4,
134 ///          |  nvsize=4, nvalign=4]
135 class DumpRecordLayout : public Tweak {
136 public:
137   const char *id() const final;
138 
prepare(const Selection & Inputs)139   bool prepare(const Selection &Inputs) override {
140     if (auto *Node = Inputs.ASTSelection.commonAncestor())
141       if (auto *D = Node->ASTNode.get<Decl>())
142         Record = dyn_cast<RecordDecl>(D);
143     return Record && Record->isThisDeclarationADefinition() &&
144            !Record->isDependentType();
145   }
apply(const Selection & Inputs)146   Expected<Effect> apply(const Selection &Inputs) override {
147     std::string Str;
148     llvm::raw_string_ostream OS(Str);
149     Inputs.AST->getASTContext().DumpRecordLayout(Record, OS);
150     return Effect::showMessage(std::move(OS.str()));
151   }
title() const152   std::string title() const override {
153     return std::string(llvm::formatv(
154         "Show {0} layout",
155         TypeWithKeyword::getTagTypeKindName(Record->getTagKind())));
156   }
kind() const157   llvm::StringLiteral kind() const override { return CodeAction::INFO_KIND; }
158   // FIXME: this is interesting to most users. However:
159   //  - triggering is too broad (e.g. triggers on comments within a class)
160   //  - showMessage has inconsistent UX (e.g. newlines are stripped in VSCode)
161   //  - the output itself is a bit hard to decipher.
hidden() const162   bool hidden() const override { return true; }
163 
164 private:
165   const RecordDecl *Record = nullptr;
166 };
167 REGISTER_TWEAK(DumpRecordLayout)
168 
169 } // namespace
170 } // namespace clangd
171 } // namespace clang
172