mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-18 14:56:44 +00:00

Some physical headers can have "conflicting" spellings, when same filename exists under two different include search paths. e.g. <stdlib.h> can be provided by both standard library and underlying libc headers. In such scenarios if the usage is from the latter include-search path, users can't spell it directly. This patch ensures we also consider spellings of such includes, in addition to their physical files to prevent conflicting suggestions.
672 lines
22 KiB
C++
672 lines
22 KiB
C++
//===--- 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
|