409 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "ClangTidyTest.h"10#include "clang/AST/RecursiveASTVisitor.h"11#include "gtest/gtest.h"12 13namespace clang {14namespace tidy {15namespace test {16namespace {17 18const char BoundDecl[] = "decl";19const char BoundIf[] = "if";20 21// We define a reduced set of very small checks that allow to test different22// 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).25class UseCharCheck : public ClangTidyCheck {26public: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);32 }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");38 }39};40 41class IfFalseCheck : public ClangTidyCheck {42public: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);48 }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());55 }56 diag(Range.getBegin(), "the cake is a lie") << FixItHint::CreateReplacement(57 CharSourceRange::getTokenRange(Range), "false");58 }59};60 61class RefactorCheck : public ClangTidyCheck {62public: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;69 70 void registerMatchers(ast_matchers::MatchFinder *Finder) final {71 using namespace ast_matchers;72 Finder->addMatcher(varDecl(matchesName(NamePattern)).bind(BoundDecl), this);73 }74 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());78 79 auto Diag = diag(VD->getLocation(), "refactor %0 into %1")80 << VD->getName() << NewName81 << FixItHint::CreateReplacement(82 CharSourceRange::getTokenRange(VD->getLocation(),83 VD->getLocation()),84 NewName);85 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);96 }97 }98 return RecursiveASTVisitor<UsageVisitor>::VisitDeclRefExpr(E);99 }100 101 private:102 const ValueDecl *VD;103 StringRef NewName;104 DiagnosticBuilder &Diag;105 };106 107 UsageVisitor(VD, NewName, Diag)108 .TraverseDecl(Result.Context->getTranslationUnitDecl());109 }110 111protected:112 const std::string NamePattern;113};114 115class StartsWithPotaCheck : public RefactorCheck {116public:117 StartsWithPotaCheck(StringRef CheckName, ClangTidyContext *Context)118 : RefactorCheck(CheckName, Context, "::pota") {}119 120 std::string newName(StringRef OldName) override {121 return "toma" + OldName.substr(4).str();122 }123};124 125class EndsWithTatoCheck : public RefactorCheck {126public:127 EndsWithTatoCheck(StringRef CheckName, ClangTidyContext *Context)128 : RefactorCheck(CheckName, Context, "tato$") {}129 130 std::string newName(StringRef OldName) override {131 return OldName.substr(0, OldName.size() - 4).str() + "melo";132 }133};134 135} // namespace136 137TEST(OverlappingReplacementsTest, UseCharCheckTest) {138 const char Code[] =139 R"(void f() {140 int a = 0;141 if (int b = 0) {142 int c = a;143 }144})";145 146 const char CharFix[] =147 R"(void f() {148 char a = 0;149 if (char b = 0) {150 char c = a;151 }152})";153 EXPECT_EQ(CharFix, runCheckOnCode<UseCharCheck>(Code));154}155 156TEST(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;164 }165})";166 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;174 }175})";176 EXPECT_EQ(IfFix, runCheckOnCode<IfFalseCheck>(Code));177}178 179TEST(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;188 }189})";190 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;199 }200})";201 EXPECT_EQ(StartsFix, runCheckOnCode<StartsWithPotaCheck>(Code));202}203 204TEST(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;213 }214})";215 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;224 }225})";226 EXPECT_EQ(EndsFix, runCheckOnCode<EndsWithTatoCheck>(Code));227}228 229TEST(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;236 }237})";238 239 const char CharIfFix[] =240 R"(void f() {241 char potassium = 0;242 if (false) {243 char Potato = potassium;244 }245})";246 Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);247 EXPECT_EQ(CharIfFix, Res);248 249 const char StartsEndsFix[] =250 R"(void f() {251 int tomassium = 0;252 if (true) {253 int Pomelo = tomassium;254 }255})";256 Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);257 EXPECT_EQ(StartsEndsFix, Res);258 259 const char CharIfStartsEndsFix[] =260 R"(void f() {261 char tomassium = 0;262 if (false) {263 char Pomelo = tomassium;264 }265})";266 Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck,267 EndsWithTatoCheck>(Code);268 EXPECT_EQ(CharIfStartsEndsFix, Res);269}270 271TEST(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;279 }280})";281 282 // Apply the UseCharCheck together with the IfFalseCheck.283 //284 // The 'If' fix contains the other, so that is the one that has to be applied.285 // } else if (int a = 0) {286 // ^^^ -> char287 // ~~~~~~~~~ -> false288 const char CharIfFix[] =289 R"(void f() {290 if (false) {291 } else if (false) {292 char potato = 0;293 if (false) potato;294 }295})";296 Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);297 EXPECT_EQ(CharIfFix, Res);298 Res = runCheckOnCode<IfFalseCheck, UseCharCheck>(Code);299 EXPECT_EQ(CharIfFix, Res);300 301 // Apply the IfFalseCheck with the StartsWithPotaCheck.302 //303 // The 'If' replacement is bigger here.304 // if (char potato = 0) {305 // ^^^^^^ -> tomato306 // ~~~~~~~~~~~~~~~ -> false307 //308 // But the refactoring is the one that contains the other here:309 // char potato = 0;310 // ^^^^^^ -> tomato311 // if (potato) potato;312 // ^^^^^^ ^^^^^^ -> tomato, tomato313 // ~~~~~~ -> false314 const char IfStartsFix[] =315 R"(void f() {316 if (false) {317 } else if (false) {318 char tomato = 0;319 if (tomato) tomato;320 }321})";322 Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);323 EXPECT_EQ(IfStartsFix, Res);324 Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck>(Code);325 EXPECT_EQ(IfStartsFix, Res);326}327 328TEST(OverlappingReplacements, TwoReplacementsInsideOne) {329 std::string Res;330 const char Code[] =331 R"(void f() {332 if (int potato = 0) {333 int a = 0;334 }335})";336 337 // The two smallest replacements should not be applied.338 // if (int potato = 0) {339 // ^^^^^^ -> tomato340 // *** -> char341 // ~~~~~~~~~~~~~~ -> false342 // But other errors from the same checks should not be affected.343 // int a = 0;344 // *** -> char345 const char Fix[] =346 R"(void f() {347 if (false) {348 char a = 0;349 }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);355}356 357TEST(OverlappingReplacementsTest,358 ApplyAtMostOneOfTheChangesWhenPartialOverlapping) {359 std::string Res;360 const char Code[] =361 R"(void f() {362 if (int potato = 0) {363 int a = potato;364 }365})";366 367 // These two replacements overlap, but none of them is completely contained368 // inside the other.369 // if (int potato = 0) {370 // ^^^^^^ -> tomato371 // ~~~~~~~~~~~~~~ -> false372 // int a = potato;373 // ^^^^^^ -> tomato374 //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;381 }382})";383 Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);384 EXPECT_EQ(IfFix, Res);385}386 387TEST(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})";395 396 // StartsWithPotaCheck will try to refactor 'potato' into 'tomato', and397 // EndsWithTatoCheck will try to use 'pomelo'. Both fixes have the same set of398 // ranges. This is a corner case of one error completely containing another:399 // the other completely contains the first one as well. Both errors are400 // discarded.401 402 Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);403 EXPECT_EQ(Code, Res);404}405 406} // namespace test407} // namespace tidy408} // namespace clang409