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