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 +
"]") {
53 return arg.Range ==
Range && arg.Message == 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;
62MATCHER_P(FixMessage, Message,
"") {
return arg.Message == Message; }
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>
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();
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) {
191 TU.AdditionalFiles[
"a.h"] = guard(
"#include \"b.h\"");
192 TU.AdditionalFiles[
"b.h"] = guard(
"void b();");
194 TU.Code = MainFile.code();
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};
209 auto Range = MainFile.range(
"b");
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")};
217 EXPECT_THAT(Findings.MissingIncludes, ElementsAre(BInfo));
220TEST(IncludeCleaner, GenerateMissingHeaderDiags) {
223#include "angled_wrapper.h"
225$insert_b[[]]#include "baz.h"
227$insert_d[[]]$insert_foo[[]]#include "fuzz.h"
229$insert_foobar[[]]$insert_quoted[[]]$insert_quoted2[[]]#include "quoted_wrapper.h"
230$insert_angled[[]]#include <e.h>
231$insert_f[[]]#include <quoted2_wrapper.h>
234#define DEF(X) const Foo *X;
235#define BAZ(X) const X x
237// No missing include insertion for ambiguous macro refs.
245 $quoted2[[quoted2]]();
251 // this should not be diagnosed, because it's ignored in the config
256 std::$vector[[vector]] v;
258 int var = $FOO[[FOO]];
269 TU.AdditionalFiles[
"a.h"] = guard(
"#include \"b.h\"");
270 TU.AdditionalFiles[
"b.h"] = guard(
"void b();");
272 TU.AdditionalFiles[
"angled_wrapper.h"] = guard(
"#include <angled.h>");
273 TU.AdditionalFiles[
"angled.h"] = guard(
"void angled();");
274 TU.ExtraArgs.push_back(
"-I" +
testPath(
"."));
276 TU.AdditionalFiles[
"quoted_wrapper.h"] = guard(
"#include \"quoted.h\"");
277 TU.AdditionalFiles[
"quoted.h"] = guard(
"void quoted();");
279 TU.AdditionalFiles[
"dir/c.h"] = guard(
"#include \"d.h\"");
280 TU.AdditionalFiles[
"dir/d.h"] =
281 guard(
"namespace ns { struct Bar { void d(); }; }");
283 TU.AdditionalFiles[
"system/e.h"] = guard(
"#include <f.h>");
284 TU.AdditionalFiles[
"system/f.h"] = guard(
"void f();");
285 TU.AdditionalFiles[
"system/quoted2_wrapper.h"] =
286 guard(
"#include <system/quoted2.h>");
287 TU.AdditionalFiles[
"system/quoted2.h"] = guard(
"void quoted2();");
288 TU.ExtraArgs.push_back(
"-isystem" +
testPath(
"system"));
290 TU.AdditionalFiles[
"fuzz.h"] = guard(
"#include \"buzz.h\"");
291 TU.AdditionalFiles[
"buzz.h"] = guard(
"void buzz();");
293 TU.AdditionalFiles[
"baz.h"] = guard(
"#include \"private.h\"");
294 TU.AdditionalFiles[
"private.h"] = guard(R
"cpp(
295 // IWYU pragma: private, include "public.h"
298 TU.AdditionalFiles["header.h"] = guard(R
"cpp(
299 namespace std { class vector {}; }
302 TU.AdditionalFiles["all.h"] = guard(
"#include \"foo.h\"");
303 TU.AdditionalFiles[
"foo.h"] = guard(R
"cpp(
304 #define BAR(x) Foo *x
309 TU.Code = MainFile.code();
313 Findings.UnusedIncludes.clear();
316 {[](llvm::StringRef Header) {
317 return Header.ends_with(
"buzz.h");
319 {[](llvm::StringRef Header) {
320 return Header.contains(
"angled.h");
322 {[](llvm::StringRef Header) {
323 return Header.contains(
"quoted.h") || Header.contains(
"quoted2.h");
327 UnorderedElementsAre(
328 AllOf(
Diag(MainFile.range(
"b"),
329 "No header providing \"b\" is directly included"),
330 withFix({
Fix(MainFile.range(
"insert_b"),
"#include \"b.h\"\n",
332 FixMessage(
"add all missing includes")})),
333 AllOf(
Diag(MainFile.range(
"angled"),
334 "No header providing \"angled\" is directly included"),
335 withFix({
Fix(MainFile.range(
"insert_angled"),
336 "#include <angled.h>\n",
"#include <angled.h>"),
337 FixMessage(
"add all missing includes")})),
339 Diag(MainFile.range(
"quoted"),
340 "No header providing \"quoted\" is directly included"),
341 withFix({
Fix(MainFile.range(
"insert_quoted"),
342 "#include \"quoted.h\"\n",
"#include \"quoted.h\""),
343 FixMessage(
"add all missing includes")})),
344 AllOf(
Diag(MainFile.range(
"quoted2"),
345 "No header providing \"quoted2\" is directly included"),
347 {
Fix(MainFile.range(
"insert_quoted2"),
348 "#include \"quoted2.h\"\n",
"#include \"quoted2.h\""),
349 FixMessage(
"add all missing includes")})),
350 AllOf(
Diag(MainFile.range(
"bar"),
351 "No header providing \"ns::Bar\" is directly included"),
352 withFix({
Fix(MainFile.range(
"insert_d"),
353 "#include \"dir/d.h\"\n",
"#include \"dir/d.h\""),
354 FixMessage(
"add all missing includes")})),
355 AllOf(
Diag(MainFile.range(
"f"),
356 "No header providing \"f\" is directly included"),
357 withFix({
Fix(MainFile.range(
"insert_f"),
"#include <f.h>\n",
359 FixMessage(
"add all missing includes")})),
361 Diag(MainFile.range(
"foobar"),
362 "No header providing \"foobar\" is directly included"),
363 withFix({
Fix(MainFile.range(
"insert_foobar"),
364 "#include \"public.h\"\n",
"#include \"public.h\""),
365 FixMessage(
"add all missing includes")})),
367 Diag(MainFile.range(
"vector"),
368 "No header providing \"std::vector\" is directly included"),
370 Fix(MainFile.range(
"insert_vector"),
"#include <vector>\n",
371 "#include <vector>"),
372 FixMessage(
"add all missing includes"),
374 AllOf(
Diag(MainFile.range(
"FOO"),
375 "No header providing \"FOO\" is directly included"),
376 withFix({
Fix(MainFile.range(
"insert_foo"),
377 "#include \"foo.h\"\n",
"#include \"foo.h\""),
378 FixMessage(
"add all missing includes")})),
379 AllOf(
Diag(MainFile.range(
"DEF"),
380 "No header providing \"Foo\" is directly included"),
381 withFix({
Fix(MainFile.range(
"insert_foo"),
382 "#include \"foo.h\"\n",
"#include \"foo.h\""),
383 FixMessage(
"add all missing includes")})),
384 AllOf(
Diag(MainFile.range(
"BAR"),
385 "No header providing \"BAR\" is directly included"),
386 withFix({
Fix(MainFile.range(
"insert_foo"),
387 "#include \"foo.h\"\n",
"#include \"foo.h\""),
388 FixMessage(
"add all missing includes")})),
389 AllOf(
Diag(MainFile.range(
"Foo"),
390 "No header providing \"Foo\" is directly included"),
391 withFix({
Fix(MainFile.range(
"insert_foo"),
392 "#include \"foo.h\"\n",
"#include \"foo.h\""),
393 FixMessage(
"add all missing includes")}))));
396TEST(IncludeCleaner, IWYUPragmas) {
399 #include "behind_keep.h" // IWYU pragma: keep
400 #include "exported.h" // IWYU pragma: export
403 void bar() { foo(); }
404 #include "keep_main_file.h" // IWYU pragma: keep
406 TU.AdditionalFiles["behind_keep.h"] = guard(
"");
407 TU.AdditionalFiles[
"keep_main_file.h"] = guard(
"");
408 TU.AdditionalFiles[
"exported.h"] = guard(
"");
409 TU.AdditionalFiles[
"public.h"] = guard(
"#include \"private.h\"");
410 TU.AdditionalFiles[
"private.h"] = guard(R
"cpp(
411 // IWYU pragma: private, include "public.h"
416 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
419TEST(IncludeCleaner, IWYUPragmaExport) {
424 TU.AdditionalFiles["foo.h"] = R
"cpp(
428 #include "bar.h" // IWYU pragma: export
432 TU.AdditionalFiles["bar.h"] = guard(R
"cpp(
438 EXPECT_THAT(Findings.UnusedIncludes,
439 ElementsAre(Pointee(writtenInclusion("\"foo.h\""))));
442TEST(IncludeCleaner, NoDiagsForObjC) {
449 TU.AdditionalFiles["foo.h"] = R
"cpp(
455 TU.ExtraArgs.emplace_back("-xobjective-c");
459 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
460 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
463TEST(IncludeCleaner, UmbrellaUsesPrivate) {
468 TU.AdditionalFiles["private.h"] = guard(R
"cpp(
469 // IWYU pragma: private, include "public.h"
472 TU.Filename = "public.h";
475 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
478TEST(IncludeCleaner, MacroExpandedThroughIncludes) {
481 #define FOO(X) const Foo *X
483 #include [["expander.inc"]]
489 TU.AdditionalFiles[
"foo.h"] = guard(
"struct Foo {};");
490 TU.AdditionalFiles[
"all.h"] = guard(
"#include \"foo.h\"");
492 TU.Code = MainFile.code();
496 EXPECT_THAT(Findings, testing::SizeIs(1));
497 auto RefRange = Findings.front().SymRefRange;
498 auto &SM =
AST.getSourceManager();
499 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
501 EXPECT_EQ(
halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
504TEST(IncludeCleaner, MissingIncludesAreUnique) {
512 TU.AdditionalFiles[
"all.h"] = guard(R
"cpp(
514 #define FOO(X) X y; X z
517 TU.Code = MainFile.code();
521 EXPECT_THAT(Findings, testing::SizeIs(1));
522 auto RefRange = Findings.front().SymRefRange;
523 auto &SM =
AST.getSourceManager();
524 EXPECT_EQ(RefRange.file(), SM.getMainFileID());
525 EXPECT_EQ(
halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range());
528TEST(IncludeCleaner, NoCrash) {
536 TU.Code = MainCode.code();
537 TU.AdditionalFiles["foo.h"] =
538 guard(
"int operator\"\"s(unsigned long long) { return 0; }");
539 TU.AdditionalFiles[
"all.h"] = guard(
"#include \"foo.h\"");
541 const auto &MissingIncludes =
543 EXPECT_THAT(MissingIncludes, testing::SizeIs(1));
544 auto &SM =
AST.getSourceManager();
546 halfOpenToRange(SM, MissingIncludes.front().SymRefRange.toCharRange(SM)),
550TEST(IncludeCleaner, IsPreferredProvider) {
556 TU.AdditionalFiles["decl.h"] =
"";
557 TU.AdditionalFiles[
"def.h"] =
"";
559 auto AST = TU.build();
560 auto &IncludeDecl =
AST.getIncludeStructure().MainFileIncludes[0];
561 auto &IncludeDef1 =
AST.getIncludeStructure().MainFileIncludes[1];
562 auto &IncludeDef2 =
AST.getIncludeStructure().MainFileIncludes[2];
564 auto &FM =
AST.getSourceManager().getFileManager();
565 auto DeclH = *FM.getOptionalFileRef(
"decl.h");
566 auto DefH = *FM.getOptionalFileRef(
"def.h");
569 std::vector<include_cleaner::Header> Providers = {
570 include_cleaner::Header(DefH), include_cleaner::Header(DeclH)};
576TEST(IncludeCleaner, BatchFix) {
579 TU.AdditionalFiles[
"foo.h"] = guard(
"class Foo;");
580 TU.AdditionalFiles[
"bar.h"] = guard(
"class Bar;");
581 TU.AdditionalFiles[
"all.h"] = guard(R
"cpp(
591 auto AST = TU.build();
595 UnorderedElementsAre(withFix({FixMessage(
"#include \"foo.h\""),
596 FixMessage(
"fix all includes")}),
597 withFix({FixMessage(
"remove #include directive"),
598 FixMessage(
"fix all includes")})));
610 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
611 FixMessage(
"fix all includes")}),
612 withFix({FixMessage(
"remove #include directive"),
613 FixMessage(
"remove all unused includes"),
614 FixMessage(
"fix all includes")}),
615 withFix({FixMessage(
"remove #include directive"),
616 FixMessage(
"remove all unused includes"),
617 FixMessage(
"fix all includes")})));
629 UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
630 FixMessage(
"add all missing includes"),
631 FixMessage(
"fix all includes")}),
632 withFix({FixMessage(
"#include \"bar.h\""),
633 FixMessage(
"add all missing includes"),
634 FixMessage(
"fix all includes")}),
635 withFix({FixMessage(
"remove #include directive"),
636 FixMessage(
"fix all includes")})));
641TEST(IncludeCleaner, VerbatimEquivalence) {
643 #include "lib/rel/public.h"
646 TU.AdditionalFiles["repo/lib/rel/private.h"] = R
"cpp(
648 // IWYU pragma: private, include "rel/public.h"
651 TU.AdditionalFiles["repo/lib/rel/public.h"] = R
"cpp(
653 #include "rel/private.h"
656 TU.ExtraArgs.push_back("-Irepo");
657 TU.ExtraArgs.push_back(
"-Irepo/lib");
659 auto AST = TU.build();
661 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
662 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
665TEST(IncludeCleaner, ResourceDirIsIgnored) {
667 #include <amintrin.h>
668 #include <imintrin.h>
673 TU.ExtraArgs.push_back("-resource-dir");
674 TU.ExtraArgs.push_back(
testPath(
"resources"));
675 TU.AdditionalFiles[
"resources/include/amintrin.h"] = guard(
"");
676 TU.AdditionalFiles[
"resources/include/imintrin.h"] = guard(R
"cpp(
677 #include <emintrin.h>
679 TU.AdditionalFiles["resources/include/emintrin.h"] = guard(R
"cpp(
682 auto AST = TU.build();
684 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
685 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
688TEST(IncludeCleaner, DifferentHeaderSameSpelling) {
698 TU.AdditionalFiles["foo/foo.h"] = guard(
"#include_next <foo.h>");
699 TU.AdditionalFiles[
"foo_inner/foo.h"] = guard(R
"cpp(
702 TU.ExtraArgs.push_back("-Ifoo");
703 TU.ExtraArgs.push_back(
"-Ifoo_inner");
705 auto AST = TU.build();
707 EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
708 EXPECT_THAT(Findings.MissingIncludes, IsEmpty());
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.
Stores and provides access to parsed AST.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
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)
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)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
A top-level diagnostic that may have Notes and Fixes.
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.
std::vector< MissingIncludeDiagInfo > MissingIncludes
Position start
The range's start position.
Position end
The range's end position.
static TestTU withCode(llvm::StringRef Code)
llvm::StringMap< std::string > AdditionalFiles