172 lines · cpp
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-exception6//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 21namespace clang {22namespace clangd {23namespace {24 25/// Dumps the AST of the selected node.26/// Input:27/// fcall("foo");28/// ^^^^^29/// Message:30/// CallExpr31/// |-DeclRefExpr fcall32/// `-StringLiteral "foo"33class DumpAST : public Tweak {34public: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 52private: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};61REGISTER_TWEAK(DumpAST)62 63llvm::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/// TranslationUnitDecl79/// FunctionDecl void foo()80/// CompoundStmt {}81/// .CallExpr fcall(2 + 2)82/// ImplicitCastExpr fcall83/// .DeclRefExpr fcall84/// BinaryOperator 2 + 285/// *IntegerLiteral 286class ShowSelectionTree : public Tweak {87public: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(llvm::to_string(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};98REGISTER_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#"}107class 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 *Inputs.AST, sourceLocToPosition(Inputs.AST->getSourceManager(),116 Inputs.Cursor)))117 Out << Sym;118 return Effect::showMessage(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};124REGISTER_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 S132/// 0 | int foo133/// | [sizeof=4, dsize=4, align=4,134/// | nvsize=4, nvalign=4]135class DumpRecordLayout : public Tweak {136public: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(Record, OS);150 return Effect::showMessage(std::move(OS.str()));151 }152 std::string title() const override {153 return std::string(llvm::formatv(154 "Show {0} layout",155 TypeWithKeyword::getTagTypeKindName(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 164private:165 const RecordDecl *Record = nullptr;166};167REGISTER_TWEAK(DumpRecordLayout)168 169} // namespace170} // namespace clangd171} // namespace clang172