clang-tools 20.0.0git
IncludeCleanerTests.cpp
Go to the documentation of this file.
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
35namespace clang {
36namespace clangd {
37namespace {
38
39using ::testing::AllOf;
40using ::testing::ElementsAre;
41using ::testing::IsEmpty;
42using ::testing::Matcher;
43using ::testing::Pointee;
44using ::testing::UnorderedElementsAre;
45
46Matcher<const Diag &>
47withFix(std::vector<::testing::Matcher<Fix>> FixMatcheres) {
48 return Field(&Diag::Fixes, testing::UnorderedElementsAreArray(FixMatcheres));
49}
50
51MATCHER_P2(Diag, Range, Message,
52 "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {
53 return arg.Range == Range && arg.Message == Message;
54}
55
56MATCHER_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}
62MATCHER_P(FixMessage, Message, "") { return arg.Message == Message; }
63
64std::string guard(llvm::StringRef Code) {
65 return "#pragma once\n" + Code.str();
66}
67
68MATCHER_P(writtenInclusion, Written, "") {
69 if (arg.Written != Written)
70 *result_listener << arg.Written;
71 return arg.Written == Written;
72}
73
74TEST(IncludeCleaner, StdlibUnused) {
75 auto TU = TestTU::withCode(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("#include <bits>");
92 TU.AdditionalFiles["queue"] = guard("#include <bits>");
93 TU.AdditionalFiles["vector"] = guard("#include <bits>");
94 TU.AdditionalFiles["string"] = guard("#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
102TEST(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 #include <non_system_angled_header.h>
112 void foo() {
113 a();
114 b();
115 c();
116 })cpp";
117 // Build expected ast with symbols coming from headers.
118 TestTU TU;
119 TU.Filename = "foo.cpp";
120 TU.AdditionalFiles["foo.h"] = guard("void foo();");
121 TU.AdditionalFiles["a.h"] = guard("void a();");
122 TU.AdditionalFiles["b.h"] = guard("void b();");
123 TU.AdditionalFiles["dir/c.h"] = guard("void c();");
124 TU.AdditionalFiles["unused.h"] = guard("void unused();");
125 TU.AdditionalFiles["dir/unused.h"] = guard("void dirUnused();");
126 TU.AdditionalFiles["dir/non_system_angled_header.h"] = guard("");
127 TU.AdditionalFiles["system/system_header.h"] = guard("");
128 TU.AdditionalFiles["unguarded.h"] = "";
129 TU.ExtraArgs.push_back("-I" + testPath("dir"));
130 TU.ExtraArgs.push_back("-isystem" + testPath("system"));
131 TU.Code = MainFile.str();
132 ParsedAST AST = TU.build();
133 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
134 EXPECT_THAT(
135 Findings.UnusedIncludes,
136 UnorderedElementsAre(Pointee(writtenInclusion("\"unused.h\"")),
137 Pointee(writtenInclusion("\"dir/unused.h\""))));
138}
139
140TEST(IncludeCleaner, IgnoredAngledHeaders) {
141 // Currently the default behavior is to ignore unused angled includes
142 auto TU = TestTU::withCode(R"cpp(
143 #include <system_header.h>
144 #include <system_unused.h>
145 #include <non_system_angled_unused.h>
146 SystemClass x;
147 )cpp");
148 TU.AdditionalFiles["system/system_header.h"] = guard("class SystemClass {};");
149 TU.AdditionalFiles["system/system_unused.h"] = guard("");
150 TU.AdditionalFiles["dir/non_system_angled_unused.h"] = guard("");
151 TU.ExtraArgs = {
152 "-isystem" + testPath("system"),
153 "-I" + testPath("dir"),
154 };
155 auto AST = TU.build();
156 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
157 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
158}
159
160TEST(IncludeCleaner, UnusedAngledHeaders) {
161 auto TU = TestTU::withCode(R"cpp(
162 #include <system_header.h>
163 #include <system_unused.h>
164 #include <non_system_angled_unused.h>
165 SystemClass x;
166 )cpp");
167 TU.AdditionalFiles["system/system_header.h"] = guard("class SystemClass {};");
168 TU.AdditionalFiles["system/system_unused.h"] = guard("");
169 TU.AdditionalFiles["dir/non_system_angled_unused.h"] = guard("");
170 TU.ExtraArgs = {
171 "-isystem" + testPath("system"),
172 "-I" + testPath("dir"),
173 };
174 auto AST = TU.build();
175 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST, true);
176 EXPECT_THAT(Findings.UnusedIncludes,
177 UnorderedElementsAre(
178 Pointee(writtenInclusion("<system_unused.h>")),
179 Pointee(writtenInclusion("<non_system_angled_unused.h>"))));
180}
181
182TEST(IncludeCleaner, ComputeMissingHeaders) {
183 Annotations MainFile(R"cpp(
184 #include "a.h"
185
186 void foo() {
187 $b[[b]]();
188 })cpp");
189 TestTU TU;
190 TU.Filename = "foo.cpp";
191 TU.AdditionalFiles["a.h"] = guard("#include \"b.h\"");
192 TU.AdditionalFiles["b.h"] = guard("void b();");
193
194 TU.Code = MainFile.code();
195 ParsedAST AST = TU.build();
196
197 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
198 const SourceManager &SM = AST.getSourceManager();
199 const NamedDecl *BDecl = nullptr;
200 for (Decl *D : AST.getASTContext().getTranslationUnitDecl()->decls()) {
201 const NamedDecl *CandidateDecl = llvm::dyn_cast<NamedDecl>(D);
202 std::string Name = CandidateDecl->getQualifiedNameAsString();
203 if (Name != "b")
204 continue;
205 BDecl = CandidateDecl;
206 }
207 ASSERT_TRUE(BDecl);
208 include_cleaner::Symbol B{*BDecl};
209 auto Range = MainFile.range("b");
210 size_t Start = llvm::cantFail(positionToOffset(MainFile.code(), Range.start));
211 size_t End = llvm::cantFail(positionToOffset(MainFile.code(), Range.end));
212 syntax::FileRange BRange{SM.getMainFileID(), static_cast<unsigned int>(Start),
213 static_cast<unsigned int>(End)};
214 include_cleaner::Header Header{
215 *SM.getFileManager().getOptionalFileRef("b.h")};
216 MissingIncludeDiagInfo BInfo{B, BRange, {Header}};
217 EXPECT_THAT(Findings.MissingIncludes, ElementsAre(BInfo));
218}
219
220TEST(IncludeCleaner, GenerateMissingHeaderDiags) {
221 Annotations MainFile(R"cpp(
222#include "a.h"
223#include "all.h"
224$insert_b[[]]#include "baz.h"
225#include "dir/c.h"
226$insert_d[[]]$insert_foo[[]]#include "fuzz.h"
227#include "header.h"
228$insert_foobar[[]]#include <e.h>
229$insert_f[[]]$insert_vector[[]]
230
231#define DEF(X) const Foo *X;
232#define BAZ(X) const X x
233
234// No missing include insertion for ambiguous macro refs.
235#if defined(FOO)
236#endif
237
238 void foo() {
239 $b[[b]]();
240
241 ns::$bar[[Bar]] bar;
242 bar.d();
243 $f[[f]]();
244
245 // this should not be diagnosed, because it's ignored in the config
246 buzz();
247
248 $foobar[[foobar]]();
249
250 std::$vector[[vector]] v;
251
252 int var = $FOO[[FOO]];
253
254 $DEF[[DEF]](a);
255
256 $BAR[[BAR]](b);
257
258 BAZ($Foo[[Foo]]);
259})cpp");
260
261 TestTU TU;
262 TU.Filename = "main.cpp";
263 TU.AdditionalFiles["a.h"] = guard("#include \"b.h\"");
264 TU.AdditionalFiles["b.h"] = guard("void b();");
265
266 TU.AdditionalFiles["dir/c.h"] = guard("#include \"d.h\"");
267 TU.AdditionalFiles["dir/d.h"] =
268 guard("namespace ns { struct Bar { void d(); }; }");
269
270 TU.AdditionalFiles["system/e.h"] = guard("#include <f.h>");
271 TU.AdditionalFiles["system/f.h"] = guard("void f();");
272 TU.ExtraArgs.push_back("-isystem" + testPath("system"));
273
274 TU.AdditionalFiles["fuzz.h"] = guard("#include \"buzz.h\"");
275 TU.AdditionalFiles["buzz.h"] = guard("void buzz();");
276
277 TU.AdditionalFiles["baz.h"] = guard("#include \"private.h\"");
278 TU.AdditionalFiles["private.h"] = guard(R"cpp(
279 // IWYU pragma: private, include "public.h"
280 void foobar();
281 )cpp");
282 TU.AdditionalFiles["header.h"] = guard(R"cpp(
283 namespace std { class vector {}; }
284 )cpp");
285
286 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
287 TU.AdditionalFiles["foo.h"] = guard(R"cpp(
288 #define BAR(x) Foo *x
289 #define FOO 1
290 struct Foo{};
291 )cpp");
292
293 TU.Code = MainFile.code();
294 ParsedAST AST = TU.build();
295
296 auto Findings = computeIncludeCleanerFindings(AST);
297 Findings.UnusedIncludes.clear();
298 std::vector<clangd::Diag> Diags = issueIncludeCleanerDiagnostics(
299 AST, TU.Code, Findings, MockFS(),
300 {[](llvm::StringRef Header) { return Header.ends_with("buzz.h"); }});
301 EXPECT_THAT(
302 Diags,
303 UnorderedElementsAre(
304 AllOf(Diag(MainFile.range("b"),
305 "No header providing \"b\" is directly included"),
306 withFix({Fix(MainFile.range("insert_b"), "#include \"b.h\"\n",
307 "#include \"b.h\""),
308 FixMessage("add all missing includes")})),
309 AllOf(Diag(MainFile.range("bar"),
310 "No header providing \"ns::Bar\" is directly included"),
311 withFix({Fix(MainFile.range("insert_d"),
312 "#include \"dir/d.h\"\n", "#include \"dir/d.h\""),
313 FixMessage("add all missing includes")})),
314 AllOf(Diag(MainFile.range("f"),
315 "No header providing \"f\" is directly included"),
316 withFix({Fix(MainFile.range("insert_f"), "#include <f.h>\n",
317 "#include <f.h>"),
318 FixMessage("add all missing includes")})),
319 AllOf(
320 Diag(MainFile.range("foobar"),
321 "No header providing \"foobar\" is directly included"),
322 withFix({Fix(MainFile.range("insert_foobar"),
323 "#include \"public.h\"\n", "#include \"public.h\""),
324 FixMessage("add all missing includes")})),
325 AllOf(
326 Diag(MainFile.range("vector"),
327 "No header providing \"std::vector\" is directly included"),
328 withFix({
329 Fix(MainFile.range("insert_vector"), "#include <vector>\n",
330 "#include <vector>"),
331 FixMessage("add all missing includes"),
332 })),
333 AllOf(Diag(MainFile.range("FOO"),
334 "No header providing \"FOO\" is directly included"),
335 withFix({Fix(MainFile.range("insert_foo"),
336 "#include \"foo.h\"\n", "#include \"foo.h\""),
337 FixMessage("add all missing includes")})),
338 AllOf(Diag(MainFile.range("DEF"),
339 "No header providing \"Foo\" is directly included"),
340 withFix({Fix(MainFile.range("insert_foo"),
341 "#include \"foo.h\"\n", "#include \"foo.h\""),
342 FixMessage("add all missing includes")})),
343 AllOf(Diag(MainFile.range("BAR"),
344 "No header providing \"BAR\" is directly included"),
345 withFix({Fix(MainFile.range("insert_foo"),
346 "#include \"foo.h\"\n", "#include \"foo.h\""),
347 FixMessage("add all missing includes")})),
348 AllOf(Diag(MainFile.range("Foo"),
349 "No header providing \"Foo\" is directly included"),
350 withFix({Fix(MainFile.range("insert_foo"),
351 "#include \"foo.h\"\n", "#include \"foo.h\""),
352 FixMessage("add all missing includes")}))));
353}
354
355TEST(IncludeCleaner, IWYUPragmas) {
356 TestTU TU;
357 TU.Code = R"cpp(
358 #include "behind_keep.h" // IWYU pragma: keep
359 #include "exported.h" // IWYU pragma: export
360 #include "public.h"
361
362 void bar() { foo(); }
363 #include "keep_main_file.h" // IWYU pragma: keep
364 )cpp";
365 TU.AdditionalFiles["behind_keep.h"] = guard("");
366 TU.AdditionalFiles["keep_main_file.h"] = guard("");
367 TU.AdditionalFiles["exported.h"] = guard("");
368 TU.AdditionalFiles["public.h"] = guard("#include \"private.h\"");
369 TU.AdditionalFiles["private.h"] = guard(R"cpp(
370 // IWYU pragma: private, include "public.h"
371 void foo() {}
372 )cpp");
373 ParsedAST AST = TU.build();
374 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
375 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
376}
377
378TEST(IncludeCleaner, IWYUPragmaExport) {
379 TestTU TU;
380 TU.Code = R"cpp(
381 #include "foo.h"
382 )cpp";
383 TU.AdditionalFiles["foo.h"] = R"cpp(
384 #ifndef FOO_H
385 #define FOO_H
386
387 #include "bar.h" // IWYU pragma: export
388
389 #endif
390 )cpp";
391 TU.AdditionalFiles["bar.h"] = guard(R"cpp(
392 void bar() {}
393 )cpp");
394 ParsedAST AST = TU.build();
395
396 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
397 EXPECT_THAT(Findings.UnusedIncludes,
398 ElementsAre(Pointee(writtenInclusion("\"foo.h\""))));
399}
400
401TEST(IncludeCleaner, NoDiagsForObjC) {
402 TestTU TU;
403 TU.Code = R"cpp(
404 #include "foo.h"
405
406 void bar() {}
407 )cpp";
408 TU.AdditionalFiles["foo.h"] = R"cpp(
409 #ifndef FOO_H
410 #define FOO_H
411
412 #endif
413 )cpp";
414 TU.ExtraArgs.emplace_back("-xobjective-c");
415
416 ParsedAST AST = TU.build();
417 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
418 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
419 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
420}
421
422TEST(IncludeCleaner, UmbrellaUsesPrivate) {
423 TestTU TU;
424 TU.Code = R"cpp(
425 #include "private.h"
426 )cpp";
427 TU.AdditionalFiles["private.h"] = guard(R"cpp(
428 // IWYU pragma: private, include "public.h"
429 void foo() {}
430 )cpp");
431 TU.Filename = "public.h";
432 ParsedAST AST = TU.build();
433 IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
434 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
435}
436
437TEST(IncludeCleaner, MacroExpandedThroughIncludes) {
438 Annotations MainFile(R"cpp(
439 #include "all.h"
440 #define FOO(X) const Foo *X
441 void foo() {
442 #include [["expander.inc"]]
443 }
444)cpp");
445
446 TestTU TU;
447 TU.AdditionalFiles["expander.inc"] = guard("FOO(f1);FOO(f2);");
448 TU.AdditionalFiles["foo.h"] = guard("struct Foo {};");
449 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
450
451 TU.Code = MainFile.code();
452 ParsedAST AST = TU.build();
453
455 EXPECT_THAT(Findings, testing::SizeIs(1));
456 auto RefRange = Findings.front().SymRefRange;
457 auto &SM = AST.getSourceManager();
458 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
459 // FIXME: Point at the spelling location, rather than the include.
460 EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
461}
462
463TEST(IncludeCleaner, MissingIncludesAreUnique) {
464 Annotations MainFile(R"cpp(
465 #include "all.h"
466 FOO([[Foo]]);
467 )cpp");
468
469 TestTU TU;
470 TU.AdditionalFiles["foo.h"] = guard("struct Foo {};");
471 TU.AdditionalFiles["all.h"] = guard(R"cpp(
472 #include "foo.h"
473 #define FOO(X) X y; X z
474 )cpp");
475
476 TU.Code = MainFile.code();
477 ParsedAST AST = TU.build();
478
480 EXPECT_THAT(Findings, testing::SizeIs(1));
481 auto RefRange = Findings.front().SymRefRange;
482 auto &SM = AST.getSourceManager();
483 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
484 EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
485}
486
487TEST(IncludeCleaner, NoCrash) {
488 TestTU TU;
489 Annotations MainCode(R"cpp(
490 #include "all.h"
491 void test() {
492 [[1s]];
493 }
494 )cpp");
495 TU.Code = MainCode.code();
496 TU.AdditionalFiles["foo.h"] =
497 guard("int operator\"\"s(unsigned long long) { return 0; }");
498 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
499 ParsedAST AST = TU.build();
500 const auto &MissingIncludes =
502 EXPECT_THAT(MissingIncludes, testing::SizeIs(1));
503 auto &SM = AST.getSourceManager();
504 EXPECT_EQ(
505 halfOpenToRange(SM, MissingIncludes.front().SymRefRange.toCharRange(SM)),
506 MainCode.range());
507}
508
509TEST(IncludeCleaner, IsPreferredProvider) {
510 auto TU = TestTU::withCode(R"cpp(
511 #include "decl.h"
512 #include "def.h"
513 #include "def.h"
514 )cpp");
515 TU.AdditionalFiles["decl.h"] = "";
516 TU.AdditionalFiles["def.h"] = "";
517
518 auto AST = TU.build();
519 auto &IncludeDecl = AST.getIncludeStructure().MainFileIncludes[0];
520 auto &IncludeDef1 = AST.getIncludeStructure().MainFileIncludes[1];
521 auto &IncludeDef2 = AST.getIncludeStructure().MainFileIncludes[2];
522
523 auto &FM = AST.getSourceManager().getFileManager();
524 auto DeclH = *FM.getOptionalFileRef("decl.h");
525 auto DefH = *FM.getOptionalFileRef("def.h");
526
527 auto Includes = convertIncludes(AST);
528 std::vector<include_cleaner::Header> Providers = {
529 include_cleaner::Header(DefH), include_cleaner::Header(DeclH)};
530 EXPECT_FALSE(isPreferredProvider(IncludeDecl, Includes, Providers));
531 EXPECT_TRUE(isPreferredProvider(IncludeDef1, Includes, Providers));
532 EXPECT_TRUE(isPreferredProvider(IncludeDef2, Includes, Providers));
533}
534
535TEST(IncludeCleaner, BatchFix) {
536 TestTU TU;
537 TU.Filename = "main.cpp";
538 TU.AdditionalFiles["foo.h"] = guard("class Foo;");
539 TU.AdditionalFiles["bar.h"] = guard("class Bar;");
540 TU.AdditionalFiles["all.h"] = guard(R"cpp(
541 #include "foo.h"
542 #include "bar.h"
543 )cpp");
544
545 TU.Code = R"cpp(
546 #include "all.h"
547
548 Foo* foo;
549 )cpp";
550 auto AST = TU.build();
551 EXPECT_THAT(
553 AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()),
554 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
555 FixMessage("fix all includes")}),
556 withFix({FixMessage("remove #include directive"),
557 FixMessage("fix all includes")})));
558
559 TU.Code = R"cpp(
560 #include "all.h"
561 #include "bar.h"
562
563 Foo* foo;
564 )cpp";
565 AST = TU.build();
566 EXPECT_THAT(
568 AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()),
569 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
570 FixMessage("fix all includes")}),
571 withFix({FixMessage("remove #include directive"),
572 FixMessage("remove all unused includes"),
573 FixMessage("fix all includes")}),
574 withFix({FixMessage("remove #include directive"),
575 FixMessage("remove all unused includes"),
576 FixMessage("fix all includes")})));
577
578 TU.Code = R"cpp(
579 #include "all.h"
580
581 Foo* foo;
582 Bar* bar;
583 )cpp";
584 AST = TU.build();
585 EXPECT_THAT(
587 AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()),
588 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
589 FixMessage("add all missing includes"),
590 FixMessage("fix all includes")}),
591 withFix({FixMessage("#include \"bar.h\""),
592 FixMessage("add all missing includes"),
593 FixMessage("fix all includes")}),
594 withFix({FixMessage("remove #include directive"),
595 FixMessage("fix all includes")})));
596}
597
598// In the presence of IWYU pragma private, we should accept spellings other
599// than the recommended one if they appear to name the same public header.
600TEST(IncludeCleaner, VerbatimEquivalence) {
601 auto TU = TestTU::withCode(R"cpp(
602 #include "lib/rel/public.h"
603 int x = Public;
604 )cpp");
605 TU.AdditionalFiles["repo/lib/rel/private.h"] = R"cpp(
606 #pragma once
607 // IWYU pragma: private, include "rel/public.h"
608 int Public;
609 )cpp";
610 TU.AdditionalFiles["repo/lib/rel/public.h"] = R"cpp(
611 #pragma once
612 #include "rel/private.h"
613 )cpp";
614
615 TU.ExtraArgs.push_back("-Irepo");
616 TU.ExtraArgs.push_back("-Irepo/lib");
617
618 auto AST = TU.build();
619 auto Findings = computeIncludeCleanerFindings(AST);
620 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
621 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
622}
623
624TEST(IncludeCleaner, ResourceDirIsIgnored) {
625 auto TU = TestTU::withCode(R"cpp(
626 #include <amintrin.h>
627 #include <imintrin.h>
628 void baz() {
629 bar();
630 }
631 )cpp");
632 TU.ExtraArgs.push_back("-resource-dir");
633 TU.ExtraArgs.push_back(testPath("resources"));
634 TU.AdditionalFiles["resources/include/amintrin.h"] = guard("");
635 TU.AdditionalFiles["resources/include/imintrin.h"] = guard(R"cpp(
636 #include <emintrin.h>
637 )cpp");
638 TU.AdditionalFiles["resources/include/emintrin.h"] = guard(R"cpp(
639 void bar();
640 )cpp");
641 auto AST = TU.build();
642 auto Findings = computeIncludeCleanerFindings(AST);
643 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
644 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
645}
646
647TEST(IncludeCleaner, DifferentHeaderSameSpelling) {
648 // `foo` is declared in foo_inner/foo.h, but there's no way to spell it
649 // directly. Make sure we don't generate unusued/missing include findings in
650 // such cases.
651 auto TU = TestTU::withCode(R"cpp(
652 #include <foo.h>
653 void baz() {
654 foo();
655 }
656 )cpp");
657 TU.AdditionalFiles["foo/foo.h"] = guard("#include_next <foo.h>");
658 TU.AdditionalFiles["foo_inner/foo.h"] = guard(R"cpp(
659 void foo();
660 )cpp");
661 TU.ExtraArgs.push_back("-Ifoo");
662 TU.ExtraArgs.push_back("-Ifoo_inner");
663
664 auto AST = TU.build();
665 auto Findings = computeIncludeCleanerFindings(AST);
666 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
667 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
668}
669} // namespace
670} // namespace clangd
671} // namespace clang
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
std::string Code
std::string MainFile
Include Cleaner is clangd functionality for providing diagnostics for misuse of transitive headers an...
CharSourceRange Range
SourceRange for the file name.
Range halfOpenToRange(const SourceManager &SM, CharSourceRange R)
Definition: SourceCode.cpp:472
MATCHER_P2(hasFlag, Flag, Path, "")
include_cleaner::Includes convertIncludes(const ParsedAST &AST)
Converts the clangd include representation to include-cleaner include representation.
MATCHER_P(named, N, "")
std::string testPath(PathRef File, llvm::sys::path::Style Style)
Definition: TestFS.cpp:93
std::vector< Diag > issueIncludeCleanerDiagnostics(ParsedAST &AST, llvm::StringRef Code, const IncludeCleanerFindings &Findings, const ThreadsafeFS &TFS, HeaderFilter IgnoreHeaders)
TEST(BackgroundQueueTest, Priority)
bool isPreferredProvider(const Inclusion &Inc, const include_cleaner::Includes &Includes, llvm::ArrayRef< include_cleaner::Header > Providers)
Whether this #include is considered to provide a particular symbol.
IncludeCleanerFindings computeIncludeCleanerFindings(ParsedAST &AST, bool AnalyzeAngledIncludes)
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
Definition: SourceCode.cpp:173
const char * testRoot()
Definition: TestFS.cpp:85
constexpr llvm::StringLiteral Message
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
std::vector< Fix > Fixes
Alternative fixes for this diagnostic, one should be chosen.
Definition: Diagnostics.h:111
std::vector< MissingIncludeDiagInfo > MissingIncludes
static TestTU withCode(llvm::StringRef Code)
Definition: TestTU.h:36