Index: clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp =================================================================== --- clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp +++ clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp @@ -13,6 +13,7 @@ #include "SourceCode.h" #include "Trace.h" #include "URI.h" +#include "refactor/Tweak.h" #include "clang/Tooling/Core/Replacement.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/ScopeExit.h" @@ -31,7 +32,14 @@ Range Selection) { CodeAction CA; CA.title = T.Title; - CA.kind = CodeAction::REFACTOR_KIND; + switch (T.Intent) { + case Tweak::Refactor: + CA.kind = CodeAction::REFACTOR_KIND; + break; + case Tweak::Info: + CA.kind = CodeAction::INFO_KIND; + break; + } // This tweak may have an expensive second stage, we only run it if the user // actually chooses it in the UI. We reply with a command that would run the // corresponding tweak. @@ -481,18 +489,25 @@ llvm::inconvertibleErrorCode(), "trying to apply a code action for a non-added file")); - auto Action = [ApplyEdit](decltype(Reply) Reply, URIForFile File, - std::string Code, - llvm::Expected> R) { + auto Action = [this, ApplyEdit](decltype(Reply) Reply, URIForFile File, + std::string Code, + llvm::Expected R) { if (!R) return Reply(R.takeError()); - WorkspaceEdit WE; - WE.changes.emplace(); - (*WE.changes)[File.uri()] = std::move(*R); - - Reply("Fix applied."); - ApplyEdit(std::move(WE)); + if (R->ApplyEdit) { + WorkspaceEdit WE; + WE.changes.emplace(); + (*WE.changes)[File.uri()] = replacementsToEdits(Code, *R->ApplyEdit); + ApplyEdit(std::move(WE)); + } + if (R->ShowMessage) { + ShowMessageParams Msg; + Msg.message = *R->ShowMessage; + Msg.type = MessageType::Info; + notify("window/showMessage", Msg); + } + Reply("Tweak applied."); }; Server->applyTweak(Params.tweakArgs->file.file(), Params.tweakArgs->selection, Params.tweakArgs->tweakID, Index: clang-tools-extra/trunk/clangd/ClangdServer.h =================================================================== --- clang-tools-extra/trunk/clangd/ClangdServer.h +++ clang-tools-extra/trunk/clangd/ClangdServer.h @@ -123,6 +123,10 @@ std::chrono::milliseconds(500); bool SuggestMissingIncludes = false; + + /// Enable hidden features mostly useful to clangd developers. + /// e.g. tweaks to dump the AST. + bool HiddenFeatures = false; }; // Sensible default options for use in tests. // Features like indexing must be enabled if desired. @@ -227,6 +231,7 @@ struct TweakRef { std::string ID; /// ID to pass for applyTweak. std::string Title; /// A single-line message to show in the UI. + Tweak::Intent Intent; }; /// Enumerate the code tweaks available to the user at a specified point. void enumerateTweaks(PathRef File, Range Sel, @@ -234,7 +239,7 @@ /// Apply the code tweak with a specified \p ID. void applyTweak(PathRef File, Range Sel, StringRef ID, - Callback> CB); + Callback CB); /// Only for testing purposes. /// Waits until all requests to worker thread are finished and dumps AST for @@ -291,6 +296,7 @@ // If this is true, suggest include insertion fixes for diagnostic errors that // can be caused by missing includes (e.g. member access in incomplete type). bool SuggestMissingIncludes = false; + bool EnableHiddenFeatures = false; // GUARDED_BY(CachedCompletionFuzzyFindRequestMutex) llvm::StringMap> Index: clang-tools-extra/trunk/clangd/ClangdServer.cpp =================================================================== --- clang-tools-extra/trunk/clangd/ClangdServer.cpp +++ clang-tools-extra/trunk/clangd/ClangdServer.cpp @@ -95,6 +95,7 @@ : nullptr), GetClangTidyOptions(Opts.GetClangTidyOptions), SuggestMissingIncludes(Opts.SuggestMissingIncludes), + EnableHiddenFeatures(Opts.HiddenFeatures), WorkspaceRoot(Opts.WorkspaceRoot), // Pass a callback into `WorkScheduler` to extract symbols from a newly // parsed file and rebuild the file index synchronously each time an AST @@ -303,16 +304,19 @@ void ClangdServer::enumerateTweaks(PathRef File, Range Sel, Callback> CB) { - auto Action = [Sel](decltype(CB) CB, std::string File, - Expected InpAST) { + auto Action = [this, Sel](decltype(CB) CB, std::string File, + Expected InpAST) { if (!InpAST) return CB(InpAST.takeError()); auto Selection = tweakSelection(Sel, *InpAST); if (!Selection) return CB(Selection.takeError()); std::vector Res; - for (auto &T : prepareTweaks(*Selection)) - Res.push_back({T->id(), T->title()}); + for (auto &T : prepareTweaks(*Selection)) { + if (T->hidden() && !EnableHiddenFeatures) + continue; + Res.push_back({T->id(), T->title(), T->intent()}); + } CB(std::move(Res)); }; @@ -321,7 +325,7 @@ } void ClangdServer::applyTweak(PathRef File, Range Sel, StringRef TweakID, - Callback> CB) { + Callback CB) { auto Action = [Sel](decltype(CB) CB, std::string File, std::string TweakID, Expected InpAST) { if (!InpAST) @@ -332,17 +336,19 @@ auto A = prepareTweak(TweakID, *Selection); if (!A) return CB(A.takeError()); - auto Raw = (*A)->apply(*Selection); - if (!Raw) - return CB(Raw.takeError()); - // FIXME: this function has I/O operations (find .clang-format file), figure - // out a way to cache the format style. - auto Style = getFormatStyleForFile(File, InpAST->Inputs.Contents, - InpAST->Inputs.FS.get()); - auto Formatted = cleanupAndFormat(InpAST->Inputs.Contents, *Raw, Style); - if (!Formatted) - return CB(Formatted.takeError()); - return CB(replacementsToEdits(InpAST->Inputs.Contents, *Formatted)); + auto Effect = (*A)->apply(*Selection); + if (!Effect) + return CB(Effect.takeError()); + if (Effect->ApplyEdit) { + // FIXME: this function has I/O operations (find .clang-format file), + // figure out a way to cache the format style. + auto Style = getFormatStyleForFile(File, InpAST->Inputs.Contents, + InpAST->Inputs.FS.get()); + if (auto Formatted = cleanupAndFormat(InpAST->Inputs.Contents, + *Effect->ApplyEdit, Style)) + Effect->ApplyEdit = std::move(*Formatted); + } + return CB(std::move(*Effect)); }; WorkScheduler.runWithAST( "ApplyTweak", File, Index: clang-tools-extra/trunk/clangd/Protocol.h =================================================================== --- clang-tools-extra/trunk/clangd/Protocol.h +++ clang-tools-extra/trunk/clangd/Protocol.h @@ -483,6 +483,28 @@ }; bool fromJSON(const llvm::json::Value &, InitializeParams &); +enum class MessageType { + /// An error message. + Error = 1, + /// A warning message. + Warning = 1, + /// An information message. + Info = 1, + /// A log message. + Log = 1, +}; +llvm::json::Value toJSON(const MessageType &); + +/// The show message notification is sent from a server to a client to ask the +/// client to display a particular message in the user interface. +struct ShowMessageParams { + /// The message type. + MessageType type = MessageType::Info; + /// The actual message. + std::string message; +}; +llvm::json::Value toJSON(const ShowMessageParams &); + struct DidOpenTextDocumentParams { /// The document that was opened. TextDocumentItem textDocument; @@ -740,6 +762,7 @@ llvm::Optional kind; const static llvm::StringLiteral QUICKFIX_KIND; const static llvm::StringLiteral REFACTOR_KIND; + const static llvm::StringLiteral INFO_KIND; /// The diagnostics that this code action resolves. llvm::Optional> diagnostics; Index: clang-tools-extra/trunk/clangd/Protocol.cpp =================================================================== --- clang-tools-extra/trunk/clangd/Protocol.cpp +++ clang-tools-extra/trunk/clangd/Protocol.cpp @@ -359,6 +359,14 @@ return true; } +llvm::json::Value toJSON(const MessageType &R) { + return static_cast(R); +} + +llvm::json::Value toJSON(const ShowMessageParams &R) { + return llvm::json::Object{{"type", R.type}, {"message", R.message}}; +} + bool fromJSON(const llvm::json::Value &Params, DidOpenTextDocumentParams &R) { llvm::json::ObjectMapper O(Params); return O && O.map("textDocument", R.textDocument); @@ -593,6 +601,7 @@ const llvm::StringLiteral CodeAction::QUICKFIX_KIND = "quickfix"; const llvm::StringLiteral CodeAction::REFACTOR_KIND = "refactor"; +const llvm::StringLiteral CodeAction::INFO_KIND = "info"; llvm::json::Value toJSON(const CodeAction &CA) { auto CodeAction = llvm::json::Object{{"title", CA.title}}; Index: clang-tools-extra/trunk/clangd/Selection.cpp =================================================================== --- clang-tools-extra/trunk/clangd/Selection.cpp +++ clang-tools-extra/trunk/clangd/Selection.cpp @@ -53,6 +53,9 @@ bool TraverseDecl(Decl *X) { if (X && isa(X)) return Base::TraverseDecl(X); // Already pushed by constructor. + // Base::TraverseDecl will suppress children, but not this node itself. + if (X && X->isImplicit()) + return true; return traverseNode(X, [&] { return Base::TraverseDecl(X); }); } bool TraverseTypeLoc(TypeLoc X) { Index: clang-tools-extra/trunk/clangd/refactor/Tweak.h =================================================================== --- clang-tools-extra/trunk/clangd/refactor/Tweak.h +++ clang-tools-extra/trunk/clangd/refactor/Tweak.h @@ -5,9 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -// Tweaks are small refactoring-like actions that run over the AST and produce -// the set of edits as a result. They are local, i.e. they should take the -// current editor context, e.g. the cursor position and selection into account. +// Tweaks are small actions that run over the AST and produce edits, messages +// etc as a result. They are local, i.e. they should take the current editor +// context, e.g. the cursor position and selection into account. // The actions are executed in two stages: // - Stage 1 should check whether the action is available in a current // context. It should be cheap and fast to compute as it is executed for all @@ -47,10 +47,36 @@ ParsedAST &AST; /// A location of the cursor in the editor. SourceLocation Cursor; - // The AST nodes that were selected. + /// The AST nodes that were selected. SelectionTree ASTSelection; // FIXME: provide a way to get sources and ASTs for other files. }; + + /// Output of a tweak. + enum Intent { + /// Apply changes that preserve the behavior of the code. + Refactor, + /// Provide information to the user. + Info, + }; + struct Effect { + /// A message to be displayed to the user. + llvm::Optional ShowMessage; + /// An edit to apply to the input file. + llvm::Optional ApplyEdit; + + static Effect applyEdit(tooling::Replacements R) { + Effect E; + E.ApplyEdit = std::move(R); + return E; + } + static Effect showMessage(StringRef S) { + Effect E; + E.ShowMessage = S; + return E; + } + }; + virtual ~Tweak() = default; /// A unique id of the action, it is always equal to the name of the class /// defining the Tweak. Definition is provided automatically by @@ -63,13 +89,19 @@ /// should be moved into 'apply'. /// Returns true iff the action is available and apply() can be called on it. virtual bool prepare(const Selection &Sel) = 0; - /// Run the second stage of the action that would produce the actual changes. + /// Run the second stage of the action that would produce the actual effect. /// EXPECTS: prepare() was called and returned true. - virtual Expected apply(const Selection &Sel) = 0; + virtual Expected apply(const Selection &Sel) = 0; + /// A one-line title of the action that should be shown to the users in the /// UI. /// EXPECTS: prepare() was called and returned true. virtual std::string title() const = 0; + /// Describes what kind of action this is. + /// EXPECTS: prepare() was called and returned true. + virtual Intent intent() const = 0; + /// Is this a 'hidden' tweak, which are off by default. + virtual bool hidden() const { return false; } }; // All tweaks must be registered in the .cpp file next to their definition. Index: clang-tools-extra/trunk/clangd/refactor/tweaks/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/clangd/refactor/tweaks/CMakeLists.txt +++ clang-tools-extra/trunk/clangd/refactor/tweaks/CMakeLists.txt @@ -12,6 +12,7 @@ # $ to a list of sources, see # clangd/tool/CMakeLists.txt for an example. add_clang_library(clangDaemonTweaks OBJECT + DumpAST.cpp RawStringLiteral.cpp SwapIfBranches.cpp Index: clang-tools-extra/trunk/clangd/refactor/tweaks/DumpAST.cpp =================================================================== --- clang-tools-extra/trunk/clangd/refactor/tweaks/DumpAST.cpp +++ clang-tools-extra/trunk/clangd/refactor/tweaks/DumpAST.cpp @@ -0,0 +1,139 @@ +//===--- DumpAST.cpp ---------------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Defines a few tweaks that expose AST and related information. +// Some of these are fairly clang-specific and hidden (e.g. textual AST dumps). +// Others are more generally useful (class layout) and are exposed by default. +//===----------------------------------------------------------------------===// +#include "refactor/Tweak.h" +#include "clang/AST/ASTTypeTraits.h" +#include "clang/AST/Type.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/ScopedPrinter.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace clangd { +namespace { + +/// Dumps the AST of the selected node. +/// Input: +/// fcall("foo"); +/// ^^^^^ +/// Message: +/// CallExpr +/// |-DeclRefExpr fcall +/// `-StringLiteral "foo" +class DumpAST : public Tweak { +public: + const char *id() const override final; + + bool prepare(const Selection &Inputs) override { + for (auto N = Inputs.ASTSelection.commonAncestor(); N && !Node; + N = N->Parent) + if (dumpable(N->ASTNode)) + Node = N->ASTNode; + return Node.hasValue(); + } + Expected apply(const Selection &Inputs) override; + std::string title() const override { + return llvm::formatv("Dump {0} AST", Node->getNodeKind().asStringRef()); + } + Intent intent() const override { return Info; } + bool hidden() const override { return true; } + +private: + static bool dumpable(const ast_type_traits::DynTypedNode &N) { + // Sadly not all node types can be dumped, and there's no API to check. + // See DynTypedNode::dump(). + return N.get() || N.get() || N.get(); + } + + llvm::Optional Node; +}; +REGISTER_TWEAK(DumpAST) + +llvm::Expected DumpAST::apply(const Selection &Inputs) { + std::string Str; + llvm::raw_string_ostream OS(Str); + Node->dump(OS, Inputs.AST.getASTContext().getSourceManager()); + return Effect::showMessage(std::move(OS.str())); +} + +/// Dumps the SelectionTree. +/// Input: +/// int fcall(int); +/// void foo() { +/// fcall(2 + 2); +/// ^^^^^ +/// } +/// Message: +/// TranslationUnitDecl +/// FunctionDecl void foo() +/// CompoundStmt {} +/// .CallExpr fcall(2 + 2) +/// ImplicitCastExpr fcall +/// .DeclRefExpr fcall +/// BinaryOperator 2 + 2 +/// *IntegerLiteral 2 +class ShowSelectionTree : public Tweak { +public: + const char *id() const override final; + + bool prepare(const Selection &Inputs) override { + return Inputs.ASTSelection.root() != nullptr; + } + Expected apply(const Selection &Inputs) override { + return Effect::showMessage(llvm::to_string(Inputs.ASTSelection)); + } + std::string title() const override { return "Show selection tree"; } + Intent intent() const override { return Info; } + bool hidden() const override { return true; } +}; +REGISTER_TWEAK(ShowSelectionTree); + +/// Shows the layout of the RecordDecl under the cursor. +/// Input: +/// struct X { int foo; }; +/// ^^^^^^^^ +/// Message: +/// 0 | struct S +/// 0 | int foo +/// | [sizeof=4, dsize=4, align=4, +/// | nvsize=4, nvalign=4] +class DumpRecordLayout : public Tweak { +public: + const char *id() const override final; + + bool prepare(const Selection &Inputs) override { + if (auto *Node = Inputs.ASTSelection.commonAncestor()) + if (auto *D = Node->ASTNode.get()) + Record = dyn_cast(D); + return Record && Record->isThisDeclarationADefinition() && + !Record->isDependentType(); + } + Expected apply(const Selection &Inputs) override { + std::string Str; + llvm::raw_string_ostream OS(Str); + Inputs.AST.getASTContext().DumpRecordLayout(Record, OS); + return Effect::showMessage(std::move(OS.str())); + } + std::string title() const override { + return llvm::formatv( + "Show {0} layout", + TypeWithKeyword::getTagTypeKindName(Record->getTagKind())); + } + Intent intent() const override { return Info; } + +private: + const RecordDecl *Record = nullptr; +}; +REGISTER_TWEAK(DumpRecordLayout); + +} // namespace +} // namespace clangd +} // namespace clang Index: clang-tools-extra/trunk/clangd/refactor/tweaks/RawStringLiteral.cpp =================================================================== --- clang-tools-extra/trunk/clangd/refactor/tweaks/RawStringLiteral.cpp +++ clang-tools-extra/trunk/clangd/refactor/tweaks/RawStringLiteral.cpp @@ -39,8 +39,9 @@ const char *id() const override final; bool prepare(const Selection &Inputs) override; - Expected apply(const Selection &Inputs) override; - std::string title() const override; + Expected apply(const Selection &Inputs) override; + std::string title() const override { return "Convert to raw string"; } + Intent intent() const override { return Refactor; } private: const clang::StringLiteral *Str = nullptr; @@ -86,16 +87,13 @@ needsRaw(Str->getBytes()) && canBeRaw(Str->getBytes()); } -Expected -RawStringLiteral::apply(const Selection &Inputs) { - return tooling::Replacements( +Expected RawStringLiteral::apply(const Selection &Inputs) { + return Effect::applyEdit(tooling::Replacements( tooling::Replacement(Inputs.AST.getSourceManager(), Str, ("R\"(" + Str->getBytes() + ")\"").str(), - Inputs.AST.getASTContext().getLangOpts())); + Inputs.AST.getASTContext().getLangOpts()))); } -std::string RawStringLiteral::title() const { return "Convert to raw string"; } - } // namespace } // namespace clangd } // namespace clang Index: clang-tools-extra/trunk/clangd/refactor/tweaks/SwapIfBranches.cpp =================================================================== --- clang-tools-extra/trunk/clangd/refactor/tweaks/SwapIfBranches.cpp +++ clang-tools-extra/trunk/clangd/refactor/tweaks/SwapIfBranches.cpp @@ -37,8 +37,9 @@ const char *id() const override final; bool prepare(const Selection &Inputs) override; - Expected apply(const Selection &Inputs) override; - std::string title() const override; + Expected apply(const Selection &Inputs) override; + std::string title() const override { return "Swap if branches"; } + Intent intent() const override { return Refactor; } private: const IfStmt *If = nullptr; @@ -60,7 +61,7 @@ dyn_cast_or_null(If->getElse()); } -Expected SwapIfBranches::apply(const Selection &Inputs) { +Expected SwapIfBranches::apply(const Selection &Inputs) { auto &Ctx = Inputs.AST.getASTContext(); auto &SrcMgr = Inputs.AST.getSourceManager(); @@ -89,11 +90,9 @@ ElseRng->getBegin(), ElseCode.size(), ThenCode))) return std::move(Err); - return Result; + return Effect::applyEdit(Result); } -std::string SwapIfBranches::title() const { return "Swap if branches"; } - } // namespace } // namespace clangd } // namespace clang Index: clang-tools-extra/trunk/clangd/tool/ClangdMain.cpp =================================================================== --- clang-tools-extra/trunk/clangd/tool/ClangdMain.cpp +++ clang-tools-extra/trunk/clangd/tool/ClangdMain.cpp @@ -263,6 +263,11 @@ "Always used text-based completion")), llvm::cl::init(CodeCompleteOptions().RunParser), llvm::cl::Hidden); +static llvm::cl::opt HiddenFeatures( + "hidden-features", + llvm::cl::desc("Enable hidden features mostly useful to clangd developers"), + llvm::cl::init(false), llvm::cl::Hidden); + namespace { /// \brief Supports a test URI scheme with relaxed constraints for lit tests. @@ -459,6 +464,7 @@ } Opts.StaticIndex = StaticIdx.get(); Opts.AsyncThreadsCount = WorkerThreadsCount; + Opts.HiddenFeatures = HiddenFeatures; clangd::CodeCompleteOptions CCOpts; CCOpts.IncludeIneligibleResults = IncludeIneligibleResults; Index: clang-tools-extra/trunk/clangd/unittests/SelectionTests.cpp =================================================================== --- clang-tools-extra/trunk/clangd/unittests/SelectionTests.cpp +++ clang-tools-extra/trunk/clangd/unittests/SelectionTests.cpp @@ -216,6 +216,16 @@ } } +// Regression test: this used to match the injected X, not the outer X. +TEST(SelectionTest, InjectedClassName) { + const char* Code = "struct ^X { int x; };"; + auto AST = TestTU::withCode(Annotations(Code).code()).build(); + auto T = makeSelectionTree(Code, AST); + ASSERT_EQ("CXXRecordDecl", nodeKind(T.commonAncestor())) << T; + auto *D = dyn_cast(T.commonAncestor()->ASTNode.get()); + EXPECT_FALSE(D->isInjectedClassName()); +} + TEST(SelectionTest, Selected) { // Selection with ^marks^. // Partially selected nodes marked with a [[range]]. Index: clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp =================================================================== --- clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp +++ clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp @@ -75,7 +75,8 @@ void checkNotAvailable(StringRef ID, llvm::StringRef Input) { return checkAvailable(ID, Input, /*Available=*/false); } -llvm::Expected apply(StringRef ID, llvm::StringRef Input) { + +llvm::Expected apply(StringRef ID, llvm::StringRef Input) { Annotations Code(Input); Range SelectionRng; if (Code.points().size() != 0) { @@ -97,15 +98,30 @@ auto T = prepareTweak(ID, S); if (!T) return T.takeError(); - auto Replacements = (*T)->apply(S); - if (!Replacements) - return Replacements.takeError(); - return applyAllReplacements(Code.code(), *Replacements); + return (*T)->apply(S); +} + +llvm::Expected applyEdit(StringRef ID, llvm::StringRef Input) { + auto Effect = apply(ID, Input); + if (!Effect) + return Effect.takeError(); + if (!Effect->ApplyEdit) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "No replacements"); + Annotations Code(Input); + return applyAllReplacements(Code.code(), *Effect->ApplyEdit); +} + +std::string getMessage(StringRef ID, llvm::StringRef Input) { + auto Effect = apply(ID, Input); + if (!Effect) + return "error: " + llvm::toString(Effect.takeError()); + return Effect->ShowMessage.getValueOr("no message produced!"); } void checkTransform(llvm::StringRef ID, llvm::StringRef Input, std::string Output) { - auto Result = apply(ID, Input); + auto Result = applyEdit(ID, Input); ASSERT_TRUE(bool(Result)) << llvm::toString(Result.takeError()) << Input; EXPECT_EQ(Output, std::string(*Result)) << Input; } @@ -216,6 +232,49 @@ checkTransform(ID, Input, Output); } +TEST(TweakTest, DumpAST) { + llvm::StringLiteral ID = "DumpAST"; + + checkAvailable(ID, "^int f^oo() { re^turn 2 ^+ 2; }"); + checkNotAvailable(ID, "/*c^omment*/ int foo() return 2 ^ + 2; }"); + + const char *Input = "int x = 2 ^+ 2;"; + const char *Output = R"(BinaryOperator.*'\+'.* +.*IntegerLiteral.*'int' 2.* +.*IntegerLiteral.*'int' 2.*)"; + EXPECT_THAT(getMessage(ID, Input), ::testing::MatchesRegex(Output)); +} + +TEST(TweakTest, ShowSelectionTree) { + llvm::StringLiteral ID = "ShowSelectionTree"; + + checkAvailable(ID, "^int f^oo() { re^turn 2 ^+ 2; }"); + checkNotAvailable(ID, "/*c^omment*/ int foo() return 2 ^ + 2; }"); + + const char *Input = "int fcall(int); int x = fca[[ll(2 +]]2);"; + const char *Output = R"(TranslationUnitDecl + VarDecl int x = fcall(2 + 2) + .CallExpr fcall(2 + 2) + ImplicitCastExpr fcall + .DeclRefExpr fcall + .BinaryOperator 2 + 2 + *IntegerLiteral 2 +)"; + EXPECT_EQ(Output, getMessage(ID, Input)); +} + +TEST(TweakTest, DumpRecordLayout) { + llvm::StringLiteral ID = "DumpRecordLayout"; + checkAvailable(ID, "^s^truct ^X ^{ int x; ^};"); + checkNotAvailable(ID, "struct X { int ^a; };"); + checkNotAvailable(ID, "struct ^X;"); + checkNotAvailable(ID, "template struct ^X { T t; };"); + checkNotAvailable(ID, "enum ^X {};"); + + const char *Input = "struct ^X { int x; int y; }"; + EXPECT_THAT(getMessage(ID, Input), ::testing::HasSubstr("0 | int x")); +} + } // namespace } // namespace clangd } // namespace clang