Bump version to 19.1.0 (final)
[llvm-project.git] / clang-tools-extra / unittests / clang-tidy / TransformerClangTidyCheckTest.cpp
blob42d40d32bfa51e57ef65033f4dd7850aea8de753
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 //===----------------------------------------------------------------------===//
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 #include <optional>
20 namespace clang {
21 namespace tidy {
22 namespace utils {
23 namespace {
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"));
47 return Rule;
50 class IfInverterCheck : public TransformerClangTidyCheck {
51 public:
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);
60 void foo() {
61 if (10 > 1.0)
62 log("oh no!");
63 else
64 log("ok");
66 )cc";
67 const std::string Expected = R"(
68 void log(const char* msg);
69 void foo() {
70 if(!(10 > 1.0)) log("ok"); else log("oh no!");
72 )";
73 EXPECT_EQ(Expected, test::runCheckOnCode<IfInverterCheck>(Input));
76 TEST(TransformerClangTidyCheckTest, DiagnosticsCorrectlyGenerated) {
77 class DiagOnlyCheck : public TransformerClangTidyCheck {
78 public:
79 DiagOnlyCheck(StringRef Name, ClangTidyContext *Context)
80 : TransformerClangTidyCheck(
81 makeRule(returnStmt(), noopEdit(node(RootID)), cat("message")),
82 Name, Context) {}
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);
99 return E;
102 TEST(TransformerClangTidyCheckTest, EmptyReplacement) {
103 class DiagOnlyCheck : public TransformerClangTidyCheck {
104 public:
105 DiagOnlyCheck(StringRef Name, ClangTidyContext *Context)
106 : TransformerClangTidyCheck(
107 makeRule(returnStmt(), edit(noReplacementEdit(node(RootID))),
108 cat("message")),
109 Name, Context) {}
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 {
124 public:
125 DiagAndNoteCheck(StringRef Name, ClangTidyContext *Context)
126 : TransformerClangTidyCheck(
127 makeRule(returnStmt(),
128 note(node(RootID), cat("some note")),
129 cat("message")),
130 Name, Context) {}
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 {
145 public:
146 GiveDiagWithPercentSymbol(StringRef Name, ClangTidyContext *Context)
147 : TransformerClangTidyCheck(makeRule(returnStmt(),
148 noopEdit(node(RootID)),
149 cat("bad code: x % y % z")),
150 Name, Context) {}
152 std::string Input = "int somecode() { return 0; }";
153 std::vector<ClangTidyError> Errors;
154 EXPECT_EQ(Input,
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 {
165 public:
166 IntLitCheck(StringRef Name, ClangTidyContext *Context)
167 : TransformerClangTidyCheck(
168 makeRule(integerLiteral(), change(cat("LIT")), cat("no message")),
169 Name, Context) {}
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); }
178 )cc";
179 const std::string Expected = R"cc(
180 #define PLUS(a,b) (a) + (b)
181 int f() { return PLUS(LIT, LIT); }
182 )cc";
184 EXPECT_EQ(Expected, test::runCheckOnCode<IntLitCheck>(Input));
187 class BinOpCheck : public TransformerClangTidyCheck {
188 public:
189 BinOpCheck(StringRef Name, ClangTidyContext *Context)
190 : TransformerClangTidyCheck(
191 makeRule(
192 binaryOperator(hasOperatorName("+"), hasRHS(expr().bind("r"))),
193 change(node("r"), cat("RIGHT")), cat("no message")),
194 Name, Context) {}
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); }
204 )cc";
205 const std::string Expected = R"cc(
206 #define M(a,b) (1 + a) * (1 + b)
207 int f() { return M(RIGHT, RIGHT); }
208 )cc";
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) {
217 if (!LangOpts.ObjC)
218 return std::nullopt;
219 return makeRule(clang::ast_matchers::functionDecl(),
220 change(cat("void changed() {}")), cat("no message"));
223 class NeedsObjCCheck : public TransformerClangTidyCheck {
224 public:
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() {}";
232 EXPECT_EQ(Input,
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")
244 return std::nullopt;
245 return makeRule(clang::ast_matchers::functionDecl(),
246 changeTo(cat("void nothing();")), cat("no message"));
249 class ConfigurableCheck : public TransformerClangTidyCheck {
250 public:
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", std::nullopt, Options));
265 Options.CheckOptions["test-check-0.Skip"] = "false";
266 EXPECT_EQ(Expected, test::runCheckOnCode<ConfigurableCheck>(
267 Input, nullptr, "input.cc", std::nullopt, 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);
276 return Rule;
279 template <IncludeFormat Format>
280 class IncludeCheck : public TransformerClangTidyCheck {
281 public:
282 IncludeCheck(StringRef Name, ClangTidyContext *Context)
283 : TransformerClangTidyCheck(replaceCall(Format), Name, Context) {}
286 TEST(TransformerClangTidyCheckTest, AddIncludeQuoted) {
288 std::string Input = R"cc(
289 int f(int x);
290 int h(int x) { return f(x); }
291 )cc";
292 std::string Expected = R"cc(#include "clang/OtherLib.h"
295 int f(int x);
296 int h(int x) { return other(); }
297 )cc";
299 EXPECT_EQ(Expected,
300 test::runCheckOnCode<IncludeCheck<IncludeFormat::Quoted>>(Input));
303 TEST(TransformerClangTidyCheckTest, AddIncludeAngled) {
304 std::string Input = R"cc(
305 int f(int x);
306 int h(int x) { return f(x); }
307 )cc";
308 std::string Expected = R"cc(#include <clang/OtherLib.h>
311 int f(int x);
312 int h(int x) { return other(); }
313 )cc";
315 EXPECT_EQ(Expected,
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);
325 return Rule;
328 public:
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"
339 #include "bar.h"
340 int h(int x) { return 5; })cc";
342 std::string TreatsAsNormalHeader = R"cc(#include "bar.h"
343 #include "input.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, test::runCheckOnCode<IncludeOrderCheck>(
350 Input, nullptr, "inputTest.cpp",
351 std::nullopt, Options, PathsToContent));
352 EXPECT_EQ(TreatsAsNormalHeader, test::runCheckOnCode<IncludeOrderCheck>(
353 Input, nullptr, "input_test.cpp",
354 std::nullopt, Options, PathsToContent));
356 Options.CheckOptions["test-check-0.IncludeStyle"] = "google";
357 EXPECT_EQ(TreatsAsNormalHeader, test::runCheckOnCode<IncludeOrderCheck>(
358 Input, nullptr, "inputTest.cc",
359 std::nullopt, Options, PathsToContent));
360 EXPECT_EQ(TreatsAsLibraryHeader, test::runCheckOnCode<IncludeOrderCheck>(
361 Input, nullptr, "input_test.cc",
362 std::nullopt, 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"
371 #include "bar.h"
372 int h(int x) { return 5; })cc";
374 std::string TreatsAsNormalHeader = R"cc(#include "bar.h"
375 #include "input.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, test::runCheckOnCode<IncludeOrderCheck>(
382 Input, nullptr, "inputTest.cpp",
383 std::nullopt, Options, PathsToContent));
384 EXPECT_EQ(TreatsAsNormalHeader, test::runCheckOnCode<IncludeOrderCheck>(
385 Input, nullptr, "input_test.cpp",
386 std::nullopt, Options, PathsToContent));
388 Options.CheckOptions["IncludeStyle"] = "google";
389 EXPECT_EQ(TreatsAsNormalHeader, test::runCheckOnCode<IncludeOrderCheck>(
390 Input, nullptr, "inputTest.cc",
391 std::nullopt, Options, PathsToContent));
392 EXPECT_EQ(TreatsAsLibraryHeader, test::runCheckOnCode<IncludeOrderCheck>(
393 Input, nullptr, "input_test.cc",
394 std::nullopt, Options, PathsToContent));
397 } // namespace
398 } // namespace utils
399 } // namespace tidy
400 } // namespace clang