1 | //===- unittest/Tooling/FixitTest.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 "clang/Tooling/FixIt.h" |
10 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
11 | #include "clang/ASTMatchers/ASTMatchers.h" |
12 | #include "clang/Testing/TestAST.h" |
13 | #include "gtest/gtest.h" |
14 | |
15 | using namespace clang; |
16 | |
17 | using tooling::fixit::getText; |
18 | using tooling::fixit::createRemoval; |
19 | using tooling::fixit::createReplacement; |
20 | |
21 | namespace { |
22 | |
23 | const CallExpr &onlyCall(ASTContext &Ctx) { |
24 | using namespace ast_matchers; |
25 | auto Calls = match(Matcher: callExpr().bind(ID: "" ), Context&: Ctx); |
26 | EXPECT_EQ(Calls.size(), 1u); |
27 | return *Calls.front().getNodeAs<CallExpr>(ID: "" ); |
28 | } |
29 | |
30 | TEST(FixItTest, getText) { |
31 | TestAST AST("void foo(int x, int y) { foo(x, y); }" ); |
32 | const CallExpr &CE = onlyCall(Ctx&: AST.context()); |
33 | EXPECT_EQ("foo(x, y)" , getText(CE, AST.context())); |
34 | EXPECT_EQ("foo(x, y)" , getText(CE.getSourceRange(), AST.context())); |
35 | EXPECT_EQ("x" , getText(*CE.getArg(0), AST.context())); |
36 | EXPECT_EQ("y" , getText(*CE.getArg(1), AST.context())); |
37 | |
38 | AST = TestAST("#define APPLY(f, x, y) f(x, y)\n" |
39 | "void foo(int x, int y) { APPLY(foo, x, y); }" ); |
40 | const CallExpr &CE2 = onlyCall(Ctx&: AST.context()); |
41 | EXPECT_EQ("APPLY(foo, x, y)" , getText(CE2, AST.context())); |
42 | } |
43 | |
44 | TEST(FixItTest, getTextWithMacro) { |
45 | TestAST AST("#define F foo(\n" |
46 | "#define OO x, y)\n" |
47 | "void foo(int x, int y) { F OO ; }" ); |
48 | const CallExpr &CE = onlyCall(Ctx&: AST.context()); |
49 | EXPECT_EQ("F OO" , getText(CE, AST.context())); |
50 | EXPECT_EQ("" , getText(*CE.getArg(0), AST.context())); |
51 | EXPECT_EQ("" , getText(*CE.getArg(1), AST.context())); |
52 | |
53 | AST = TestAST("#define FOO(x, y) (void)x; (void)y; foo(x, y);\n" |
54 | "void foo(int x, int y) { FOO(x,y) }" ); |
55 | const CallExpr &CE2 = onlyCall(Ctx&: AST.context()); |
56 | EXPECT_EQ("" , getText(CE2, AST.context())); |
57 | EXPECT_EQ("x" , getText(*CE2.getArg(0), AST.context())); |
58 | EXPECT_EQ("y" , getText(*CE2.getArg(1), AST.context())); |
59 | } |
60 | |
61 | TEST(FixItTest, createRemoval) { |
62 | TestAST AST("void foo(int x, int y) { foo(x, y); }" ); |
63 | const CallExpr &CE = onlyCall(Ctx&: AST.context()); |
64 | |
65 | FixItHint Hint = createRemoval(Node: CE); |
66 | EXPECT_EQ("foo(x, y)" , getText(Hint.RemoveRange.getAsRange(), AST.context())); |
67 | EXPECT_TRUE(Hint.InsertFromRange.isInvalid()); |
68 | EXPECT_TRUE(Hint.CodeToInsert.empty()); |
69 | |
70 | FixItHint Hint0 = createRemoval(Node: *CE.getArg(Arg: 0)); |
71 | EXPECT_EQ("x" , getText(Hint0.RemoveRange.getAsRange(), AST.context())); |
72 | EXPECT_TRUE(Hint0.InsertFromRange.isInvalid()); |
73 | EXPECT_TRUE(Hint0.CodeToInsert.empty()); |
74 | |
75 | FixItHint Hint1 = createRemoval(Node: *CE.getArg(Arg: 1)); |
76 | EXPECT_EQ("y" , getText(Hint1.RemoveRange.getAsRange(), AST.context())); |
77 | EXPECT_TRUE(Hint1.InsertFromRange.isInvalid()); |
78 | EXPECT_TRUE(Hint1.CodeToInsert.empty()); |
79 | |
80 | AST = TestAST("void foo(int x, int y) { foo(x + y, y + x); }" ); |
81 | const CallExpr &CE2 = onlyCall(Ctx&: AST.context()); |
82 | Hint0 = createRemoval(Node: *CE2.getArg(Arg: 0)); |
83 | EXPECT_EQ("x + y" , getText(Hint0.RemoveRange.getAsRange(), AST.context())); |
84 | |
85 | Hint1 = createRemoval(Node: *CE2.getArg(Arg: 1)); |
86 | EXPECT_EQ("y + x" , getText(Hint1.RemoveRange.getAsRange(), AST.context())); |
87 | } |
88 | |
89 | TEST(FixItTest, createRemovalWithMacro) { |
90 | TestAST AST("#define FOO foo(1, 1)\n" |
91 | "void foo(int x, int y) { FOO; }" ); |
92 | const CallExpr &CE = onlyCall(Ctx&: AST.context()); |
93 | FixItHint Hint = createRemoval(Node: CE); |
94 | EXPECT_EQ("FOO" , getText(Hint.RemoveRange.getAsRange(), AST.context())); |
95 | EXPECT_TRUE(Hint.InsertFromRange.isInvalid()); |
96 | EXPECT_TRUE(Hint.CodeToInsert.empty()); |
97 | |
98 | FixItHint Hint0 = createRemoval(Node: *CE.getArg(Arg: 0)); |
99 | EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:17>" , |
100 | Hint0.RemoveRange.getBegin().printToString(AST.sourceManager())); |
101 | EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:17>" , |
102 | Hint0.RemoveRange.getEnd().printToString(AST.sourceManager())); |
103 | EXPECT_TRUE(Hint0.InsertFromRange.isInvalid()); |
104 | EXPECT_TRUE(Hint0.CodeToInsert.empty()); |
105 | |
106 | FixItHint Hint1 = createRemoval(Node: *CE.getArg(Arg: 1)); |
107 | EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:20>" , |
108 | Hint1.RemoveRange.getBegin().printToString(AST.sourceManager())); |
109 | EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:20>" , |
110 | Hint1.RemoveRange.getEnd().printToString(AST.sourceManager())); |
111 | EXPECT_TRUE(Hint1.InsertFromRange.isInvalid()); |
112 | EXPECT_TRUE(Hint1.CodeToInsert.empty()); |
113 | |
114 | AST = TestAST("#define FOO(x, y) (void)x; (void)y; foo(x, y);\n" |
115 | "void foo(int x, int y) { FOO(x,y) }" ); |
116 | const CallExpr &CE2 = onlyCall(Ctx&: AST.context()); |
117 | Hint = createRemoval(Node: CE2); |
118 | EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:37>" , |
119 | Hint.RemoveRange.getBegin().printToString(AST.sourceManager())); |
120 | EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:45>" , |
121 | Hint.RemoveRange.getEnd().printToString(AST.sourceManager())); |
122 | EXPECT_TRUE(Hint.InsertFromRange.isInvalid()); |
123 | EXPECT_TRUE(Hint.CodeToInsert.empty()); |
124 | } |
125 | |
126 | TEST(FixItTest, createReplacement) { |
127 | for (const char *Code : { |
128 | "void foo(int x, int y) { foo(x, y); }" , |
129 | |
130 | "#define APPLY(f, x, y) f(x, y)\n" |
131 | "void foo(int x, int y) { APPLY(foo, x, y); }" , |
132 | |
133 | "#define APPLY(f, P) f(P)\n" |
134 | "#define PAIR(x, y) x, y\n" |
135 | "void foo(int x, int y) { APPLY(foo, PAIR(x, y)); }\n" , |
136 | }) { |
137 | TestAST AST(Code); |
138 | const CallExpr &CE = onlyCall(Ctx&: AST.context()); |
139 | const Expr *P0 = CE.getArg(Arg: 0); |
140 | const Expr *P1 = CE.getArg(Arg: 1); |
141 | FixItHint Hint0 = createReplacement(Destination: *P0, Source: *P1, Context: AST.context()); |
142 | FixItHint Hint1 = createReplacement(Destination: *P1, Source: *P0, Context: AST.context()); |
143 | |
144 | // Validate Hint0 fields. |
145 | EXPECT_EQ("x" , getText(Hint0.RemoveRange.getAsRange(), AST.context())); |
146 | EXPECT_TRUE(Hint0.InsertFromRange.isInvalid()); |
147 | EXPECT_EQ(Hint0.CodeToInsert, "y" ); |
148 | |
149 | // Validate Hint1 fields. |
150 | EXPECT_EQ("y" , getText(Hint1.RemoveRange.getAsRange(), AST.context())); |
151 | EXPECT_TRUE(Hint1.InsertFromRange.isInvalid()); |
152 | EXPECT_EQ(Hint1.CodeToInsert, "x" ); |
153 | } |
154 | } |
155 | |
156 | TEST(FixItTest, createReplacementWithMacro) { |
157 | TestAST AST("#define FOO foo(1, 1)\n" |
158 | "void foo(int x, int y) { FOO; }" ); |
159 | const CallExpr &CE = onlyCall(Ctx&: AST.context()); |
160 | FixItHint Hint = |
161 | createReplacement(Destination: *CE.getArg(Arg: 0), Source: *CE.getArg(Arg: 1), Context: AST.context()); |
162 | EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:17>" , |
163 | Hint.RemoveRange.getBegin().printToString(AST.sourceManager())); |
164 | EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:1:17>" , |
165 | Hint.RemoveRange.getEnd().printToString(AST.sourceManager())); |
166 | EXPECT_TRUE(Hint.InsertFromRange.isInvalid()); |
167 | EXPECT_TRUE(Hint.CodeToInsert.empty()); |
168 | |
169 | AST = TestAST("#define FOO(x, y) (void)x; (void)y; foo(x, y);\n" |
170 | "void foo(int x, int y) { FOO(x,y) }" ); |
171 | const CallExpr &CE2 = onlyCall(Ctx&: AST.context()); |
172 | Hint = createReplacement(Destination: *CE2.getArg(Arg: 0), Source: *CE2.getArg(Arg: 1), Context: AST.context()); |
173 | EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:2:30>" , |
174 | Hint.RemoveRange.getEnd().printToString(AST.sourceManager())); |
175 | EXPECT_EQ("input.mm:2:26 <Spelling=input.mm:2:30>" , |
176 | Hint.RemoveRange.getBegin().printToString(AST.sourceManager())); |
177 | EXPECT_TRUE(Hint.InsertFromRange.isInvalid()); |
178 | EXPECT_EQ("y" , Hint.CodeToInsert); |
179 | |
180 | AST = TestAST("void foo(int x, int y) { foo(x + y, y + x); }" ); |
181 | const CallExpr &CE3 = onlyCall(Ctx&: AST.context()); |
182 | Hint = createReplacement(Destination: *CE3.getArg(Arg: 0), Source: *CE3.getArg(Arg: 1), Context: AST.context()); |
183 | EXPECT_EQ("x + y" , getText(Hint.RemoveRange.getAsRange(), AST.context())); |
184 | EXPECT_TRUE(Hint.InsertFromRange.isInvalid()); |
185 | EXPECT_EQ("y + x" , Hint.CodeToInsert); |
186 | } |
187 | |
188 | } // end anonymous namespace |
189 | |