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"
39using ::testing::AllOf;
40using ::testing::ElementsAre;
41using ::testing::IsEmpty;
42using ::testing::Matcher;
43using ::testing::Pointee;
44using ::testing::UnorderedElementsAre;
47withFix(std::vector<::testing::Matcher<Fix>> FixMatcheres) {
48 return Field(&
Diag::Fixes, testing::UnorderedElementsAreArray(FixMatcheres));
52 "Diag at " + llvm::to_string(Range) +
" = [" + Message +
"]") {
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;
64std::string guard(llvm::StringRef
Code) {
65 return "#pragma once\n" +
Code.str();
69 if (arg.Written != Written)
70 *result_listener << arg.Written;
71 return arg.Written == Written;
74TEST(IncludeCleaner, StdlibUnused) {
78 #include <vector> // IWYU pragma: keep
79 #include <string> // IWYU pragma: export
83 TU.AdditionalFiles[
"bits"] = R
"cpp(
86 template <typename> class list {};
87 template <typename> class queue {};
88 template <typename> class vector {};
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();
98 EXPECT_THAT(Findings.UnusedIncludes,
99 ElementsAre(
Pointee(writtenInclusion(
"<queue>"))));
102TEST(IncludeCleaner, GetUnusedHeaders) {
103 llvm::StringLiteral
MainFile = R
"cpp(
107 #include "dir/unused.h"
108 #include "unguarded.h"
110 #include <system_header.h>
111 #include <non_system_angled_header.h>
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"));
132 ParsedAST
AST = TU.build();
135 Findings.UnusedIncludes,
136 UnorderedElementsAre(
Pointee(writtenInclusion(
"\"unused.h\"")),
137 Pointee(writtenInclusion(
"\"dir/unused.h\""))));
140TEST(IncludeCleaner, IgnoredAngledHeaders) {
143 #include <system_header.h>
144 #include <system_unused.h>
145 #include <non_system_angled_unused.h>
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(
"");
155 auto AST = TU.build();
157 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
160TEST(IncludeCleaner, UnusedAngledHeaders) {
162 #include <system_header.h>
163 #include <system_unused.h>
164 #include <non_system_angled_unused.h>
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(
"");
174 auto AST = TU.build();
176 EXPECT_THAT(Findings.UnusedIncludes,
177 UnorderedElementsAre(
178 Pointee(writtenInclusion(
"<system_unused.h>")),
179 Pointee(writtenInclusion(
"<non_system_angled_unused.h>"))));
182TEST(IncludeCleaner, ComputeMissingHeaders) {
190 TU.Filename = "foo.cpp";
191 TU.AdditionalFiles[
"a.h"] = guard(
"#include \"b.h\"");
192 TU.AdditionalFiles[
"b.h"] = guard(
"void b();");
195 ParsedAST
AST = TU.build();
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();
205 BDecl = CandidateDecl;
208 include_cleaner::Symbol
B{*BDecl};
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));
220TEST(IncludeCleaner, GenerateMissingHeaderDiags) {
224$insert_b[[]]#include "baz.h"
226$insert_d[[]]$insert_foo[[]]#include "fuzz.h"
228$insert_foobar[[]]#include <e.h>
229$insert_f[[]]$insert_vector[[]]
231#define DEF(X) const Foo *X;
232#define BAZ(X) const X x
234// No missing include insertion for ambiguous macro refs.
245 // this should not be diagnosed, because it's ignored in the config
250 std::$vector[[vector]] v;
252 int var = $FOO[[FOO]];
262 TU.Filename = "main.cpp";
263 TU.AdditionalFiles[
"a.h"] = guard(
"#include \"b.h\"");
264 TU.AdditionalFiles[
"b.h"] = guard(
"void b();");
266 TU.AdditionalFiles[
"dir/c.h"] = guard(
"#include \"d.h\"");
267 TU.AdditionalFiles[
"dir/d.h"] =
268 guard(
"namespace ns { struct Bar { void d(); }; }");
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"));
274 TU.AdditionalFiles[
"fuzz.h"] = guard(
"#include \"buzz.h\"");
275 TU.AdditionalFiles[
"buzz.h"] = guard(
"void buzz();");
277 TU.AdditionalFiles[
"baz.h"] = guard(
"#include \"private.h\"");
278 TU.AdditionalFiles[
"private.h"] = guard(R
"cpp(
279 // IWYU pragma: private, include "public.h"
282 TU.AdditionalFiles["header.h"] = guard(R
"cpp(
283 namespace std { class vector {}; }
286 TU.AdditionalFiles["all.h"] = guard(
"#include \"foo.h\"");
287 TU.AdditionalFiles[
"foo.h"] = guard(R
"cpp(
288 #define BAR(x) Foo *x
294 ParsedAST AST = TU.build();
297 Findings.UnusedIncludes.clear();
299 AST, TU.Code, Findings, MockFS(),
300 {[](llvm::StringRef Header) {
return Header.ends_with(
"buzz.h"); }});
303 UnorderedElementsAre(
305 "No header providing \"b\" is directly included"),
306 withFix({Fix(
MainFile.range(
"insert_b"),
"#include \"b.h\"\n",
308 FixMessage(
"add all missing includes")})),
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")})),
315 "No header providing \"f\" is directly included"),
316 withFix({Fix(
MainFile.range(
"insert_f"),
"#include <f.h>\n",
318 FixMessage(
"add all missing includes")})),
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")})),
327 "No header providing \"std::vector\" is directly included"),
329 Fix(
MainFile.range(
"insert_vector"),
"#include <vector>\n",
330 "#include <vector>"),
331 FixMessage(
"add all missing includes"),
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")})),
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")})),
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")})),
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")}))));
355TEST(IncludeCleaner, IWYUPragmas) {
358 #include "behind_keep.h" // IWYU pragma: keep
359 #include "exported.h" // IWYU pragma: export
362 void bar() { foo(); }
363 #include "keep_main_file.h" // IWYU pragma: keep
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"
373 ParsedAST AST = TU.build();
375 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
378TEST(IncludeCleaner, IWYUPragmaExport) {
383 TU.AdditionalFiles["foo.h"] = R
"cpp(
387 #include "bar.h" // IWYU pragma: export
391 TU.AdditionalFiles["bar.h"] = guard(R
"cpp(
394 ParsedAST AST = TU.build();
397 EXPECT_THAT(Findings.UnusedIncludes,
398 ElementsAre(Pointee(writtenInclusion("\"foo.h\""))));
401TEST(IncludeCleaner, NoDiagsForObjC) {
408 TU.AdditionalFiles["foo.h"] = R
"cpp(
414 TU.ExtraArgs.emplace_back("-xobjective-c");
416 ParsedAST
AST = TU.build();
418 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
419 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
422TEST(IncludeCleaner, UmbrellaUsesPrivate) {
427 TU.AdditionalFiles["private.h"] = guard(R
"cpp(
428 // IWYU pragma: private, include "public.h"
431 TU.Filename = "public.h";
432 ParsedAST
AST = TU.build();
434 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
437TEST(IncludeCleaner, MacroExpandedThroughIncludes) {
440 #define FOO(X) const Foo *X
442 #include [["expander.inc"]]
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\"");
452 ParsedAST
AST = TU.build();
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());
463TEST(IncludeCleaner, MissingIncludesAreUnique) {
470 TU.AdditionalFiles["foo.h"] = guard(
"struct Foo {};");
471 TU.AdditionalFiles[
"all.h"] = guard(R
"cpp(
473 #define FOO(X) X y; X z
477 ParsedAST AST = TU.build();
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());
487TEST(IncludeCleaner, NoCrash) {
489 Annotations MainCode(R
"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();
505 halfOpenToRange(SM, MissingIncludes.front().SymRefRange.toCharRange(SM)),
509TEST(IncludeCleaner, IsPreferredProvider) {
515 TU.AdditionalFiles["decl.h"] =
"";
516 TU.AdditionalFiles[
"def.h"] =
"";
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];
523 auto &FM =
AST.getSourceManager().getFileManager();
524 auto DeclH = *FM.getOptionalFileRef(
"decl.h");
525 auto DefH = *FM.getOptionalFileRef(
"def.h");
528 std::vector<include_cleaner::Header> Providers = {
529 include_cleaner::Header(DefH), include_cleaner::Header(DeclH)};
535TEST(IncludeCleaner, BatchFix) {
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(
550 auto AST = TU.build();
554 UnorderedElementsAre(withFix({FixMessage(
"#include \"foo.h\""),
555 FixMessage(
"fix all includes")}),
556 withFix({FixMessage(
"remove #include directive"),
557 FixMessage(
"fix all includes")})));
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")})));
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")})));
600TEST(IncludeCleaner, VerbatimEquivalence) {
602 #include "lib/rel/public.h"
605 TU.AdditionalFiles["repo/lib/rel/private.h"] = R
"cpp(
607 // IWYU pragma: private, include "rel/public.h"
610 TU.AdditionalFiles["repo/lib/rel/public.h"] = R
"cpp(
612 #include "rel/private.h"
615 TU.ExtraArgs.push_back("-Irepo");
616 TU.ExtraArgs.push_back(
"-Irepo/lib");
618 auto AST = TU.build();
620 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
621 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
624TEST(IncludeCleaner, ResourceDirIsIgnored) {
626 #include <amintrin.h>
627 #include <imintrin.h>
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>
638 TU.AdditionalFiles["resources/include/emintrin.h"] = guard(R
"cpp(
641 auto AST = TU.build();
643 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
644 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
647TEST(IncludeCleaner, DifferentHeaderSameSpelling) {
657 TU.AdditionalFiles["foo/foo.h"] = guard(
"#include_next <foo.h>");
658 TU.AdditionalFiles[
"foo_inner/foo.h"] = guard(R
"cpp(
661 TU.ExtraArgs.push_back("-Ifoo");
662 TU.ExtraArgs.push_back(
"-Ifoo_inner");
664 auto AST = TU.build();
666 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
667 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
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)
MATCHER_P2(hasFlag, Flag, Path, "")
include_cleaner::Includes convertIncludes(const ParsedAST &AST)
Converts the clangd include representation to include-cleaner include representation.
std::string testPath(PathRef File, llvm::sys::path::Style Style)
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.
constexpr llvm::StringLiteral Message
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
std::vector< Fix > Fixes
Alternative fixes for this diagnostic, one should be chosen.
std::vector< MissingIncludeDiagInfo > MissingIncludes
static TestTU withCode(llvm::StringRef Code)