1 //===---- OverlappingReplacementsTest.cpp - clang-tidy --------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "ClangTidyTest.h" 11 #include "clang/AST/RecursiveASTVisitor.h" 12 #include "gtest/gtest.h" 13 14 namespace clang { 15 namespace tidy { 16 namespace test { 17 namespace { 18 19 const char BoundDecl[] = "decl"; 20 const char BoundIf[] = "if"; 21 22 // We define a reduced set of very small checks that allow to test different 23 // overlapping situations (no overlapping, replacements partially overlap, etc), 24 // as well as different kinds of diagnostics (one check produces several errors, 25 // several replacement ranges in an error, etc). 26 class UseCharCheck : public ClangTidyCheck { 27 public: 28 UseCharCheck(StringRef CheckName, ClangTidyContext *Context) 29 : ClangTidyCheck(CheckName, Context) {} 30 void registerMatchers(ast_matchers::MatchFinder *Finder) override { 31 using namespace ast_matchers; 32 Finder->addMatcher(varDecl(hasType(isInteger())).bind(BoundDecl), this); 33 } 34 void check(const ast_matchers::MatchFinder::MatchResult &Result) override { 35 auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl); 36 diag(VD->getLocStart(), "use char") << FixItHint::CreateReplacement( 37 CharSourceRange::getTokenRange(VD->getLocStart(), VD->getLocStart()), 38 "char"); 39 } 40 }; 41 42 class IfFalseCheck : public ClangTidyCheck { 43 public: 44 IfFalseCheck(StringRef CheckName, ClangTidyContext *Context) 45 : ClangTidyCheck(CheckName, Context) {} 46 void registerMatchers(ast_matchers::MatchFinder *Finder) override { 47 using namespace ast_matchers; 48 Finder->addMatcher(ifStmt().bind(BoundIf), this); 49 } 50 void check(const ast_matchers::MatchFinder::MatchResult &Result) override { 51 auto *If = Result.Nodes.getNodeAs<IfStmt>(BoundIf); 52 auto *Cond = If->getCond(); 53 SourceRange Range = Cond->getSourceRange(); 54 if (auto *D = If->getConditionVariable()) { 55 Range = SourceRange(D->getLocStart(), D->getLocEnd()); 56 } 57 diag(Range.getBegin(), "the cake is a lie") << FixItHint::CreateReplacement( 58 CharSourceRange::getTokenRange(Range), "false"); 59 } 60 }; 61 62 class RefactorCheck : public ClangTidyCheck { 63 public: 64 RefactorCheck(StringRef CheckName, ClangTidyContext *Context) 65 : ClangTidyCheck(CheckName, Context), NamePattern("::$") {} 66 RefactorCheck(StringRef CheckName, ClangTidyContext *Context, 67 StringRef NamePattern) 68 : ClangTidyCheck(CheckName, Context), NamePattern(NamePattern) {} 69 virtual std::string newName(StringRef OldName) = 0; 70 71 void registerMatchers(ast_matchers::MatchFinder *Finder) final { 72 using namespace ast_matchers; 73 Finder->addMatcher(varDecl(matchesName(NamePattern)).bind(BoundDecl), this); 74 } 75 76 void check(const ast_matchers::MatchFinder::MatchResult &Result) final { 77 auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl); 78 std::string NewName = newName(VD->getName()); 79 80 auto Diag = diag(VD->getLocation(), "refactor") 81 << 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 111 protected: 112 const std::string NamePattern; 113 }; 114 115 class StartsWithPotaCheck : public RefactorCheck { 116 public: 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 125 class EndsWithTatoCheck : public RefactorCheck { 126 public: 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 } // namespace 136 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; 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 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; 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 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; 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 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; 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 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; 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 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; 279 } 280 })"; 281 282 // Apply the UseCharCheck together with the IfFalseCheck. 283 // 284 // The 'If' fix is bigger, 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; 294 } 295 })"; 296 Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code); 297 // FIXME: EXPECT_EQ(CharIfFix, Res); 298 299 // Apply the IfFalseCheck with the StartsWithPotaCheck. 300 // 301 // The 'If' replacement is bigger here. 302 // if (char potato = 0) { 303 // ^^^^^^ -> tomato 304 // ~~~~~~~~~~~~~~~ -> false 305 // 306 // But the refactoring is bigger here: 307 // char potato = 0; 308 // ^^^^^^ -> tomato 309 // if (potato) potato; 310 // ^^^^^^ ^^^^^^ -> tomato, tomato 311 // ~~~~~~ -> false 312 const char IfStartsFix[] = 313 R"(void f() { 314 if (false) { 315 } else if (false) { 316 char tomato = 0; 317 if (tomato) tomato; 318 } 319 })"; 320 Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code); 321 // FIXME: EXPECT_EQ(IfStartsFix, Res); 322 323 // Silence warnings. 324 (void)CharIfFix; 325 (void)IfStartsFix; 326 } 327 328 TEST(OverlappingReplacementsTest, ApplyFullErrorOrNothingWhenOverlapping) { 329 std::string Res; 330 const char Code[] = 331 R"(void f() { 332 int potato = 0; 333 potato += potato * potato; 334 if (char this_name_make_this_if_really_long = potato) potato; 335 })"; 336 337 // StartsWithPotaCheck will try to refactor 'potato' into 'tomato', 338 // and EndsWithTatoCheck will try to use 'pomelo'. We have to apply 339 // either all conversions from one check, or all from the other. 340 const char StartsFix[] = 341 R"(void f() { 342 int tomato = 0; 343 tomato += tomato * tomato; 344 if (char this_name_make_this_if_really_long = tomato) tomato; 345 })"; 346 const char EndsFix[] = 347 R"(void f() { 348 int pomelo = 0; 349 pomelo += pomelo * pomelo; 350 if (char this_name_make_this_if_really_long = pomelo) pomelo; 351 })"; 352 // In case of overlapping, we will prioritize the biggest fix. However, these 353 // two fixes have the same size and position, so we don't know yet which one 354 // will have preference. 355 Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code); 356 // FIXME: EXPECT_TRUE(Res == StartsFix || Res == EndsFix); 357 358 // StartsWithPotaCheck will try to refactor 'potato' into 'tomato', but 359 // replacing the 'if' condition is a bigger change than all the refactoring 360 // changes together (48 vs 36), so this is the one that is going to be 361 // applied. 362 const char IfFix[] = 363 R"(void f() { 364 int potato = 0; 365 potato += potato * potato; 366 if (true) potato; 367 })"; 368 Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck>(Code); 369 // FIXME: EXPECT_EQ(IfFix, Res); 370 371 // Silence warnings. 372 (void)StartsFix; 373 (void)EndsFix; 374 (void)IfFix; 375 } 376 377 } // namespace test 378 } // namespace tidy 379 } // namespace clang 380