Bump version to 19.1.0 (final)
[llvm-project.git] / clang-tools-extra / unittests / clang-tidy / OverlappingReplacementsTest.cpp
blob3aaf5491d22c59460e6d4200dbe61e33e4bce2d1
1 //===---- OverlappingReplacementsTest.cpp - clang-tidy --------------------===//
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 //===----------------------------------------------------------------------===//
9 #include "ClangTidyTest.h"
10 #include "clang/AST/RecursiveASTVisitor.h"
11 #include "gtest/gtest.h"
13 namespace clang {
14 namespace tidy {
15 namespace test {
16 namespace {
18 const char BoundDecl[] = "decl";
19 const char BoundIf[] = "if";
21 // We define a reduced set of very small checks that allow to test different
22 // overlapping situations (no overlapping, replacements partially overlap, etc),
23 // as well as different kinds of diagnostics (one check produces several errors,
24 // several replacement ranges in an error, etc).
25 class UseCharCheck : public ClangTidyCheck {
26 public:
27 UseCharCheck(StringRef CheckName, ClangTidyContext *Context)
28 : ClangTidyCheck(CheckName, Context) {}
29 void registerMatchers(ast_matchers::MatchFinder *Finder) override {
30 using namespace ast_matchers;
31 Finder->addMatcher(varDecl(hasType(isInteger())).bind(BoundDecl), this);
33 void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
34 auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl);
35 diag(VD->getBeginLoc(), "use char") << FixItHint::CreateReplacement(
36 CharSourceRange::getTokenRange(VD->getBeginLoc(), VD->getBeginLoc()),
37 "char");
41 class IfFalseCheck : public ClangTidyCheck {
42 public:
43 IfFalseCheck(StringRef CheckName, ClangTidyContext *Context)
44 : ClangTidyCheck(CheckName, Context) {}
45 void registerMatchers(ast_matchers::MatchFinder *Finder) override {
46 using namespace ast_matchers;
47 Finder->addMatcher(ifStmt().bind(BoundIf), this);
49 void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
50 auto *If = Result.Nodes.getNodeAs<IfStmt>(BoundIf);
51 auto *Cond = If->getCond();
52 SourceRange Range = Cond->getSourceRange();
53 if (auto *D = If->getConditionVariable()) {
54 Range = SourceRange(D->getBeginLoc(), D->getEndLoc());
56 diag(Range.getBegin(), "the cake is a lie") << FixItHint::CreateReplacement(
57 CharSourceRange::getTokenRange(Range), "false");
61 class RefactorCheck : public ClangTidyCheck {
62 public:
63 RefactorCheck(StringRef CheckName, ClangTidyContext *Context)
64 : ClangTidyCheck(CheckName, Context), NamePattern("::$") {}
65 RefactorCheck(StringRef CheckName, ClangTidyContext *Context,
66 StringRef NamePattern)
67 : ClangTidyCheck(CheckName, Context), NamePattern(NamePattern) {}
68 virtual std::string newName(StringRef OldName) = 0;
70 void registerMatchers(ast_matchers::MatchFinder *Finder) final {
71 using namespace ast_matchers;
72 Finder->addMatcher(varDecl(matchesName(NamePattern)).bind(BoundDecl), this);
75 void check(const ast_matchers::MatchFinder::MatchResult &Result) final {
76 auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl);
77 std::string NewName = newName(VD->getName());
79 auto Diag = diag(VD->getLocation(), "refactor %0 into %1")
80 << VD->getName() << NewName
81 << FixItHint::CreateReplacement(
82 CharSourceRange::getTokenRange(VD->getLocation(),
83 VD->getLocation()),
84 NewName);
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);
98 return RecursiveASTVisitor<UsageVisitor>::VisitDeclRefExpr(E);
101 private:
102 const ValueDecl *VD;
103 StringRef NewName;
104 DiagnosticBuilder &Diag;
107 UsageVisitor(VD, NewName, Diag)
108 .TraverseDecl(Result.Context->getTranslationUnitDecl());
111 protected:
112 const std::string NamePattern;
115 class StartsWithPotaCheck : public RefactorCheck {
116 public:
117 StartsWithPotaCheck(StringRef CheckName, ClangTidyContext *Context)
118 : RefactorCheck(CheckName, Context, "::pota") {}
120 std::string newName(StringRef OldName) override {
121 return "toma" + OldName.substr(4).str();
125 class EndsWithTatoCheck : public RefactorCheck {
126 public:
127 EndsWithTatoCheck(StringRef CheckName, ClangTidyContext *Context)
128 : RefactorCheck(CheckName, Context, "tato$") {}
130 std::string newName(StringRef OldName) override {
131 return OldName.substr(0, OldName.size() - 4).str() + "melo";
135 } // namespace
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;
144 })";
146 const char CharFix[] =
147 R"(void f() {
148 char a = 0;
149 if (char b = 0) {
150 char c = a;
152 })";
153 EXPECT_EQ(CharFix, runCheckOnCode<UseCharCheck>(Code));
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;
165 })";
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;
175 })";
176 EXPECT_EQ(IfFix, runCheckOnCode<IfFalseCheck>(Code));
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;
189 })";
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;
200 })";
201 EXPECT_EQ(StartsFix, runCheckOnCode<StartsWithPotaCheck>(Code));
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;
214 })";
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;
225 })";
226 EXPECT_EQ(EndsFix, runCheckOnCode<EndsWithTatoCheck>(Code));
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;
237 })";
239 const char CharIfFix[] =
240 R"(void f() {
241 char potassium = 0;
242 if (false) {
243 char Potato = potassium;
245 })";
246 Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
247 EXPECT_EQ(CharIfFix, Res);
249 const char StartsEndsFix[] =
250 R"(void f() {
251 int tomassium = 0;
252 if (true) {
253 int Pomelo = tomassium;
255 })";
256 Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
257 EXPECT_EQ(StartsEndsFix, Res);
259 const char CharIfStartsEndsFix[] =
260 R"(void f() {
261 char tomassium = 0;
262 if (false) {
263 char Pomelo = tomassium;
265 })";
266 Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck,
267 EndsWithTatoCheck>(Code);
268 EXPECT_EQ(CharIfStartsEndsFix, Res);
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;
280 })";
282 // Apply the UseCharCheck together with the IfFalseCheck.
284 // The 'If' fix contains the other, 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;
295 })";
296 Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
297 EXPECT_EQ(CharIfFix, Res);
298 Res = runCheckOnCode<IfFalseCheck, UseCharCheck>(Code);
299 EXPECT_EQ(CharIfFix, Res);
301 // Apply the IfFalseCheck with the StartsWithPotaCheck.
303 // The 'If' replacement is bigger here.
304 // if (char potato = 0) {
305 // ^^^^^^ -> tomato
306 // ~~~~~~~~~~~~~~~ -> false
308 // But the refactoring is the one that contains the other here:
309 // char potato = 0;
310 // ^^^^^^ -> tomato
311 // if (potato) potato;
312 // ^^^^^^ ^^^^^^ -> tomato, tomato
313 // ~~~~~~ -> false
314 const char IfStartsFix[] =
315 R"(void f() {
316 if (false) {
317 } else if (false) {
318 char tomato = 0;
319 if (tomato) tomato;
321 })";
322 Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);
323 EXPECT_EQ(IfStartsFix, Res);
324 Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck>(Code);
325 EXPECT_EQ(IfStartsFix, Res);
328 TEST(OverlappingReplacements, TwoReplacementsInsideOne) {
329 std::string Res;
330 const char Code[] =
331 R"(void f() {
332 if (int potato = 0) {
333 int a = 0;
335 })";
337 // The two smallest replacements should not be applied.
338 // if (int potato = 0) {
339 // ^^^^^^ -> tomato
340 // *** -> char
341 // ~~~~~~~~~~~~~~ -> false
342 // But other errors from the same checks should not be affected.
343 // int a = 0;
344 // *** -> char
345 const char Fix[] =
346 R"(void f() {
347 if (false) {
348 char a = 0;
350 })";
351 Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck>(Code);
352 EXPECT_EQ(Fix, Res);
353 Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck, UseCharCheck>(Code);
354 EXPECT_EQ(Fix, Res);
357 TEST(OverlappingReplacementsTest,
358 ApplyAtMostOneOfTheChangesWhenPartialOverlapping) {
359 std::string Res;
360 const char Code[] =
361 R"(void f() {
362 if (int potato = 0) {
363 int a = potato;
365 })";
367 // These two replacements overlap, but none of them is completely contained
368 // inside the other.
369 // if (int potato = 0) {
370 // ^^^^^^ -> tomato
371 // ~~~~~~~~~~~~~~ -> false
372 // int a = potato;
373 // ^^^^^^ -> tomato
375 // The 'StartsWithPotaCheck' fix has endpoints inside the 'IfFalseCheck' fix,
376 // so it is going to be set as inapplicable. The 'if' fix will be applied.
377 const char IfFix[] =
378 R"(void f() {
379 if (false) {
380 int a = potato;
382 })";
383 Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);
384 EXPECT_EQ(IfFix, Res);
387 TEST(OverlappingReplacementsTest, TwoErrorsHavePerfectOverlapping) {
388 std::string Res;
389 const char Code[] =
390 R"(void f() {
391 int potato = 0;
392 potato += potato * potato;
393 if (char a = potato) potato;
394 })";
396 // StartsWithPotaCheck will try to refactor 'potato' into 'tomato', and
397 // EndsWithTatoCheck will try to use 'pomelo'. Both fixes have the same set of
398 // ranges. This is a corner case of one error completely containing another:
399 // the other completely contains the first one as well. Both errors are
400 // discarded.
402 Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
403 EXPECT_EQ(Code, Res);
406 } // namespace test
407 } // namespace tidy
408 } // namespace clang