1 //===-- TweakTesting.cpp ------------------------------------------------*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "TweakTesting.h"
11 #include "SourceCode.h"
13 #include "refactor/Tweak.h"
14 #include "llvm/Support/Error.h"
15 #include "gmock/gmock.h"
16 #include "gtest/gtest.h"
23 using Context
= TweakTest::CodeContext
;
25 std::pair
<llvm::StringRef
, llvm::StringRef
> wrapping(Context Ctx
) {
29 case TweakTest::Function
:
30 return {"void wrapperFunction(){\n", "\n}"};
31 case TweakTest::Expression
:
32 return {"auto expressionWrapper(){return\n", "\n;}"};
34 llvm_unreachable("Unknown TweakTest::CodeContext enum");
37 std::string
wrap(Context Ctx
, llvm::StringRef Inner
) {
38 auto Wrapping
= wrapping(Ctx
);
39 return (Wrapping
.first
+ Inner
+ Wrapping
.second
).str();
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());
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()};
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
>>
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
);
76 llvm::consumeError(T
.takeError());
85 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
);
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();
97 auto Result
= applyTweak(
98 AST
, rangeOrPoint(Input
), TweakID
, Index
.get(),
99 &AST
.getSourceManager().getFileManager().getVirtualFileSystem());
101 return "unavailable";
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())
110 std::string EditedMainFile
;
111 for (auto &It
: Effect
.ApplyEdits
) {
112 auto NewText
= It
.second
.apply();
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
);
120 ADD_FAILURE() << "There were changes to additional files, but client "
121 "provided a nullptr for EditedFiles.";
123 EditedFiles
->insert_or_assign(It
.first(), Unwrapped
.str());
126 return EditedMainFile
;
129 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();
143 TweakTest::WrappedAST
TweakTest::build(llvm::StringRef Code
) const {
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()};
153 std::string
TweakTest::decorate(llvm::StringRef Code
, unsigned Point
) {
154 return (Code
.substr(0, Point
) + "^" + Code
.substr(Point
)).str();
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
))
165 } // namespace clangd