//===---- TransformerClangTidyCheckTest.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 "../clang-tidy/utils/TransformerClangTidyCheck.h" #include "ClangTidyTest.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Transformer/RangeSelector.h" #include "clang/Tooling/Transformer/Stencil.h" #include "clang/Tooling/Transformer/Transformer.h" #include "gtest/gtest.h" namespace clang { namespace tidy { namespace utils { namespace { using namespace ::clang::ast_matchers; using transformer::cat; using transformer::change; using transformer::IncludeFormat; using transformer::makeRule; using transformer::node; using transformer::RewriteRule; using transformer::statement; // Invert the code of an if-statement, while maintaining its semantics. RewriteRule invertIf() { StringRef C = "C", T = "T", E = "E"; RewriteRule Rule = makeRule(ifStmt(hasCondition(expr().bind(C)), hasThen(stmt().bind(T)), hasElse(stmt().bind(E))), change(statement(std::string(RewriteRule::RootID)), cat("if(!(", node(std::string(C)), ")) ", statement(std::string(E)), " else ", statement(std::string(T)))), cat("negate condition and reverse `then` and `else` branches")); return Rule; } class IfInverterCheck : public TransformerClangTidyCheck { public: IfInverterCheck(StringRef Name, ClangTidyContext *Context) : TransformerClangTidyCheck(invertIf(), Name, Context) {} }; // Basic test of using a rewrite rule as a ClangTidy. TEST(TransformerClangTidyCheckTest, Basic) { const std::string Input = R"cc( void log(const char* msg); void foo() { if (10 > 1.0) log("oh no!"); else log("ok"); } )cc"; const std::string Expected = R"( void log(const char* msg); void foo() { if(!(10 > 1.0)) log("ok"); else log("oh no!"); } )"; EXPECT_EQ(Expected, test::runCheckOnCode(Input)); } class IntLitCheck : public TransformerClangTidyCheck { public: IntLitCheck(StringRef Name, ClangTidyContext *Context) : TransformerClangTidyCheck( makeRule(integerLiteral(), change(cat("LIT")), cat("no message")), Name, Context) {} }; // Tests that two changes in a single macro expansion do not lead to conflicts // in applying the changes. TEST(TransformerClangTidyCheckTest, TwoChangesInOneMacroExpansion) { const std::string Input = R"cc( #define PLUS(a,b) (a) + (b) int f() { return PLUS(3, 4); } )cc"; const std::string Expected = R"cc( #define PLUS(a,b) (a) + (b) int f() { return PLUS(LIT, LIT); } )cc"; EXPECT_EQ(Expected, test::runCheckOnCode(Input)); } class BinOpCheck : public TransformerClangTidyCheck { public: BinOpCheck(StringRef Name, ClangTidyContext *Context) : TransformerClangTidyCheck( makeRule( binaryOperator(hasOperatorName("+"), hasRHS(expr().bind("r"))), change(node("r"), cat("RIGHT")), cat("no message")), Name, Context) {} }; // Tests case where the rule's match spans both source from the macro and its // argument, while the change spans only the argument AND there are two such // matches. We verify that both replacements succeed. TEST(TransformerClangTidyCheckTest, TwoMatchesInMacroExpansion) { const std::string Input = R"cc( #define M(a,b) (1 + a) * (1 + b) int f() { return M(3, 4); } )cc"; const std::string Expected = R"cc( #define M(a,b) (1 + a) * (1 + b) int f() { return M(RIGHT, RIGHT); } )cc"; EXPECT_EQ(Expected, test::runCheckOnCode(Input)); } // A trivial rewrite-rule generator that requires Objective-C code. Optional needsObjC(const LangOptions &LangOpts, const ClangTidyCheck::OptionsView &Options) { if (!LangOpts.ObjC) return None; return makeRule(clang::ast_matchers::functionDecl(), change(cat("void changed() {}")), cat("no message")); } class NeedsObjCCheck : public TransformerClangTidyCheck { public: NeedsObjCCheck(StringRef Name, ClangTidyContext *Context) : TransformerClangTidyCheck(needsObjC, Name, Context) {} }; // Verify that the check only rewrites the code when the input is Objective-C. TEST(TransformerClangTidyCheckTest, DisableByLang) { const std::string Input = "void log() {}"; EXPECT_EQ(Input, test::runCheckOnCode(Input, nullptr, "input.cc")); EXPECT_EQ("void changed() {}", test::runCheckOnCode(Input, nullptr, "input.mm")); } // A trivial rewrite rule generator that checks config options. Optional noSkip(const LangOptions &LangOpts, const ClangTidyCheck::OptionsView &Options) { if (Options.get("Skip", "false") == "true") return None; return makeRule(clang::ast_matchers::functionDecl(), changeTo(cat("void nothing();")), cat("no message")); } class ConfigurableCheck : public TransformerClangTidyCheck { public: ConfigurableCheck(StringRef Name, ClangTidyContext *Context) : TransformerClangTidyCheck(noSkip, Name, Context) {} }; // Tests operation with config option "Skip" set to true and false. TEST(TransformerClangTidyCheckTest, DisableByConfig) { const std::string Input = "void log(int);"; const std::string Expected = "void nothing();"; ClangTidyOptions Options; Options.CheckOptions["test-check-0.Skip"] = "true"; EXPECT_EQ(Input, test::runCheckOnCode( Input, nullptr, "input.cc", None, Options)); Options.CheckOptions["test-check-0.Skip"] = "false"; EXPECT_EQ(Expected, test::runCheckOnCode( Input, nullptr, "input.cc", None, Options)); } RewriteRule replaceCall(IncludeFormat Format) { using namespace ::clang::ast_matchers; RewriteRule Rule = makeRule(callExpr(callee(functionDecl(hasName("f")))), change(cat("other()")), cat("no message")); addInclude(Rule, "clang/OtherLib.h", Format); return Rule; } template class IncludeCheck : public TransformerClangTidyCheck { public: IncludeCheck(StringRef Name, ClangTidyContext *Context) : TransformerClangTidyCheck(replaceCall(Format), Name, Context) {} }; TEST(TransformerClangTidyCheckTest, AddIncludeQuoted) { std::string Input = R"cc( int f(int x); int h(int x) { return f(x); } )cc"; std::string Expected = R"cc(#include "clang/OtherLib.h" int f(int x); int h(int x) { return other(); } )cc"; EXPECT_EQ(Expected, test::runCheckOnCode>(Input)); } TEST(TransformerClangTidyCheckTest, AddIncludeAngled) { std::string Input = R"cc( int f(int x); int h(int x) { return f(x); } )cc"; std::string Expected = R"cc(#include int f(int x); int h(int x) { return other(); } )cc"; EXPECT_EQ(Expected, test::runCheckOnCode>(Input)); } class IncludeOrderCheck : public TransformerClangTidyCheck { static RewriteRule rule() { using namespace ::clang::ast_matchers; RewriteRule Rule = transformer::makeRule(integerLiteral(), change(cat("5")), cat("no message")); addInclude(Rule, "bar.h", IncludeFormat::Quoted); return Rule; } public: IncludeOrderCheck(StringRef Name, ClangTidyContext *Context) : TransformerClangTidyCheck(rule(), Name, Context) {} }; TEST(TransformerClangTidyCheckTest, AddIncludeObeysSortStyleLocalOption) { std::string Input = R"cc(#include "input.h" int h(int x) { return 3; })cc"; std::string TreatsAsLibraryHeader = R"cc(#include "input.h" #include "bar.h" int h(int x) { return 5; })cc"; std::string TreatsAsNormalHeader = R"cc(#include "bar.h" #include "input.h" int h(int x) { return 5; })cc"; ClangTidyOptions Options; std::map PathsToContent = {{"input.h", "\n"}}; Options.CheckOptions["test-check-0.IncludeStyle"] = "llvm"; EXPECT_EQ(TreatsAsLibraryHeader, test::runCheckOnCode( Input, nullptr, "inputTest.cpp", None, Options, PathsToContent)); EXPECT_EQ(TreatsAsNormalHeader, test::runCheckOnCode( Input, nullptr, "input_test.cpp", None, Options, PathsToContent)); Options.CheckOptions["test-check-0.IncludeStyle"] = "google"; EXPECT_EQ(TreatsAsNormalHeader, test::runCheckOnCode( Input, nullptr, "inputTest.cc", None, Options, PathsToContent)); EXPECT_EQ(TreatsAsLibraryHeader, test::runCheckOnCode( Input, nullptr, "input_test.cc", None, Options, PathsToContent)); } TEST(TransformerClangTidyCheckTest, AddIncludeObeysSortStyleGlobalOption) { std::string Input = R"cc(#include "input.h" int h(int x) { return 3; })cc"; std::string TreatsAsLibraryHeader = R"cc(#include "input.h" #include "bar.h" int h(int x) { return 5; })cc"; std::string TreatsAsNormalHeader = R"cc(#include "bar.h" #include "input.h" int h(int x) { return 5; })cc"; ClangTidyOptions Options; std::map PathsToContent = {{"input.h", "\n"}}; Options.CheckOptions["IncludeStyle"] = "llvm"; EXPECT_EQ(TreatsAsLibraryHeader, test::runCheckOnCode( Input, nullptr, "inputTest.cpp", None, Options, PathsToContent)); EXPECT_EQ(TreatsAsNormalHeader, test::runCheckOnCode( Input, nullptr, "input_test.cpp", None, Options, PathsToContent)); Options.CheckOptions["IncludeStyle"] = "google"; EXPECT_EQ(TreatsAsNormalHeader, test::runCheckOnCode( Input, nullptr, "inputTest.cc", None, Options, PathsToContent)); EXPECT_EQ(TreatsAsLibraryHeader, test::runCheckOnCode( Input, nullptr, "input_test.cc", None, Options, PathsToContent)); } } // namespace } // namespace utils } // namespace tidy } // namespace clang