brintos

brintos / llvm-project-archived public Read only

0
0
Text · 11.0 KiB · 3aaf549 Raw
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