clang-tools 24.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 "Compiler.h"
11#include "Diagnostics.h"
12#include "IncludeCleaner.h"
13#include "ParsedAST.h"
14#include "SourceCode.h"
15#include "TestFS.h"
16#include "TestTU.h"
17#include "clang-include-cleaner/Analysis.h"
18#include "clang-include-cleaner/Types.h"
19#include "clang/AST/DeclBase.h"
20#include "clang/Basic/SourceManager.h"
21#include "clang/Tooling/Syntax/Tokens.h"
22#include "llvm/ADT/ArrayRef.h"
23#include "llvm/ADT/ScopeExit.h"
24#include "llvm/ADT/StringMap.h"
25#include "llvm/ADT/StringRef.h"
26#include "llvm/Support/Casting.h"
27#include "llvm/Support/Error.h"
28#include "llvm/Support/ScopedPrinter.h"
29#include "gmock/gmock.h"
30#include "gtest/gtest.h"
31#include <cstddef>
32#include <optional>
33#include <string>
34#include <vector>
35
36namespace clang {
37namespace clangd {
38namespace {
39
40using ::testing::AllOf;
41using ::testing::ElementsAre;
42using ::testing::IsEmpty;
43using ::testing::Matcher;
44using ::testing::Pointee;
45using ::testing::UnorderedElementsAre;
46
47Matcher<const Diag &>
48withFix(std::vector<::testing::Matcher<Fix>> FixMatcheres) {
49 return Field(&Diag::Fixes, testing::UnorderedElementsAreArray(FixMatcheres));
50}
51
52MATCHER_P2(Diag, Range, Message,
53 "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {
54 return arg.Range == Range && arg.Message == Message;
55}
56
57MATCHER_P3(Fix, Range, Replacement, Message,
58 "Fix " + llvm::to_string(Range) + " => " +
59 ::testing::PrintToString(Replacement) + " = [" + Message + "]") {
60 return arg.Message == Message && arg.Edits.size() == 1 &&
61 arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement;
62}
63MATCHER_P(FixMessage, Message, "") { return arg.Message == Message; }
64
65std::string guard(llvm::StringRef Code) {
66 return "#pragma once\n" + Code.str();
67}
68
69MATCHER_P(writtenInclusion, Written, "") {
70 if (arg.Written != Written)
71 *result_listener << arg.Written;
72 return arg.Written == Written;
73}
74
75TEST(IncludeCleaner, StdlibUnused) {
76 auto TU = TestTU::withCode(R"cpp(
77 #include <list>
78 #include <queue>
79 #include <vector> // IWYU pragma: keep
80 #include <string> // IWYU pragma: export
81 std::list<int> x;
82 )cpp");
83 // Layout of std library impl is not relevant.
84 TU.AdditionalFiles["bits"] = R"cpp(
85 #pragma once
86 namespace std {
87 template <typename> class list {};
88 template <typename> class queue {};
89 template <typename> class vector {};
90 }
91 )cpp";
92 TU.AdditionalFiles["list"] = guard("#include <bits>");
93 TU.AdditionalFiles["queue"] = guard("#include <bits>");
94 TU.AdditionalFiles["vector"] = guard("#include <bits>");
95 TU.AdditionalFiles["string"] = guard("#include <bits>");
96 TU.ExtraArgs = {"-isystem", testRoot()};
97 auto AST = TU.build();
99 EXPECT_THAT(Findings.UnusedIncludes,
100 ElementsAre(Pointee(writtenInclusion("<queue>"))));
101}
102
103TEST(IncludeCleaner, GetUnusedHeaders) {
104 llvm::StringLiteral MainFile = R"cpp(
105 #include "a.h"
106 #include "b.h"
107 #include "dir/c.h"
108 #include "dir/unused.h"
109 #include "unguarded.h"
110 #include "unused.h"
111 #include <system_header.h>
112 #include <non_system_angled_header.h>
113 void foo() {
114 a();
115 b();
116 c();
117 })cpp";
118 // Build expected ast with symbols coming from headers.
119 TestTU TU;
120 TU.Filename = "foo.cpp";
121 TU.AdditionalFiles["foo.h"] = guard("void foo();");
122 TU.AdditionalFiles["a.h"] = guard("void a();");
123 TU.AdditionalFiles["b.h"] = guard("void b();");
124 TU.AdditionalFiles["dir/c.h"] = guard("void c();");
125 TU.AdditionalFiles["unused.h"] = guard("void unused();");
126 TU.AdditionalFiles["dir/unused.h"] = guard("void dirUnused();");
127 TU.AdditionalFiles["dir/non_system_angled_header.h"] = guard("");
128 TU.AdditionalFiles["system/system_header.h"] = guard("");
129 TU.AdditionalFiles["unguarded.h"] = "";
130 TU.ExtraArgs.push_back("-I" + testPath("dir"));
131 TU.ExtraArgs.push_back("-isystem" + testPath("system"));
132 TU.Code = MainFile.str();
133 ParsedAST AST = TU.build();
135 EXPECT_THAT(
136 Findings.UnusedIncludes,
137 UnorderedElementsAre(Pointee(writtenInclusion("\"unused.h\"")),
138 Pointee(writtenInclusion("\"dir/unused.h\""))));
139}
140
141TEST(IncludeCleaner, IgnoredAngledHeaders) {
142 // Currently the default behavior is to ignore unused angled includes
143 auto TU = TestTU::withCode(R"cpp(
144 #include <system_header.h>
145 #include <system_unused.h>
146 #include <non_system_angled_unused.h>
147 SystemClass x;
148 )cpp");
149 TU.AdditionalFiles["system/system_header.h"] = guard("class SystemClass {};");
150 TU.AdditionalFiles["system/system_unused.h"] = guard("");
151 TU.AdditionalFiles["dir/non_system_angled_unused.h"] = guard("");
152 TU.ExtraArgs = {
153 "-isystem" + testPath("system"),
154 "-I" + testPath("dir"),
155 };
156 auto AST = TU.build();
158 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
159}
160
161TEST(IncludeCleaner, UnusedAngledHeaders) {
162 auto TU = TestTU::withCode(R"cpp(
163 #include <system_header.h>
164 #include <system_unused.h>
165 #include <non_system_angled_unused.h>
166 SystemClass x;
167 )cpp");
168 TU.AdditionalFiles["system/system_header.h"] = guard("class SystemClass {};");
169 TU.AdditionalFiles["system/system_unused.h"] = guard("");
170 TU.AdditionalFiles["dir/non_system_angled_unused.h"] = guard("");
171 TU.ExtraArgs = {
172 "-isystem" + testPath("system"),
173 "-I" + testPath("dir"),
174 };
175 auto AST = TU.build();
177 EXPECT_THAT(Findings.UnusedIncludes,
178 UnorderedElementsAre(
179 Pointee(writtenInclusion("<system_unused.h>")),
180 Pointee(writtenInclusion("<non_system_angled_unused.h>"))));
181}
182
183TEST(IncludeCleaner, ComputeMissingHeaders) {
184 Annotations MainFile(R"cpp(
185 #include "a.h"
186
187 void foo() {
188 $b[[b]]();
189 })cpp");
190 TestTU TU;
191 TU.Filename = "foo.cpp";
192 TU.AdditionalFiles["a.h"] = guard("#include \"b.h\"");
193 TU.AdditionalFiles["b.h"] = guard("void b();");
194
195 TU.Code = MainFile.code();
196 ParsedAST AST = TU.build();
197
199 const SourceManager &SM = AST.getSourceManager();
200 const NamedDecl *BDecl = nullptr;
201 for (Decl *D : AST.getASTContext().getTranslationUnitDecl()->decls()) {
202 const NamedDecl *CandidateDecl = llvm::dyn_cast<NamedDecl>(D);
203 std::string Name = CandidateDecl->getQualifiedNameAsString();
204 if (Name != "b")
205 continue;
206 BDecl = CandidateDecl;
207 }
208 ASSERT_TRUE(BDecl);
209 include_cleaner::Symbol B{*BDecl};
210 auto Range = MainFile.range("b");
211 size_t Start = llvm::cantFail(positionToOffset(MainFile.code(), Range.start));
212 size_t End = llvm::cantFail(positionToOffset(MainFile.code(), Range.end));
213 syntax::FileRange BRange{SM.getMainFileID(), static_cast<unsigned int>(Start),
214 static_cast<unsigned int>(End)};
215 include_cleaner::Header Header{
216 *SM.getFileManager().getOptionalFileRef("b.h")};
217 MissingIncludeDiagInfo BInfo{B, BRange, {Header}};
218 EXPECT_THAT(Findings.MissingIncludes, ElementsAre(BInfo));
219}
220
221TEST(IncludeCleaner, GenerateMissingHeaderDiags) {
222 Annotations MainFile(R"cpp(
223#include "a.h"
224#include "angled_wrapper.h"
225#include "all.h"
226$insert_b[[]]#include "baz.h"
227#include "dir/c.h"
228$insert_d[[]]$insert_foo[[]]#include "fuzz.h"
229#include "header.h"
230$insert_foobar[[]]$insert_quoted[[]]$insert_quoted2[[]]#include "quoted_wrapper.h"
231$insert_angled[[]]#include <e.h>
232$insert_f[[]]#include <quoted2_wrapper.h>
233$insert_vector[[]]
234
235#define DEF(X) const Foo *X;
236#define BAZ(X) const X x
237
238// No missing include insertion for ambiguous macro refs.
239#if defined(FOO)
240#endif
241
242 void foo() {
243 $b[[b]]();
244 $angled[[angled]]();
245 $quoted[[quoted]]();
246 $quoted2[[quoted2]]();
247
248 ns::$bar[[Bar]] bar;
249 bar.d();
250 $f[[f]]();
251
252 // this should not be diagnosed, because it's ignored in the config
253 buzz();
254
255 $foobar[[foobar]]();
256
257 std::$vector[[vector]] v;
258
259 int var = $FOO[[FOO]];
260
261 $DEF[[DEF]](a);
262
263 $BAR[[BAR]](b);
264
265 BAZ($Foo[[Foo]]);
266})cpp");
267
268 TestTU TU;
269 TU.Filename = "main.cpp";
270 TU.AdditionalFiles["a.h"] = guard("#include \"b.h\"");
271 TU.AdditionalFiles["b.h"] = guard("void b();");
272
273 TU.AdditionalFiles["angled_wrapper.h"] = guard("#include <angled.h>");
274 TU.AdditionalFiles["angled.h"] = guard("void angled();");
275 TU.ExtraArgs.push_back("-I" + testPath("."));
276
277 TU.AdditionalFiles["quoted_wrapper.h"] = guard("#include \"quoted.h\"");
278 TU.AdditionalFiles["quoted.h"] = guard("void quoted();");
279
280 TU.AdditionalFiles["dir/c.h"] = guard("#include \"d.h\"");
281 TU.AdditionalFiles["dir/d.h"] =
282 guard("namespace ns { struct Bar { void d(); }; }");
283
284 TU.AdditionalFiles["system/e.h"] = guard("#include <f.h>");
285 TU.AdditionalFiles["system/f.h"] = guard("void f();");
286 TU.AdditionalFiles["system/quoted2_wrapper.h"] =
287 guard("#include <system/quoted2.h>");
288 TU.AdditionalFiles["system/quoted2.h"] = guard("void quoted2();");
289 TU.ExtraArgs.push_back("-isystem" + testPath("system"));
290
291 TU.AdditionalFiles["fuzz.h"] = guard("#include \"buzz.h\"");
292 TU.AdditionalFiles["buzz.h"] = guard("void buzz();");
293
294 TU.AdditionalFiles["baz.h"] = guard("#include \"private.h\"");
295 TU.AdditionalFiles["private.h"] = guard(R"cpp(
296 // IWYU pragma: private, include "public.h"
297 void foobar();
298 )cpp");
299 TU.AdditionalFiles["header.h"] = guard(R"cpp(
300 namespace std { class vector {}; }
301 )cpp");
302
303 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
304 TU.AdditionalFiles["foo.h"] = guard(R"cpp(
305 #define BAR(x) Foo *x
306 #define FOO 1
307 struct Foo{};
308 )cpp");
309
310 TU.Code = MainFile.code();
311 ParsedAST AST = TU.build();
312
313 auto Findings = computeIncludeCleanerFindings(AST);
314 Findings.UnusedIncludes.clear();
315 std::vector<clangd::Diag> Diags = issueIncludeCleanerDiagnostics(
316 AST, TU.Code, Findings, MockFS(),
317 /*IgnoreHeaders=*/{[](llvm::StringRef Header) {
318 return Header.ends_with("buzz.h");
319 }},
320 /*AngledHeaders=*/{[](llvm::StringRef Header) {
321 return Header.contains("angled.h");
322 }},
323 /*QuotedHeaders=*/{[](llvm::StringRef Header) {
324 return Header.contains("quoted.h") || Header.contains("quoted2.h");
325 }});
326 EXPECT_THAT(
327 Diags,
328 UnorderedElementsAre(
329 AllOf(Diag(MainFile.range("b"),
330 "No header providing \"b\" is directly included"),
331 withFix({Fix(MainFile.range("insert_b"), "#include \"b.h\"\n",
332 "#include \"b.h\""),
333 FixMessage("add all missing includes")})),
334 AllOf(Diag(MainFile.range("angled"),
335 "No header providing \"angled\" is directly included"),
336 withFix({Fix(MainFile.range("insert_angled"),
337 "#include <angled.h>\n", "#include <angled.h>"),
338 FixMessage("add all missing includes")})),
339 AllOf(
340 Diag(MainFile.range("quoted"),
341 "No header providing \"quoted\" is directly included"),
342 withFix({Fix(MainFile.range("insert_quoted"),
343 "#include \"quoted.h\"\n", "#include \"quoted.h\""),
344 FixMessage("add all missing includes")})),
345 AllOf(Diag(MainFile.range("quoted2"),
346 "No header providing \"quoted2\" is directly included"),
347 withFix(
348 {Fix(MainFile.range("insert_quoted2"),
349 "#include \"quoted2.h\"\n", "#include \"quoted2.h\""),
350 FixMessage("add all missing includes")})),
351 AllOf(Diag(MainFile.range("bar"),
352 "No header providing \"ns::Bar\" is directly included"),
353 withFix({Fix(MainFile.range("insert_d"),
354 "#include \"dir/d.h\"\n", "#include \"dir/d.h\""),
355 FixMessage("add all missing includes")})),
356 AllOf(Diag(MainFile.range("f"),
357 "No header providing \"f\" is directly included"),
358 withFix({Fix(MainFile.range("insert_f"), "#include <f.h>\n",
359 "#include <f.h>"),
360 FixMessage("add all missing includes")})),
361 AllOf(
362 Diag(MainFile.range("foobar"),
363 "No header providing \"foobar\" is directly included"),
364 withFix({Fix(MainFile.range("insert_foobar"),
365 "#include \"public.h\"\n", "#include \"public.h\""),
366 FixMessage("add all missing includes")})),
367 AllOf(
368 Diag(MainFile.range("vector"),
369 "No header providing \"std::vector\" is directly included"),
370 withFix({
371 Fix(MainFile.range("insert_vector"), "#include <vector>\n",
372 "#include <vector>"),
373 FixMessage("add all missing includes"),
374 })),
375 AllOf(Diag(MainFile.range("FOO"),
376 "No header providing \"FOO\" is directly included"),
377 withFix({Fix(MainFile.range("insert_foo"),
378 "#include \"foo.h\"\n", "#include \"foo.h\""),
379 FixMessage("add all missing includes")})),
380 AllOf(Diag(MainFile.range("DEF"),
381 "No header providing \"Foo\" is directly included"),
382 withFix({Fix(MainFile.range("insert_foo"),
383 "#include \"foo.h\"\n", "#include \"foo.h\""),
384 FixMessage("add all missing includes")})),
385 AllOf(Diag(MainFile.range("BAR"),
386 "No header providing \"BAR\" is directly included"),
387 withFix({Fix(MainFile.range("insert_foo"),
388 "#include \"foo.h\"\n", "#include \"foo.h\""),
389 FixMessage("add all missing includes")})),
390 AllOf(Diag(MainFile.range("Foo"),
391 "No header providing \"Foo\" is directly included"),
392 withFix({Fix(MainFile.range("insert_foo"),
393 "#include \"foo.h\"\n", "#include \"foo.h\""),
394 FixMessage("add all missing includes")}))));
395}
396
397TEST(IncludeCleaner, IWYUPragmas) {
398 TestTU TU;
399 TU.Code = R"cpp(
400 #include "behind_keep.h" // IWYU pragma: keep
401 #include "exported.h" // IWYU pragma: export
402 #include "public.h"
403
404 void bar() { foo(); }
405 #include "keep_main_file.h" // IWYU pragma: keep
406 )cpp";
407 TU.AdditionalFiles["behind_keep.h"] = guard("");
408 TU.AdditionalFiles["keep_main_file.h"] = guard("");
409 TU.AdditionalFiles["exported.h"] = guard("");
410 TU.AdditionalFiles["public.h"] = guard("#include \"private.h\"");
411 TU.AdditionalFiles["private.h"] = guard(R"cpp(
412 // IWYU pragma: private, include "public.h"
413 void foo() {}
414 )cpp");
415 ParsedAST AST = TU.build();
417 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
418}
419
420TEST(IncludeCleaner, IWYUPragmaExport) {
421 TestTU TU;
422 TU.Code = R"cpp(
423 #include "foo.h"
424 )cpp";
425 TU.AdditionalFiles["foo.h"] = R"cpp(
426 #ifndef FOO_H
427 #define FOO_H
428
429 #include "bar.h" // IWYU pragma: export
430
431 #endif
432 )cpp";
433 TU.AdditionalFiles["bar.h"] = guard(R"cpp(
434 void bar() {}
435 )cpp");
436 ParsedAST AST = TU.build();
437
439 EXPECT_THAT(Findings.UnusedIncludes,
440 ElementsAre(Pointee(writtenInclusion("\"foo.h\""))));
441}
442
443TEST(IncludeCleaner, NoDiagsForObjC) {
444 TestTU TU;
445 TU.Code = R"cpp(
446 #include "foo.h"
447
448 void bar() {}
449 )cpp";
450 TU.AdditionalFiles["foo.h"] = R"cpp(
451 #ifndef FOO_H
452 #define FOO_H
453
454 #endif
455 )cpp";
456 TU.ExtraArgs.emplace_back("-xobjective-c");
457
458 ParsedAST AST = TU.build();
460 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
461 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
462}
463
464TEST(IncludeCleaner, UmbrellaUsesPrivate) {
465 TestTU TU;
466 TU.Code = R"cpp(
467 #include "private.h"
468 )cpp";
469 TU.AdditionalFiles["private.h"] = guard(R"cpp(
470 // IWYU pragma: private, include "public.h"
471 void foo() {}
472 )cpp");
473 TU.Filename = "public.h";
474 ParsedAST AST = TU.build();
476 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
477}
478
479TEST(IncludeCleaner, MacroExpandedThroughIncludes) {
480 Annotations MainFile(R"cpp(
481 #include "all.h"
482 #define FOO(X) const Foo *X
483 void foo() {
484 #include [["expander.inc"]]
485 }
486)cpp");
487
488 TestTU TU;
489 TU.AdditionalFiles["expander.inc"] = guard("FOO(f1);FOO(f2);");
490 TU.AdditionalFiles["foo.h"] = guard("struct Foo {};");
491 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
492
493 TU.Code = MainFile.code();
494 ParsedAST AST = TU.build();
495
497 EXPECT_THAT(Findings, testing::SizeIs(1));
498 auto RefRange = Findings.front().SymRefRange;
499 auto &SM = AST.getSourceManager();
500 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
501 // FIXME: Point at the spelling location, rather than the include.
502 EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
503}
504
505TEST(IncludeCleaner, MissingIncludesAreUnique) {
506 Annotations MainFile(R"cpp(
507 #include "all.h"
508 FOO([[Foo]]);
509 )cpp");
510
511 TestTU TU;
512 TU.AdditionalFiles["foo.h"] = guard("struct Foo {};");
513 TU.AdditionalFiles["all.h"] = guard(R"cpp(
514 #include "foo.h"
515 #define FOO(X) X y; X z
516 )cpp");
517
518 TU.Code = MainFile.code();
519 ParsedAST AST = TU.build();
520
522 EXPECT_THAT(Findings, testing::SizeIs(1));
523 auto RefRange = Findings.front().SymRefRange;
524 auto &SM = AST.getSourceManager();
525 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
526 EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
527}
528
529TEST(IncludeCleaner, NoHangOnRefExpandedInsidePreamblePatchInclude) {
530 llvm::StringLiteral Baseline = R"cpp(// comment
531#include "all.h"
532#define RET Foo
533)cpp";
534 Annotations Modified(R"cpp(// comment
535#include "all.h"
536#define RET Foo
537#include [["rettype.inc"]]
538plugin_callback();
539)cpp");
540
541 TestTU TU;
542 TU.AdditionalFiles["foo.h"] = guard("struct Foo {};");
543 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
544 TU.AdditionalFiles["rettype.inc"] = "RET\n";
545
546 TU.Code = Baseline.str();
547 auto BaselinePreamble = TU.preamble();
548 ASSERT_TRUE(BaselinePreamble);
549
550 IgnoreDiagnostics Diags;
551 MockFS FS;
552 TU.Code = Modified.code().str();
553 auto CI = buildCompilerInvocation(TU.inputs(FS), Diags);
554 ASSERT_TRUE(CI);
555 auto AST = ParsedAST::build(testPath(TU.Filename), TU.inputs(FS),
556 std::move(CI), {}, std::move(BaselinePreamble));
557 ASSERT_TRUE(AST);
559 ASSERT_THAT(Findings, testing::SizeIs(1));
560 auto RefRange = Findings.front().SymRefRange;
561 const auto &SM = AST->getSourceManager();
562 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
563 EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), Modified.range());
564}
565
566TEST(IncludeCleaner, NoCrash) {
567 TestTU TU;
568 Annotations MainCode(R"cpp(
569 #include "all.h"
570 void test() {
571 [[1s]];
572 }
573 )cpp");
574 TU.Code = MainCode.code();
575 TU.AdditionalFiles["foo.h"] =
576 guard("int operator\"\"s(unsigned long long) { return 0; }");
577 TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
578 ParsedAST AST = TU.build();
579 const auto &MissingIncludes =
581 EXPECT_THAT(MissingIncludes, testing::SizeIs(1));
582 auto &SM = AST.getSourceManager();
583 EXPECT_EQ(
584 halfOpenToRange(SM, MissingIncludes.front().SymRefRange.toCharRange(SM)),
585 MainCode.range());
586}
587
588TEST(IncludeCleaner, IsPreferredProvider) {
589 auto TU = TestTU::withCode(R"cpp(
590 #include "decl.h"
591 #include "def.h"
592 #include "def.h"
593 )cpp");
594 TU.AdditionalFiles["decl.h"] = "";
595 TU.AdditionalFiles["def.h"] = "";
596
597 auto AST = TU.build();
598 auto &IncludeDecl = AST.getIncludeStructure().MainFileIncludes[0];
599 auto &IncludeDef1 = AST.getIncludeStructure().MainFileIncludes[1];
600 auto &IncludeDef2 = AST.getIncludeStructure().MainFileIncludes[2];
601
602 auto &FM = AST.getSourceManager().getFileManager();
603 auto DeclH = *FM.getOptionalFileRef("decl.h");
604 auto DefH = *FM.getOptionalFileRef("def.h");
605
606 auto Includes = convertIncludes(AST);
607 std::vector<include_cleaner::Header> Providers = {
608 include_cleaner::Header(DefH), include_cleaner::Header(DeclH)};
609 EXPECT_FALSE(isPreferredProvider(IncludeDecl, Includes, Providers));
610 EXPECT_TRUE(isPreferredProvider(IncludeDef1, Includes, Providers));
611 EXPECT_TRUE(isPreferredProvider(IncludeDef2, Includes, Providers));
612}
613
614TEST(IncludeCleaner, BatchFix) {
615 TestTU TU;
616 TU.Filename = "main.cpp";
617 TU.AdditionalFiles["foo.h"] = guard("class Foo;");
618 TU.AdditionalFiles["bar.h"] = guard("class Bar;");
619 TU.AdditionalFiles["all.h"] = guard(R"cpp(
620 #include "foo.h"
621 #include "bar.h"
622 )cpp");
623
624 TU.Code = R"cpp(
625 #include "all.h"
626
627 Foo* foo;
628 )cpp";
629 auto AST = TU.build();
630 EXPECT_THAT(
633 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
634 FixMessage("fix all includes")}),
635 withFix({FixMessage("remove #include directive"),
636 FixMessage("fix all includes")})));
637
638 TU.Code = R"cpp(
639 #include "all.h"
640 #include "bar.h"
641
642 Foo* foo;
643 )cpp";
644 AST = TU.build();
645 EXPECT_THAT(
648 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
649 FixMessage("fix all includes")}),
650 withFix({FixMessage("remove #include directive"),
651 FixMessage("remove all unused includes"),
652 FixMessage("fix all includes")}),
653 withFix({FixMessage("remove #include directive"),
654 FixMessage("remove all unused includes"),
655 FixMessage("fix all includes")})));
656
657 TU.Code = R"cpp(
658 #include "all.h"
659
660 Foo* foo;
661 Bar* bar;
662 )cpp";
663 AST = TU.build();
664 EXPECT_THAT(
667 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
668 FixMessage("add all missing includes"),
669 FixMessage("fix all includes")}),
670 withFix({FixMessage("#include \"bar.h\""),
671 FixMessage("add all missing includes"),
672 FixMessage("fix all includes")}),
673 withFix({FixMessage("remove #include directive"),
674 FixMessage("fix all includes")})));
675}
676
677// In the presence of IWYU pragma private, we should accept spellings other
678// than the recommended one if they appear to name the same public header.
679TEST(IncludeCleaner, VerbatimEquivalence) {
680 auto TU = TestTU::withCode(R"cpp(
681 #include "lib/rel/public.h"
682 int x = Public;
683 )cpp");
684 TU.AdditionalFiles["repo/lib/rel/private.h"] = R"cpp(
685 #pragma once
686 // IWYU pragma: private, include "rel/public.h"
687 int Public;
688 )cpp";
689 TU.AdditionalFiles["repo/lib/rel/public.h"] = R"cpp(
690 #pragma once
691 #include "rel/private.h"
692 )cpp";
693
694 TU.ExtraArgs.push_back("-Irepo");
695 TU.ExtraArgs.push_back("-Irepo/lib");
696
697 auto AST = TU.build();
698 auto Findings = computeIncludeCleanerFindings(AST);
699 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
700 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
701}
702
703TEST(IncludeCleaner, ResourceDirIsIgnored) {
704 auto TU = TestTU::withCode(R"cpp(
705 #include <amintrin.h>
706 #include <imintrin.h>
707 void baz() {
708 bar();
709 }
710 )cpp");
711 TU.ExtraArgs.push_back("-resource-dir");
712 TU.ExtraArgs.push_back(testPath("resources"));
713 TU.AdditionalFiles["resources/include/amintrin.h"] = guard("");
714 TU.AdditionalFiles["resources/include/imintrin.h"] = guard(R"cpp(
715 #include <emintrin.h>
716 )cpp");
717 TU.AdditionalFiles["resources/include/emintrin.h"] = guard(R"cpp(
718 void bar();
719 )cpp");
720 auto AST = TU.build();
721 auto Findings = computeIncludeCleanerFindings(AST);
722 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
723 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
724}
725
726TEST(IncludeCleaner, DifferentHeaderSameSpelling) {
727 // `foo` is declared in foo_inner/foo.h, but there's no way to spell it
728 // directly. Make sure we don't generate unusued/missing include findings in
729 // such cases.
730 auto TU = TestTU::withCode(R"cpp(
731 #include <foo.h>
732 void baz() {
733 foo();
734 }
735 )cpp");
736 TU.AdditionalFiles["foo/foo.h"] = guard("#include_next <foo.h>");
737 TU.AdditionalFiles["foo_inner/foo.h"] = guard(R"cpp(
738 void foo();
739 )cpp");
740 TU.ExtraArgs.push_back("-Ifoo");
741 TU.ExtraArgs.push_back("-Ifoo_inner");
742
743 auto AST = TU.build();
744 auto Findings = computeIncludeCleanerFindings(AST);
745 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
746 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
747}
748} // namespace
749} // namespace clangd
750} // namespace clang
static cl::opt< bool > Fix("fix", desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), cl::cat(ClangTidyCategory))
Include Cleaner is clangd functionality for providing diagnostics for misuse of transitive headers an...
Same as llvm::Annotations, but adjusts functions to LSP-specific types for positions and ranges.
Definition Annotations.h:23
Stores and provides access to parsed AST.
Definition ParsedAST.h:47
static std::optional< ParsedAST > build(llvm::StringRef Filename, const ParseInputs &Inputs, std::unique_ptr< clang::CompilerInvocation > CI, llvm::ArrayRef< Diag > CompilerInvocationDiags, std::shared_ptr< const PreambleData > Preamble)
Attempts to run Clang and store the parsed AST.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:44
Range halfOpenToRange(const SourceManager &SM, CharSourceRange R)
std::unique_ptr< CompilerInvocation > buildCompilerInvocation(const ParseInputs &Inputs, clang::DiagnosticConsumer &D, std::vector< std::string > *CC1Args)
Builds compiler invocation that could be used to build AST or preamble.
Definition Compiler.cpp:96
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:94
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.
std::vector< Diag > issueIncludeCleanerDiagnostics(ParsedAST &AST, llvm::StringRef Code, const IncludeCleanerFindings &Findings, const ThreadsafeFS &TFS, HeaderFilter IgnoreHeaders, HeaderFilter AngledHeaders, HeaderFilter QuotedHeaders)
const char * testRoot()
Definition TestFS.cpp:85
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
A top-level diagnostic that may have Notes and Fixes.
Definition Diagnostics.h:98
std::vector< Fix > Fixes
Alternative fixes for this diagnostic, one should be chosen.
Represents a single fix-it that editor can apply to fix the error.
Definition Diagnostics.h:81
std::vector< MissingIncludeDiagInfo > MissingIncludes
Position start
The range's start position.
Definition Protocol.h:188
Position end
The range's end position.
Definition Protocol.h:191
std::string Code
Definition TestTU.h:49
std::string Filename
Definition TestTU.h:50
static TestTU withCode(llvm::StringRef Code)
Definition TestTU.h:36
llvm::StringMap< std::string > AdditionalFiles
Definition TestTU.h:57