xref: /llvm-project/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.cpp (revision d5953e3e3092f7142a07aa012fc9665ede09e53b)
1 //===-- TweakTesting.cpp ------------------------------------------------*-===//
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 
9 #include "TweakTesting.h"
10 
11 #include "SourceCode.h"
12 #include "TestTU.h"
13 #include "refactor/Tweak.h"
14 #include "llvm/Support/Error.h"
15 #include "gmock/gmock.h"
16 #include "gtest/gtest.h"
17 #include <optional>
18 #include <string>
19 
20 namespace clang {
21 namespace clangd {
22 namespace {
23 using Context = TweakTest::CodeContext;
24 
wrapping(Context Ctx)25 std::pair<llvm::StringRef, llvm::StringRef> wrapping(Context Ctx) {
26   switch (Ctx) {
27   case TweakTest::File:
28     return {"", ""};
29   case TweakTest::Function:
30     return {"void wrapperFunction(){\n", "\n}"};
31   case TweakTest::Expression:
32     return {"auto expressionWrapper(){return\n", "\n;}"};
33   }
34   llvm_unreachable("Unknown TweakTest::CodeContext enum");
35 }
36 
wrap(Context Ctx,llvm::StringRef Inner)37 std::string wrap(Context Ctx, llvm::StringRef Inner) {
38   auto Wrapping = wrapping(Ctx);
39   return (Wrapping.first + Inner + Wrapping.second).str();
40 }
41 
unwrap(Context Ctx,llvm::StringRef Outer)42 llvm::StringRef unwrap(Context Ctx, llvm::StringRef Outer) {
43   auto Wrapping = wrapping(Ctx);
44   // Unwrap only if the code matches the expected wrapping.
45   // Don't allow the begin/end wrapping to overlap!
46   if (Outer.starts_with(Wrapping.first) && Outer.ends_with(Wrapping.second) &&
47       Outer.size() >= Wrapping.first.size() + Wrapping.second.size())
48     return Outer.drop_front(Wrapping.first.size())
49         .drop_back(Wrapping.second.size());
50   return Outer;
51 }
52 
rangeOrPoint(const llvm::Annotations & A)53 llvm::Annotations::Range rangeOrPoint(const llvm::Annotations &A) {
54   if (A.points().size() != 0) {
55     assert(A.ranges().size() == 0 &&
56            "both a cursor point and a selection range were specified");
57     return {A.point(), A.point()};
58   }
59   return A.range();
60 }
61 
62 // Prepare and apply the specified tweak based on the selection in Input.
63 // Returns std::nullopt if and only if prepare() failed.
64 std::optional<llvm::Expected<Tweak::Effect>>
applyTweak(ParsedAST & AST,llvm::Annotations::Range Range,StringRef TweakID,const SymbolIndex * Index,llvm::vfs::FileSystem * FS)65 applyTweak(ParsedAST &AST, llvm::Annotations::Range Range, StringRef TweakID,
66            const SymbolIndex *Index, llvm::vfs::FileSystem *FS) {
67   std::optional<llvm::Expected<Tweak::Effect>> Result;
68   SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), Range.Begin,
69                             Range.End, [&](SelectionTree ST) {
70                               Tweak::Selection S(Index, AST, Range.Begin,
71                                                  Range.End, std::move(ST), FS);
72                               if (auto T = prepareTweak(TweakID, S, nullptr)) {
73                                 Result = (*T)->apply(S);
74                                 return true;
75                               } else {
76                                 llvm::consumeError(T.takeError());
77                                 return false;
78                               }
79                             });
80   return Result;
81 }
82 
83 } // namespace
84 
apply(llvm::StringRef MarkedCode,llvm::StringMap<std::string> * EditedFiles) const85 std::string TweakTest::apply(llvm::StringRef MarkedCode,
86                              llvm::StringMap<std::string> *EditedFiles) const {
87   std::string WrappedCode = wrap(Context, MarkedCode);
88   llvm::Annotations Input(WrappedCode);
89   TestTU TU;
90   TU.Filename = std::string(FileName);
91   TU.HeaderCode = Header;
92   TU.AdditionalFiles = std::move(ExtraFiles);
93   TU.Code = std::string(Input.code());
94   TU.ExtraArgs = ExtraArgs;
95   ParsedAST AST = TU.build();
96 
97   auto Result = applyTweak(
98       AST, rangeOrPoint(Input), TweakID, Index.get(),
99       &AST.getSourceManager().getFileManager().getVirtualFileSystem());
100   if (!Result)
101     return "unavailable";
102   if (!*Result)
103     return "fail: " + llvm::toString(Result->takeError());
104   const auto &Effect = **Result;
105   if ((*Result)->ShowMessage)
106     return "message:\n" + *Effect.ShowMessage;
107   if (Effect.ApplyEdits.empty())
108     return "no effect";
109 
110   std::string EditedMainFile;
111   for (auto &It : Effect.ApplyEdits) {
112     auto NewText = It.second.apply();
113     if (!NewText)
114       return "bad edits: " + llvm::toString(NewText.takeError());
115     llvm::StringRef Unwrapped = unwrap(Context, *NewText);
116     if (It.first() == testPath(TU.Filename))
117       EditedMainFile = std::string(Unwrapped);
118     else {
119       if (!EditedFiles)
120         ADD_FAILURE() << "There were changes to additional files, but client "
121                          "provided a nullptr for EditedFiles.";
122       else
123         EditedFiles->insert_or_assign(It.first(), Unwrapped.str());
124     }
125   }
126   return EditedMainFile;
127 }
128 
isAvailable(WrappedAST & AST,llvm::Annotations::Range Range) const129 bool TweakTest::isAvailable(WrappedAST &AST,
130                             llvm::Annotations::Range Range) const {
131   // Adjust range for wrapping offset.
132   Range.Begin += AST.second;
133   Range.End += AST.second;
134   auto Result = applyTweak(
135       AST.first, Range, TweakID, Index.get(),
136       &AST.first.getSourceManager().getFileManager().getVirtualFileSystem());
137   // We only care if prepare() succeeded, but must handle Errors.
138   if (Result && !*Result)
139     consumeError(Result->takeError());
140   return Result.has_value();
141 }
142 
build(llvm::StringRef Code) const143 TweakTest::WrappedAST TweakTest::build(llvm::StringRef Code) const {
144   TestTU TU;
145   TU.Filename = std::string(FileName);
146   TU.HeaderCode = Header;
147   TU.Code = wrap(Context, Code);
148   TU.ExtraArgs = ExtraArgs;
149   TU.AdditionalFiles = std::move(ExtraFiles);
150   return {TU.build(), wrapping(Context).first.size()};
151 }
152 
decorate(llvm::StringRef Code,unsigned Point)153 std::string TweakTest::decorate(llvm::StringRef Code, unsigned Point) {
154   return (Code.substr(0, Point) + "^" + Code.substr(Point)).str();
155 }
156 
decorate(llvm::StringRef Code,llvm::Annotations::Range Range)157 std::string TweakTest::decorate(llvm::StringRef Code,
158                                 llvm::Annotations::Range Range) {
159   return (Code.substr(0, Range.Begin) + "[[" +
160           Code.substr(Range.Begin, Range.End - Range.Begin) + "]]" +
161           Code.substr(Range.End))
162       .str();
163 }
164 
165 } // namespace clangd
166 } // namespace clang
167