llvm-project/clang-tools-extra/unittests/clang-tidy/OverlappingReplacementsTest.cpp
Chandler Carruth 2946cd7010 Update the file headers across all of the LLVM projects in the monorepo
to reflect the new license.

We understand that people may be surprised that we're moving the header
entirely to discuss the new license. We checked this carefully with the
Foundation's lawyer and we believe this is the correct approach.

Essentially, all code in the project is now made available by the LLVM
project under our new license, so you will see that the license headers
include that license only. Some of our contributors have contributed
code under our old license, and accordingly, we have retained a copy of
our old license notice in the top-level files in each project and
repository.

llvm-svn: 351636
2019-01-19 08:50:56 +00:00

409 lines
11 KiB
C++

//===---- OverlappingReplacementsTest.cpp - clang-tidy --------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#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->getBeginLoc(), "use char") << FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(VD->getBeginLoc(), VD->getBeginLoc()),
"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->getBeginLoc(), D->getEndLoc());
}
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