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