xref: /llvm-project/clang-tools-extra/unittests/clang-tidy/OverlappingReplacementsTest.cpp (revision 32af5bc51a180e51f1ba8124c5025cdcd33d4ff1)
1 //===---- 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 
14 namespace clang {
15 namespace tidy {
16 namespace test {
17 namespace {
18 
19 const char BoundDecl[] = "decl";
20 const 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).
26 class UseCharCheck : public ClangTidyCheck {
27 public:
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 
42 class IfFalseCheck : public ClangTidyCheck {
43 public:
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 
62 class RefactorCheck : public ClangTidyCheck {
63 public:
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 
111 protected:
112   const std::string NamePattern;
113 };
114 
115 class StartsWithPotaCheck : public RefactorCheck {
116 public:
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 
125 class EndsWithTatoCheck : public RefactorCheck {
126 public:
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 
137 TEST(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 
156 TEST(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 
179 TEST(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 
204 TEST(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 
229 TEST(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 
271 TEST(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 
328 TEST(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
380