blob: b7c4805c38ca47e36e1ff914c69f3f8c0ba58caf [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
Angel Garcia Gomez16693572015-10-16 11:43:49 +000080 auto Diag = diag(VD->getLocation(), "refactor %0 into %1")
81 << VD->getName() << NewName
Angel Garcia Gomez32af5bc2015-10-06 13:52:51 +000082 << FixItHint::CreateReplacement(
Angel Garcia Gomez16693572015-10-16 11:43:49 +000083 CharSourceRange::getTokenRange(VD->getLocation(),
84 VD->getLocation()),
85 NewName);
Angel Garcia Gomez32af5bc2015-10-06 13:52:51 +000086
87 class UsageVisitor : public RecursiveASTVisitor<UsageVisitor> {
88 public:
89 UsageVisitor(const ValueDecl *VD, StringRef NewName,
90 DiagnosticBuilder &Diag)
91 : VD(VD), NewName(NewName), Diag(Diag) {}
92 bool VisitDeclRefExpr(DeclRefExpr *E) {
93 if (const ValueDecl *D = E->getDecl()) {
94 if (VD->getCanonicalDecl() == D->getCanonicalDecl()) {
95 Diag << FixItHint::CreateReplacement(
96 CharSourceRange::getTokenRange(E->getSourceRange()), NewName);
97 }
98 }
99 return RecursiveASTVisitor<UsageVisitor>::VisitDeclRefExpr(E);
100 }
101
102 private:
103 const ValueDecl *VD;
104 StringRef NewName;
105 DiagnosticBuilder &Diag;
106 };
107
108 UsageVisitor(VD, NewName, Diag)
109 .TraverseDecl(Result.Context->getTranslationUnitDecl());
110 }
111
112protected:
113 const std::string NamePattern;
114};
115
116class StartsWithPotaCheck : public RefactorCheck {
117public:
118 StartsWithPotaCheck(StringRef CheckName, ClangTidyContext *Context)
119 : RefactorCheck(CheckName, Context, "::pota") {}
120
121 std::string newName(StringRef OldName) override {
122 return "toma" + OldName.substr(4).str();
123 }
124};
125
126class EndsWithTatoCheck : public RefactorCheck {
127public:
128 EndsWithTatoCheck(StringRef CheckName, ClangTidyContext *Context)
129 : RefactorCheck(CheckName, Context, "tato$") {}
130
131 std::string newName(StringRef OldName) override {
132 return OldName.substr(0, OldName.size() - 4).str() + "melo";
133 }
134};
135
136} // namespace
137
138TEST(OverlappingReplacementsTest, UseCharCheckTest) {
139 const char Code[] =
140 R"(void f() {
141 int a = 0;
142 if (int b = 0) {
143 int c = a;
144 }
145})";
146
147 const char CharFix[] =
148 R"(void f() {
149 char a = 0;
150 if (char b = 0) {
151 char c = a;
152 }
153})";
154 EXPECT_EQ(CharFix, runCheckOnCode<UseCharCheck>(Code));
155}
156
157TEST(OverlappingReplacementsTest, IfFalseCheckTest) {
158 const char Code[] =
159 R"(void f() {
160 int potato = 0;
161 if (int b = 0) {
162 int c = potato;
163 } else if (true) {
164 int d = 0;
165 }
166})";
167
168 const char IfFix[] =
169 R"(void f() {
170 int potato = 0;
171 if (false) {
172 int c = potato;
173 } else if (false) {
174 int d = 0;
175 }
176})";
177 EXPECT_EQ(IfFix, runCheckOnCode<IfFalseCheck>(Code));
178}
179
180TEST(OverlappingReplacementsTest, StartsWithCheckTest) {
181 const char Code[] =
182 R"(void f() {
183 int a = 0;
184 int potato = 0;
185 if (int b = 0) {
186 int c = potato;
187 } else if (true) {
188 int d = 0;
189 }
190})";
191
192 const char StartsFix[] =
193 R"(void f() {
194 int a = 0;
195 int tomato = 0;
196 if (int b = 0) {
197 int c = tomato;
198 } else if (true) {
199 int d = 0;
200 }
201})";
202 EXPECT_EQ(StartsFix, runCheckOnCode<StartsWithPotaCheck>(Code));
203}
204
205TEST(OverlappingReplacementsTest, EndsWithCheckTest) {
206 const char Code[] =
207 R"(void f() {
208 int a = 0;
209 int potato = 0;
210 if (int b = 0) {
211 int c = potato;
212 } else if (true) {
213 int d = 0;
214 }
215})";
216
217 const char EndsFix[] =
218 R"(void f() {
219 int a = 0;
220 int pomelo = 0;
221 if (int b = 0) {
222 int c = pomelo;
223 } else if (true) {
224 int d = 0;
225 }
226})";
227 EXPECT_EQ(EndsFix, runCheckOnCode<EndsWithTatoCheck>(Code));
228}
229
230TEST(OverlappingReplacementTest, ReplacementsDoNotOverlap) {
231 std::string Res;
232 const char Code[] =
233 R"(void f() {
234 int potassium = 0;
235 if (true) {
236 int Potato = potassium;
237 }
238})";
239
240 const char CharIfFix[] =
241 R"(void f() {
242 char potassium = 0;
243 if (false) {
244 char Potato = potassium;
245 }
246})";
247 Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
248 EXPECT_EQ(CharIfFix, Res);
249
250 const char StartsEndsFix[] =
251 R"(void f() {
252 int tomassium = 0;
253 if (true) {
254 int Pomelo = tomassium;
255 }
256})";
257 Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
258 EXPECT_EQ(StartsEndsFix, Res);
259
260 const char CharIfStartsEndsFix[] =
261 R"(void f() {
262 char tomassium = 0;
263 if (false) {
264 char Pomelo = tomassium;
265 }
266})";
267 Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck,
268 EndsWithTatoCheck>(Code);
269 EXPECT_EQ(CharIfStartsEndsFix, Res);
270}
271
272TEST(OverlappingReplacementsTest, ReplacementInsideOtherReplacement) {
273 std::string Res;
274 const char Code[] =
275 R"(void f() {
276 if (char potato = 0) {
277 } else if (int a = 0) {
278 char potato = 0;
279 if (potato) potato;
280 }
281})";
282
283 // Apply the UseCharCheck together with the IfFalseCheck.
284 //
Angel Garcia Gomez16693572015-10-16 11:43:49 +0000285 // The 'If' fix contains the other, so that is the one that has to be applied.
Angel Garcia Gomez32af5bc2015-10-06 13:52:51 +0000286 // } else if (int a = 0) {
287 // ^^^ -> char
288 // ~~~~~~~~~ -> false
289 const char CharIfFix[] =
290 R"(void f() {
291 if (false) {
292 } else if (false) {
293 char potato = 0;
294 if (false) potato;
295 }
296})";
297 Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
Angel Garcia Gomez16693572015-10-16 11:43:49 +0000298 EXPECT_EQ(CharIfFix, Res);
299 Res = runCheckOnCode<IfFalseCheck, UseCharCheck>(Code);
300 EXPECT_EQ(CharIfFix, Res);
Angel Garcia Gomez32af5bc2015-10-06 13:52:51 +0000301
302 // Apply the IfFalseCheck with the StartsWithPotaCheck.
303 //
304 // The 'If' replacement is bigger here.
305 // if (char potato = 0) {
306 // ^^^^^^ -> tomato
307 // ~~~~~~~~~~~~~~~ -> false
308 //
Angel Garcia Gomez16693572015-10-16 11:43:49 +0000309 // But the refactoring is the one that contains the other here:
Angel Garcia Gomez32af5bc2015-10-06 13:52:51 +0000310 // char potato = 0;
311 // ^^^^^^ -> tomato
312 // if (potato) potato;
313 // ^^^^^^ ^^^^^^ -> tomato, tomato
314 // ~~~~~~ -> false
315 const char IfStartsFix[] =
316 R"(void f() {
317 if (false) {
318 } else if (false) {
319 char tomato = 0;
320 if (tomato) tomato;
321 }
322})";
323 Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);
Angel Garcia Gomez16693572015-10-16 11:43:49 +0000324 EXPECT_EQ(IfStartsFix, Res);
325 Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck>(Code);
326 EXPECT_EQ(IfStartsFix, Res);
Angel Garcia Gomez32af5bc2015-10-06 13:52:51 +0000327}
328
Angel Garcia Gomez16693572015-10-16 11:43:49 +0000329TEST(OverlappingReplacements, TwoReplacementsInsideOne) {
330 std::string Res;
331 const char Code[] =
332 R"(void f() {
333 if (int potato = 0) {
334 int a = 0;
335 }
336})";
337
338 // The two smallest replacements should not be applied.
339 // if (int potato = 0) {
340 // ^^^^^^ -> tomato
341 // *** -> char
342 // ~~~~~~~~~~~~~~ -> false
343 // But other errors from the same checks should not be affected.
344 // int a = 0;
345 // *** -> char
346 const char Fix[] =
347 R"(void f() {
348 if (false) {
349 char a = 0;
350 }
351})";
352 Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck>(Code);
353 EXPECT_EQ(Fix, Res);
354 Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck, UseCharCheck>(Code);
355 EXPECT_EQ(Fix, Res);
356}
357
358TEST(OverlappingReplacementsTest,
359 ApplyAtMostOneOfTheChangesWhenPartialOverlapping) {
360 std::string Res;
361 const char Code[] =
362 R"(void f() {
363 if (int potato = 0) {
364 int a = potato;
365 }
366})";
367
368 // These two replacements overlap, but none of them is completely contained
369 // inside the other.
370 // if (int potato = 0) {
371 // ^^^^^^ -> tomato
372 // ~~~~~~~~~~~~~~ -> false
373 // int a = potato;
374 // ^^^^^^ -> tomato
375 //
376 // The 'StartsWithPotaCheck' fix has endpoints inside the 'IfFalseCheck' fix,
377 // so it is going to be set as inapplicable. The 'if' fix will be applied.
378 const char IfFix[] =
379 R"(void f() {
380 if (false) {
381 int a = potato;
382 }
383})";
384 Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);
385 EXPECT_EQ(IfFix, Res);
386}
387
388TEST(OverlappingReplacementsTest, TwoErrorsHavePerfectOverlapping) {
Angel Garcia Gomez32af5bc2015-10-06 13:52:51 +0000389 std::string Res;
390 const char Code[] =
391 R"(void f() {
392 int potato = 0;
393 potato += potato * potato;
Angel Garcia Gomez16693572015-10-16 11:43:49 +0000394 if (char a = potato) potato;
Angel Garcia Gomez32af5bc2015-10-06 13:52:51 +0000395})";
396
Angel Garcia Gomez16693572015-10-16 11:43:49 +0000397 // StartsWithPotaCheck will try to refactor 'potato' into 'tomato', and
398 // EndsWithTatoCheck will try to use 'pomelo'. Both fixes have the same set of
399 // ranges. This is a corner case of one error completely containing another:
400 // the other completely contains the first one as well. Both errors are
401 // discarded.
402
Angel Garcia Gomez32af5bc2015-10-06 13:52:51 +0000403 Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
Angel Garcia Gomez16693572015-10-16 11:43:49 +0000404 EXPECT_EQ(Code, Res);
Angel Garcia Gomez32af5bc2015-10-06 13:52:51 +0000405}
406
407} // namespace test
408} // namespace tidy
409} // namespace clang