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(path&: Result, style: llvm::sys::path::Style::native, a: 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" , std::nullopt, 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["IgnoreHeaders" ] = llvm::StringRef{llvm::formatv( |
75 | Fmt: "bar.h;{0};{1};vector;<list>;" , |
76 | Vals: llvm::Regex::escape(String: appendPathFileSystemIndependent(Segments: {"foo" , "qux.h" })), |
77 | Vals: llvm::Regex::escape(String: appendPathFileSystemIndependent(Segments: {"baz" , "qux" })))}; |
78 | EXPECT_EQ( |
79 | PostCode, |
80 | runCheckOnCode<IncludeCleanerCheck>( |
81 | PreCode, &Errors, "file.cpp" , std::nullopt, Opts, |
82 | {{"bar.h" , "#pragma once" }, |
83 | {"vector" , "#pragma once" }, |
84 | {"list" , "#pragma once" }, |
85 | {appendPathFileSystemIndependent({"foo" , "qux.h" }), "#pragma once" }, |
86 | {appendPathFileSystemIndependent({"baz" , "qux" , "qux.h" }), |
87 | "#pragma once" }})); |
88 | } |
89 | |
90 | TEST(IncludeCleanerCheckTest, BasicMissingIncludes) { |
91 | const char *PreCode = R"( |
92 | #include "bar.h" |
93 | |
94 | int BarResult = bar(); |
95 | int BazResult = baz(); |
96 | )" ; |
97 | const char *PostCode = R"( |
98 | #include "bar.h" |
99 | #include "baz.h" |
100 | |
101 | int BarResult = bar(); |
102 | int BazResult = baz(); |
103 | )" ; |
104 | |
105 | std::vector<ClangTidyError> Errors; |
106 | EXPECT_EQ(PostCode, |
107 | runCheckOnCode<IncludeCleanerCheck>( |
108 | PreCode, &Errors, "file.cpp" , std::nullopt, ClangTidyOptions(), |
109 | {{"bar.h" , R"(#pragma once |
110 | #include "baz.h" |
111 | int bar(); |
112 | )" }, |
113 | {"baz.h" , R"(#pragma once |
114 | int baz(); |
115 | )" }})); |
116 | } |
117 | |
118 | TEST(IncludeCleanerCheckTest, DedupsMissingIncludes) { |
119 | llvm::Annotations Code(R"( |
120 | #include "baz.h" // IWYU pragma: keep |
121 | |
122 | int BarResult1 = $diag1^bar(); |
123 | int BarResult2 = $diag2^bar();)" ); |
124 | |
125 | { |
126 | std::vector<ClangTidyError> Errors; |
127 | runCheckOnCode<IncludeCleanerCheck>(Code: Code.code(), Errors: &Errors, Filename: "file.cpp" , |
128 | ExtraArgs: std::nullopt, ExtraOptions: ClangTidyOptions(), |
129 | PathsToContent: {{"baz.h" , R"(#pragma once |
130 | #include "bar.h" |
131 | )" }, |
132 | {"bar.h" , R"(#pragma once |
133 | int bar(); |
134 | )" }}); |
135 | ASSERT_THAT(Errors.size(), testing::Eq(1U)); |
136 | EXPECT_EQ(Errors.front().Message.Message, |
137 | "no header providing \"bar\" is directly included" ); |
138 | EXPECT_EQ(Errors.front().Message.FileOffset, Code.point("diag1" )); |
139 | } |
140 | { |
141 | std::vector<ClangTidyError> Errors; |
142 | ClangTidyOptions Opts; |
143 | Opts.CheckOptions.insert(KV: {"DeduplicateFindings" , "false" }); |
144 | runCheckOnCode<IncludeCleanerCheck>(Code: Code.code(), Errors: &Errors, Filename: "file.cpp" , |
145 | ExtraArgs: std::nullopt, ExtraOptions: Opts, |
146 | PathsToContent: {{"baz.h" , R"(#pragma once |
147 | #include "bar.h" |
148 | )" }, |
149 | {"bar.h" , R"(#pragma once |
150 | int bar(); |
151 | )" }}); |
152 | ASSERT_THAT(Errors.size(), testing::Eq(2U)); |
153 | EXPECT_EQ(Errors.front().Message.Message, |
154 | "no header providing \"bar\" is directly included" ); |
155 | EXPECT_EQ(Errors.front().Message.FileOffset, Code.point("diag1" )); |
156 | EXPECT_EQ(Errors.back().Message.Message, |
157 | "no header providing \"bar\" is directly included" ); |
158 | EXPECT_EQ(Errors.back().Message.FileOffset, Code.point("diag2" )); |
159 | } |
160 | } |
161 | |
162 | TEST(IncludeCleanerCheckTest, SuppressMissingIncludes) { |
163 | const char *PreCode = R"( |
164 | #include "bar.h" |
165 | |
166 | int BarResult = bar(); |
167 | int BazResult = baz(); |
168 | int QuxResult = qux(); |
169 | int PrivResult = test(); |
170 | std::vector x; |
171 | )" ; |
172 | |
173 | ClangTidyOptions Opts; |
174 | Opts.CheckOptions["IgnoreHeaders" ] = llvm::StringRef{ |
175 | "public.h;<vector>;baz.h;" + |
176 | llvm::Regex::escape(String: appendPathFileSystemIndependent(Segments: {"foo" , "qux.h" }))}; |
177 | std::vector<ClangTidyError> Errors; |
178 | EXPECT_EQ(PreCode, runCheckOnCode<IncludeCleanerCheck>( |
179 | PreCode, &Errors, "file.cpp" , std::nullopt, Opts, |
180 | {{"bar.h" , R"(#pragma once |
181 | #include "baz.h" |
182 | #include "foo/qux.h" |
183 | #include "private.h" |
184 | int bar(); |
185 | namespace std { struct vector {}; } |
186 | )" }, |
187 | {"baz.h" , R"(#pragma once |
188 | int baz(); |
189 | )" }, |
190 | {"private.h" , R"(#pragma once |
191 | // IWYU pragma: private, include "public.h" |
192 | int test(); |
193 | )" }, |
194 | {appendPathFileSystemIndependent({"foo" , "qux.h" }), |
195 | R"(#pragma once |
196 | int qux(); |
197 | )" }})); |
198 | } |
199 | |
200 | TEST(IncludeCleanerCheckTest, MultipleTimeMissingInclude) { |
201 | const char *PreCode = R"( |
202 | #include "bar.h" |
203 | |
204 | int BarResult = bar(); |
205 | int BazResult_0 = baz_0(); |
206 | int BazResult_1 = baz_1(); |
207 | )" ; |
208 | const char *PostCode = R"( |
209 | #include "bar.h" |
210 | #include "baz.h" |
211 | |
212 | int BarResult = bar(); |
213 | int BazResult_0 = baz_0(); |
214 | int BazResult_1 = baz_1(); |
215 | )" ; |
216 | |
217 | std::vector<ClangTidyError> Errors; |
218 | EXPECT_EQ(PostCode, |
219 | runCheckOnCode<IncludeCleanerCheck>( |
220 | PreCode, &Errors, "file.cpp" , std::nullopt, 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, |
248 | runCheckOnCode<IncludeCleanerCheck>( |
249 | PreCode, &Errors, "file.cpp" , std::nullopt, ClangTidyOptions(), |
250 | {{"string" , R"(#pragma once |
251 | namespace std { class string {}; } |
252 | )" }, |
253 | {"vector" , R"(#pragma once |
254 | #include <string> |
255 | namespace std { class vector {}; } |
256 | )" }})); |
257 | } |
258 | |
259 | TEST(IncludeCleanerCheckTest, PragmaMissingIncludes) { |
260 | const char *PreCode = R"( |
261 | #include "bar.h" |
262 | |
263 | int BarResult = bar(); |
264 | int FooBarResult = foobar(); |
265 | )" ; |
266 | const char *PostCode = R"( |
267 | #include "bar.h" |
268 | #include "public.h" |
269 | |
270 | int BarResult = bar(); |
271 | int FooBarResult = foobar(); |
272 | )" ; |
273 | |
274 | std::vector<ClangTidyError> Errors; |
275 | EXPECT_EQ(PostCode, |
276 | runCheckOnCode<IncludeCleanerCheck>( |
277 | PreCode, &Errors, "file.cpp" , std::nullopt, ClangTidyOptions(), |
278 | {{"bar.h" , R"(#pragma once |
279 | #include "private.h" |
280 | int bar(); |
281 | )" }, |
282 | {"private.h" , R"(#pragma once |
283 | // IWYU pragma: private, include "public.h" |
284 | int foobar(); |
285 | )" }})); |
286 | } |
287 | |
288 | TEST(IncludeCleanerCheckTest, DeclFromMacroExpansion) { |
289 | const char *PreCode = R"( |
290 | #include "foo.h" |
291 | |
292 | DECLARE(myfunc) { |
293 | int a; |
294 | } |
295 | )" ; |
296 | |
297 | std::vector<ClangTidyError> Errors; |
298 | EXPECT_EQ(PreCode, |
299 | runCheckOnCode<IncludeCleanerCheck>( |
300 | PreCode, &Errors, "file.cpp" , std::nullopt, ClangTidyOptions(), |
301 | {{"foo.h" , |
302 | R"(#pragma once |
303 | #define DECLARE(X) void X() |
304 | )" }})); |
305 | |
306 | PreCode = R"( |
307 | #include "foo.h" |
308 | |
309 | DECLARE { |
310 | int a; |
311 | } |
312 | )" ; |
313 | |
314 | EXPECT_EQ(PreCode, |
315 | runCheckOnCode<IncludeCleanerCheck>( |
316 | PreCode, &Errors, "file.cpp" , std::nullopt, ClangTidyOptions(), |
317 | {{"foo.h" , |
318 | R"(#pragma once |
319 | #define DECLARE void myfunc() |
320 | )" }})); |
321 | } |
322 | |
323 | } // namespace |
324 | } // namespace test |
325 | } // namespace tidy |
326 | } // namespace clang |
327 | |