//===---- 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::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 = tooling::makeRule( ifStmt(hasCondition(expr().bind(C)), hasThen(stmt().bind(T)), hasElse(stmt().bind(E))), change( statement(RewriteRule::RootID), cat("if(!(", node(C), ")) ", statement(E), " else ", statement(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(tooling::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( tooling::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 tooling::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 tooling::makeRule(clang::ast_matchers::functionDecl(), change(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 = tooling::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)); } } // namespace } // namespace utils } // namespace tidy } // namespace clang