blob: 01c43a738c032df030c909759e8a9e375d62571c [file] [log] [blame]
Angel Garcia Gomez32af5bc2015-10-06 13:52:51 +00001//===---- OverlappingReplacementsTest.cpp - clang-tidy --------------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "ClangTidyTest.h"
11#include "clang/AST/RecursiveASTVisitor.h"
12#include "gtest/gtest.h"
13
14namespace clang {
15namespace tidy {
16namespace test {
17namespace {
18
19const char BoundDecl[] = "decl";
20const char BoundIf[] = "if";
21
22// We define a reduced set of very small checks that allow to test different
23// overlapping situations (no overlapping, replacements partially overlap, etc),
24// as well as different kinds of diagnostics (one check produces several errors,
25// several replacement ranges in an error, etc).
26class UseCharCheck : public ClangTidyCheck {
27public:
28 UseCharCheck(StringRef CheckName, ClangTidyContext *Context)
29 : ClangTidyCheck(CheckName, Context) {}
30 void registerMatchers(ast_matchers::MatchFinder *Finder) override {
31 using namespace ast_matchers;
32 Finder->addMatcher(varDecl(hasType(isInteger())).bind(BoundDecl), this);
33 }
34 void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
35 auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl);
36 diag(VD->getLocStart(), "use char") << FixItHint::CreateReplacement(
37 CharSourceRange::getTokenRange(VD->getLocStart(), VD->getLocStart()),
38 "char");
39 }
40};
41
42class IfFalseCheck : public ClangTidyCheck {
43public:
44 IfFalseCheck(StringRef CheckName, ClangTidyContext *Context)
45 : ClangTidyCheck(CheckName, Context) {}
46 void registerMatchers(ast_matchers::MatchFinder *Finder) override {
47 using namespace ast_matchers;
48 Finder->addMatcher(ifStmt().bind(BoundIf), this);
49 }
50 void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
51 auto *If = Result.Nodes.getNodeAs<IfStmt>(BoundIf);
52 auto *Cond = If->getCond();
53 SourceRange Range = Cond->getSourceRange();
54 if (auto *D = If->getConditionVariable()) {
55 Range = SourceRange(D->getLocStart(), D->getLocEnd());
56 }
57 diag(Range.getBegin(), "the cake is a lie") << FixItHint::CreateReplacement(
58 CharSourceRange::getTokenRange(Range), "false");
59 }
60};
61
62class RefactorCheck : public ClangTidyCheck {
63public:
64 RefactorCheck(StringRef CheckName, ClangTidyContext *Context)
65 : ClangTidyCheck(CheckName, Context), NamePattern("::$") {}
66 RefactorCheck(StringRef CheckName, ClangTidyContext *Context,
67 StringRef NamePattern)
68 : ClangTidyCheck(CheckName, Context), NamePattern(NamePattern) {}
69 virtual std::string newName(StringRef OldName) = 0;
70
71 void registerMatchers(ast_matchers::MatchFinder *Finder) final {
72 using namespace ast_matchers;
73 Finder->addMatcher(varDecl(matchesName(NamePattern)).bind(BoundDecl), this);
74 }
75
76 void check(const ast_matchers::MatchFinder::MatchResult &Result) final {
77 auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl);
78 std::string NewName = newName(VD->getName());
79
80 auto Diag = diag(VD->getLocation(), "refactor")
81 << FixItHint::CreateReplacement(
82 CharSourceRange::getTokenRange(VD->getLocation(),
83 VD->getLocation()),
84 NewName);
85
86 class UsageVisitor : public RecursiveASTVisitor<UsageVisitor> {
87 public:
88 UsageVisitor(const ValueDecl *VD, StringRef NewName,
89 DiagnosticBuilder &Diag)
90 : VD(VD), NewName(NewName), Diag(Diag) {}
91 bool VisitDeclRefExpr(DeclRefExpr *E) {
92 if (const ValueDecl *D = E->getDecl()) {
93 if (VD->getCanonicalDecl() == D->getCanonicalDecl()) {
94 Diag << FixItHint::CreateReplacement(
95 CharSourceRange::getTokenRange(E->getSourceRange()), NewName);
96 }
97 }
98 return RecursiveASTVisitor<UsageVisitor>::VisitDeclRefExpr(E);
99 }
100
101 private:
102 const ValueDecl *VD;
103 StringRef NewName;
104 DiagnosticBuilder &Diag;
105 };
106
107 UsageVisitor(VD, NewName, Diag)
108 .TraverseDecl(Result.Context->getTranslationUnitDecl());
109 }
110
111protected:
112 const std::string NamePattern;
113};
114
115class StartsWithPotaCheck : public RefactorCheck {
116public:
117 StartsWithPotaCheck(StringRef CheckName, ClangTidyContext *Context)
118 : RefactorCheck(CheckName, Context, "::pota") {}
119
120 std::string newName(StringRef OldName) override {
121 return "toma" + OldName.substr(4).str();
122 }
123};
124
125class EndsWithTatoCheck : public RefactorCheck {
126public:
127 EndsWithTatoCheck(StringRef CheckName, ClangTidyContext *Context)
128 : RefactorCheck(CheckName, Context, "tato$") {}
129
130 std::string newName(StringRef OldName) override {
131 return OldName.substr(0, OldName.size() - 4).str() + "melo";
132 }
133};
134
135} // namespace
136
137TEST(OverlappingReplacementsTest, UseCharCheckTest) {
138 const char Code[] =
139 R"(void f() {
140 int a = 0;
141 if (int b = 0) {
142 int c = a;
143 }
144})";
145
146 const char CharFix[] =
147 R"(void f() {
148 char a = 0;
149 if (char b = 0) {
150 char c = a;
151 }
152})";
153 EXPECT_EQ(CharFix, runCheckOnCode<UseCharCheck>(Code));
154}
155
156TEST(OverlappingReplacementsTest, IfFalseCheckTest) {
157 const char Code[] =
158 R"(void f() {
159 int potato = 0;
160 if (int b = 0) {
161 int c = potato;
162 } else if (true) {
163 int d = 0;
164 }
165})";
166
167 const char IfFix[] =
168 R"(void f() {
169 int potato = 0;
170 if (false) {
171 int c = potato;
172 } else if (false) {
173 int d = 0;
174 }
175})";
176 EXPECT_EQ(IfFix, runCheckOnCode<IfFalseCheck>(Code));
177}
178
179TEST(OverlappingReplacementsTest, StartsWithCheckTest) {
180 const char Code[] =
181 R"(void f() {
182 int a = 0;
183 int potato = 0;
184 if (int b = 0) {
185 int c = potato;
186 } else if (true) {
187 int d = 0;
188 }
189})";
190
191 const char StartsFix[] =
192 R"(void f() {
193 int a = 0;
194 int tomato = 0;
195 if (int b = 0) {
196 int c = tomato;
197 } else if (true) {
198 int d = 0;
199 }
200})";
201 EXPECT_EQ(StartsFix, runCheckOnCode<StartsWithPotaCheck>(Code));
202}
203
204TEST(OverlappingReplacementsTest, EndsWithCheckTest) {
205 const char Code[] =
206 R"(void f() {
207 int a = 0;
208 int potato = 0;
209 if (int b = 0) {
210 int c = potato;
211 } else if (true) {
212 int d = 0;
213 }
214})";
215
216 const char EndsFix[] =
217 R"(void f() {
218 int a = 0;
219 int pomelo = 0;
220 if (int b = 0) {
221 int c = pomelo;
222 } else if (true) {
223 int d = 0;
224 }
225})";
226 EXPECT_EQ(EndsFix, runCheckOnCode<EndsWithTatoCheck>(Code));
227}
228
229TEST(OverlappingReplacementTest, ReplacementsDoNotOverlap) {
230 std::string Res;
231 const char Code[] =
232 R"(void f() {
233 int potassium = 0;
234 if (true) {
235 int Potato = potassium;
236 }
237})";
238
239 const char CharIfFix[] =
240 R"(void f() {
241 char potassium = 0;
242 if (false) {
243 char Potato = potassium;
244 }
245})";
246 Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
247 EXPECT_EQ(CharIfFix, Res);
248
249 const char StartsEndsFix[] =
250 R"(void f() {
251 int tomassium = 0;
252 if (true) {
253 int Pomelo = tomassium;
254 }
255})";
256 Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
257 EXPECT_EQ(StartsEndsFix, Res);
258
259 const char CharIfStartsEndsFix[] =
260 R"(void f() {
261 char tomassium = 0;
262 if (false) {
263 char Pomelo = tomassium;
264 }
265})";
266 Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck,
267 EndsWithTatoCheck>(Code);
268 EXPECT_EQ(CharIfStartsEndsFix, Res);
269}
270
271TEST(OverlappingReplacementsTest, ReplacementInsideOtherReplacement) {
272 std::string Res;
273 const char Code[] =
274 R"(void f() {
275 if (char potato = 0) {
276 } else if (int a = 0) {
277 char potato = 0;
278 if (potato) potato;
279 }
280})";
281
282 // Apply the UseCharCheck together with the IfFalseCheck.
283 //
284 // The 'If' fix is bigger, so that is the one that has to be applied.
285 // } else if (int a = 0) {
286 // ^^^ -> char
287 // ~~~~~~~~~ -> false
288 const char CharIfFix[] =
289 R"(void f() {
290 if (false) {
291 } else if (false) {
292 char potato = 0;
293 if (false) potato;
294 }
295})";
296 Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
297 // FIXME: EXPECT_EQ(CharIfFix, Res);
298
299 // Apply the IfFalseCheck with the StartsWithPotaCheck.
300 //
301 // The 'If' replacement is bigger here.
302 // if (char potato = 0) {
303 // ^^^^^^ -> tomato
304 // ~~~~~~~~~~~~~~~ -> false
305 //
306 // But the refactoring is bigger here:
307 // char potato = 0;
308 // ^^^^^^ -> tomato
309 // if (potato) potato;
310 // ^^^^^^ ^^^^^^ -> tomato, tomato
311 // ~~~~~~ -> false
312 const char IfStartsFix[] =
313 R"(void f() {
314 if (false) {
315 } else if (false) {
316 char tomato = 0;
317 if (tomato) tomato;
318 }
319})";
320 Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);
321 // FIXME: EXPECT_EQ(IfStartsFix, Res);
322
323 // Silence warnings.
324 (void)CharIfFix;
325 (void)IfStartsFix;
326}
327
328TEST(OverlappingReplacementsTest, ApplyFullErrorOrNothingWhenOverlapping) {
329 std::string Res;
330 const char Code[] =
331 R"(void f() {
332 int potato = 0;
333 potato += potato * potato;
334 if (char this_name_make_this_if_really_long = potato) potato;
335})";
336
337 // StartsWithPotaCheck will try to refactor 'potato' into 'tomato',
338 // and EndsWithTatoCheck will try to use 'pomelo'. We have to apply
339 // either all conversions from one check, or all from the other.
340 const char StartsFix[] =
341 R"(void f() {
342 int tomato = 0;
343 tomato += tomato * tomato;
344 if (char this_name_make_this_if_really_long = tomato) tomato;
345})";
346 const char EndsFix[] =
347 R"(void f() {
348 int pomelo = 0;
349 pomelo += pomelo * pomelo;
350 if (char this_name_make_this_if_really_long = pomelo) pomelo;
351})";
352 // In case of overlapping, we will prioritize the biggest fix. However, these
353 // two fixes have the same size and position, so we don't know yet which one
354 // will have preference.
355 Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
356 // FIXME: EXPECT_TRUE(Res == StartsFix || Res == EndsFix);
357
358 // StartsWithPotaCheck will try to refactor 'potato' into 'tomato', but
359 // replacing the 'if' condition is a bigger change than all the refactoring
360 // changes together (48 vs 36), so this is the one that is going to be
361 // applied.
362 const char IfFix[] =
363 R"(void f() {
364 int potato = 0;
365 potato += potato * potato;
366 if (true) potato;
367})";
368 Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck>(Code);
369 // FIXME: EXPECT_EQ(IfFix, Res);
370
371 // Silence warnings.
372 (void)StartsFix;
373 (void)EndsFix;
374 (void)IfFix;
375}
376
377} // namespace test
378} // namespace tidy
379} // namespace clang