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
26using namespace clang::tidy::misc;
27
28namespace clang {
29namespace tidy {
30namespace test {
31namespace {
32
33std::string
34appendPathFileSystemIndependent(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
41TEST(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
56TEST(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
90TEST(IncludeCleanerCheckTest, BasicMissingIncludes) {
91 const char *PreCode = R"(
92#include "bar.h"
93
94int BarResult = bar();
95int BazResult = baz();
96)";
97 const char *PostCode = R"(
98#include "bar.h"
99#include "baz.h"
100
101int BarResult = bar();
102int 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
118TEST(IncludeCleanerCheckTest, DedupsMissingIncludes) {
119 llvm::Annotations Code(R"(
120#include "baz.h" // IWYU pragma: keep
121
122int BarResult1 = $diag1^bar();
123int 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
162TEST(IncludeCleanerCheckTest, SuppressMissingIncludes) {
163 const char *PreCode = R"(
164#include "bar.h"
165
166int BarResult = bar();
167int BazResult = baz();
168int QuxResult = qux();
169int PrivResult = test();
170std::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
200TEST(IncludeCleanerCheckTest, MultipleTimeMissingInclude) {
201 const char *PreCode = R"(
202#include "bar.h"
203
204int BarResult = bar();
205int BazResult_0 = baz_0();
206int BazResult_1 = baz_1();
207)";
208 const char *PostCode = R"(
209#include "bar.h"
210#include "baz.h"
211
212int BarResult = bar();
213int BazResult_0 = baz_0();
214int 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
231TEST(IncludeCleanerCheckTest, SystemMissingIncludes) {
232 const char *PreCode = R"(
233#include <vector>
234
235std::string HelloString;
236std::vector Vec;
237)";
238 const char *PostCode = R"(
239#include <string>
240#include <vector>
241
242std::string HelloString;
243std::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
259TEST(IncludeCleanerCheckTest, PragmaMissingIncludes) {
260 const char *PreCode = R"(
261#include "bar.h"
262
263int BarResult = bar();
264int FooBarResult = foobar();
265)";
266 const char *PostCode = R"(
267#include "bar.h"
268#include "public.h"
269
270int BarResult = bar();
271int 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
288TEST(IncludeCleanerCheckTest, DeclFromMacroExpansion) {
289 const char *PreCode = R"(
290#include "foo.h"
291
292DECLARE(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
309DECLARE {
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

source code of clang-tools-extra/unittests/clang-tidy/IncludeCleanerTest.cpp