brintos

brintos / llvm-project-archived public Read only

0
0
Text · 14.0 KiB · fc43f25 Raw
401 lines · cpp
1//===---- TransformerClangTidyCheckTest.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 "../clang-tidy/utils/TransformerClangTidyCheck.h"10#include "ClangTidyTest.h"11#include "clang/ASTMatchers/ASTMatchers.h"12#include "clang/Tooling/Transformer/RangeSelector.h"13#include "clang/Tooling/Transformer/RewriteRule.h"14#include "clang/Tooling/Transformer/Stencil.h"15#include "clang/Tooling/Transformer/Transformer.h"16#include "gmock/gmock.h"17#include "gtest/gtest.h"18#include <optional>19 20namespace clang {21namespace tidy {22namespace utils {23namespace {24using namespace ::clang::ast_matchers;25 26using transformer::cat;27using transformer::change;28using transformer::IncludeFormat;29using transformer::makeRule;30using transformer::node;31using transformer::noopEdit;32using transformer::note;33using transformer::RewriteRuleWith;34using transformer::RootID;35using transformer::statement;36 37// Invert the code of an if-statement, while maintaining its semantics.38RewriteRuleWith<std::string> invertIf() {39  StringRef C = "C", T = "T", E = "E";40  RewriteRuleWith<std::string> Rule = makeRule(41      ifStmt(hasCondition(expr().bind(C)), hasThen(stmt().bind(T)),42             hasElse(stmt().bind(E))),43      change(statement(RootID), cat("if(!(", node(std::string(C)), ")) ",44                                    statement(std::string(E)), " else ",45                                    statement(std::string(T)))),46      cat("negate condition and reverse `then` and `else` branches"));47  return Rule;48}49 50class IfInverterCheck : public TransformerClangTidyCheck {51public:52  IfInverterCheck(StringRef Name, ClangTidyContext *Context)53      : TransformerClangTidyCheck(invertIf(), Name, Context) {}54};55 56// Basic test of using a rewrite rule as a ClangTidy.57TEST(TransformerClangTidyCheckTest, Basic) {58  const std::string Input = R"cc(59    void log(const char* msg);60    void foo() {61      if (10 > 1.0)62        log("oh no!");63      else64        log("ok");65    }66  )cc";67  const std::string Expected = R"(68    void log(const char* msg);69    void foo() {70      if(!(10 > 1.0)) log("ok"); else log("oh no!");71    }72  )";73  EXPECT_EQ(Expected, test::runCheckOnCode<IfInverterCheck>(Input));74}75 76TEST(TransformerClangTidyCheckTest, DiagnosticsCorrectlyGenerated) {77  class DiagOnlyCheck : public TransformerClangTidyCheck {78  public:79    DiagOnlyCheck(StringRef Name, ClangTidyContext *Context)80        : TransformerClangTidyCheck(81              makeRule(returnStmt(), noopEdit(node(RootID)), cat("message")),82              Name, Context) {}83  };84  std::string Input = "int h() { return 5; }";85  std::vector<ClangTidyError> Errors;86  EXPECT_EQ(Input, test::runCheckOnCode<DiagOnlyCheck>(Input, &Errors));87  EXPECT_EQ(Errors.size(), 1U);88  EXPECT_EQ(Errors[0].Message.Message, "message");89  EXPECT_THAT(Errors[0].Message.Ranges, testing::IsEmpty());90  EXPECT_THAT(Errors[0].Notes, testing::IsEmpty());91 92  // The diagnostic is anchored to the match, "return 5".93  EXPECT_EQ(Errors[0].Message.FileOffset, 10U);94}95 96transformer::ASTEdit noReplacementEdit(transformer::RangeSelector Target) {97  transformer::ASTEdit E;98  E.TargetRange = std::move(Target);99  return E;100}101 102TEST(TransformerClangTidyCheckTest, EmptyReplacement) {103  class DiagOnlyCheck : public TransformerClangTidyCheck {104  public:105    DiagOnlyCheck(StringRef Name, ClangTidyContext *Context)106        : TransformerClangTidyCheck(107              makeRule(returnStmt(), edit(noReplacementEdit(node(RootID))),108                       cat("message")),109              Name, Context) {}110  };111  std::string Input = "int h() { return 5; }";112  std::vector<ClangTidyError> Errors;113  EXPECT_EQ("int h() { }", test::runCheckOnCode<DiagOnlyCheck>(Input, &Errors));114  EXPECT_EQ(Errors.size(), 1U);115  EXPECT_EQ(Errors[0].Message.Message, "message");116  EXPECT_THAT(Errors[0].Message.Ranges, testing::IsEmpty());117 118  // The diagnostic is anchored to the match, "return 5".119  EXPECT_EQ(Errors[0].Message.FileOffset, 10U);120}121 122TEST(TransformerClangTidyCheckTest, NotesCorrectlyGenerated) {123  class DiagAndNoteCheck : public TransformerClangTidyCheck {124  public:125    DiagAndNoteCheck(StringRef Name, ClangTidyContext *Context)126        : TransformerClangTidyCheck(127              makeRule(returnStmt(),128                       note(node(RootID), cat("some note")),129                       cat("message")),130              Name, Context) {}131  };132  std::string Input = "int h() { return 5; }";133  std::vector<ClangTidyError> Errors;134  EXPECT_EQ(Input, test::runCheckOnCode<DiagAndNoteCheck>(Input, &Errors));135  EXPECT_EQ(Errors.size(), 1U);136  EXPECT_EQ(Errors[0].Notes.size(), 1U);137  EXPECT_EQ(Errors[0].Notes[0].Message, "some note");138 139  // The note is anchored to the match, "return 5".140  EXPECT_EQ(Errors[0].Notes[0].FileOffset, 10U);141}142 143TEST(TransformerClangTidyCheckTest, DiagnosticMessageEscaped) {144  class GiveDiagWithPercentSymbol : public TransformerClangTidyCheck {145  public:146    GiveDiagWithPercentSymbol(StringRef Name, ClangTidyContext *Context)147        : TransformerClangTidyCheck(makeRule(returnStmt(),148                                             noopEdit(node(RootID)),149                                             cat("bad code: x % y % z")),150                                    Name, Context) {}151  };152  std::string Input = "int somecode() { return 0; }";153  std::vector<ClangTidyError> Errors;154  EXPECT_EQ(Input,155            test::runCheckOnCode<GiveDiagWithPercentSymbol>(Input, &Errors));156  ASSERT_EQ(Errors.size(), 1U);157  // The message stored in this field shouldn't include escaped percent signs,158  // because the diagnostic printer should have _unescaped_ them when processing159  // the diagnostic. The only behavior observable/verifiable by the test is that160  // the presence of the '%' doesn't crash Clang.161  EXPECT_EQ(Errors[0].Message.Message, "bad code: x % y % z");162}163 164class IntLitCheck : public TransformerClangTidyCheck {165public:166  IntLitCheck(StringRef Name, ClangTidyContext *Context)167      : TransformerClangTidyCheck(168            makeRule(integerLiteral(), change(cat("LIT")), cat("no message")),169            Name, Context) {}170};171 172// Tests that two changes in a single macro expansion do not lead to conflicts173// in applying the changes.174TEST(TransformerClangTidyCheckTest, TwoChangesInOneMacroExpansion) {175  const std::string Input = R"cc(176#define PLUS(a,b) (a) + (b)177    int f() { return PLUS(3, 4); }178  )cc";179  const std::string Expected = R"cc(180#define PLUS(a,b) (a) + (b)181    int f() { return PLUS(LIT, LIT); }182  )cc";183 184  EXPECT_EQ(Expected, test::runCheckOnCode<IntLitCheck>(Input));185}186 187class BinOpCheck : public TransformerClangTidyCheck {188public:189  BinOpCheck(StringRef Name, ClangTidyContext *Context)190      : TransformerClangTidyCheck(191            makeRule(192                binaryOperator(hasOperatorName("+"), hasRHS(expr().bind("r"))),193                change(node("r"), cat("RIGHT")), cat("no message")),194            Name, Context) {}195};196 197// Tests case where the rule's match spans both source from the macro and its198// argument, while the change spans only the argument AND there are two such199// matches. We verify that both replacements succeed.200TEST(TransformerClangTidyCheckTest, TwoMatchesInMacroExpansion) {201  const std::string Input = R"cc(202#define M(a,b) (1 + a) * (1 + b)203    int f() { return M(3, 4); }204  )cc";205  const std::string Expected = R"cc(206#define M(a,b) (1 + a) * (1 + b)207    int f() { return M(RIGHT, RIGHT); }208  )cc";209 210  EXPECT_EQ(Expected, test::runCheckOnCode<BinOpCheck>(Input));211}212 213// A trivial rewrite-rule generator that requires Objective-C code.214std::optional<RewriteRuleWith<std::string>>215needsObjC(const LangOptions &LangOpts,216          const ClangTidyCheck::OptionsView &Options) {217  if (!LangOpts.ObjC)218    return std::nullopt;219  return makeRule(clang::ast_matchers::functionDecl(),220                  change(cat("void changed() {}")), cat("no message"));221}222 223class NeedsObjCCheck : public TransformerClangTidyCheck {224public:225  NeedsObjCCheck(StringRef Name, ClangTidyContext *Context)226      : TransformerClangTidyCheck(needsObjC, Name, Context) {}227};228 229// Verify that the check only rewrites the code when the input is Objective-C.230TEST(TransformerClangTidyCheckTest, DisableByLang) {231  const std::string Input = "void log() {}";232  EXPECT_EQ(Input,233            test::runCheckOnCode<NeedsObjCCheck>(Input, nullptr, "input.cc"));234 235  EXPECT_EQ("void changed() {}",236            test::runCheckOnCode<NeedsObjCCheck>(Input, nullptr, "input.mm"));237}238 239// A trivial rewrite rule generator that checks config options.240std::optional<RewriteRuleWith<std::string>>241noSkip(const LangOptions &LangOpts,242       const ClangTidyCheck::OptionsView &Options) {243  if (Options.get("Skip", "false") == "true")244    return std::nullopt;245  return makeRule(clang::ast_matchers::functionDecl(),246                  changeTo(cat("void nothing();")), cat("no message"));247}248 249class ConfigurableCheck : public TransformerClangTidyCheck {250public:251  ConfigurableCheck(StringRef Name, ClangTidyContext *Context)252      : TransformerClangTidyCheck(noSkip, Name, Context) {}253};254 255// Tests operation with config option "Skip" set to true and false.256TEST(TransformerClangTidyCheckTest, DisableByConfig) {257  const std::string Input = "void log(int);";258  const std::string Expected = "void nothing();";259  ClangTidyOptions Options;260 261  Options.CheckOptions["test-check-0.Skip"] = "true";262  EXPECT_EQ(Input, test::runCheckOnCode<ConfigurableCheck>(263                       Input, nullptr, "input.cc", {}, Options));264 265  Options.CheckOptions["test-check-0.Skip"] = "false";266  EXPECT_EQ(Expected, test::runCheckOnCode<ConfigurableCheck>(267                          Input, nullptr, "input.cc", {}, Options));268}269 270RewriteRuleWith<std::string> replaceCall(IncludeFormat Format) {271  using namespace ::clang::ast_matchers;272  RewriteRuleWith<std::string> Rule =273      makeRule(callExpr(callee(functionDecl(hasName("f")))),274               change(cat("other()")), cat("no message"));275  addInclude(Rule, "clang/OtherLib.h", Format);276  return Rule;277}278 279template <IncludeFormat Format>280class IncludeCheck : public TransformerClangTidyCheck {281public:282  IncludeCheck(StringRef Name, ClangTidyContext *Context)283      : TransformerClangTidyCheck(replaceCall(Format), Name, Context) {}284};285 286TEST(TransformerClangTidyCheckTest, AddIncludeQuoted) {287 288  std::string Input = R"cc(289    int f(int x);290    int h(int x) { return f(x); }291  )cc";292  std::string Expected = R"cc(#include "clang/OtherLib.h"293 294 295    int f(int x);296    int h(int x) { return other(); }297  )cc";298 299  EXPECT_EQ(Expected,300            test::runCheckOnCode<IncludeCheck<IncludeFormat::Quoted>>(Input));301}302 303TEST(TransformerClangTidyCheckTest, AddIncludeAngled) {304  std::string Input = R"cc(305    int f(int x);306    int h(int x) { return f(x); }307  )cc";308  std::string Expected = R"cc(#include <clang/OtherLib.h>309 310 311    int f(int x);312    int h(int x) { return other(); }313  )cc";314 315  EXPECT_EQ(Expected,316            test::runCheckOnCode<IncludeCheck<IncludeFormat::Angled>>(Input));317}318 319class IncludeOrderCheck : public TransformerClangTidyCheck {320  static RewriteRuleWith<std::string> rule() {321    using namespace ::clang::ast_matchers;322    RewriteRuleWith<std::string> Rule = transformer::makeRule(323        integerLiteral(), change(cat("5")), cat("no message"));324    addInclude(Rule, "bar.h", IncludeFormat::Quoted);325    return Rule;326  }327 328public:329  IncludeOrderCheck(StringRef Name, ClangTidyContext *Context)330      : TransformerClangTidyCheck(rule(), Name, Context) {}331};332 333TEST(TransformerClangTidyCheckTest, AddIncludeObeysSortStyleLocalOption) {334  std::string Input = R"cc(#include "input.h"335int h(int x) { return 3; })cc";336 337  std::string TreatsAsLibraryHeader = R"cc(#include "input.h"338 339#include "bar.h"340int h(int x) { return 5; })cc";341 342  std::string TreatsAsNormalHeader = R"cc(#include "bar.h"343#include "input.h"344int h(int x) { return 5; })cc";345 346  ClangTidyOptions Options;347  std::map<StringRef, StringRef> PathsToContent = {{"input.h", "\n"}};348  Options.CheckOptions["test-check-0.IncludeStyle"] = "llvm";349  EXPECT_EQ(TreatsAsLibraryHeader,350            test::runCheckOnCode<IncludeOrderCheck>(351                Input, nullptr, "inputTest.cpp", {}, Options, PathsToContent));352  EXPECT_EQ(TreatsAsNormalHeader,353            test::runCheckOnCode<IncludeOrderCheck>(354                Input, nullptr, "input_test.cpp", {}, Options, PathsToContent));355 356  Options.CheckOptions["test-check-0.IncludeStyle"] = "google";357  EXPECT_EQ(TreatsAsNormalHeader,358            test::runCheckOnCode<IncludeOrderCheck>(359                Input, nullptr, "inputTest.cc", {}, Options, PathsToContent));360  EXPECT_EQ(TreatsAsLibraryHeader,361            test::runCheckOnCode<IncludeOrderCheck>(362                Input, nullptr, "input_test.cc", {}, Options, PathsToContent));363}364 365TEST(TransformerClangTidyCheckTest, AddIncludeObeysSortStyleGlobalOption) {366  std::string Input = R"cc(#include "input.h"367int h(int x) { return 3; })cc";368 369  std::string TreatsAsLibraryHeader = R"cc(#include "input.h"370 371#include "bar.h"372int h(int x) { return 5; })cc";373 374  std::string TreatsAsNormalHeader = R"cc(#include "bar.h"375#include "input.h"376int h(int x) { return 5; })cc";377 378  ClangTidyOptions Options;379  std::map<StringRef, StringRef> PathsToContent = {{"input.h", "\n"}};380  Options.CheckOptions["IncludeStyle"] = "llvm";381  EXPECT_EQ(TreatsAsLibraryHeader,382            test::runCheckOnCode<IncludeOrderCheck>(383                Input, nullptr, "inputTest.cpp", {}, Options, PathsToContent));384  EXPECT_EQ(TreatsAsNormalHeader,385            test::runCheckOnCode<IncludeOrderCheck>(386                Input, nullptr, "input_test.cpp", {}, Options, PathsToContent));387 388  Options.CheckOptions["IncludeStyle"] = "google";389  EXPECT_EQ(TreatsAsNormalHeader,390            test::runCheckOnCode<IncludeOrderCheck>(391                Input, nullptr, "inputTest.cc", {}, Options, PathsToContent));392  EXPECT_EQ(TreatsAsLibraryHeader,393            test::runCheckOnCode<IncludeOrderCheck>(394                Input, nullptr, "input_test.cc", {}, Options, PathsToContent));395}396 397} // namespace398} // namespace utils399} // namespace tidy400} // namespace clang401