1 //===---- TransformerClangTidyCheckTest.cpp - clang-tidy ------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
24 using namespace ::clang::ast_matchers
;
26 using transformer::cat
;
27 using transformer::change
;
28 using transformer::IncludeFormat
;
29 using transformer::makeRule
;
30 using transformer::node
;
31 using transformer::noopEdit
;
32 using transformer::note
;
33 using transformer::RewriteRuleWith
;
34 using transformer::RootID
;
35 using transformer::statement
;
37 // Invert the code of an if-statement, while maintaining its semantics.
38 RewriteRuleWith
<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"));
50 class IfInverterCheck
: public TransformerClangTidyCheck
{
52 IfInverterCheck(StringRef Name
, ClangTidyContext
*Context
)
53 : TransformerClangTidyCheck(invertIf(), Name
, Context
) {}
56 // Basic test of using a rewrite rule as a ClangTidy.
57 TEST(TransformerClangTidyCheckTest
, Basic
) {
58 const std::string Input
= R
"cc(
59 void log(const char* msg);
67 const std::string Expected
= R
"(
68 void log(const char* msg);
70 if(!(10 > 1.0)) log("ok
"); else log("oh no
!");
73 EXPECT_EQ(Expected
, test::runCheckOnCode
<IfInverterCheck
>(Input
));
76 TEST(TransformerClangTidyCheckTest
, DiagnosticsCorrectlyGenerated
) {
77 class DiagOnlyCheck
: public TransformerClangTidyCheck
{
79 DiagOnlyCheck(StringRef Name
, ClangTidyContext
*Context
)
80 : TransformerClangTidyCheck(
81 makeRule(returnStmt(), noopEdit(node(RootID
)), cat("message")),
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());
92 // The diagnostic is anchored to the match, "return 5".
93 EXPECT_EQ(Errors
[0].Message
.FileOffset
, 10U);
96 transformer::ASTEdit
noReplacementEdit(transformer::RangeSelector Target
) {
97 transformer::ASTEdit E
;
98 E
.TargetRange
= std::move(Target
);
102 TEST(TransformerClangTidyCheckTest
, EmptyReplacement
) {
103 class DiagOnlyCheck
: public TransformerClangTidyCheck
{
105 DiagOnlyCheck(StringRef Name
, ClangTidyContext
*Context
)
106 : TransformerClangTidyCheck(
107 makeRule(returnStmt(), edit(noReplacementEdit(node(RootID
))),
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());
118 // The diagnostic is anchored to the match, "return 5".
119 EXPECT_EQ(Errors
[0].Message
.FileOffset
, 10U);
122 TEST(TransformerClangTidyCheckTest
, NotesCorrectlyGenerated
) {
123 class DiagAndNoteCheck
: public TransformerClangTidyCheck
{
125 DiagAndNoteCheck(StringRef Name
, ClangTidyContext
*Context
)
126 : TransformerClangTidyCheck(
127 makeRule(returnStmt(),
128 note(node(RootID
), cat("some note")),
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");
139 // The note is anchored to the match, "return 5".
140 EXPECT_EQ(Errors
[0].Notes
[0].FileOffset
, 10U);
143 TEST(TransformerClangTidyCheckTest
, DiagnosticMessageEscaped
) {
144 class GiveDiagWithPercentSymbol
: public TransformerClangTidyCheck
{
146 GiveDiagWithPercentSymbol(StringRef Name
, ClangTidyContext
*Context
)
147 : TransformerClangTidyCheck(makeRule(returnStmt(),
148 noopEdit(node(RootID
)),
149 cat("bad code: x % y % z")),
152 std::string Input
= "int somecode() { return 0; }";
153 std::vector
<ClangTidyError
> Errors
;
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 processing
159 // the diagnostic. The only behavior observable/verifiable by the test is that
160 // the presence of the '%' doesn't crash Clang.
161 EXPECT_EQ(Errors
[0].Message
.Message
, "bad code: x % y % z");
164 class IntLitCheck
: public TransformerClangTidyCheck
{
166 IntLitCheck(StringRef Name
, ClangTidyContext
*Context
)
167 : TransformerClangTidyCheck(
168 makeRule(integerLiteral(), change(cat("LIT")), cat("no message")),
172 // Tests that two changes in a single macro expansion do not lead to conflicts
173 // in applying the changes.
174 TEST(TransformerClangTidyCheckTest
, TwoChangesInOneMacroExpansion
) {
175 const std::string Input
= R
"cc(
176 #define PLUS(a,b) (a) + (b)
177 int f() { return PLUS(3, 4); }
179 const std::string Expected
= R
"cc(
180 #define PLUS(a,b) (a) + (b)
181 int f() { return PLUS(LIT, LIT); }
184 EXPECT_EQ(Expected
, test::runCheckOnCode
<IntLitCheck
>(Input
));
187 class BinOpCheck
: public TransformerClangTidyCheck
{
189 BinOpCheck(StringRef Name
, ClangTidyContext
*Context
)
190 : TransformerClangTidyCheck(
192 binaryOperator(hasOperatorName("+"), hasRHS(expr().bind("r"))),
193 change(node("r"), cat("RIGHT")), cat("no message")),
197 // Tests case where the rule's match spans both source from the macro and its
198 // argument, while the change spans only the argument AND there are two such
199 // matches. We verify that both replacements succeed.
200 TEST(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); }
205 const std::string Expected
= R
"cc(
206 #define M(a,b) (1 + a) * (1 + b)
207 int f() { return M(RIGHT, RIGHT); }
210 EXPECT_EQ(Expected
, test::runCheckOnCode
<BinOpCheck
>(Input
));
213 // A trivial rewrite-rule generator that requires Objective-C code.
214 std::optional
<RewriteRuleWith
<std::string
>>
215 needsObjC(const LangOptions
&LangOpts
,
216 const ClangTidyCheck::OptionsView
&Options
) {
219 return makeRule(clang::ast_matchers::functionDecl(),
220 change(cat("void changed() {}")), cat("no message"));
223 class NeedsObjCCheck
: public TransformerClangTidyCheck
{
225 NeedsObjCCheck(StringRef Name
, ClangTidyContext
*Context
)
226 : TransformerClangTidyCheck(needsObjC
, Name
, Context
) {}
229 // Verify that the check only rewrites the code when the input is Objective-C.
230 TEST(TransformerClangTidyCheckTest
, DisableByLang
) {
231 const std::string Input
= "void log() {}";
233 test::runCheckOnCode
<NeedsObjCCheck
>(Input
, nullptr, "input.cc"));
235 EXPECT_EQ("void changed() {}",
236 test::runCheckOnCode
<NeedsObjCCheck
>(Input
, nullptr, "input.mm"));
239 // A trivial rewrite rule generator that checks config options.
240 std::optional
<RewriteRuleWith
<std::string
>>
241 noSkip(const LangOptions
&LangOpts
,
242 const ClangTidyCheck::OptionsView
&Options
) {
243 if (Options
.get("Skip", "false") == "true")
245 return makeRule(clang::ast_matchers::functionDecl(),
246 changeTo(cat("void nothing();")), cat("no message"));
249 class ConfigurableCheck
: public TransformerClangTidyCheck
{
251 ConfigurableCheck(StringRef Name
, ClangTidyContext
*Context
)
252 : TransformerClangTidyCheck(noSkip
, Name
, Context
) {}
255 // Tests operation with config option "Skip" set to true and false.
256 TEST(TransformerClangTidyCheckTest
, DisableByConfig
) {
257 const std::string Input
= "void log(int);";
258 const std::string Expected
= "void nothing();";
259 ClangTidyOptions Options
;
261 Options
.CheckOptions
["test-check-0.Skip"] = "true";
262 EXPECT_EQ(Input
, test::runCheckOnCode
<ConfigurableCheck
>(
263 Input
, nullptr, "input.cc", {}, Options
));
265 Options
.CheckOptions
["test-check-0.Skip"] = "false";
266 EXPECT_EQ(Expected
, test::runCheckOnCode
<ConfigurableCheck
>(
267 Input
, nullptr, "input.cc", {}, Options
));
270 RewriteRuleWith
<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
);
279 template <IncludeFormat Format
>
280 class IncludeCheck
: public TransformerClangTidyCheck
{
282 IncludeCheck(StringRef Name
, ClangTidyContext
*Context
)
283 : TransformerClangTidyCheck(replaceCall(Format
), Name
, Context
) {}
286 TEST(TransformerClangTidyCheckTest
, AddIncludeQuoted
) {
288 std::string Input
= R
"cc(
290 int h(int x) { return f(x); }
292 std::string Expected
= R
"cc(#include "clang
/OtherLib
.h
"
296 int h(int x) { return other(); }
300 test::runCheckOnCode
<IncludeCheck
<IncludeFormat::Quoted
>>(Input
));
303 TEST(TransformerClangTidyCheckTest
, AddIncludeAngled
) {
304 std::string Input
= R
"cc(
306 int h(int x) { return f(x); }
308 std::string Expected
= R
"cc(#include <clang/OtherLib.h>
312 int h(int x) { return other(); }
316 test::runCheckOnCode
<IncludeCheck
<IncludeFormat::Angled
>>(Input
));
319 class 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
);
329 IncludeOrderCheck(StringRef Name
, ClangTidyContext
*Context
)
330 : TransformerClangTidyCheck(rule(), Name
, Context
) {}
333 TEST(TransformerClangTidyCheckTest
, AddIncludeObeysSortStyleLocalOption
) {
334 std::string Input
= R
"cc(#include "input
.h
"
335 int h(int x) { return 3; })cc";
337 std::string TreatsAsLibraryHeader
= R
"cc(#include "input
.h
"
340 int h(int x) { return 5; })cc";
342 std::string TreatsAsNormalHeader
= R
"cc(#include "bar
.h
"
344 int h(int x) { return 5; })cc";
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
));
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
));
365 TEST(TransformerClangTidyCheckTest
, AddIncludeObeysSortStyleGlobalOption
) {
366 std::string Input
= R
"cc(#include "input
.h
"
367 int h(int x) { return 3; })cc";
369 std::string TreatsAsLibraryHeader
= R
"cc(#include "input
.h
"
372 int h(int x) { return 5; })cc";
374 std::string TreatsAsNormalHeader
= R
"cc(#include "bar
.h
"
376 int h(int x) { return 5; })cc";
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
));
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
));