534 lines
17 KiB
C++
534 lines
17 KiB
C++
//===--- AnalysisTest.cpp -------------------------------------------------===//
|
|
//
|
|
// 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 "clang-include-cleaner/Analysis.h"
|
|
#include "AnalysisInternal.h"
|
|
#include "TypesInternal.h"
|
|
#include "clang-include-cleaner/Record.h"
|
|
#include "clang-include-cleaner/Types.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/Basic/FileManager.h"
|
|
#include "clang/Basic/IdentifierTable.h"
|
|
#include "clang/Basic/SourceLocation.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "clang/Format/Format.h"
|
|
#include "clang/Frontend/FrontendActions.h"
|
|
#include "clang/Testing/TestAST.h"
|
|
#include "clang/Tooling/Inclusions/StandardLibrary.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/Support/ScopedPrinter.h"
|
|
#include "llvm/Testing/Annotations/Annotations.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include <cstddef>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace clang::include_cleaner {
|
|
namespace {
|
|
using testing::AllOf;
|
|
using testing::Contains;
|
|
using testing::ElementsAre;
|
|
using testing::Pair;
|
|
using testing::UnorderedElementsAre;
|
|
|
|
std::string guard(llvm::StringRef Code) {
|
|
return "#pragma once\n" + Code.str();
|
|
}
|
|
|
|
class WalkUsedTest : public testing::Test {
|
|
protected:
|
|
TestInputs Inputs;
|
|
PragmaIncludes PI;
|
|
WalkUsedTest() {
|
|
Inputs.MakeAction = [this] {
|
|
struct Hook : public SyntaxOnlyAction {
|
|
public:
|
|
Hook(PragmaIncludes *Out) : Out(Out) {}
|
|
bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
|
|
Out->record(CI);
|
|
return true;
|
|
}
|
|
|
|
PragmaIncludes *Out;
|
|
};
|
|
return std::make_unique<Hook>(&PI);
|
|
};
|
|
}
|
|
|
|
std::multimap<size_t, std::vector<Header>>
|
|
offsetToProviders(TestAST &AST,
|
|
llvm::ArrayRef<SymbolReference> MacroRefs = {}) {
|
|
const auto &SM = AST.sourceManager();
|
|
llvm::SmallVector<Decl *> TopLevelDecls;
|
|
for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) {
|
|
if (!SM.isWrittenInMainFile(SM.getExpansionLoc(D->getLocation())))
|
|
continue;
|
|
TopLevelDecls.emplace_back(D);
|
|
}
|
|
std::multimap<size_t, std::vector<Header>> OffsetToProviders;
|
|
walkUsed(TopLevelDecls, MacroRefs, &PI, AST.preprocessor(),
|
|
[&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) {
|
|
auto [FID, Offset] = SM.getDecomposedLoc(Ref.RefLocation);
|
|
if (FID != SM.getMainFileID())
|
|
ADD_FAILURE() << "Reference outside of the main file!";
|
|
OffsetToProviders.emplace(Offset, Providers.vec());
|
|
});
|
|
return OffsetToProviders;
|
|
}
|
|
};
|
|
|
|
TEST_F(WalkUsedTest, Basic) {
|
|
llvm::Annotations Code(R"cpp(
|
|
#include "header.h"
|
|
#include "private.h"
|
|
|
|
// No reference reported for the Parameter "p".
|
|
void $bar^bar($private^Private p) {
|
|
$foo^foo();
|
|
std::$vector^vector $vconstructor^$v^v;
|
|
$builtin^__builtin_popcount(1);
|
|
std::$move^move(3);
|
|
}
|
|
)cpp");
|
|
Inputs.Code = Code.code();
|
|
Inputs.ExtraFiles["header.h"] = guard(R"cpp(
|
|
void foo();
|
|
namespace std { class vector {}; int&& move(int&&); }
|
|
)cpp");
|
|
Inputs.ExtraFiles["private.h"] = guard(R"cpp(
|
|
// IWYU pragma: private, include "path/public.h"
|
|
class Private {};
|
|
)cpp");
|
|
|
|
TestAST AST(Inputs);
|
|
auto &SM = AST.sourceManager();
|
|
auto HeaderFile = Header(*AST.fileManager().getOptionalFileRef("header.h"));
|
|
auto PrivateFile = Header(*AST.fileManager().getOptionalFileRef("private.h"));
|
|
auto PublicFile = Header("\"path/public.h\"");
|
|
auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
|
|
auto VectorSTL = Header(*tooling::stdlib::Header::named("<vector>"));
|
|
auto UtilitySTL = Header(*tooling::stdlib::Header::named("<utility>"));
|
|
EXPECT_THAT(
|
|
offsetToProviders(AST),
|
|
UnorderedElementsAre(
|
|
Pair(Code.point("bar"), UnorderedElementsAre(MainFile)),
|
|
Pair(Code.point("private"),
|
|
UnorderedElementsAre(PublicFile, PrivateFile)),
|
|
Pair(Code.point("foo"), UnorderedElementsAre(HeaderFile)),
|
|
Pair(Code.point("vector"), UnorderedElementsAre(VectorSTL)),
|
|
Pair(Code.point("vconstructor"), UnorderedElementsAre(VectorSTL)),
|
|
Pair(Code.point("v"), UnorderedElementsAre(MainFile)),
|
|
Pair(Code.point("builtin"), testing::IsEmpty()),
|
|
Pair(Code.point("move"), UnorderedElementsAre(UtilitySTL))));
|
|
}
|
|
|
|
TEST_F(WalkUsedTest, MultipleProviders) {
|
|
llvm::Annotations Code(R"cpp(
|
|
#include "header1.h"
|
|
#include "header2.h"
|
|
void foo();
|
|
|
|
void bar() {
|
|
$foo^foo();
|
|
}
|
|
)cpp");
|
|
Inputs.Code = Code.code();
|
|
Inputs.ExtraFiles["header1.h"] = guard(R"cpp(
|
|
void foo();
|
|
)cpp");
|
|
Inputs.ExtraFiles["header2.h"] = guard(R"cpp(
|
|
void foo();
|
|
)cpp");
|
|
|
|
TestAST AST(Inputs);
|
|
auto &SM = AST.sourceManager();
|
|
auto HeaderFile1 = Header(*AST.fileManager().getOptionalFileRef("header1.h"));
|
|
auto HeaderFile2 = Header(*AST.fileManager().getOptionalFileRef("header2.h"));
|
|
auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
|
|
EXPECT_THAT(
|
|
offsetToProviders(AST),
|
|
Contains(Pair(Code.point("foo"),
|
|
UnorderedElementsAre(HeaderFile1, HeaderFile2, MainFile))));
|
|
}
|
|
|
|
TEST_F(WalkUsedTest, MacroRefs) {
|
|
llvm::Annotations Code(R"cpp(
|
|
#include "hdr.h"
|
|
int $3^x = $1^ANSWER;
|
|
int $4^y = $2^ANSWER;
|
|
)cpp");
|
|
llvm::Annotations Hdr(guard("#define ^ANSWER 42"));
|
|
Inputs.Code = Code.code();
|
|
Inputs.ExtraFiles["hdr.h"] = Hdr.code();
|
|
TestAST AST(Inputs);
|
|
auto &SM = AST.sourceManager();
|
|
auto &PP = AST.preprocessor();
|
|
auto HdrFile = *SM.getFileManager().getOptionalFileRef("hdr.h");
|
|
auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
|
|
|
|
auto HdrID = SM.translateFile(HdrFile);
|
|
|
|
Symbol Answer1 = Macro{PP.getIdentifierInfo("ANSWER"),
|
|
SM.getComposedLoc(HdrID, Hdr.point())};
|
|
Symbol Answer2 = Macro{PP.getIdentifierInfo("ANSWER"),
|
|
SM.getComposedLoc(HdrID, Hdr.point())};
|
|
EXPECT_THAT(
|
|
offsetToProviders(
|
|
AST,
|
|
{SymbolReference{
|
|
Answer1, SM.getComposedLoc(SM.getMainFileID(), Code.point("1")),
|
|
RefType::Explicit},
|
|
SymbolReference{
|
|
Answer2, SM.getComposedLoc(SM.getMainFileID(), Code.point("2")),
|
|
RefType::Explicit}}),
|
|
UnorderedElementsAre(
|
|
Pair(Code.point("1"), UnorderedElementsAre(HdrFile)),
|
|
Pair(Code.point("2"), UnorderedElementsAre(HdrFile)),
|
|
Pair(Code.point("3"), UnorderedElementsAre(MainFile)),
|
|
Pair(Code.point("4"), UnorderedElementsAre(MainFile))));
|
|
}
|
|
|
|
class AnalyzeTest : public testing::Test {
|
|
protected:
|
|
TestInputs Inputs;
|
|
PragmaIncludes PI;
|
|
RecordedPP PP;
|
|
AnalyzeTest() {
|
|
Inputs.MakeAction = [this] {
|
|
struct Hook : public SyntaxOnlyAction {
|
|
public:
|
|
Hook(RecordedPP &PP, PragmaIncludes &PI) : PP(PP), PI(PI) {}
|
|
bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
|
|
CI.getPreprocessor().addPPCallbacks(PP.record(CI.getPreprocessor()));
|
|
PI.record(CI);
|
|
return true;
|
|
}
|
|
|
|
RecordedPP &PP;
|
|
PragmaIncludes &PI;
|
|
};
|
|
return std::make_unique<Hook>(PP, PI);
|
|
};
|
|
}
|
|
};
|
|
|
|
TEST_F(AnalyzeTest, Basic) {
|
|
Inputs.Code = R"cpp(
|
|
#include "a.h"
|
|
#include "b.h"
|
|
#include "keep.h" // IWYU pragma: keep
|
|
|
|
int x = a + c;
|
|
)cpp";
|
|
Inputs.ExtraFiles["a.h"] = guard("int a;");
|
|
Inputs.ExtraFiles["b.h"] = guard(R"cpp(
|
|
#include "c.h"
|
|
int b;
|
|
)cpp");
|
|
Inputs.ExtraFiles["c.h"] = guard("int c;");
|
|
Inputs.ExtraFiles["keep.h"] = guard("");
|
|
TestAST AST(Inputs);
|
|
auto Decls = AST.context().getTranslationUnitDecl()->decls();
|
|
auto Results =
|
|
analyze(std::vector<Decl *>{Decls.begin(), Decls.end()},
|
|
PP.MacroReferences, PP.Includes, &PI, AST.preprocessor());
|
|
|
|
const Include *B = PP.Includes.atLine(3);
|
|
ASSERT_EQ(B->Spelled, "b.h");
|
|
EXPECT_THAT(Results.Missing, ElementsAre("\"c.h\""));
|
|
EXPECT_THAT(Results.Unused, ElementsAre(B));
|
|
}
|
|
|
|
TEST_F(AnalyzeTest, PrivateUsedInPublic) {
|
|
// Check that umbrella header uses private include.
|
|
Inputs.Code = R"cpp(#include "private.h")cpp";
|
|
Inputs.ExtraFiles["private.h"] =
|
|
guard("// IWYU pragma: private, include \"public.h\"");
|
|
Inputs.FileName = "public.h";
|
|
TestAST AST(Inputs);
|
|
EXPECT_FALSE(PP.Includes.all().empty());
|
|
auto Results = analyze({}, {}, PP.Includes, &PI, AST.preprocessor());
|
|
EXPECT_THAT(Results.Unused, testing::IsEmpty());
|
|
}
|
|
|
|
TEST_F(AnalyzeTest, NoCrashWhenUnresolved) {
|
|
// Check that umbrella header uses private include.
|
|
Inputs.Code = R"cpp(#include "not_found.h")cpp";
|
|
Inputs.ErrorOK = true;
|
|
TestAST AST(Inputs);
|
|
EXPECT_FALSE(PP.Includes.all().empty());
|
|
auto Results = analyze({}, {}, PP.Includes, &PI, AST.preprocessor());
|
|
EXPECT_THAT(Results.Unused, testing::IsEmpty());
|
|
}
|
|
|
|
TEST_F(AnalyzeTest, ResourceDirIsIgnored) {
|
|
Inputs.ExtraArgs.push_back("-resource-dir");
|
|
Inputs.ExtraArgs.push_back("resources");
|
|
Inputs.ExtraArgs.push_back("-internal-isystem");
|
|
Inputs.ExtraArgs.push_back("resources/include");
|
|
Inputs.Code = R"cpp(
|
|
#include <amintrin.h>
|
|
#include <imintrin.h>
|
|
void baz() {
|
|
bar();
|
|
}
|
|
)cpp";
|
|
Inputs.ExtraFiles["resources/include/amintrin.h"] = guard("");
|
|
Inputs.ExtraFiles["resources/include/emintrin.h"] = guard(R"cpp(
|
|
void bar();
|
|
)cpp");
|
|
Inputs.ExtraFiles["resources/include/imintrin.h"] = guard(R"cpp(
|
|
#include <emintrin.h>
|
|
)cpp");
|
|
TestAST AST(Inputs);
|
|
auto Results = analyze({}, {}, PP.Includes, &PI, AST.preprocessor());
|
|
EXPECT_THAT(Results.Unused, testing::IsEmpty());
|
|
EXPECT_THAT(Results.Missing, testing::IsEmpty());
|
|
}
|
|
|
|
TEST(FixIncludes, Basic) {
|
|
llvm::StringRef Code = R"cpp(#include "d.h"
|
|
#include "a.h"
|
|
#include "b.h"
|
|
#include <c.h>
|
|
)cpp";
|
|
|
|
Includes Inc;
|
|
Include I;
|
|
I.Spelled = "a.h";
|
|
I.Line = 2;
|
|
Inc.add(I);
|
|
I.Spelled = "b.h";
|
|
I.Line = 3;
|
|
Inc.add(I);
|
|
I.Spelled = "c.h";
|
|
I.Line = 4;
|
|
I.Angled = true;
|
|
Inc.add(I);
|
|
|
|
AnalysisResults Results;
|
|
Results.Missing.push_back("\"aa.h\"");
|
|
Results.Missing.push_back("\"ab.h\"");
|
|
Results.Missing.push_back("<e.h>");
|
|
Results.Unused.push_back(Inc.atLine(3));
|
|
Results.Unused.push_back(Inc.atLine(4));
|
|
|
|
EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),
|
|
R"cpp(#include "d.h"
|
|
#include "a.h"
|
|
#include "aa.h"
|
|
#include "ab.h"
|
|
#include <e.h>
|
|
)cpp");
|
|
|
|
Results = {};
|
|
Results.Missing.push_back("\"d.h\"");
|
|
Code = R"cpp(#include "a.h")cpp";
|
|
EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),
|
|
R"cpp(#include "d.h"
|
|
#include "a.h")cpp");
|
|
}
|
|
|
|
MATCHER_P3(expandedAt, FileID, Offset, SM, "") {
|
|
auto [ExpanedFileID, ExpandedOffset] = SM->getDecomposedExpansionLoc(arg);
|
|
return ExpanedFileID == FileID && ExpandedOffset == Offset;
|
|
}
|
|
MATCHER_P3(spelledAt, FileID, Offset, SM, "") {
|
|
auto [SpelledFileID, SpelledOffset] = SM->getDecomposedSpellingLoc(arg);
|
|
return SpelledFileID == FileID && SpelledOffset == Offset;
|
|
}
|
|
TEST(WalkUsed, FilterRefsNotSpelledInMainFile) {
|
|
// Each test is expected to have a single expected ref of `target` symbol
|
|
// (or have none).
|
|
// The location in the reported ref is a macro location. $expand points to
|
|
// the macro location, and $spell points to the spelled location.
|
|
struct {
|
|
llvm::StringRef Header;
|
|
llvm::StringRef Main;
|
|
} TestCases[] = {
|
|
// Tests for decl references.
|
|
{
|
|
/*Header=*/"int target();",
|
|
R"cpp(
|
|
#define CALL_FUNC $spell^target()
|
|
|
|
int b = $expand^CALL_FUNC;
|
|
)cpp",
|
|
},
|
|
{/*Header=*/R"cpp(
|
|
int target();
|
|
#define CALL_FUNC target()
|
|
)cpp",
|
|
// No ref of `target` being reported, as it is not spelled in main file.
|
|
"int a = CALL_FUNC;"},
|
|
{
|
|
/*Header=*/R"cpp(
|
|
int target();
|
|
#define PLUS_ONE(X) X() + 1
|
|
)cpp",
|
|
R"cpp(
|
|
int a = $expand^PLUS_ONE($spell^target);
|
|
)cpp",
|
|
},
|
|
{
|
|
/*Header=*/R"cpp(
|
|
int target();
|
|
#define PLUS_ONE(X) X() + 1
|
|
)cpp",
|
|
R"cpp(
|
|
int a = $expand^PLUS_ONE($spell^target);
|
|
)cpp",
|
|
},
|
|
// Tests for macro references
|
|
{/*Header=*/"#define target 1",
|
|
R"cpp(
|
|
#define USE_target $spell^target
|
|
int b = $expand^USE_target;
|
|
)cpp"},
|
|
{/*Header=*/R"cpp(
|
|
#define target 1
|
|
#define USE_target target
|
|
)cpp",
|
|
// No ref of `target` being reported, it is not spelled in main file.
|
|
R"cpp(
|
|
int a = USE_target;
|
|
)cpp"},
|
|
};
|
|
|
|
for (const auto &T : TestCases) {
|
|
llvm::Annotations Main(T.Main);
|
|
TestInputs Inputs(Main.code());
|
|
Inputs.ExtraFiles["header.h"] = guard(T.Header);
|
|
RecordedPP Recorded;
|
|
Inputs.MakeAction = [&]() {
|
|
struct RecordAction : public SyntaxOnlyAction {
|
|
RecordedPP &Out;
|
|
RecordAction(RecordedPP &Out) : Out(Out) {}
|
|
bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
|
|
auto &PP = CI.getPreprocessor();
|
|
PP.addPPCallbacks(Out.record(PP));
|
|
return true;
|
|
}
|
|
};
|
|
return std::make_unique<RecordAction>(Recorded);
|
|
};
|
|
Inputs.ExtraArgs.push_back("-include");
|
|
Inputs.ExtraArgs.push_back("header.h");
|
|
TestAST AST(Inputs);
|
|
llvm::SmallVector<Decl *> TopLevelDecls;
|
|
for (Decl *D : AST.context().getTranslationUnitDecl()->decls())
|
|
TopLevelDecls.emplace_back(D);
|
|
auto &SM = AST.sourceManager();
|
|
|
|
SourceLocation RefLoc;
|
|
walkUsed(TopLevelDecls, Recorded.MacroReferences,
|
|
/*PragmaIncludes=*/nullptr, AST.preprocessor(),
|
|
[&](const SymbolReference &Ref, llvm::ArrayRef<Header>) {
|
|
if (!Ref.RefLocation.isMacroID())
|
|
return;
|
|
if (llvm::to_string(Ref.Target) == "target") {
|
|
ASSERT_TRUE(RefLoc.isInvalid())
|
|
<< "Expected only one 'target' ref loc per testcase";
|
|
RefLoc = Ref.RefLocation;
|
|
}
|
|
});
|
|
FileID MainFID = SM.getMainFileID();
|
|
if (RefLoc.isValid()) {
|
|
EXPECT_THAT(RefLoc, AllOf(expandedAt(MainFID, Main.point("expand"), &SM),
|
|
spelledAt(MainFID, Main.point("spell"), &SM)))
|
|
<< T.Main.str();
|
|
} else {
|
|
EXPECT_THAT(Main.points(), testing::IsEmpty());
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Tag {
|
|
friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Tag &T) {
|
|
return OS << "Anon Tag";
|
|
}
|
|
};
|
|
TEST(Hints, Ordering) {
|
|
auto Hinted = [](Hints Hints) {
|
|
return clang::include_cleaner::Hinted<Tag>({}, Hints);
|
|
};
|
|
EXPECT_LT(Hinted(Hints::None), Hinted(Hints::CompleteSymbol));
|
|
EXPECT_LT(Hinted(Hints::CompleteSymbol), Hinted(Hints::PublicHeader));
|
|
EXPECT_LT(Hinted(Hints::PreferredHeader), Hinted(Hints::PublicHeader));
|
|
EXPECT_LT(Hinted(Hints::CompleteSymbol | Hints::PreferredHeader),
|
|
Hinted(Hints::PublicHeader));
|
|
}
|
|
|
|
// Test ast traversal & redecl selection end-to-end for templates, as explicit
|
|
// instantiations/specializations are not redecls of the primary template. We
|
|
// need to make sure we're selecting the right ones.
|
|
TEST_F(WalkUsedTest, TemplateDecls) {
|
|
llvm::Annotations Code(R"cpp(
|
|
#include "fwd.h"
|
|
#include "def.h"
|
|
#include "partial.h"
|
|
template <> struct $exp_spec^Foo<char> {};
|
|
template struct $exp^Foo<int>;
|
|
$full^Foo<int> x;
|
|
$implicit^Foo<bool> y;
|
|
$partial^Foo<int*> z;
|
|
)cpp");
|
|
Inputs.Code = Code.code();
|
|
Inputs.ExtraFiles["fwd.h"] = guard("template<typename> struct Foo;");
|
|
Inputs.ExtraFiles["def.h"] = guard("template<typename> struct Foo {};");
|
|
Inputs.ExtraFiles["partial.h"] =
|
|
guard("template<typename T> struct Foo<T*> {};");
|
|
TestAST AST(Inputs);
|
|
auto &SM = AST.sourceManager();
|
|
auto Fwd = *SM.getFileManager().getOptionalFileRef("fwd.h");
|
|
auto Def = *SM.getFileManager().getOptionalFileRef("def.h");
|
|
auto Partial = *SM.getFileManager().getOptionalFileRef("partial.h");
|
|
|
|
EXPECT_THAT(
|
|
offsetToProviders(AST),
|
|
AllOf(Contains(
|
|
Pair(Code.point("exp_spec"), UnorderedElementsAre(Fwd, Def))),
|
|
Contains(Pair(Code.point("exp"), UnorderedElementsAre(Fwd, Def))),
|
|
Contains(Pair(Code.point("full"), UnorderedElementsAre(Fwd, Def))),
|
|
Contains(
|
|
Pair(Code.point("implicit"), UnorderedElementsAre(Fwd, Def))),
|
|
Contains(
|
|
Pair(Code.point("partial"), UnorderedElementsAre(Partial)))));
|
|
}
|
|
|
|
TEST_F(WalkUsedTest, IgnoresIdentityMacros) {
|
|
llvm::Annotations Code(R"cpp(
|
|
#include "header.h"
|
|
void $bar^bar() {
|
|
$stdin^stdin();
|
|
}
|
|
)cpp");
|
|
Inputs.Code = Code.code();
|
|
Inputs.ExtraFiles["header.h"] = guard(R"cpp(
|
|
#include "inner.h"
|
|
void stdin();
|
|
)cpp");
|
|
Inputs.ExtraFiles["inner.h"] = guard(R"cpp(
|
|
#define stdin stdin
|
|
)cpp");
|
|
|
|
TestAST AST(Inputs);
|
|
auto &SM = AST.sourceManager();
|
|
auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
|
|
EXPECT_THAT(offsetToProviders(AST),
|
|
UnorderedElementsAre(
|
|
// FIXME: we should have a reference from stdin to header.h
|
|
Pair(Code.point("bar"), UnorderedElementsAre(MainFile))));
|
|
}
|
|
} // namespace
|
|
} // namespace clang::include_cleaner
|