xref: /llvm-project/clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp (revision 222dd235ffc39b3695a3c002593097bec216a8fa)
1 //===--- IncludeCleanerTest.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 "ClangTidyDiagnosticConsumer.h"
10 #include "ClangTidyOptions.h"
11 #include "ClangTidyTest.h"
12 #include "misc/IncludeCleanerCheck.h"
13 #include "llvm/ADT/SmallString.h"
14 #include "llvm/ADT/StringRef.h"
15 #include "llvm/Support/FormatVariadic.h"
16 #include "llvm/Support/Path.h"
17 #include "llvm/Support/Regex.h"
18 #include "llvm/Testing/Annotations/Annotations.h"
19 #include "gmock/gmock.h"
20 #include "gtest/gtest.h"
21 #include <initializer_list>
22 
23 #include <optional>
24 #include <vector>
25 
26 using namespace clang::tidy::misc;
27 
28 namespace clang {
29 namespace tidy {
30 namespace test {
31 namespace {
32 
33 std::string
34 appendPathFileSystemIndependent(std::initializer_list<std::string> Segments) {
35   llvm::SmallString<32> Result;
36   for (const auto &Segment : Segments)
37     llvm::sys::path::append(Result, llvm::sys::path::Style::native, Segment);
38   return std::string(Result.str());
39 }
40 
41 TEST(IncludeCleanerCheckTest, BasicUnusedIncludes) {
42   const char *PreCode = R"(
43 #include "bar.h"
44 #include <vector>
45 #include "bar.h"
46 )";
47   const char *PostCode = "\n";
48 
49   std::vector<ClangTidyError> Errors;
50   EXPECT_EQ(PostCode,
51             runCheckOnCode<IncludeCleanerCheck>(
52                 PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),
53                 {{"bar.h", "#pragma once"}, {"vector", "#pragma once"}}));
54 }
55 
56 TEST(IncludeCleanerCheckTest, SuppressUnusedIncludes) {
57   const char *PreCode = R"(
58 #include "bar.h"
59 #include "foo/qux.h"
60 #include "baz/qux/qux.h"
61 #include <vector>
62 #include <list>
63 )";
64 
65   const char *PostCode = R"(
66 #include "bar.h"
67 #include "foo/qux.h"
68 #include <vector>
69 #include <list>
70 )";
71 
72   std::vector<ClangTidyError> Errors;
73   ClangTidyOptions Opts;
74   Opts.CheckOptions["test-check-0.IgnoreHeaders"] = llvm::StringRef{
75       llvm::formatv("bar.h;{0};{1};vector;<list>;",
76                     llvm::Regex::escape(
77                         appendPathFileSystemIndependent({"foo", "qux.h"})),
78                     llvm::Regex::escape(
79                         appendPathFileSystemIndependent({"baz", "qux"})))};
80   EXPECT_EQ(
81       PostCode,
82       runCheckOnCode<IncludeCleanerCheck>(
83           PreCode, &Errors, "file.cpp", {}, Opts,
84           {{"bar.h", "#pragma once"},
85            {"vector", "#pragma once"},
86            {"list", "#pragma once"},
87            {appendPathFileSystemIndependent({"foo", "qux.h"}), "#pragma once"},
88            {appendPathFileSystemIndependent({"baz", "qux", "qux.h"}),
89             "#pragma once"}}));
90 }
91 
92 TEST(IncludeCleanerCheckTest, BasicMissingIncludes) {
93   const char *PreCode = R"(
94 #include "bar.h"
95 
96 int BarResult = bar();
97 int BazResult = baz();
98 )";
99   const char *PostCode = R"(
100 #include "bar.h"
101 #include "baz.h"
102 
103 int BarResult = bar();
104 int BazResult = baz();
105 )";
106 
107   std::vector<ClangTidyError> Errors;
108   EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>(
109                           PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),
110                           {{"bar.h", R"(#pragma once
111                               #include "baz.h"
112                               int bar();
113                            )"},
114                            {"baz.h", R"(#pragma once
115                               int baz();
116                            )"}}));
117 }
118 
119 TEST(IncludeCleanerCheckTest, DedupsMissingIncludes) {
120   llvm::Annotations Code(R"(
121 #include "baz.h" // IWYU pragma: keep
122 
123 int BarResult1 = $diag1^bar();
124 int BarResult2 = $diag2^bar();)");
125 
126   {
127     std::vector<ClangTidyError> Errors;
128     runCheckOnCode<IncludeCleanerCheck>(Code.code(), &Errors, "file.cpp", {},
129                                         ClangTidyOptions(),
130                                         {{"baz.h", R"(#pragma once
131                               #include "bar.h"
132                            )"},
133                                          {"bar.h", R"(#pragma once
134                               int bar();
135                            )"}});
136     ASSERT_THAT(Errors.size(), testing::Eq(1U));
137     EXPECT_EQ(Errors.front().Message.Message,
138               "no header providing \"bar\" is directly included");
139     EXPECT_EQ(Errors.front().Message.FileOffset, Code.point("diag1"));
140   }
141   {
142     std::vector<ClangTidyError> Errors;
143     ClangTidyOptions Opts;
144     Opts.CheckOptions["test-check-0.DeduplicateFindings"] = "false";
145     runCheckOnCode<IncludeCleanerCheck>(Code.code(), &Errors, "file.cpp", {},
146                                         Opts,
147                                         {{"baz.h", R"(#pragma once
148                               #include "bar.h"
149                            )"},
150                                          {"bar.h", R"(#pragma once
151                               int bar();
152                            )"}});
153     ASSERT_THAT(Errors.size(), testing::Eq(2U));
154     EXPECT_EQ(Errors.front().Message.Message,
155               "no header providing \"bar\" is directly included");
156     EXPECT_EQ(Errors.front().Message.FileOffset, Code.point("diag1"));
157     EXPECT_EQ(Errors.back().Message.Message,
158               "no header providing \"bar\" is directly included");
159     EXPECT_EQ(Errors.back().Message.FileOffset, Code.point("diag2"));
160   }
161 }
162 
163 TEST(IncludeCleanerCheckTest, SuppressMissingIncludes) {
164   const char *PreCode = R"(
165 #include "bar.h"
166 
167 int BarResult = bar();
168 int BazResult = baz();
169 int QuxResult = qux();
170 int PrivResult = test();
171 std::vector x;
172 )";
173 
174   ClangTidyOptions Opts;
175   Opts.CheckOptions["test-check-0.IgnoreHeaders"] = llvm::StringRef{
176       "public.h;<vector>;baz.h;" +
177       llvm::Regex::escape(appendPathFileSystemIndependent({"foo", "qux.h"}))};
178   std::vector<ClangTidyError> Errors;
179   EXPECT_EQ(PreCode, runCheckOnCode<IncludeCleanerCheck>(
180                          PreCode, &Errors, "file.cpp", {}, Opts,
181                          {{"bar.h", R"(#pragma once
182                               #include "baz.h"
183                               #include "foo/qux.h"
184                               #include "private.h"
185                               int bar();
186                               namespace std { struct vector {}; }
187                            )"},
188                           {"baz.h", R"(#pragma once
189                               int baz();
190                            )"},
191                           {"private.h", R"(#pragma once
192                               // IWYU pragma: private, include "public.h"
193                               int test();
194                            )"},
195                           {appendPathFileSystemIndependent({"foo", "qux.h"}),
196                            R"(#pragma once
197                               int qux();
198                            )"}}));
199 }
200 
201 TEST(IncludeCleanerCheckTest, MultipleTimeMissingInclude) {
202   const char *PreCode = R"(
203 #include "bar.h"
204 
205 int BarResult = bar();
206 int BazResult_0 = baz_0();
207 int BazResult_1 = baz_1();
208 )";
209   const char *PostCode = R"(
210 #include "bar.h"
211 #include "baz.h"
212 
213 int BarResult = bar();
214 int BazResult_0 = baz_0();
215 int BazResult_1 = baz_1();
216 )";
217 
218   std::vector<ClangTidyError> Errors;
219   EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>(
220                           PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),
221                           {{"bar.h", R"(#pragma once
222                               #include "baz.h"
223                               int bar();
224                            )"},
225                            {"baz.h", R"(#pragma once
226                               int baz_0();
227                               int baz_1();
228                            )"}}));
229 }
230 
231 TEST(IncludeCleanerCheckTest, SystemMissingIncludes) {
232   const char *PreCode = R"(
233 #include <vector>
234 
235 std::string HelloString;
236 std::vector Vec;
237 )";
238   const char *PostCode = R"(
239 #include <string>
240 #include <vector>
241 
242 std::string HelloString;
243 std::vector Vec;
244 )";
245 
246   std::vector<ClangTidyError> Errors;
247   EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>(
248                           PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),
249                           {{"string", R"(#pragma once
250                               namespace std { class string {}; }
251                             )"},
252                            {"vector", R"(#pragma once
253                               #include <string>
254                               namespace std { class vector {}; }
255                             )"}}));
256 }
257 
258 TEST(IncludeCleanerCheckTest, PragmaMissingIncludes) {
259   const char *PreCode = R"(
260 #include "bar.h"
261 
262 int BarResult = bar();
263 int FooBarResult = foobar();
264 )";
265   const char *PostCode = R"(
266 #include "bar.h"
267 #include "public.h"
268 
269 int BarResult = bar();
270 int FooBarResult = foobar();
271 )";
272 
273   std::vector<ClangTidyError> Errors;
274   EXPECT_EQ(PostCode, runCheckOnCode<IncludeCleanerCheck>(
275                           PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),
276                           {{"bar.h", R"(#pragma once
277                               #include "private.h"
278                               int bar();
279                            )"},
280                            {"private.h", R"(#pragma once
281                                 // IWYU pragma: private, include "public.h"
282                                 int foobar();
283                                )"}}));
284 }
285 
286 TEST(IncludeCleanerCheckTest, DeclFromMacroExpansion) {
287   const char *PreCode = R"(
288 #include "foo.h"
289 
290 DECLARE(myfunc) {
291    int a;
292 }
293 )";
294 
295   std::vector<ClangTidyError> Errors;
296   EXPECT_EQ(PreCode, runCheckOnCode<IncludeCleanerCheck>(
297                          PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),
298                          {{"foo.h",
299                            R"(#pragma once
300                      #define DECLARE(X) void X()
301                   )"}}));
302 
303   PreCode = R"(
304 #include "foo.h"
305 
306 DECLARE {
307    int a;
308 }
309 )";
310 
311   EXPECT_EQ(PreCode, runCheckOnCode<IncludeCleanerCheck>(
312                          PreCode, &Errors, "file.cpp", {}, ClangTidyOptions(),
313                          {{"foo.h",
314                            R"(#pragma once
315                      #define DECLARE void myfunc()
316                   )"}}));
317 }
318 
319 } // namespace
320 } // namespace test
321 } // namespace tidy
322 } // namespace clang
323