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 | |
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; |
45 | std::string title() const override { |
46 | return std::string( |
47 | llvm::formatv("Dump {0} AST" , Node->getNodeKind().asStringRef())); |
48 | } |
49 | llvm::StringLiteral kind() const override { return CodeAction::INFO_KIND; } |
50 | bool hidden() const override { return true; } |
51 | |
52 | private: |
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 | }; |
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(S: 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 | |
90 | bool prepare(const Selection &Inputs) override { return true; } |
91 | Expected<Effect> apply(const Selection &Inputs) override { |
92 | return Effect::showMessage(S: llvm::to_string(Value: Inputs.ASTSelection)); |
93 | } |
94 | std::string title() const override { return "Show selection tree" ; } |
95 | llvm::StringLiteral kind() const override { return CodeAction::INFO_KIND; } |
96 | 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; |
109 | bool prepare(const Selection &Inputs) override { return true; } |
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 | AST&: *Inputs.AST, Pos: sourceLocToPosition(SM: Inputs.AST->getSourceManager(), |
116 | Loc: Inputs.Cursor))) |
117 | Out << Sym; |
118 | return Effect::showMessage(S: Out.str()); |
119 | } |
120 | std::string title() const override { return "Dump symbol under the cursor" ; } |
121 | llvm::StringLiteral kind() const override { return CodeAction::INFO_KIND; } |
122 | 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 | |
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 | } |
146 | Expected<Effect> apply(const Selection &Inputs) override { |
147 | std::string Str; |
148 | llvm::raw_string_ostream OS(Str); |
149 | Inputs.AST->getASTContext().DumpRecordLayout(RD: Record, OS); |
150 | return Effect::showMessage(S: std::move(OS.str())); |
151 | } |
152 | std::string title() const override { |
153 | return std::string(llvm::formatv( |
154 | "Show {0} layout" , |
155 | TypeWithKeyword::getTagTypeKindName(Kind: Record->getTagKind()))); |
156 | } |
157 | 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. |
162 | 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 | |