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