diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h --- a/clang-tools-extra/clangd/Config.h +++ b/clang-tools-extra/clangd/Config.h @@ -29,6 +29,8 @@ #include "llvm/ADT/Optional.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringSet.h" +#include "llvm/Support/Regex.h" +#include #include #include @@ -100,6 +102,12 @@ } ClangTidy; UnusedIncludesPolicy UnusedIncludes = None; + + /// IncludeCleaner will not diagnose usages of these headers matched by + /// these regexes. + struct { + std::vector> IgnoreHeader; + } Includes; } Diagnostics; /// Style of the codebase. diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp --- a/clang-tools-extra/clangd/ConfigCompile.cpp +++ b/clang-tools-extra/clangd/ConfigCompile.cpp @@ -45,7 +45,9 @@ #include "llvm/Support/SMLoc.h" #include "llvm/Support/SourceMgr.h" #include +#include #include +#include namespace clang { namespace clangd { @@ -432,6 +434,7 @@ Out.Apply.push_back([Val](const Params &, Config &C) { C.Diagnostics.UnusedIncludes = *Val; }); + compile(std::move(F.Includes)); compile(std::move(F.ClangTidy)); } @@ -507,6 +510,40 @@ } } + void compile(Fragment::DiagnosticsBlock::IncludesBlock &&F) { +#ifdef CLANGD_PATH_CASE_INSENSITIVE + static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase; +#else + static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags; +#endif + auto Filters = std::make_shared>(); + for (auto &HeaderPattern : F.IgnoreHeader) { + // Anchor on the right. + std::string AnchoredPattern = "(" + *HeaderPattern + ")$"; + llvm::Regex CompiledRegex(AnchoredPattern, Flags); + std::string RegexError; + if (!CompiledRegex.isValid(RegexError)) { + diag(Warning, + llvm::formatv("Invalid regular expression: {0}", *HeaderPattern) + .str(), + HeaderPattern.Range); + continue; + } + Filters->push_back(std::move(CompiledRegex)); + } + if (Filters->empty()) + return; + auto Filter = [Filters](llvm::StringRef Path) { + for (auto &Regex : *Filters) + if (Regex.match(Path)) + return true; + return false; + }; + Out.Apply.push_back([Filter](const Params &, Config &C) { + C.Diagnostics.Includes.IgnoreHeader.emplace_back(Filter); + }); + } + void compile(Fragment::CompletionBlock &&F) { if (F.AllScopes) { Out.Apply.push_back( diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h --- a/clang-tools-extra/clangd/ConfigFragment.h +++ b/clang-tools-extra/clangd/ConfigFragment.h @@ -232,6 +232,15 @@ /// - None llvm::Optional> UnusedIncludes; + /// Controls IncludeCleaner diagnostics. + struct IncludesBlock { + /// Regexes that will be used to avoid diagnosing certain includes as + // unused or missing. These can match any suffix of the header file in + // question. + std::vector> IgnoreHeader; + }; + IncludesBlock Includes; + /// Controls how clang-tidy will run over the code base. /// /// The settings are merged with any settings found in .clang-tidy 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 @@ -23,10 +23,14 @@ #include "clang/Lex/HeaderSearch.h" #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/Syntax/Tokens.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLFunctionalExtras.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringSet.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" +#include "llvm/Support/Regex.h" +#include namespace clang { namespace clangd { @@ -233,7 +237,9 @@ } } -static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST) { +static bool mayConsiderUnused( + const Inclusion &Inc, ParsedAST &AST, + llvm::ArrayRef> HeaderFilters) { if (Inc.BehindPragmaKeep) return false; @@ -258,6 +264,15 @@ FE->getName()); return false; } + for (auto &Filter : HeaderFilters) { + // Convert the path to Unix slashes and try to match aginast the fiilter. + 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; } @@ -369,6 +384,7 @@ 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) @@ -377,7 +393,8 @@ continue; auto IncludeID = static_cast(*MFI.HeaderID); bool Used = ReferencedFiles.contains(IncludeID); - if (!Used && !mayConsiderUnused(MFI, AST)) { + if (!Used && + !mayConsiderUnused(MFI, AST, Cfg.Diagnostics.Includes.IgnoreHeader)) { dlog("{0} was not used, but is not eligible to be diagnosed as unused", MFI.Written); continue; diff --git a/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp b/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp --- a/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp +++ b/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp @@ -263,6 +263,24 @@ EXPECT_TRUE(compileAndApply()); EXPECT_EQ(Conf.Diagnostics.UnusedIncludes, Config::UnusedIncludesPolicy::Strict); + + Frag = {}; + EXPECT_TRUE(Conf.Diagnostics.Includes.IgnoreHeader.empty()) + << Conf.Diagnostics.Includes.IgnoreHeader.size(); + Frag.Diagnostics.Includes.IgnoreHeader.push_back( + Located("foo.h")); + Frag.Diagnostics.Includes.IgnoreHeader.push_back( + Located(".*inc")); + EXPECT_TRUE(compileAndApply()); + auto HeaderFilter = [this](llvm::StringRef Path) { + for (auto &Filter : Conf.Diagnostics.Includes.IgnoreHeader) { + if (Filter(Path)) + return true; + } + return false; + }; + EXPECT_TRUE(HeaderFilter("foo.h")); + EXPECT_FALSE(HeaderFilter("bar.h")); } TEST_F(ConfigCompileTests, DiagnosticSuppression) { diff --git a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp --- a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp +++ b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp @@ -1677,6 +1677,8 @@ ]] #include "used.h" + #include "ignore.h" + #include void foo() { @@ -1693,12 +1695,19 @@ #pragma once void used() {} )cpp"; + TU.AdditionalFiles["ignore.h"] = R"cpp( + #pragma once + void ignore() {} + )cpp"; TU.AdditionalFiles["system/system_header.h"] = ""; TU.ExtraArgs = {"-isystem" + testPath("system")}; // Off by default. EXPECT_THAT(*TU.build().getDiagnostics(), IsEmpty()); Config Cfg; Cfg.Diagnostics.UnusedIncludes = Config::UnusedIncludesPolicy::Strict; + // Set filtering. + Cfg.Diagnostics.Includes.IgnoreHeader.emplace_back( + [](llvm::StringRef Header) { return Header.endswith("ignore.h"); }); WithContextValue WithCfg(Config::Key, std::move(Cfg)); EXPECT_THAT( *TU.build().getDiagnostics(),