diff --git a/clang-tools-extra/clangd/IncludeCleaner.h b/clang-tools-extra/clangd/IncludeCleaner.h --- a/clang-tools-extra/clangd/IncludeCleaner.h +++ b/clang-tools-extra/clangd/IncludeCleaner.h @@ -18,6 +18,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INCLUDECLEANER_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INCLUDECLEANER_H +#include "Config.h" #include "Headers.h" #include "ParsedAST.h" #include "index/CanonicalIncludes.h" @@ -37,6 +38,31 @@ llvm::DenseSet Stdlib; }; +class CachedIncludeCleaner { + +public: + CachedIncludeCleaner( + ParsedAST &AST, + const std::vector &MacroReferences) + : AST(AST), MacroReferences(MacroReferences), SM(AST.getSourceManager()) { + } + + void run(); + std::vector> computeMissingIncludes(); + std::vector computeUnusedIncludesExperimental(); + +private: + ParsedAST &AST; + const std::vector &MacroReferences; + llvm::SmallVector Refs; + llvm::SmallVector> ProviderRefs; + SourceManager &SM; + + include_cleaner::Includes + convertIncludes(const SourceManager &SM, + std::vector MainFileIncludes); +}; + /// Finds locations of all symbols used in the main file. /// /// - RecursiveASTVisitor finds references to symbols and records their @@ -96,18 +122,14 @@ const llvm::DenseSet &ReferencedFiles, const llvm::StringSet<> &ReferencedPublicHeaders); -std::vector> -computeMissingIncludes(ParsedAST &AST); std::vector computeUnusedIncludes(ParsedAST &AST); -// The same as computeUnusedIncludes, but it is an experimental and -// include-cleaner-lib-based implementation. -std::vector -computeUnusedIncludesExperimental(ParsedAST &AST); -std::vector issueMissingIncludesDiagnostics(ParsedAST &AST, - llvm::StringRef Code); -std::vector issueUnusedIncludesDiagnostics(ParsedAST &AST, - llvm::StringRef Code); +std::vector +issueMissingIncludesDiagnostics(CachedIncludeCleaner &IncludeCleaner, + ParsedAST &AST, llvm::StringRef Code); +std::vector +issueUnusedIncludesDiagnostics(CachedIncludeCleaner &IncludeCleaner, + ParsedAST &AST, llvm::StringRef Code); /// Affects whether standard library includes should be considered for /// removal. This is off by default for now due to implementation limitations: diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp --- a/clang-tools-extra/clangd/IncludeCleaner.cpp +++ b/clang-tools-extra/clangd/IncludeCleaner.cpp @@ -29,6 +29,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" #include "llvm/Support/FormatVariadic.h" @@ -44,6 +45,195 @@ static bool AnalyzeStdlib = false; void setIncludeCleanerAnalyzesStdlib(bool B) { AnalyzeStdlib = B; } +static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST, + const Config &Cfg) { + if (Inc.BehindPragmaKeep) + return false; + + // FIXME(kirillbobyrev): We currently do not support the umbrella headers. + // System headers are likely to be standard library headers. + // Until we have good support for umbrella headers, don't warn about them. + if (Inc.Written.front() == '<') { + if (AnalyzeStdlib && tooling::stdlib::Header::named(Inc.Written)) + return true; + return false; + } + assert(Inc.HeaderID); + auto HID = static_cast(*Inc.HeaderID); + // FIXME: Ignore the headers with IWYU export pragmas for now, remove this + // check when we have more precise tracking of exported headers. + if (AST.getIncludeStructure().hasIWYUExport(HID)) + return false; + auto FE = AST.getSourceManager().getFileManager().getFileRef( + AST.getIncludeStructure().getRealPath(HID)); + assert(FE); + // Headers without include guards have side effects and are not + // self-contained, skip them. + if (!AST.getPreprocessor().getHeaderSearchInfo().isFileMultipleIncludeGuarded( + &FE->getFileEntry())) { + dlog("{0} doesn't have header guard and will not be considered unused", + FE->getName()); + return false; + } + for (auto &Filter : Cfg.Diagnostics.Includes.IgnoreHeader) { + // Convert the path to Unix slashes and try to match against the filter. + llvm::SmallString<64> Path(Inc.Resolved); + llvm::sys::path::native(Path, llvm::sys::path::Style::posix); + if (Filter(Inc.Resolved)) { + dlog("{0} header is filtered out by the configuration", FE->getName()); + return false; + } + } + return true; +} + +std::vector getUnused(ParsedAST &AST, + const llvm::DenseSet &ReferencedFiles, + const llvm::StringSet<> &ReferencedPublicHeaders) { + trace::Span Tracer("IncludeCleaner::getUnused"); + const Config &Cfg = Config::current(); + std::vector Unused; + for (const Inclusion &MFI : AST.getIncludeStructure().MainFileIncludes) { + if (!MFI.HeaderID) + continue; + if (ReferencedPublicHeaders.contains(MFI.Written)) + continue; + auto IncludeID = static_cast(*MFI.HeaderID); + bool Used = ReferencedFiles.contains(IncludeID); + if (!Used && !mayConsiderUnused(MFI, AST, Cfg)) { + dlog("{0} was not used, but is not eligible to be diagnosed as unused", + MFI.Written); + continue; + } + if (!Used) + Unused.push_back(&MFI); + dlog("{0} is {1}", MFI.Written, Used ? "USED" : "UNUSED"); + } + return Unused; +} + +void CachedIncludeCleaner::run() { + const SourceManager &SM = AST.getSourceManager(); + include_cleaner::walkUsed( + AST.getLocalTopLevelDecls(), MacroReferences, AST.getPragmaIncludes(), SM, + [&](const include_cleaner::SymbolReference &Ref, + llvm::ArrayRef Providers) { + assert(Refs.size() == ProviderRefs.size()); + Refs.push_back(Ref); + + llvm::SmallVector SymbolProviders; + for (const include_cleaner::Header &H : Providers) { + SymbolProviders.push_back(H); + } + ProviderRefs.push_back(SymbolProviders); + }); +} + +std::vector> +CachedIncludeCleaner::computeMissingIncludes() { + if (Refs.empty()) { + run(); + } + std::vector MainFileIncludes = + AST.getIncludeStructure().MainFileIncludes; + + include_cleaner::Includes IncludeCleanerIncludes = + convertIncludes(AST.getSourceManager(), MainFileIncludes); + std::string FileName = + SM.getFileEntryRefForID(AST.getSourceManager().getMainFileID()) + ->getName() + .str(); + + std::vector> Missing; + const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID()); + for (unsigned I = 0; I < Refs.size(); ++I) { + include_cleaner::SymbolReference Ref = Refs[I]; + llvm::SmallVector Providers = ProviderRefs[I]; + bool Satisfied = false; + for (const include_cleaner::Header &H : Providers) { + if (H.kind() == include_cleaner::Header::Physical && + H.physical() == MainFile) { + Satisfied = true; + } + if (!IncludeCleanerIncludes.match(H).empty()) { + Satisfied = true; + } + } + if (!Satisfied && !Providers.empty() && + Ref.RT == include_cleaner::RefType::Explicit) { + std::string SpelledHeader = include_cleaner::spellHeader( + Providers.front(), AST.getPreprocessor().getHeaderSearchInfo(), + MainFile); + std::pair FileIDAndOffset = + AST.getSourceManager().getDecomposedLoc(Ref.RefLocation); + Missing.push_back({SpelledHeader, FileIDAndOffset.second}); + } + } + return Missing; +} + +// Compute unused #includes using the include-cleaner-lib-based +// implementation. +std::vector +CachedIncludeCleaner::computeUnusedIncludesExperimental() { + if (Refs.empty()) { + run(); + } + const auto &Includes = AST.getIncludeStructure(); + // FIXME: this map should probably be in IncludeStructure. + llvm::StringMap> BySpelling; + for (const auto &Inc : Includes.MainFileIncludes) { + if (Inc.HeaderID) + BySpelling.try_emplace(Inc.Written) + .first->second.push_back( + static_cast(*Inc.HeaderID)); + } + + llvm::DenseSet Used; + for (unsigned I = 0; I < Refs.size(); ++I) { + llvm::SmallVector Providers = ProviderRefs[I]; + for (const include_cleaner::Header &H : Providers) { + switch (H.kind()) { + case include_cleaner::Header::Physical: + if (auto HeaderID = Includes.getID(H.physical())) + Used.insert(*HeaderID); + break; + case include_cleaner::Header::Standard: + for (auto HeaderID : Includes.StdlibHeaders.lookup(H.standard())) + Used.insert(HeaderID); + break; + case include_cleaner::Header::Verbatim: + for (auto HeaderID : BySpelling.lookup(H.verbatim())) + Used.insert(HeaderID); + break; + } + } + } + return getUnused(AST, Used, /*ReferencedPublicHeaders*/ {}); +} + +include_cleaner::Includes +CachedIncludeCleaner::convertIncludes(const SourceManager &SM, + std::vector MainFileIncludes) { + include_cleaner::Includes Includes; + for (const Inclusion &Inc : MainFileIncludes) { + llvm::ErrorOr ResolvedOrError = + SM.getFileManager().getFile(Inc.Resolved); + const FileEntry *Resolved = nullptr; + if (bool(ResolvedOrError)) { + Resolved = ResolvedOrError.get(); + } + SourceLocation HashLocation = + SM.getComposedLoc(SM.getMainFileID(), Inc.HashOffset); + llvm::StringRef WrittenRef = llvm::StringRef(Inc.Written); + bool Angled = WrittenRef.starts_with("<") ? true : false; + Includes.add(include_cleaner::Include{WrittenRef.trim("\"<>"), Resolved, + HashLocation, (unsigned)Inc.HashLine, + Angled}); + } + return Includes; +} + namespace { /// Crawler traverses the AST and feeds in the locations of (sometimes @@ -261,48 +451,6 @@ } } -static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST, - const Config &Cfg) { - if (Inc.BehindPragmaKeep) - return false; - - // FIXME(kirillbobyrev): We currently do not support the umbrella headers. - // System headers are likely to be standard library headers. - // Until we have good support for umbrella headers, don't warn about them. - if (Inc.Written.front() == '<') { - if (AnalyzeStdlib && tooling::stdlib::Header::named(Inc.Written)) - return true; - return false; - } - assert(Inc.HeaderID); - auto HID = static_cast(*Inc.HeaderID); - // FIXME: Ignore the headers with IWYU export pragmas for now, remove this - // check when we have more precise tracking of exported headers. - if (AST.getIncludeStructure().hasIWYUExport(HID)) - return false; - auto FE = AST.getSourceManager().getFileManager().getFileRef( - AST.getIncludeStructure().getRealPath(HID)); - assert(FE); - // Headers without include guards have side effects and are not - // self-contained, skip them. - if (!AST.getPreprocessor().getHeaderSearchInfo().isFileMultipleIncludeGuarded( - &FE->getFileEntry())) { - dlog("{0} doesn't have header guard and will not be considered unused", - FE->getName()); - return false; - } - for (auto &Filter : Cfg.Diagnostics.Includes.IgnoreHeader) { - // Convert the path to Unix slashes and try to match against the filter. - llvm::SmallString<64> Path(Inc.Resolved); - llvm::sys::path::native(Path, llvm::sys::path::Style::posix); - if (Filter(Inc.Resolved)) { - dlog("{0} header is filtered out by the configuration", FE->getName()); - return false; - } - } - return true; -} - // In case symbols are coming from non self-contained header, we need to find // its first includer that is self-contained. This is the header users can // include, so it will be responsible for bringing the symbols from given @@ -406,32 +554,6 @@ }); } -std::vector -getUnused(ParsedAST &AST, - const llvm::DenseSet &ReferencedFiles, - const llvm::StringSet<> &ReferencedPublicHeaders) { - trace::Span Tracer("IncludeCleaner::getUnused"); - const Config &Cfg = Config::current(); - std::vector Unused; - for (const Inclusion &MFI : AST.getIncludeStructure().MainFileIncludes) { - if (!MFI.HeaderID) - continue; - if (ReferencedPublicHeaders.contains(MFI.Written)) - continue; - auto IncludeID = static_cast(*MFI.HeaderID); - bool Used = ReferencedFiles.contains(IncludeID); - if (!Used && !mayConsiderUnused(MFI, AST, Cfg)) { - dlog("{0} was not used, but is not eligible to be diagnosed as unused", - MFI.Written); - continue; - } - if (!Used) - Unused.push_back(&MFI); - dlog("{0} is {1}", MFI.Written, Used ? "USED" : "UNUSED"); - } - return Unused; -} - #ifndef NDEBUG // Is FID a , etc? static bool isSpecialBuffer(FileID FID, const SourceManager &SM) { @@ -463,27 +585,6 @@ return TranslatedHeaderIDs; } -std::vector -collectMacroReferences(ParsedAST &AST) { - const auto &SM = AST.getSourceManager(); - // FIXME: !!this is a hacky way to collect macro references. - std::vector Macros; - auto &PP = AST.getPreprocessor(); - for (const syntax::Token &Tok : - AST.getTokens().spelledTokens(SM.getMainFileID())) { - auto Macro = locateMacroAt(Tok, PP); - if (!Macro) - continue; - if (auto DefLoc = Macro->Info->getDefinitionLoc(); DefLoc.isValid()) - Macros.push_back( - {Tok.location(), - include_cleaner::Macro{/*Name=*/PP.getIdentifierInfo(Tok.text(SM)), - DefLoc}, - include_cleaner::RefType::Explicit}); - } - return Macros; -} - // This is the original clangd-own implementation for computing unused // #includes. Eventually it will be deprecated and replaced by the // include-cleaner-lib-based implementation. @@ -499,122 +600,9 @@ return getUnused(AST, ReferencedHeaders, ReferencedFiles.SpelledUmbrellas); } -// Compute unused #includes using the include-cleaner-lib-based implementation. -std::vector -computeUnusedIncludesExperimental(ParsedAST &AST) { - const auto &SM = AST.getSourceManager(); - const auto &Includes = AST.getIncludeStructure(); - // FIXME: this map should probably be in IncludeStructure. - llvm::StringMap> BySpelling; - for (const auto &Inc : Includes.MainFileIncludes) { - if (Inc.HeaderID) - BySpelling.try_emplace(Inc.Written) - .first->second.push_back( - static_cast(*Inc.HeaderID)); - } - - std::vector Macros = - collectMacroReferences(AST); - - llvm::DenseSet Used; - include_cleaner::walkUsed( - AST.getLocalTopLevelDecls(), /*MacroRefs=*/Macros, - AST.getPragmaIncludes(), SM, - [&](const include_cleaner::SymbolReference &Ref, - llvm::ArrayRef Providers) { - for (const auto &H : Providers) { - switch (H.kind()) { - case include_cleaner::Header::Physical: - if (auto HeaderID = Includes.getID(H.physical())) - Used.insert(*HeaderID); - break; - case include_cleaner::Header::Standard: - for (auto HeaderID : Includes.StdlibHeaders.lookup(H.standard())) - Used.insert(HeaderID); - break; - case include_cleaner::Header::Verbatim: - for (auto HeaderID : BySpelling.lookup(H.verbatim())) - Used.insert(HeaderID); - break; - } - } - }); - return getUnused(AST, Used, /*ReferencedPublicHeaders*/ {}); -} - -include_cleaner::Includes -convertIncludes(const SourceManager &SM, - std::vector MainFileIncludes) { - include_cleaner::Includes Includes; - for (const Inclusion &Inc : MainFileIncludes) { - llvm::ErrorOr ResolvedOrError = - SM.getFileManager().getFile(Inc.Resolved); - const FileEntry *Resolved = nullptr; - if (bool(ResolvedOrError)) { - Resolved = ResolvedOrError.get(); - } - SourceLocation HashLocation = - SM.getComposedLoc(SM.getMainFileID(), Inc.HashOffset); - llvm::StringRef WrittenRef = llvm::StringRef(Inc.Written); - bool Angled = WrittenRef.starts_with("<") ? true : false; - Includes.add(include_cleaner::Include{WrittenRef.trim("\"<>"), Resolved, - HashLocation, (unsigned)Inc.HashLine, - Angled}); - } - return Includes; -} - -// Compute missing #includes. Uses the include-cleaner-lib-based implementation. -std::vector> -computeMissingIncludes(ParsedAST &AST) { - std::vector Macros = - collectMacroReferences(AST); - std::vector MainFileIncludes = - AST.getIncludeStructure().MainFileIncludes; - - include_cleaner::Includes IncludeCleanerIncludes = - convertIncludes(AST.getSourceManager(), MainFileIncludes); - std::string FileName = - AST.getSourceManager() - .getFileEntryRefForID(AST.getSourceManager().getMainFileID()) - ->getName() - .str(); - if (FileName.find("foo") != std::string::npos) { - vlog("Include cleaner includes: {0}", IncludeCleanerIncludes.all().size()); - } - - std::vector> Missing; - SourceManager &SM = AST.getSourceManager(); - const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID()); - include_cleaner::walkUsed( - AST.getLocalTopLevelDecls(), Macros, AST.getPragmaIncludes(), SM, - [&](const include_cleaner::SymbolReference &Ref, - llvm::ArrayRef Providers) { - bool Satisfied = false; - for (const include_cleaner::Header &H : Providers) { - if (H.kind() == include_cleaner::Header::Physical && - H.physical() == MainFile) { - Satisfied = true; - } - if (!IncludeCleanerIncludes.match(H).empty()) { - Satisfied = true; - } - } - if (!Satisfied && !Providers.empty() && - Ref.RT == include_cleaner::RefType::Explicit) { - std::string SpelledHeader = include_cleaner::spellHeader( - Providers.front(), AST.getPreprocessor().getHeaderSearchInfo(), - MainFile); - std::pair FileIDAndOffset = - AST.getSourceManager().getDecomposedLoc(Ref.RefLocation); - Missing.push_back({SpelledHeader, FileIDAndOffset.second}); - } - }); - return Missing; -} - -std::vector issueMissingIncludesDiagnostics(ParsedAST &AST, - llvm::StringRef Code) { +std::vector +issueMissingIncludesDiagnostics(CachedIncludeCleaner &IncludeCleaner, + ParsedAST &AST, llvm::StringRef Code) { const Config &Cfg = Config::current(); if (Cfg.Diagnostics.MissingIncludes == Config::MissingIncludesPolicy::None || Cfg.Diagnostics.SuppressAll || @@ -638,7 +626,7 @@ FixLine = MainFileIncludes[MainFileIncludes.size() - 1].HashLine + 1; std::vector Result; - const auto &MissingIncludes = computeMissingIncludes(AST); + const auto &MissingIncludes = IncludeCleaner.computeMissingIncludes(); for (const std::pair &Missing : MissingIncludes) { Diag D; D.Message = llvm::formatv("header {0} is missing", Missing.first); @@ -669,8 +657,9 @@ return Result; } -std::vector issueUnusedIncludesDiagnostics(ParsedAST &AST, - llvm::StringRef Code) { +std::vector +issueUnusedIncludesDiagnostics(CachedIncludeCleaner &IncludeCleaner, + ParsedAST &AST, llvm::StringRef Code) { const Config &Cfg = Config::current(); if (Cfg.Diagnostics.UnusedIncludes == Config::UnusedIncludesPolicy::None || Cfg.Diagnostics.SuppressAll || @@ -686,9 +675,10 @@ .getFileEntryRefForID(AST.getSourceManager().getMainFileID()) ->getName() .str(); + const auto &UnusedIncludes = Cfg.Diagnostics.UnusedIncludes == Config::UnusedIncludesPolicy::Experiment - ? computeUnusedIncludesExperimental(AST) + ? IncludeCleaner.computeUnusedIncludesExperimental() : computeUnusedIncludes(AST); for (const auto *Inc : UnusedIncludes) { Diag D; diff --git a/clang-tools-extra/clangd/ParsedAST.h b/clang-tools-extra/clangd/ParsedAST.h --- a/clang-tools-extra/clangd/ParsedAST.h +++ b/clang-tools-extra/clangd/ParsedAST.h @@ -168,6 +168,9 @@ std::unique_ptr Resolver; }; +std::vector +collectMacroReferences(ParsedAST &AST); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -341,6 +341,27 @@ } // namespace +std::vector +collectMacroReferences(ParsedAST &AST) { + const auto &SM = AST.getSourceManager(); + // FIXME: !!this is a hacky way to collect macro references. + std::vector Macros; + auto &PP = AST.getPreprocessor(); + for (const syntax::Token &Tok : + AST.getTokens().spelledTokens(SM.getMainFileID())) { + auto Macro = locateMacroAt(Tok, PP); + if (!Macro) + continue; + if (auto DefLoc = Macro->Info->getDefinitionLoc(); DefLoc.isValid()) + Macros.push_back( + {Tok.location(), + include_cleaner::Macro{/*Name=*/PP.getIdentifierInfo(Tok.text(SM)), + DefLoc}, + include_cleaner::RefType::Explicit}); + } + return Macros; +} + std::optional ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs, std::unique_ptr CI, @@ -689,10 +710,12 @@ std::move(Diags), std::move(Includes), std::move(CanonIncludes)); if (Result.Diags) { + std::vector MacroReferences = collectMacroReferences(Result); + CachedIncludeCleaner IncludeCleaner(Result, MacroReferences); auto UnusedHeadersDiags = - issueUnusedIncludesDiagnostics(Result, Inputs.Contents); + issueUnusedIncludesDiagnostics(IncludeCleaner, Result, Inputs.Contents); auto MissingHeadersDiags = - issueMissingIncludesDiagnostics(Result, Inputs.Contents); + issueMissingIncludesDiagnostics(IncludeCleaner, Result, Inputs.Contents); Result.Diags->insert(Result.Diags->end(), make_move_iterator(UnusedHeadersDiags.begin()), make_move_iterator(UnusedHeadersDiags.end())); diff --git a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp --- a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp +++ b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp @@ -342,7 +342,9 @@ auto AST = TU.build(); EXPECT_THAT(computeUnusedIncludes(AST), ElementsAre(Pointee(writtenInclusion("")))); - EXPECT_THAT(computeUnusedIncludesExperimental(AST), + std::vector MacroReferences = collectMacroReferences(AST); + CachedIncludeCleaner IncludeCleaner(AST, MacroReferences); + EXPECT_THAT(IncludeCleaner.computeUnusedIncludesExperimental(), ElementsAre(Pointee(writtenInclusion("")))); } @@ -379,8 +381,10 @@ computeUnusedIncludes(AST), UnorderedElementsAre(Pointee(writtenInclusion("\"unused.h\"")), Pointee(writtenInclusion("\"dir/unused.h\"")))); + std::vector MacroReferences = collectMacroReferences(AST); + CachedIncludeCleaner IncludeCleaner(AST, MacroReferences); EXPECT_THAT( - computeUnusedIncludesExperimental(AST), + IncludeCleaner.computeUnusedIncludesExperimental(), UnorderedElementsAre(Pointee(writtenInclusion("\"unused.h\"")), Pointee(writtenInclusion("\"dir/unused.h\"")))); } @@ -413,8 +417,10 @@ TU.Code = MainFile.str(); ParsedAST AST = TU.build(); + std::vector MacroReferences = collectMacroReferences(AST); + CachedIncludeCleaner IncludeCleaner(AST, MacroReferences); std::vector> MissingIncludes = - computeMissingIncludes(AST); + IncludeCleaner.computeMissingIncludes(); std::pair b{"\"b.h\"", 86}; std::pair d{"\"dir/d.h\"", 97}; @@ -591,7 +597,10 @@ ReferencedFiles.User.contains(AST.getSourceManager().getMainFileID())); EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty())); EXPECT_THAT(computeUnusedIncludes(AST), IsEmpty()); - EXPECT_THAT(computeUnusedIncludesExperimental(AST), IsEmpty()); + + std::vector MacroReferences = collectMacroReferences(AST); + CachedIncludeCleaner IncludeCleaner(AST, MacroReferences); + EXPECT_THAT(IncludeCleaner.computeUnusedIncludesExperimental(), IsEmpty()); } TEST(IncludeCleaner, RecursiveInclusion) { @@ -620,7 +629,10 @@ EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty())); EXPECT_THAT(computeUnusedIncludes(AST), IsEmpty()); - EXPECT_THAT(computeUnusedIncludesExperimental(AST), IsEmpty()); + + std::vector MacroReferences = clang::clangd::collectMacroReferences(AST); + CachedIncludeCleaner IncludeCleaner(AST, MacroReferences); + EXPECT_THAT(IncludeCleaner.computeUnusedIncludesExperimental(), IsEmpty()); } TEST(IncludeCleaner, IWYUPragmaExport) { @@ -645,7 +657,9 @@ // FIXME: This is not correct: foo.h is unused but is not diagnosed as such // because we ignore headers with IWYU export pragmas for now. EXPECT_THAT(computeUnusedIncludes(AST), IsEmpty()); - EXPECT_THAT(computeUnusedIncludesExperimental(AST), IsEmpty()); + std::vector MacroReferences = collectMacroReferences(AST); + CachedIncludeCleaner IncludeCleaner(AST, MacroReferences); + EXPECT_THAT(IncludeCleaner.computeUnusedIncludesExperimental(), IsEmpty()); } TEST(IncludeCleaner, NoDiagsForObjC) {