1 | //===--- IncludeCleanerTests.cpp --------------------------------*- C++ -*-===// |
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 "Annotations.h" |
10 | #include "Diagnostics.h" |
11 | #include "IncludeCleaner.h" |
12 | #include "ParsedAST.h" |
13 | #include "SourceCode.h" |
14 | #include "TestFS.h" |
15 | #include "TestTU.h" |
16 | #include "clang-include-cleaner/Analysis.h" |
17 | #include "clang-include-cleaner/Types.h" |
18 | #include "clang/AST/DeclBase.h" |
19 | #include "clang/Basic/SourceManager.h" |
20 | #include "clang/Tooling/Syntax/Tokens.h" |
21 | #include "llvm/ADT/ArrayRef.h" |
22 | #include "llvm/ADT/ScopeExit.h" |
23 | #include "llvm/ADT/StringMap.h" |
24 | #include "llvm/ADT/StringRef.h" |
25 | #include "llvm/Support/Casting.h" |
26 | #include "llvm/Support/Error.h" |
27 | #include "llvm/Support/ScopedPrinter.h" |
28 | #include "gmock/gmock.h" |
29 | #include "gtest/gtest.h" |
30 | #include <cstddef> |
31 | #include <optional> |
32 | #include <string> |
33 | #include <vector> |
34 | |
35 | namespace clang { |
36 | namespace clangd { |
37 | namespace { |
38 | |
39 | using ::testing::AllOf; |
40 | using ::testing::ElementsAre; |
41 | using ::testing::IsEmpty; |
42 | using ::testing::Matcher; |
43 | using ::testing::Pointee; |
44 | using ::testing::UnorderedElementsAre; |
45 | |
46 | Matcher<const Diag &> |
47 | withFix(std::vector<::testing::Matcher<Fix>> FixMatcheres) { |
48 | return Field(field: &Diag::Fixes, matcher: testing::UnorderedElementsAreArray(container: FixMatcheres)); |
49 | } |
50 | |
51 | MATCHER_P2(Diag, Range, Message, |
52 | "Diag at " + llvm::to_string(Range) + " = [" + Message + "]" ) { |
53 | return arg.Range == Range && arg.Message == Message; |
54 | } |
55 | |
56 | MATCHER_P3(Fix, Range, Replacement, Message, |
57 | "Fix " + llvm::to_string(Range) + " => " + |
58 | ::testing::PrintToString(Replacement) + " = [" + Message + "]" ) { |
59 | return arg.Message == Message && arg.Edits.size() == 1 && |
60 | arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement; |
61 | } |
62 | MATCHER_P(FixMessage, Message, "" ) { return arg.Message == Message; } |
63 | |
64 | std::string guard(llvm::StringRef Code) { |
65 | return "#pragma once\n" + Code.str(); |
66 | } |
67 | |
68 | MATCHER_P(writtenInclusion, Written, "" ) { |
69 | if (arg.Written != Written) |
70 | *result_listener << arg.Written; |
71 | return arg.Written == Written; |
72 | } |
73 | |
74 | TEST(IncludeCleaner, StdlibUnused) { |
75 | auto TU = TestTU::withCode(Code: R"cpp( |
76 | #include <list> |
77 | #include <queue> |
78 | #include <vector> // IWYU pragma: keep |
79 | #include <string> // IWYU pragma: export |
80 | std::list<int> x; |
81 | )cpp" ); |
82 | // Layout of std library impl is not relevant. |
83 | TU.AdditionalFiles["bits" ] = R"cpp( |
84 | #pragma once |
85 | namespace std { |
86 | template <typename> class list {}; |
87 | template <typename> class queue {}; |
88 | template <typename> class vector {}; |
89 | } |
90 | )cpp" ; |
91 | TU.AdditionalFiles["list" ] = guard(Code: "#include <bits>" ); |
92 | TU.AdditionalFiles["queue" ] = guard(Code: "#include <bits>" ); |
93 | TU.AdditionalFiles["vector" ] = guard(Code: "#include <bits>" ); |
94 | TU.AdditionalFiles["string" ] = guard(Code: "#include <bits>" ); |
95 | TU.ExtraArgs = {"-isystem" , testRoot()}; |
96 | auto AST = TU.build(); |
97 | IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
98 | EXPECT_THAT(Findings.UnusedIncludes, |
99 | ElementsAre(Pointee(writtenInclusion("<queue>" )))); |
100 | } |
101 | |
102 | TEST(IncludeCleaner, GetUnusedHeaders) { |
103 | llvm::StringLiteral MainFile = R"cpp( |
104 | #include "a.h" |
105 | #include "b.h" |
106 | #include "dir/c.h" |
107 | #include "dir/unused.h" |
108 | #include "unguarded.h" |
109 | #include "unused.h" |
110 | #include <system_header.h> |
111 | void foo() { |
112 | a(); |
113 | b(); |
114 | c(); |
115 | })cpp" ; |
116 | // Build expected ast with symbols coming from headers. |
117 | TestTU TU; |
118 | TU.Filename = "foo.cpp" ; |
119 | TU.AdditionalFiles["foo.h" ] = guard(Code: "void foo();" ); |
120 | TU.AdditionalFiles["a.h" ] = guard(Code: "void a();" ); |
121 | TU.AdditionalFiles["b.h" ] = guard(Code: "void b();" ); |
122 | TU.AdditionalFiles["dir/c.h" ] = guard(Code: "void c();" ); |
123 | TU.AdditionalFiles["unused.h" ] = guard(Code: "void unused();" ); |
124 | TU.AdditionalFiles["dir/unused.h" ] = guard(Code: "void dirUnused();" ); |
125 | TU.AdditionalFiles["system/system_header.h" ] = guard(Code: "" ); |
126 | TU.AdditionalFiles["unguarded.h" ] = "" ; |
127 | TU.ExtraArgs.push_back(x: "-I" + testPath(File: "dir" )); |
128 | TU.ExtraArgs.push_back(x: "-isystem" + testPath(File: "system" )); |
129 | TU.Code = MainFile.str(); |
130 | ParsedAST AST = TU.build(); |
131 | IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
132 | EXPECT_THAT( |
133 | Findings.UnusedIncludes, |
134 | UnorderedElementsAre(Pointee(writtenInclusion("\"unused.h\"" )), |
135 | Pointee(writtenInclusion("\"dir/unused.h\"" )))); |
136 | } |
137 | |
138 | TEST(IncludeCleaner, ComputeMissingHeaders) { |
139 | Annotations MainFile(R"cpp( |
140 | #include "a.h" |
141 | |
142 | void foo() { |
143 | $b[[b]](); |
144 | })cpp" ); |
145 | TestTU TU; |
146 | TU.Filename = "foo.cpp" ; |
147 | TU.AdditionalFiles["a.h" ] = guard(Code: "#include \"b.h\"" ); |
148 | TU.AdditionalFiles["b.h" ] = guard(Code: "void b();" ); |
149 | |
150 | TU.Code = MainFile.code(); |
151 | ParsedAST AST = TU.build(); |
152 | |
153 | IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
154 | const SourceManager &SM = AST.getSourceManager(); |
155 | const NamedDecl *BDecl = nullptr; |
156 | for (Decl *D : AST.getASTContext().getTranslationUnitDecl()->decls()) { |
157 | const NamedDecl *CandidateDecl = llvm::dyn_cast<NamedDecl>(D); |
158 | std::string Name = CandidateDecl->getQualifiedNameAsString(); |
159 | if (Name != "b" ) |
160 | continue; |
161 | BDecl = CandidateDecl; |
162 | } |
163 | ASSERT_TRUE(BDecl); |
164 | include_cleaner::Symbol B{*BDecl}; |
165 | auto Range = MainFile.range(Name: "b" ); |
166 | size_t Start = llvm::cantFail(ValOrErr: positionToOffset(Code: MainFile.code(), P: Range.start)); |
167 | size_t End = llvm::cantFail(ValOrErr: positionToOffset(Code: MainFile.code(), P: Range.end)); |
168 | syntax::FileRange BRange{SM.getMainFileID(), static_cast<unsigned int>(Start), |
169 | static_cast<unsigned int>(End)}; |
170 | include_cleaner::Header { |
171 | *SM.getFileManager().getOptionalFileRef(Filename: "b.h" )}; |
172 | MissingIncludeDiagInfo BInfo{.Symbol: B, .SymRefRange: BRange, .Providers: {Header}}; |
173 | EXPECT_THAT(Findings.MissingIncludes, ElementsAre(BInfo)); |
174 | } |
175 | |
176 | TEST(IncludeCleaner, GenerateMissingHeaderDiags) { |
177 | Annotations MainFile(R"cpp( |
178 | #include "a.h" |
179 | #include "all.h" |
180 | $insert_b[[]]#include "baz.h" |
181 | #include "dir/c.h" |
182 | $insert_d[[]]$insert_foo[[]]#include "fuzz.h" |
183 | #include "header.h" |
184 | $insert_foobar[[]]#include <e.h> |
185 | $insert_f[[]]$insert_vector[[]] |
186 | |
187 | #define DEF(X) const Foo *X; |
188 | #define BAZ(X) const X x |
189 | |
190 | // No missing include insertion for ambiguous macro refs. |
191 | #if defined(FOO) |
192 | #endif |
193 | |
194 | void foo() { |
195 | $b[[b]](); |
196 | |
197 | ns::$bar[[Bar]] bar; |
198 | bar.d(); |
199 | $f[[f]](); |
200 | |
201 | // this should not be diagnosed, because it's ignored in the config |
202 | buzz(); |
203 | |
204 | $foobar[[foobar]](); |
205 | |
206 | std::$vector[[vector]] v; |
207 | |
208 | int var = $FOO[[FOO]]; |
209 | |
210 | $DEF[[DEF]](a); |
211 | |
212 | $BAR[[BAR]](b); |
213 | |
214 | BAZ($Foo[[Foo]]); |
215 | })cpp" ); |
216 | |
217 | TestTU TU; |
218 | TU.Filename = "main.cpp" ; |
219 | TU.AdditionalFiles["a.h" ] = guard(Code: "#include \"b.h\"" ); |
220 | TU.AdditionalFiles["b.h" ] = guard(Code: "void b();" ); |
221 | |
222 | TU.AdditionalFiles["dir/c.h" ] = guard(Code: "#include \"d.h\"" ); |
223 | TU.AdditionalFiles["dir/d.h" ] = |
224 | guard(Code: "namespace ns { struct Bar { void d(); }; }" ); |
225 | |
226 | TU.AdditionalFiles["system/e.h" ] = guard(Code: "#include <f.h>" ); |
227 | TU.AdditionalFiles["system/f.h" ] = guard(Code: "void f();" ); |
228 | TU.ExtraArgs.push_back(x: "-isystem" + testPath(File: "system" )); |
229 | |
230 | TU.AdditionalFiles["fuzz.h" ] = guard(Code: "#include \"buzz.h\"" ); |
231 | TU.AdditionalFiles["buzz.h" ] = guard(Code: "void buzz();" ); |
232 | |
233 | TU.AdditionalFiles["baz.h" ] = guard(Code: "#include \"private.h\"" ); |
234 | TU.AdditionalFiles["private.h" ] = guard(Code: R"cpp( |
235 | // IWYU pragma: private, include "public.h" |
236 | void foobar(); |
237 | )cpp" ); |
238 | TU.AdditionalFiles["header.h" ] = guard(Code: R"cpp( |
239 | namespace std { class vector {}; } |
240 | )cpp" ); |
241 | |
242 | TU.AdditionalFiles["all.h" ] = guard(Code: "#include \"foo.h\"" ); |
243 | TU.AdditionalFiles["foo.h" ] = guard(Code: R"cpp( |
244 | #define BAR(x) Foo *x |
245 | #define FOO 1 |
246 | struct Foo{}; |
247 | )cpp" ); |
248 | |
249 | TU.Code = MainFile.code(); |
250 | ParsedAST AST = TU.build(); |
251 | |
252 | auto Findings = computeIncludeCleanerFindings(AST); |
253 | Findings.UnusedIncludes.clear(); |
254 | std::vector<clangd::Diag> Diags = issueIncludeCleanerDiagnostics( |
255 | AST, Code: TU.Code, Findings, TFS: MockFS(), |
256 | IgnoreHeader: {[](llvm::StringRef ) { return Header.ends_with(Suffix: "buzz.h" ); }}); |
257 | EXPECT_THAT( |
258 | Diags, |
259 | UnorderedElementsAre( |
260 | AllOf(Diag(MainFile.range("b" ), |
261 | "No header providing \"b\" is directly included" ), |
262 | withFix({Fix(MainFile.range("insert_b" ), "#include \"b.h\"\n" , |
263 | "#include \"b.h\"" ), |
264 | FixMessage("add all missing includes" )})), |
265 | AllOf(Diag(MainFile.range("bar" ), |
266 | "No header providing \"ns::Bar\" is directly included" ), |
267 | withFix({Fix(MainFile.range("insert_d" ), |
268 | "#include \"dir/d.h\"\n" , "#include \"dir/d.h\"" ), |
269 | FixMessage("add all missing includes" )})), |
270 | AllOf(Diag(MainFile.range("f" ), |
271 | "No header providing \"f\" is directly included" ), |
272 | withFix({Fix(MainFile.range("insert_f" ), "#include <f.h>\n" , |
273 | "#include <f.h>" ), |
274 | FixMessage("add all missing includes" )})), |
275 | AllOf( |
276 | Diag(MainFile.range("foobar" ), |
277 | "No header providing \"foobar\" is directly included" ), |
278 | withFix({Fix(MainFile.range("insert_foobar" ), |
279 | "#include \"public.h\"\n" , "#include \"public.h\"" ), |
280 | FixMessage("add all missing includes" )})), |
281 | AllOf( |
282 | Diag(MainFile.range("vector" ), |
283 | "No header providing \"std::vector\" is directly included" ), |
284 | withFix({ |
285 | Fix(MainFile.range("insert_vector" ), "#include <vector>\n" , |
286 | "#include <vector>" ), |
287 | FixMessage("add all missing includes" ), |
288 | })), |
289 | AllOf(Diag(MainFile.range("FOO" ), |
290 | "No header providing \"FOO\" is directly included" ), |
291 | withFix({Fix(MainFile.range("insert_foo" ), |
292 | "#include \"foo.h\"\n" , "#include \"foo.h\"" ), |
293 | FixMessage("add all missing includes" )})), |
294 | AllOf(Diag(MainFile.range("DEF" ), |
295 | "No header providing \"Foo\" is directly included" ), |
296 | withFix({Fix(MainFile.range("insert_foo" ), |
297 | "#include \"foo.h\"\n" , "#include \"foo.h\"" ), |
298 | FixMessage("add all missing includes" )})), |
299 | AllOf(Diag(MainFile.range("BAR" ), |
300 | "No header providing \"BAR\" is directly included" ), |
301 | withFix({Fix(MainFile.range("insert_foo" ), |
302 | "#include \"foo.h\"\n" , "#include \"foo.h\"" ), |
303 | FixMessage("add all missing includes" )})), |
304 | AllOf(Diag(MainFile.range("Foo" ), |
305 | "No header providing \"Foo\" is directly included" ), |
306 | withFix({Fix(MainFile.range("insert_foo" ), |
307 | "#include \"foo.h\"\n" , "#include \"foo.h\"" ), |
308 | FixMessage("add all missing includes" )})))); |
309 | } |
310 | |
311 | TEST(IncludeCleaner, IWYUPragmas) { |
312 | TestTU TU; |
313 | TU.Code = R"cpp( |
314 | #include "behind_keep.h" // IWYU pragma: keep |
315 | #include "exported.h" // IWYU pragma: export |
316 | #include "public.h" |
317 | |
318 | void bar() { foo(); } |
319 | #include "keep_main_file.h" // IWYU pragma: keep |
320 | )cpp" ; |
321 | TU.AdditionalFiles["behind_keep.h" ] = guard(Code: "" ); |
322 | TU.AdditionalFiles["keep_main_file.h" ] = guard(Code: "" ); |
323 | TU.AdditionalFiles["exported.h" ] = guard(Code: "" ); |
324 | TU.AdditionalFiles["public.h" ] = guard(Code: "#include \"private.h\"" ); |
325 | TU.AdditionalFiles["private.h" ] = guard(Code: R"cpp( |
326 | // IWYU pragma: private, include "public.h" |
327 | void foo() {} |
328 | )cpp" ); |
329 | ParsedAST AST = TU.build(); |
330 | IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
331 | EXPECT_THAT(Findings.UnusedIncludes, IsEmpty()); |
332 | } |
333 | |
334 | TEST(IncludeCleaner, IWYUPragmaExport) { |
335 | TestTU TU; |
336 | TU.Code = R"cpp( |
337 | #include "foo.h" |
338 | )cpp" ; |
339 | TU.AdditionalFiles["foo.h" ] = R"cpp( |
340 | #ifndef FOO_H |
341 | #define FOO_H |
342 | |
343 | #include "bar.h" // IWYU pragma: export |
344 | |
345 | #endif |
346 | )cpp" ; |
347 | TU.AdditionalFiles["bar.h" ] = guard(Code: R"cpp( |
348 | void bar() {} |
349 | )cpp" ); |
350 | ParsedAST AST = TU.build(); |
351 | |
352 | IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
353 | EXPECT_THAT(Findings.UnusedIncludes, |
354 | ElementsAre(Pointee(writtenInclusion("\"foo.h\"" )))); |
355 | } |
356 | |
357 | TEST(IncludeCleaner, NoDiagsForObjC) { |
358 | TestTU TU; |
359 | TU.Code = R"cpp( |
360 | #include "foo.h" |
361 | |
362 | void bar() {} |
363 | )cpp" ; |
364 | TU.AdditionalFiles["foo.h" ] = R"cpp( |
365 | #ifndef FOO_H |
366 | #define FOO_H |
367 | |
368 | #endif |
369 | )cpp" ; |
370 | TU.ExtraArgs.emplace_back(args: "-xobjective-c" ); |
371 | |
372 | ParsedAST AST = TU.build(); |
373 | IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
374 | EXPECT_THAT(Findings.MissingIncludes, IsEmpty()); |
375 | EXPECT_THAT(Findings.UnusedIncludes, IsEmpty()); |
376 | } |
377 | |
378 | TEST(IncludeCleaner, UmbrellaUsesPrivate) { |
379 | TestTU TU; |
380 | TU.Code = R"cpp( |
381 | #include "private.h" |
382 | )cpp" ; |
383 | TU.AdditionalFiles["private.h" ] = guard(Code: R"cpp( |
384 | // IWYU pragma: private, include "public.h" |
385 | void foo() {} |
386 | )cpp" ); |
387 | TU.Filename = "public.h" ; |
388 | ParsedAST AST = TU.build(); |
389 | IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
390 | EXPECT_THAT(Findings.UnusedIncludes, IsEmpty()); |
391 | } |
392 | |
393 | TEST(IncludeCleaner, MacroExpandedThroughIncludes) { |
394 | Annotations MainFile(R"cpp( |
395 | #include "all.h" |
396 | #define FOO(X) const Foo *X |
397 | void foo() { |
398 | #include [["expander.inc"]] |
399 | } |
400 | )cpp" ); |
401 | |
402 | TestTU TU; |
403 | TU.AdditionalFiles["expander.inc" ] = guard(Code: "FOO(f1);FOO(f2);" ); |
404 | TU.AdditionalFiles["foo.h" ] = guard(Code: "struct Foo {};" ); |
405 | TU.AdditionalFiles["all.h" ] = guard(Code: "#include \"foo.h\"" ); |
406 | |
407 | TU.Code = MainFile.code(); |
408 | ParsedAST AST = TU.build(); |
409 | |
410 | auto Findings = computeIncludeCleanerFindings(AST).MissingIncludes; |
411 | EXPECT_THAT(Findings, testing::SizeIs(1)); |
412 | auto RefRange = Findings.front().SymRefRange; |
413 | auto &SM = AST.getSourceManager(); |
414 | EXPECT_EQ(RefRange.file(), SM.getMainFileID()); |
415 | // FIXME: Point at the spelling location, rather than the include. |
416 | EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range()); |
417 | } |
418 | |
419 | TEST(IncludeCleaner, MissingIncludesAreUnique) { |
420 | Annotations MainFile(R"cpp( |
421 | #include "all.h" |
422 | FOO([[Foo]]); |
423 | )cpp" ); |
424 | |
425 | TestTU TU; |
426 | TU.AdditionalFiles["foo.h" ] = guard(Code: "struct Foo {};" ); |
427 | TU.AdditionalFiles["all.h" ] = guard(Code: R"cpp( |
428 | #include "foo.h" |
429 | #define FOO(X) X y; X z |
430 | )cpp" ); |
431 | |
432 | TU.Code = MainFile.code(); |
433 | ParsedAST AST = TU.build(); |
434 | |
435 | auto Findings = computeIncludeCleanerFindings(AST).MissingIncludes; |
436 | EXPECT_THAT(Findings, testing::SizeIs(1)); |
437 | auto RefRange = Findings.front().SymRefRange; |
438 | auto &SM = AST.getSourceManager(); |
439 | EXPECT_EQ(RefRange.file(), SM.getMainFileID()); |
440 | EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range()); |
441 | } |
442 | |
443 | TEST(IncludeCleaner, NoCrash) { |
444 | TestTU TU; |
445 | Annotations MainCode(R"cpp( |
446 | #include "all.h" |
447 | void test() { |
448 | [[1s]]; |
449 | } |
450 | )cpp" ); |
451 | TU.Code = MainCode.code(); |
452 | TU.AdditionalFiles["foo.h" ] = |
453 | guard(Code: "int operator\"\"s(unsigned long long) { return 0; }" ); |
454 | TU.AdditionalFiles["all.h" ] = guard(Code: "#include \"foo.h\"" ); |
455 | ParsedAST AST = TU.build(); |
456 | const auto &MissingIncludes = |
457 | computeIncludeCleanerFindings(AST).MissingIncludes; |
458 | EXPECT_THAT(MissingIncludes, testing::SizeIs(1)); |
459 | auto &SM = AST.getSourceManager(); |
460 | EXPECT_EQ( |
461 | halfOpenToRange(SM, MissingIncludes.front().SymRefRange.toCharRange(SM)), |
462 | MainCode.range()); |
463 | } |
464 | |
465 | TEST(IncludeCleaner, IsPreferredProvider) { |
466 | auto TU = TestTU::withCode(Code: R"cpp( |
467 | #include "decl.h" |
468 | #include "def.h" |
469 | #include "def.h" |
470 | )cpp" ); |
471 | TU.AdditionalFiles["decl.h" ] = "" ; |
472 | TU.AdditionalFiles["def.h" ] = "" ; |
473 | |
474 | auto AST = TU.build(); |
475 | auto &IncludeDecl = AST.getIncludeStructure().MainFileIncludes[0]; |
476 | auto &IncludeDef1 = AST.getIncludeStructure().MainFileIncludes[1]; |
477 | auto &IncludeDef2 = AST.getIncludeStructure().MainFileIncludes[2]; |
478 | |
479 | auto &FM = AST.getSourceManager().getFileManager(); |
480 | auto DeclH = *FM.getOptionalFileRef(Filename: "decl.h" ); |
481 | auto DefH = *FM.getOptionalFileRef(Filename: "def.h" ); |
482 | |
483 | auto Includes = convertIncludes(AST); |
484 | std::vector<include_cleaner::Header> Providers = { |
485 | include_cleaner::Header(DefH), include_cleaner::Header(DeclH)}; |
486 | EXPECT_FALSE(isPreferredProvider(IncludeDecl, Includes, Providers)); |
487 | EXPECT_TRUE(isPreferredProvider(IncludeDef1, Includes, Providers)); |
488 | EXPECT_TRUE(isPreferredProvider(IncludeDef2, Includes, Providers)); |
489 | } |
490 | |
491 | TEST(IncludeCleaner, BatchFix) { |
492 | TestTU TU; |
493 | TU.Filename = "main.cpp" ; |
494 | TU.AdditionalFiles["foo.h" ] = guard(Code: "class Foo;" ); |
495 | TU.AdditionalFiles["bar.h" ] = guard(Code: "class Bar;" ); |
496 | TU.AdditionalFiles["all.h" ] = guard(Code: R"cpp( |
497 | #include "foo.h" |
498 | #include "bar.h" |
499 | )cpp" ); |
500 | |
501 | TU.Code = R"cpp( |
502 | #include "all.h" |
503 | |
504 | Foo* foo; |
505 | )cpp" ; |
506 | auto AST = TU.build(); |
507 | EXPECT_THAT( |
508 | issueIncludeCleanerDiagnostics( |
509 | AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()), |
510 | UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\"" ), |
511 | FixMessage("fix all includes" )}), |
512 | withFix({FixMessage("remove #include directive" ), |
513 | FixMessage("fix all includes" )}))); |
514 | |
515 | TU.Code = R"cpp( |
516 | #include "all.h" |
517 | #include "bar.h" |
518 | |
519 | Foo* foo; |
520 | )cpp" ; |
521 | AST = TU.build(); |
522 | EXPECT_THAT( |
523 | issueIncludeCleanerDiagnostics( |
524 | AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()), |
525 | UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\"" ), |
526 | FixMessage("fix all includes" )}), |
527 | withFix({FixMessage("remove #include directive" ), |
528 | FixMessage("remove all unused includes" ), |
529 | FixMessage("fix all includes" )}), |
530 | withFix({FixMessage("remove #include directive" ), |
531 | FixMessage("remove all unused includes" ), |
532 | FixMessage("fix all includes" )}))); |
533 | |
534 | TU.Code = R"cpp( |
535 | #include "all.h" |
536 | |
537 | Foo* foo; |
538 | Bar* bar; |
539 | )cpp" ; |
540 | AST = TU.build(); |
541 | EXPECT_THAT( |
542 | issueIncludeCleanerDiagnostics( |
543 | AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()), |
544 | UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\"" ), |
545 | FixMessage("add all missing includes" ), |
546 | FixMessage("fix all includes" )}), |
547 | withFix({FixMessage("#include \"bar.h\"" ), |
548 | FixMessage("add all missing includes" ), |
549 | FixMessage("fix all includes" )}), |
550 | withFix({FixMessage("remove #include directive" ), |
551 | FixMessage("fix all includes" )}))); |
552 | } |
553 | |
554 | // In the presence of IWYU pragma private, we should accept spellings other |
555 | // than the recommended one if they appear to name the same public header. |
556 | TEST(IncludeCleaner, VerbatimEquivalence) { |
557 | auto TU = TestTU::withCode(Code: R"cpp( |
558 | #include "lib/rel/public.h" |
559 | int x = Public; |
560 | )cpp" ); |
561 | TU.AdditionalFiles["repo/lib/rel/private.h" ] = R"cpp( |
562 | #pragma once |
563 | // IWYU pragma: private, include "rel/public.h" |
564 | int Public; |
565 | )cpp" ; |
566 | TU.AdditionalFiles["repo/lib/rel/public.h" ] = R"cpp( |
567 | #pragma once |
568 | #include "rel/private.h" |
569 | )cpp" ; |
570 | |
571 | TU.ExtraArgs.push_back(x: "-Irepo" ); |
572 | TU.ExtraArgs.push_back(x: "-Irepo/lib" ); |
573 | |
574 | auto AST = TU.build(); |
575 | auto Findings = computeIncludeCleanerFindings(AST); |
576 | EXPECT_THAT(Findings.MissingIncludes, IsEmpty()); |
577 | EXPECT_THAT(Findings.UnusedIncludes, IsEmpty()); |
578 | } |
579 | |
580 | TEST(IncludeCleaner, ResourceDirIsIgnored) { |
581 | auto TU = TestTU::withCode(Code: R"cpp( |
582 | #include <amintrin.h> |
583 | #include <imintrin.h> |
584 | void baz() { |
585 | bar(); |
586 | } |
587 | )cpp" ); |
588 | TU.ExtraArgs.push_back(x: "-resource-dir" ); |
589 | TU.ExtraArgs.push_back(x: testPath(File: "resources" )); |
590 | TU.AdditionalFiles["resources/include/amintrin.h" ] = guard(Code: "" ); |
591 | TU.AdditionalFiles["resources/include/imintrin.h" ] = guard(Code: R"cpp( |
592 | #include <emintrin.h> |
593 | )cpp" ); |
594 | TU.AdditionalFiles["resources/include/emintrin.h" ] = guard(Code: R"cpp( |
595 | void bar(); |
596 | )cpp" ); |
597 | auto AST = TU.build(); |
598 | auto Findings = computeIncludeCleanerFindings(AST); |
599 | EXPECT_THAT(Findings.UnusedIncludes, IsEmpty()); |
600 | EXPECT_THAT(Findings.MissingIncludes, IsEmpty()); |
601 | } |
602 | |
603 | } // namespace |
604 | } // namespace clangd |
605 | } // namespace clang |
606 | |