| //===--- IncludeCleanerTests.cpp --------------------------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "Annotations.h" |
| #include "Diagnostics.h" |
| #include "IncludeCleaner.h" |
| #include "ParsedAST.h" |
| #include "SourceCode.h" |
| #include "TestFS.h" |
| #include "TestTU.h" |
| #include "clang-include-cleaner/Analysis.h" |
| #include "clang-include-cleaner/Types.h" |
| #include "clang/AST/DeclBase.h" |
| #include "clang/Basic/SourceManager.h" |
| #include "clang/Tooling/Syntax/Tokens.h" |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/ScopeExit.h" |
| #include "llvm/ADT/StringMap.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/ScopedPrinter.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include <cstddef> |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| namespace clang { |
| namespace clangd { |
| namespace { |
| |
| using ::testing::AllOf; |
| using ::testing::ElementsAre; |
| using ::testing::IsEmpty; |
| using ::testing::Matcher; |
| using ::testing::Pointee; |
| using ::testing::UnorderedElementsAre; |
| |
| Matcher<const Diag &> |
| withFix(std::vector<::testing::Matcher<Fix>> FixMatcheres) { |
| return Field(&Diag::Fixes, testing::UnorderedElementsAreArray(FixMatcheres)); |
| } |
| |
| MATCHER_P2(Diag, Range, Message, |
| "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") { |
| return arg.Range == Range && arg.Message == Message; |
| } |
| |
| MATCHER_P3(Fix, Range, Replacement, Message, |
| "Fix " + llvm::to_string(Range) + " => " + |
| ::testing::PrintToString(Replacement) + " = [" + Message + "]") { |
| return arg.Message == Message && arg.Edits.size() == 1 && |
| arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement; |
| } |
| MATCHER_P(FixMessage, Message, "") { return arg.Message == Message; } |
| |
| std::string guard(llvm::StringRef Code) { |
| return "#pragma once\n" + Code.str(); |
| } |
| |
| MATCHER_P(writtenInclusion, Written, "") { |
| if (arg.Written != Written) |
| *result_listener << arg.Written; |
| return arg.Written == Written; |
| } |
| |
| TEST(IncludeCleaner, StdlibUnused) { |
| auto TU = TestTU::withCode(R"cpp( |
| #include <list> |
| #include <queue> |
| #include <vector> // IWYU pragma: keep |
| #include <string> // IWYU pragma: export |
| std::list<int> x; |
| )cpp"); |
| // Layout of std library impl is not relevant. |
| TU.AdditionalFiles["bits"] = R"cpp( |
| #pragma once |
| namespace std { |
| template <typename> class list {}; |
| template <typename> class queue {}; |
| template <typename> class vector {}; |
| } |
| )cpp"; |
| TU.AdditionalFiles["list"] = guard("#include <bits>"); |
| TU.AdditionalFiles["queue"] = guard("#include <bits>"); |
| TU.AdditionalFiles["vector"] = guard("#include <bits>"); |
| TU.AdditionalFiles["string"] = guard("#include <bits>"); |
| TU.ExtraArgs = {"-isystem", testRoot()}; |
| auto AST = TU.build(); |
| IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
| EXPECT_THAT(Findings.UnusedIncludes, |
| ElementsAre(Pointee(writtenInclusion("<queue>")))); |
| } |
| |
| TEST(IncludeCleaner, GetUnusedHeaders) { |
| llvm::StringLiteral MainFile = R"cpp( |
| #include "a.h" |
| #include "b.h" |
| #include "dir/c.h" |
| #include "dir/unused.h" |
| #include "unguarded.h" |
| #include "unused.h" |
| #include <system_header.h> |
| #include <non_system_angled_header.h> |
| void foo() { |
| a(); |
| b(); |
| c(); |
| })cpp"; |
| // Build expected ast with symbols coming from headers. |
| TestTU TU; |
| TU.Filename = "foo.cpp"; |
| TU.AdditionalFiles["foo.h"] = guard("void foo();"); |
| TU.AdditionalFiles["a.h"] = guard("void a();"); |
| TU.AdditionalFiles["b.h"] = guard("void b();"); |
| TU.AdditionalFiles["dir/c.h"] = guard("void c();"); |
| TU.AdditionalFiles["unused.h"] = guard("void unused();"); |
| TU.AdditionalFiles["dir/unused.h"] = guard("void dirUnused();"); |
| TU.AdditionalFiles["dir/non_system_angled_header.h"] = guard(""); |
| TU.AdditionalFiles["system/system_header.h"] = guard(""); |
| TU.AdditionalFiles["unguarded.h"] = ""; |
| TU.ExtraArgs.push_back("-I" + testPath("dir")); |
| TU.ExtraArgs.push_back("-isystem" + testPath("system")); |
| TU.Code = MainFile.str(); |
| ParsedAST AST = TU.build(); |
| IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
| EXPECT_THAT( |
| Findings.UnusedIncludes, |
| UnorderedElementsAre(Pointee(writtenInclusion("\"unused.h\"")), |
| Pointee(writtenInclusion("\"dir/unused.h\"")))); |
| } |
| |
| TEST(IncludeCleaner, IgnoredAngledHeaders) { |
| // Currently the default behavior is to ignore unused angled includes |
| auto TU = TestTU::withCode(R"cpp( |
| #include <system_header.h> |
| #include <system_unused.h> |
| #include <non_system_angled_unused.h> |
| SystemClass x; |
| )cpp"); |
| TU.AdditionalFiles["system/system_header.h"] = guard("class SystemClass {};"); |
| TU.AdditionalFiles["system/system_unused.h"] = guard(""); |
| TU.AdditionalFiles["dir/non_system_angled_unused.h"] = guard(""); |
| TU.ExtraArgs = { |
| "-isystem" + testPath("system"), |
| "-I" + testPath("dir"), |
| }; |
| auto AST = TU.build(); |
| IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
| EXPECT_THAT(Findings.UnusedIncludes, IsEmpty()); |
| } |
| |
| TEST(IncludeCleaner, UnusedAngledHeaders) { |
| auto TU = TestTU::withCode(R"cpp( |
| #include <system_header.h> |
| #include <system_unused.h> |
| #include <non_system_angled_unused.h> |
| SystemClass x; |
| )cpp"); |
| TU.AdditionalFiles["system/system_header.h"] = guard("class SystemClass {};"); |
| TU.AdditionalFiles["system/system_unused.h"] = guard(""); |
| TU.AdditionalFiles["dir/non_system_angled_unused.h"] = guard(""); |
| TU.ExtraArgs = { |
| "-isystem" + testPath("system"), |
| "-I" + testPath("dir"), |
| }; |
| auto AST = TU.build(); |
| IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST, true); |
| EXPECT_THAT(Findings.UnusedIncludes, |
| UnorderedElementsAre( |
| Pointee(writtenInclusion("<system_unused.h>")), |
| Pointee(writtenInclusion("<non_system_angled_unused.h>")))); |
| } |
| |
| TEST(IncludeCleaner, ComputeMissingHeaders) { |
| Annotations MainFile(R"cpp( |
| #include "a.h" |
| |
| void foo() { |
| $b[[b]](); |
| })cpp"); |
| TestTU TU; |
| TU.Filename = "foo.cpp"; |
| TU.AdditionalFiles["a.h"] = guard("#include \"b.h\""); |
| TU.AdditionalFiles["b.h"] = guard("void b();"); |
| |
| TU.Code = MainFile.code(); |
| ParsedAST AST = TU.build(); |
| |
| IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
| const SourceManager &SM = AST.getSourceManager(); |
| const NamedDecl *BDecl = nullptr; |
| for (Decl *D : AST.getASTContext().getTranslationUnitDecl()->decls()) { |
| const NamedDecl *CandidateDecl = llvm::dyn_cast<NamedDecl>(D); |
| std::string Name = CandidateDecl->getQualifiedNameAsString(); |
| if (Name != "b") |
| continue; |
| BDecl = CandidateDecl; |
| } |
| ASSERT_TRUE(BDecl); |
| include_cleaner::Symbol B{*BDecl}; |
| auto Range = MainFile.range("b"); |
| size_t Start = llvm::cantFail(positionToOffset(MainFile.code(), Range.start)); |
| size_t End = llvm::cantFail(positionToOffset(MainFile.code(), Range.end)); |
| syntax::FileRange BRange{SM.getMainFileID(), static_cast<unsigned int>(Start), |
| static_cast<unsigned int>(End)}; |
| include_cleaner::Header Header{ |
| *SM.getFileManager().getOptionalFileRef("b.h")}; |
| MissingIncludeDiagInfo BInfo{B, BRange, {Header}}; |
| EXPECT_THAT(Findings.MissingIncludes, ElementsAre(BInfo)); |
| } |
| |
| TEST(IncludeCleaner, GenerateMissingHeaderDiags) { |
| Annotations MainFile(R"cpp( |
| #include "a.h" |
| #include "all.h" |
| $insert_b[[]]#include "baz.h" |
| #include "dir/c.h" |
| $insert_d[[]]$insert_foo[[]]#include "fuzz.h" |
| #include "header.h" |
| $insert_foobar[[]]#include <e.h> |
| $insert_f[[]]$insert_vector[[]] |
| |
| #define DEF(X) const Foo *X; |
| #define BAZ(X) const X x |
| |
| // No missing include insertion for ambiguous macro refs. |
| #if defined(FOO) |
| #endif |
| |
| void foo() { |
| $b[[b]](); |
| |
| ns::$bar[[Bar]] bar; |
| bar.d(); |
| $f[[f]](); |
| |
| // this should not be diagnosed, because it's ignored in the config |
| buzz(); |
| |
| $foobar[[foobar]](); |
| |
| std::$vector[[vector]] v; |
| |
| int var = $FOO[[FOO]]; |
| |
| $DEF[[DEF]](a); |
| |
| $BAR[[BAR]](b); |
| |
| BAZ($Foo[[Foo]]); |
| })cpp"); |
| |
| TestTU TU; |
| TU.Filename = "main.cpp"; |
| TU.AdditionalFiles["a.h"] = guard("#include \"b.h\""); |
| TU.AdditionalFiles["b.h"] = guard("void b();"); |
| |
| TU.AdditionalFiles["dir/c.h"] = guard("#include \"d.h\""); |
| TU.AdditionalFiles["dir/d.h"] = |
| guard("namespace ns { struct Bar { void d(); }; }"); |
| |
| TU.AdditionalFiles["system/e.h"] = guard("#include <f.h>"); |
| TU.AdditionalFiles["system/f.h"] = guard("void f();"); |
| TU.ExtraArgs.push_back("-isystem" + testPath("system")); |
| |
| TU.AdditionalFiles["fuzz.h"] = guard("#include \"buzz.h\""); |
| TU.AdditionalFiles["buzz.h"] = guard("void buzz();"); |
| |
| TU.AdditionalFiles["baz.h"] = guard("#include \"private.h\""); |
| TU.AdditionalFiles["private.h"] = guard(R"cpp( |
| // IWYU pragma: private, include "public.h" |
| void foobar(); |
| )cpp"); |
| TU.AdditionalFiles["header.h"] = guard(R"cpp( |
| namespace std { class vector {}; } |
| )cpp"); |
| |
| TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\""); |
| TU.AdditionalFiles["foo.h"] = guard(R"cpp( |
| #define BAR(x) Foo *x |
| #define FOO 1 |
| struct Foo{}; |
| )cpp"); |
| |
| TU.Code = MainFile.code(); |
| ParsedAST AST = TU.build(); |
| |
| auto Findings = computeIncludeCleanerFindings(AST); |
| Findings.UnusedIncludes.clear(); |
| std::vector<clangd::Diag> Diags = issueIncludeCleanerDiagnostics( |
| AST, TU.Code, Findings, MockFS(), |
| {[](llvm::StringRef Header) { return Header.ends_with("buzz.h"); }}); |
| EXPECT_THAT( |
| Diags, |
| UnorderedElementsAre( |
| AllOf(Diag(MainFile.range("b"), |
| "No header providing \"b\" is directly included"), |
| withFix({Fix(MainFile.range("insert_b"), "#include \"b.h\"\n", |
| "#include \"b.h\""), |
| FixMessage("add all missing includes")})), |
| AllOf(Diag(MainFile.range("bar"), |
| "No header providing \"ns::Bar\" is directly included"), |
| withFix({Fix(MainFile.range("insert_d"), |
| "#include \"dir/d.h\"\n", "#include \"dir/d.h\""), |
| FixMessage("add all missing includes")})), |
| AllOf(Diag(MainFile.range("f"), |
| "No header providing \"f\" is directly included"), |
| withFix({Fix(MainFile.range("insert_f"), "#include <f.h>\n", |
| "#include <f.h>"), |
| FixMessage("add all missing includes")})), |
| AllOf( |
| Diag(MainFile.range("foobar"), |
| "No header providing \"foobar\" is directly included"), |
| withFix({Fix(MainFile.range("insert_foobar"), |
| "#include \"public.h\"\n", "#include \"public.h\""), |
| FixMessage("add all missing includes")})), |
| AllOf( |
| Diag(MainFile.range("vector"), |
| "No header providing \"std::vector\" is directly included"), |
| withFix({ |
| Fix(MainFile.range("insert_vector"), "#include <vector>\n", |
| "#include <vector>"), |
| FixMessage("add all missing includes"), |
| })), |
| AllOf(Diag(MainFile.range("FOO"), |
| "No header providing \"FOO\" is directly included"), |
| withFix({Fix(MainFile.range("insert_foo"), |
| "#include \"foo.h\"\n", "#include \"foo.h\""), |
| FixMessage("add all missing includes")})), |
| AllOf(Diag(MainFile.range("DEF"), |
| "No header providing \"Foo\" is directly included"), |
| withFix({Fix(MainFile.range("insert_foo"), |
| "#include \"foo.h\"\n", "#include \"foo.h\""), |
| FixMessage("add all missing includes")})), |
| AllOf(Diag(MainFile.range("BAR"), |
| "No header providing \"BAR\" is directly included"), |
| withFix({Fix(MainFile.range("insert_foo"), |
| "#include \"foo.h\"\n", "#include \"foo.h\""), |
| FixMessage("add all missing includes")})), |
| AllOf(Diag(MainFile.range("Foo"), |
| "No header providing \"Foo\" is directly included"), |
| withFix({Fix(MainFile.range("insert_foo"), |
| "#include \"foo.h\"\n", "#include \"foo.h\""), |
| FixMessage("add all missing includes")})))); |
| } |
| |
| TEST(IncludeCleaner, IWYUPragmas) { |
| TestTU TU; |
| TU.Code = R"cpp( |
| #include "behind_keep.h" // IWYU pragma: keep |
| #include "exported.h" // IWYU pragma: export |
| #include "public.h" |
| |
| void bar() { foo(); } |
| #include "keep_main_file.h" // IWYU pragma: keep |
| )cpp"; |
| TU.AdditionalFiles["behind_keep.h"] = guard(""); |
| TU.AdditionalFiles["keep_main_file.h"] = guard(""); |
| TU.AdditionalFiles["exported.h"] = guard(""); |
| TU.AdditionalFiles["public.h"] = guard("#include \"private.h\""); |
| TU.AdditionalFiles["private.h"] = guard(R"cpp( |
| // IWYU pragma: private, include "public.h" |
| void foo() {} |
| )cpp"); |
| ParsedAST AST = TU.build(); |
| IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
| EXPECT_THAT(Findings.UnusedIncludes, IsEmpty()); |
| } |
| |
| TEST(IncludeCleaner, IWYUPragmaExport) { |
| TestTU TU; |
| TU.Code = R"cpp( |
| #include "foo.h" |
| )cpp"; |
| TU.AdditionalFiles["foo.h"] = R"cpp( |
| #ifndef FOO_H |
| #define FOO_H |
| |
| #include "bar.h" // IWYU pragma: export |
| |
| #endif |
| )cpp"; |
| TU.AdditionalFiles["bar.h"] = guard(R"cpp( |
| void bar() {} |
| )cpp"); |
| ParsedAST AST = TU.build(); |
| |
| IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
| EXPECT_THAT(Findings.UnusedIncludes, |
| ElementsAre(Pointee(writtenInclusion("\"foo.h\"")))); |
| } |
| |
| TEST(IncludeCleaner, NoDiagsForObjC) { |
| TestTU TU; |
| TU.Code = R"cpp( |
| #include "foo.h" |
| |
| void bar() {} |
| )cpp"; |
| TU.AdditionalFiles["foo.h"] = R"cpp( |
| #ifndef FOO_H |
| #define FOO_H |
| |
| #endif |
| )cpp"; |
| TU.ExtraArgs.emplace_back("-xobjective-c"); |
| |
| ParsedAST AST = TU.build(); |
| IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
| EXPECT_THAT(Findings.MissingIncludes, IsEmpty()); |
| EXPECT_THAT(Findings.UnusedIncludes, IsEmpty()); |
| } |
| |
| TEST(IncludeCleaner, UmbrellaUsesPrivate) { |
| TestTU TU; |
| TU.Code = R"cpp( |
| #include "private.h" |
| )cpp"; |
| TU.AdditionalFiles["private.h"] = guard(R"cpp( |
| // IWYU pragma: private, include "public.h" |
| void foo() {} |
| )cpp"); |
| TU.Filename = "public.h"; |
| ParsedAST AST = TU.build(); |
| IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST); |
| EXPECT_THAT(Findings.UnusedIncludes, IsEmpty()); |
| } |
| |
| TEST(IncludeCleaner, MacroExpandedThroughIncludes) { |
| Annotations MainFile(R"cpp( |
| #include "all.h" |
| #define FOO(X) const Foo *X |
| void foo() { |
| #include [["expander.inc"]] |
| } |
| )cpp"); |
| |
| TestTU TU; |
| TU.AdditionalFiles["expander.inc"] = guard("FOO(f1);FOO(f2);"); |
| TU.AdditionalFiles["foo.h"] = guard("struct Foo {};"); |
| TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\""); |
| |
| TU.Code = MainFile.code(); |
| ParsedAST AST = TU.build(); |
| |
| auto Findings = computeIncludeCleanerFindings(AST).MissingIncludes; |
| EXPECT_THAT(Findings, testing::SizeIs(1)); |
| auto RefRange = Findings.front().SymRefRange; |
| auto &SM = AST.getSourceManager(); |
| EXPECT_EQ(RefRange.file(), SM.getMainFileID()); |
| // FIXME: Point at the spelling location, rather than the include. |
| EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range()); |
| } |
| |
| TEST(IncludeCleaner, MissingIncludesAreUnique) { |
| Annotations MainFile(R"cpp( |
| #include "all.h" |
| FOO([[Foo]]); |
| )cpp"); |
| |
| TestTU TU; |
| TU.AdditionalFiles["foo.h"] = guard("struct Foo {};"); |
| TU.AdditionalFiles["all.h"] = guard(R"cpp( |
| #include "foo.h" |
| #define FOO(X) X y; X z |
| )cpp"); |
| |
| TU.Code = MainFile.code(); |
| ParsedAST AST = TU.build(); |
| |
| auto Findings = computeIncludeCleanerFindings(AST).MissingIncludes; |
| EXPECT_THAT(Findings, testing::SizeIs(1)); |
| auto RefRange = Findings.front().SymRefRange; |
| auto &SM = AST.getSourceManager(); |
| EXPECT_EQ(RefRange.file(), SM.getMainFileID()); |
| EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range()); |
| } |
| |
| TEST(IncludeCleaner, NoCrash) { |
| TestTU TU; |
| Annotations MainCode(R"cpp( |
| #include "all.h" |
| void test() { |
| [[1s]]; |
| } |
| )cpp"); |
| TU.Code = MainCode.code(); |
| TU.AdditionalFiles["foo.h"] = |
| guard("int operator\"\"s(unsigned long long) { return 0; }"); |
| TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\""); |
| ParsedAST AST = TU.build(); |
| const auto &MissingIncludes = |
| computeIncludeCleanerFindings(AST).MissingIncludes; |
| EXPECT_THAT(MissingIncludes, testing::SizeIs(1)); |
| auto &SM = AST.getSourceManager(); |
| EXPECT_EQ( |
| halfOpenToRange(SM, MissingIncludes.front().SymRefRange.toCharRange(SM)), |
| MainCode.range()); |
| } |
| |
| TEST(IncludeCleaner, IsPreferredProvider) { |
| auto TU = TestTU::withCode(R"cpp( |
| #include "decl.h" |
| #include "def.h" |
| #include "def.h" |
| )cpp"); |
| TU.AdditionalFiles["decl.h"] = ""; |
| TU.AdditionalFiles["def.h"] = ""; |
| |
| auto AST = TU.build(); |
| auto &IncludeDecl = AST.getIncludeStructure().MainFileIncludes[0]; |
| auto &IncludeDef1 = AST.getIncludeStructure().MainFileIncludes[1]; |
| auto &IncludeDef2 = AST.getIncludeStructure().MainFileIncludes[2]; |
| |
| auto &FM = AST.getSourceManager().getFileManager(); |
| auto DeclH = *FM.getOptionalFileRef("decl.h"); |
| auto DefH = *FM.getOptionalFileRef("def.h"); |
| |
| auto Includes = convertIncludes(AST); |
| std::vector<include_cleaner::Header> Providers = { |
| include_cleaner::Header(DefH), include_cleaner::Header(DeclH)}; |
| EXPECT_FALSE(isPreferredProvider(IncludeDecl, Includes, Providers)); |
| EXPECT_TRUE(isPreferredProvider(IncludeDef1, Includes, Providers)); |
| EXPECT_TRUE(isPreferredProvider(IncludeDef2, Includes, Providers)); |
| } |
| |
| TEST(IncludeCleaner, BatchFix) { |
| TestTU TU; |
| TU.Filename = "main.cpp"; |
| TU.AdditionalFiles["foo.h"] = guard("class Foo;"); |
| TU.AdditionalFiles["bar.h"] = guard("class Bar;"); |
| TU.AdditionalFiles["all.h"] = guard(R"cpp( |
| #include "foo.h" |
| #include "bar.h" |
| )cpp"); |
| |
| TU.Code = R"cpp( |
| #include "all.h" |
| |
| Foo* foo; |
| )cpp"; |
| auto AST = TU.build(); |
| EXPECT_THAT( |
| issueIncludeCleanerDiagnostics( |
| AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()), |
| UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""), |
| FixMessage("fix all includes")}), |
| withFix({FixMessage("remove #include directive"), |
| FixMessage("fix all includes")}))); |
| |
| TU.Code = R"cpp( |
| #include "all.h" |
| #include "bar.h" |
| |
| Foo* foo; |
| )cpp"; |
| AST = TU.build(); |
| EXPECT_THAT( |
| issueIncludeCleanerDiagnostics( |
| AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()), |
| UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""), |
| FixMessage("fix all includes")}), |
| withFix({FixMessage("remove #include directive"), |
| FixMessage("remove all unused includes"), |
| FixMessage("fix all includes")}), |
| withFix({FixMessage("remove #include directive"), |
| FixMessage("remove all unused includes"), |
| FixMessage("fix all includes")}))); |
| |
| TU.Code = R"cpp( |
| #include "all.h" |
| |
| Foo* foo; |
| Bar* bar; |
| )cpp"; |
| AST = TU.build(); |
| EXPECT_THAT( |
| issueIncludeCleanerDiagnostics( |
| AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()), |
| UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""), |
| FixMessage("add all missing includes"), |
| FixMessage("fix all includes")}), |
| withFix({FixMessage("#include \"bar.h\""), |
| FixMessage("add all missing includes"), |
| FixMessage("fix all includes")}), |
| withFix({FixMessage("remove #include directive"), |
| FixMessage("fix all includes")}))); |
| } |
| |
| // In the presence of IWYU pragma private, we should accept spellings other |
| // than the recommended one if they appear to name the same public header. |
| TEST(IncludeCleaner, VerbatimEquivalence) { |
| auto TU = TestTU::withCode(R"cpp( |
| #include "lib/rel/public.h" |
| int x = Public; |
| )cpp"); |
| TU.AdditionalFiles["repo/lib/rel/private.h"] = R"cpp( |
| #pragma once |
| // IWYU pragma: private, include "rel/public.h" |
| int Public; |
| )cpp"; |
| TU.AdditionalFiles["repo/lib/rel/public.h"] = R"cpp( |
| #pragma once |
| #include "rel/private.h" |
| )cpp"; |
| |
| TU.ExtraArgs.push_back("-Irepo"); |
| TU.ExtraArgs.push_back("-Irepo/lib"); |
| |
| auto AST = TU.build(); |
| auto Findings = computeIncludeCleanerFindings(AST); |
| EXPECT_THAT(Findings.MissingIncludes, IsEmpty()); |
| EXPECT_THAT(Findings.UnusedIncludes, IsEmpty()); |
| } |
| |
| TEST(IncludeCleaner, ResourceDirIsIgnored) { |
| auto TU = TestTU::withCode(R"cpp( |
| #include <amintrin.h> |
| #include <imintrin.h> |
| void baz() { |
| bar(); |
| } |
| )cpp"); |
| TU.ExtraArgs.push_back("-resource-dir"); |
| TU.ExtraArgs.push_back(testPath("resources")); |
| TU.AdditionalFiles["resources/include/amintrin.h"] = guard(""); |
| TU.AdditionalFiles["resources/include/imintrin.h"] = guard(R"cpp( |
| #include <emintrin.h> |
| )cpp"); |
| TU.AdditionalFiles["resources/include/emintrin.h"] = guard(R"cpp( |
| void bar(); |
| )cpp"); |
| auto AST = TU.build(); |
| auto Findings = computeIncludeCleanerFindings(AST); |
| EXPECT_THAT(Findings.UnusedIncludes, IsEmpty()); |
| EXPECT_THAT(Findings.MissingIncludes, IsEmpty()); |
| } |
| |
| TEST(IncludeCleaner, DifferentHeaderSameSpelling) { |
| // `foo` is declared in foo_inner/foo.h, but there's no way to spell it |
| // directly. Make sure we don't generate unusued/missing include findings in |
| // such cases. |
| auto TU = TestTU::withCode(R"cpp( |
| #include <foo.h> |
| void baz() { |
| foo(); |
| } |
| )cpp"); |
| TU.AdditionalFiles["foo/foo.h"] = guard("#include_next <foo.h>"); |
| TU.AdditionalFiles["foo_inner/foo.h"] = guard(R"cpp( |
| void foo(); |
| )cpp"); |
| TU.ExtraArgs.push_back("-Ifoo"); |
| TU.ExtraArgs.push_back("-Ifoo_inner"); |
| |
| auto AST = TU.build(); |
| auto Findings = computeIncludeCleanerFindings(AST); |
| EXPECT_THAT(Findings.UnusedIncludes, IsEmpty()); |
| EXPECT_THAT(Findings.MissingIncludes, IsEmpty()); |
| } |
| } // namespace |
| } // namespace clangd |
| } // namespace clang |