blob: b7c4805c38ca47e36e1ff914c69f3f8c0ba58caf [file] [log] [blame]
//===---- OverlappingReplacementsTest.cpp - clang-tidy --------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "ClangTidyTest.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "gtest/gtest.h"
namespace clang {
namespace tidy {
namespace test {
namespace {
const char BoundDecl[] = "decl";
const char BoundIf[] = "if";
// We define a reduced set of very small checks that allow to test different
// overlapping situations (no overlapping, replacements partially overlap, etc),
// as well as different kinds of diagnostics (one check produces several errors,
// several replacement ranges in an error, etc).
class UseCharCheck : public ClangTidyCheck {
public:
UseCharCheck(StringRef CheckName, ClangTidyContext *Context)
: ClangTidyCheck(CheckName, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override {
using namespace ast_matchers;
Finder->addMatcher(varDecl(hasType(isInteger())).bind(BoundDecl), this);
}
void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl);
diag(VD->getLocStart(), "use char") << FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(VD->getLocStart(), VD->getLocStart()),
"char");
}
};
class IfFalseCheck : public ClangTidyCheck {
public:
IfFalseCheck(StringRef CheckName, ClangTidyContext *Context)
: ClangTidyCheck(CheckName, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override {
using namespace ast_matchers;
Finder->addMatcher(ifStmt().bind(BoundIf), this);
}
void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
auto *If = Result.Nodes.getNodeAs<IfStmt>(BoundIf);
auto *Cond = If->getCond();
SourceRange Range = Cond->getSourceRange();
if (auto *D = If->getConditionVariable()) {
Range = SourceRange(D->getLocStart(), D->getLocEnd());
}
diag(Range.getBegin(), "the cake is a lie") << FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(Range), "false");
}
};
class RefactorCheck : public ClangTidyCheck {
public:
RefactorCheck(StringRef CheckName, ClangTidyContext *Context)
: ClangTidyCheck(CheckName, Context), NamePattern("::$") {}
RefactorCheck(StringRef CheckName, ClangTidyContext *Context,
StringRef NamePattern)
: ClangTidyCheck(CheckName, Context), NamePattern(NamePattern) {}
virtual std::string newName(StringRef OldName) = 0;
void registerMatchers(ast_matchers::MatchFinder *Finder) final {
using namespace ast_matchers;
Finder->addMatcher(varDecl(matchesName(NamePattern)).bind(BoundDecl), this);
}
void check(const ast_matchers::MatchFinder::MatchResult &Result) final {
auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl);
std::string NewName = newName(VD->getName());
auto Diag = diag(VD->getLocation(), "refactor %0 into %1")
<< VD->getName() << NewName
<< FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(VD->getLocation(),
VD->getLocation()),
NewName);
class UsageVisitor : public RecursiveASTVisitor<UsageVisitor> {
public:
UsageVisitor(const ValueDecl *VD, StringRef NewName,
DiagnosticBuilder &Diag)
: VD(VD), NewName(NewName), Diag(Diag) {}
bool VisitDeclRefExpr(DeclRefExpr *E) {
if (const ValueDecl *D = E->getDecl()) {
if (VD->getCanonicalDecl() == D->getCanonicalDecl()) {
Diag << FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(E->getSourceRange()), NewName);
}
}
return RecursiveASTVisitor<UsageVisitor>::VisitDeclRefExpr(E);
}
private:
const ValueDecl *VD;
StringRef NewName;
DiagnosticBuilder &Diag;
};
UsageVisitor(VD, NewName, Diag)
.TraverseDecl(Result.Context->getTranslationUnitDecl());
}
protected:
const std::string NamePattern;
};
class StartsWithPotaCheck : public RefactorCheck {
public:
StartsWithPotaCheck(StringRef CheckName, ClangTidyContext *Context)
: RefactorCheck(CheckName, Context, "::pota") {}
std::string newName(StringRef OldName) override {
return "toma" + OldName.substr(4).str();
}
};
class EndsWithTatoCheck : public RefactorCheck {
public:
EndsWithTatoCheck(StringRef CheckName, ClangTidyContext *Context)
: RefactorCheck(CheckName, Context, "tato$") {}
std::string newName(StringRef OldName) override {
return OldName.substr(0, OldName.size() - 4).str() + "melo";
}
};
} // namespace
TEST(OverlappingReplacementsTest, UseCharCheckTest) {
const char Code[] =
R"(void f() {
int a = 0;
if (int b = 0) {
int c = a;
}
})";
const char CharFix[] =
R"(void f() {
char a = 0;
if (char b = 0) {
char c = a;
}
})";
EXPECT_EQ(CharFix, runCheckOnCode<UseCharCheck>(Code));
}
TEST(OverlappingReplacementsTest, IfFalseCheckTest) {
const char Code[] =
R"(void f() {
int potato = 0;
if (int b = 0) {
int c = potato;
} else if (true) {
int d = 0;
}
})";
const char IfFix[] =
R"(void f() {
int potato = 0;
if (false) {
int c = potato;
} else if (false) {
int d = 0;
}
})";
EXPECT_EQ(IfFix, runCheckOnCode<IfFalseCheck>(Code));
}
TEST(OverlappingReplacementsTest, StartsWithCheckTest) {
const char Code[] =
R"(void f() {
int a = 0;
int potato = 0;
if (int b = 0) {
int c = potato;
} else if (true) {
int d = 0;
}
})";
const char StartsFix[] =
R"(void f() {
int a = 0;
int tomato = 0;
if (int b = 0) {
int c = tomato;
} else if (true) {
int d = 0;
}
})";
EXPECT_EQ(StartsFix, runCheckOnCode<StartsWithPotaCheck>(Code));
}
TEST(OverlappingReplacementsTest, EndsWithCheckTest) {
const char Code[] =
R"(void f() {
int a = 0;
int potato = 0;
if (int b = 0) {
int c = potato;
} else if (true) {
int d = 0;
}
})";
const char EndsFix[] =
R"(void f() {
int a = 0;
int pomelo = 0;
if (int b = 0) {
int c = pomelo;
} else if (true) {
int d = 0;
}
})";
EXPECT_EQ(EndsFix, runCheckOnCode<EndsWithTatoCheck>(Code));
}
TEST(OverlappingReplacementTest, ReplacementsDoNotOverlap) {
std::string Res;
const char Code[] =
R"(void f() {
int potassium = 0;
if (true) {
int Potato = potassium;
}
})";
const char CharIfFix[] =
R"(void f() {
char potassium = 0;
if (false) {
char Potato = potassium;
}
})";
Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
EXPECT_EQ(CharIfFix, Res);
const char StartsEndsFix[] =
R"(void f() {
int tomassium = 0;
if (true) {
int Pomelo = tomassium;
}
})";
Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
EXPECT_EQ(StartsEndsFix, Res);
const char CharIfStartsEndsFix[] =
R"(void f() {
char tomassium = 0;
if (false) {
char Pomelo = tomassium;
}
})";
Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck,
EndsWithTatoCheck>(Code);
EXPECT_EQ(CharIfStartsEndsFix, Res);
}
TEST(OverlappingReplacementsTest, ReplacementInsideOtherReplacement) {
std::string Res;
const char Code[] =
R"(void f() {
if (char potato = 0) {
} else if (int a = 0) {
char potato = 0;
if (potato) potato;
}
})";
// Apply the UseCharCheck together with the IfFalseCheck.
//
// The 'If' fix contains the other, so that is the one that has to be applied.
// } else if (int a = 0) {
// ^^^ -> char
// ~~~~~~~~~ -> false
const char CharIfFix[] =
R"(void f() {
if (false) {
} else if (false) {
char potato = 0;
if (false) potato;
}
})";
Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
EXPECT_EQ(CharIfFix, Res);
Res = runCheckOnCode<IfFalseCheck, UseCharCheck>(Code);
EXPECT_EQ(CharIfFix, Res);
// Apply the IfFalseCheck with the StartsWithPotaCheck.
//
// The 'If' replacement is bigger here.
// if (char potato = 0) {
// ^^^^^^ -> tomato
// ~~~~~~~~~~~~~~~ -> false
//
// But the refactoring is the one that contains the other here:
// char potato = 0;
// ^^^^^^ -> tomato
// if (potato) potato;
// ^^^^^^ ^^^^^^ -> tomato, tomato
// ~~~~~~ -> false
const char IfStartsFix[] =
R"(void f() {
if (false) {
} else if (false) {
char tomato = 0;
if (tomato) tomato;
}
})";
Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);
EXPECT_EQ(IfStartsFix, Res);
Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck>(Code);
EXPECT_EQ(IfStartsFix, Res);
}
TEST(OverlappingReplacements, TwoReplacementsInsideOne) {
std::string Res;
const char Code[] =
R"(void f() {
if (int potato = 0) {
int a = 0;
}
})";
// The two smallest replacements should not be applied.
// if (int potato = 0) {
// ^^^^^^ -> tomato
// *** -> char
// ~~~~~~~~~~~~~~ -> false
// But other errors from the same checks should not be affected.
// int a = 0;
// *** -> char
const char Fix[] =
R"(void f() {
if (false) {
char a = 0;
}
})";
Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck>(Code);
EXPECT_EQ(Fix, Res);
Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck, UseCharCheck>(Code);
EXPECT_EQ(Fix, Res);
}
TEST(OverlappingReplacementsTest,
ApplyAtMostOneOfTheChangesWhenPartialOverlapping) {
std::string Res;
const char Code[] =
R"(void f() {
if (int potato = 0) {
int a = potato;
}
})";
// These two replacements overlap, but none of them is completely contained
// inside the other.
// if (int potato = 0) {
// ^^^^^^ -> tomato
// ~~~~~~~~~~~~~~ -> false
// int a = potato;
// ^^^^^^ -> tomato
//
// The 'StartsWithPotaCheck' fix has endpoints inside the 'IfFalseCheck' fix,
// so it is going to be set as inapplicable. The 'if' fix will be applied.
const char IfFix[] =
R"(void f() {
if (false) {
int a = potato;
}
})";
Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);
EXPECT_EQ(IfFix, Res);
}
TEST(OverlappingReplacementsTest, TwoErrorsHavePerfectOverlapping) {
std::string Res;
const char Code[] =
R"(void f() {
int potato = 0;
potato += potato * potato;
if (char a = potato) potato;
})";
// StartsWithPotaCheck will try to refactor 'potato' into 'tomato', and
// EndsWithTatoCheck will try to use 'pomelo'. Both fixes have the same set of
// ranges. This is a corner case of one error completely containing another:
// the other completely contains the first one as well. Both errors are
// discarded.
Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
EXPECT_EQ(Code, Res);
}
} // namespace test
} // namespace tidy
} // namespace clang