1 //===---- OverlappingReplacementsTest.cpp - clang-tidy --------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "ClangTidyTest.h"
10 #include "clang/AST/RecursiveASTVisitor.h"
11 #include "gtest/gtest.h"
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
{
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()),
41 class IfFalseCheck
: public ClangTidyCheck
{
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
{
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(),
86 class UsageVisitor
: public RecursiveASTVisitor
<UsageVisitor
> {
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
);
104 DiagnosticBuilder
&Diag
;
107 UsageVisitor(VD
, NewName
, Diag
)
108 .TraverseDecl(Result
.Context
->getTranslationUnitDecl());
112 const std::string NamePattern
;
115 class StartsWithPotaCheck
: public RefactorCheck
{
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
{
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";
137 TEST(OverlappingReplacementsTest
, UseCharCheckTest
) {
146 const char CharFix
[] =
153 EXPECT_EQ(CharFix
, runCheckOnCode
<UseCharCheck
>(Code
));
156 TEST(OverlappingReplacementsTest
, IfFalseCheckTest
) {
176 EXPECT_EQ(IfFix
, runCheckOnCode
<IfFalseCheck
>(Code
));
179 TEST(OverlappingReplacementsTest
, StartsWithCheckTest
) {
191 const char StartsFix
[] =
201 EXPECT_EQ(StartsFix
, runCheckOnCode
<StartsWithPotaCheck
>(Code
));
204 TEST(OverlappingReplacementsTest
, EndsWithCheckTest
) {
216 const char EndsFix
[] =
226 EXPECT_EQ(EndsFix
, runCheckOnCode
<EndsWithTatoCheck
>(Code
));
229 TEST(OverlappingReplacementTest
, ReplacementsDoNotOverlap
) {
235 int Potato = potassium;
239 const char CharIfFix
[] =
243 char Potato = potassium;
246 Res
= runCheckOnCode
<UseCharCheck
, IfFalseCheck
>(Code
);
247 EXPECT_EQ(CharIfFix
, Res
);
249 const char StartsEndsFix
[] =
253 int Pomelo = tomassium;
256 Res
= runCheckOnCode
<StartsWithPotaCheck
, EndsWithTatoCheck
>(Code
);
257 EXPECT_EQ(StartsEndsFix
, Res
);
259 const char CharIfStartsEndsFix
[] =
263 char Pomelo = tomassium;
266 Res
= runCheckOnCode
<UseCharCheck
, IfFalseCheck
, StartsWithPotaCheck
,
267 EndsWithTatoCheck
>(Code
);
268 EXPECT_EQ(CharIfStartsEndsFix
, Res
);
271 TEST(OverlappingReplacementsTest
, ReplacementInsideOtherReplacement
) {
275 if (char potato = 0) {
276 } else if (int a = 0) {
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) {
287 // ~~~~~~~~~ -> false
288 const char CharIfFix
[] =
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) {
306 // ~~~~~~~~~~~~~~~ -> false
308 // But the refactoring is the one that contains the other here:
311 // if (potato) potato;
312 // ^^^^^^ ^^^^^^ -> tomato, tomato
314 const char IfStartsFix
[] =
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
) {
332 if (int potato = 0) {
337 // The two smallest replacements should not be applied.
338 // if (int potato = 0) {
341 // ~~~~~~~~~~~~~~ -> false
342 // But other errors from the same checks should not be affected.
351 Res
= runCheckOnCode
<UseCharCheck
, IfFalseCheck
, StartsWithPotaCheck
>(Code
);
353 Res
= runCheckOnCode
<StartsWithPotaCheck
, IfFalseCheck
, UseCharCheck
>(Code
);
357 TEST(OverlappingReplacementsTest
,
358 ApplyAtMostOneOfTheChangesWhenPartialOverlapping
) {
362 if (int potato = 0) {
367 // These two replacements overlap, but none of them is completely contained
369 // if (int potato = 0) {
371 // ~~~~~~~~~~~~~~ -> false
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.
383 Res
= runCheckOnCode
<IfFalseCheck
, StartsWithPotaCheck
>(Code
);
384 EXPECT_EQ(IfFix
, Res
);
387 TEST(OverlappingReplacementsTest
, TwoErrorsHavePerfectOverlapping
) {
392 potato += potato * potato;
393 if (char a = potato) potato;
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
402 Res
= runCheckOnCode
<StartsWithPotaCheck
, EndsWithTatoCheck
>(Code
);
403 EXPECT_EQ(Code
, Res
);