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