1 | //===- unittest/Tooling/RefactoringCallbacksTest.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 "RewriterTestContext.h" |
10 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
11 | #include "clang/ASTMatchers/ASTMatchers.h" |
12 | #include "clang/Tooling/RefactoringCallbacks.h" |
13 | #include "gtest/gtest.h" |
14 | |
15 | namespace clang { |
16 | namespace tooling { |
17 | |
18 | using namespace ast_matchers; |
19 | |
20 | template <typename T> |
21 | void expectRewritten(const std::string &Code, const std::string &Expected, |
22 | const T &AMatcher, RefactoringCallback &Callback) { |
23 | std::map<std::string, Replacements> FileToReplace; |
24 | ASTMatchRefactorer Finder(FileToReplace); |
25 | Finder.addMatcher(traverse(TK_AsIs, AMatcher), &Callback); |
26 | std::unique_ptr<tooling::FrontendActionFactory> Factory( |
27 | tooling::newFrontendActionFactory(ConsumerFactory: &Finder)); |
28 | ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), Code)) |
29 | << "Parsing error in \"" << Code << "\"" ; |
30 | RewriterTestContext Context; |
31 | FileID ID = Context.createInMemoryFile(Name: "input.cc" , Content: Code); |
32 | EXPECT_TRUE(tooling::applyAllReplacements(FileToReplace["input.cc" ], |
33 | Context.Rewrite)); |
34 | EXPECT_EQ(Expected, Context.getRewrittenText(ID)); |
35 | } |
36 | |
37 | TEST(RefactoringCallbacksTest, ReplacesStmtsWithString) { |
38 | std::string Code = "void f() { int i = 1; }" ; |
39 | std::string Expected = "void f() { ; }" ; |
40 | ReplaceStmtWithText Callback("id" , ";" ); |
41 | expectRewritten(Code, Expected, AMatcher: declStmt().bind(ID: "id" ), Callback); |
42 | } |
43 | |
44 | TEST(RefactoringCallbacksTest, ReplacesStmtsInCalledMacros) { |
45 | std::string Code = "#define A void f() { int i = 1; }\nA" ; |
46 | std::string Expected = "#define A void f() { ; }\nA" ; |
47 | ReplaceStmtWithText Callback("id" , ";" ); |
48 | expectRewritten(Code, Expected, AMatcher: declStmt().bind(ID: "id" ), Callback); |
49 | } |
50 | |
51 | TEST(RefactoringCallbacksTest, IgnoresStmtsInUncalledMacros) { |
52 | std::string Code = "#define A void f() { int i = 1; }" ; |
53 | std::string Expected = "#define A void f() { int i = 1; }" ; |
54 | ReplaceStmtWithText Callback("id" , ";" ); |
55 | expectRewritten(Code, Expected, AMatcher: declStmt().bind(ID: "id" ), Callback); |
56 | } |
57 | |
58 | TEST(RefactoringCallbacksTest, ReplacesInteger) { |
59 | std::string Code = "void f() { int i = 1; }" ; |
60 | std::string Expected = "void f() { int i = 2; }" ; |
61 | ReplaceStmtWithText Callback("id" , "2" ); |
62 | expectRewritten(Code, Expected, AMatcher: expr(integerLiteral()).bind(ID: "id" ), Callback); |
63 | } |
64 | |
65 | TEST(RefactoringCallbacksTest, ReplacesStmtWithStmt) { |
66 | std::string Code = "void f() { int i = false ? 1 : i * 2; }" ; |
67 | std::string Expected = "void f() { int i = i * 2; }" ; |
68 | ReplaceStmtWithStmt Callback("always-false" , "should-be" ); |
69 | expectRewritten( |
70 | Code, Expected, |
71 | AMatcher: conditionalOperator(hasCondition(InnerMatcher: cxxBoolLiteral(equals(Value: false))), |
72 | hasFalseExpression(InnerMatcher: expr().bind(ID: "should-be" ))) |
73 | .bind(ID: "always-false" ), |
74 | Callback); |
75 | } |
76 | |
77 | TEST(RefactoringCallbacksTest, ReplacesIfStmt) { |
78 | std::string Code = "bool a; void f() { if (a) f(); else a = true; }" ; |
79 | std::string Expected = "bool a; void f() { f(); }" ; |
80 | ReplaceIfStmtWithItsBody Callback("id" , true); |
81 | expectRewritten(Code, Expected, |
82 | AMatcher: ifStmt(hasCondition(InnerMatcher: implicitCastExpr(hasSourceExpression( |
83 | InnerMatcher: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "a" )))))))) |
84 | .bind(ID: "id" ), |
85 | Callback); |
86 | } |
87 | |
88 | TEST(RefactoringCallbacksTest, RemovesEntireIfOnEmptyElse) { |
89 | std::string Code = "void f() { if (false) int i = 0; }" ; |
90 | std::string Expected = "void f() { }" ; |
91 | ReplaceIfStmtWithItsBody Callback("id" , false); |
92 | expectRewritten( |
93 | Code, Expected, |
94 | AMatcher: ifStmt(hasCondition(InnerMatcher: cxxBoolLiteral(equals(Value: false)))).bind(ID: "id" ), Callback); |
95 | } |
96 | |
97 | TEST(RefactoringCallbacksTest, TemplateJustText) { |
98 | std::string Code = "void f() { int i = 1; }" ; |
99 | std::string Expected = "void f() { FOO }" ; |
100 | auto Callback = ReplaceNodeWithTemplate::create(FromId: "id" , ToTemplate: "FOO" ); |
101 | EXPECT_FALSE(Callback.takeError()); |
102 | expectRewritten(Code, Expected, AMatcher: declStmt().bind(ID: "id" ), Callback&: **Callback); |
103 | } |
104 | |
105 | TEST(RefactoringCallbacksTest, TemplateSimpleSubst) { |
106 | std::string Code = "void f() { int i = 1; }" ; |
107 | std::string Expected = "void f() { long x = 1; }" ; |
108 | auto Callback = ReplaceNodeWithTemplate::create(FromId: "decl" , ToTemplate: "long x = ${init}" ); |
109 | EXPECT_FALSE(Callback.takeError()); |
110 | expectRewritten(Code, Expected, |
111 | AMatcher: varDecl(hasInitializer(InnerMatcher: expr().bind(ID: "init" ))).bind(ID: "decl" ), |
112 | Callback&: **Callback); |
113 | } |
114 | |
115 | TEST(RefactoringCallbacksTest, TemplateLiteral) { |
116 | std::string Code = "void f() { int i = 1; }" ; |
117 | std::string Expected = "void f() { string x = \"$-1\"; }" ; |
118 | auto Callback = ReplaceNodeWithTemplate::create(FromId: "decl" , |
119 | ToTemplate: "string x = \"$$-${init}\"" ); |
120 | EXPECT_FALSE(Callback.takeError()); |
121 | expectRewritten(Code, Expected, |
122 | AMatcher: varDecl(hasInitializer(InnerMatcher: expr().bind(ID: "init" ))).bind(ID: "decl" ), |
123 | Callback&: **Callback); |
124 | } |
125 | |
126 | static void ExpectStringError(const std::string &Expected, |
127 | llvm::Error E) { |
128 | std::string Found; |
129 | handleAllErrors(E: std::move(E), Handlers: [&](const llvm::StringError &SE) { |
130 | llvm::raw_string_ostream Stream(Found); |
131 | SE.log(OS&: Stream); |
132 | }); |
133 | EXPECT_EQ(Expected, Found); |
134 | } |
135 | |
136 | TEST(RefactoringCallbacksTest, TemplateUnterminated) { |
137 | auto Callback = ReplaceNodeWithTemplate::create(FromId: "decl" , |
138 | ToTemplate: "string x = \"$$-${init\"" ); |
139 | ExpectStringError(Expected: "Unterminated ${...} in replacement template near ${init\"" , |
140 | E: Callback.takeError()); |
141 | } |
142 | |
143 | TEST(RefactoringCallbacksTest, TemplateUnknownDollar) { |
144 | auto Callback = ReplaceNodeWithTemplate::create(FromId: "decl" , |
145 | ToTemplate: "string x = \"$<" ); |
146 | ExpectStringError(Expected: "Invalid $ in replacement template near $<" , |
147 | E: Callback.takeError()); |
148 | } |
149 | |
150 | } // namespace tooling |
151 | } // end namespace clang |
152 | |