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(field: &Diag::Fixes, matcher: testing::UnorderedElementsAreArray(container: 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(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
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 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
138TEST(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 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
176TEST(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 Header) { 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
311TEST(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
334TEST(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
357TEST(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
378TEST(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
393TEST(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
419TEST(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
443TEST(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
465TEST(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
491TEST(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.
556TEST(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
580TEST(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

source code of clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp