Skip to content

Commit bdf0b75

Browse files
committed
[clangd] IncludeCleaner: Add filtering mechanism
This introduces filtering out inclusions based on the resolved path. This mechanism will be important for disabling warnings for headers that we can not diagnose correctly yet. Reviewed By: sammccall Differential Revision: https://reviews.llvm.org/D123488
1 parent 0f8b8d7 commit bdf0b75

File tree

6 files changed

+99
-2
lines changed

6 files changed

+99
-2
lines changed

clang-tools-extra/clangd/Config.h

+8
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
#include "llvm/ADT/Optional.h"
3030
#include "llvm/ADT/StringMap.h"
3131
#include "llvm/ADT/StringSet.h"
32+
#include "llvm/Support/Regex.h"
33+
#include <functional>
3234
#include <string>
3335
#include <vector>
3436

@@ -100,6 +102,12 @@ struct Config {
100102
} ClangTidy;
101103

102104
UnusedIncludesPolicy UnusedIncludes = None;
105+
106+
/// IncludeCleaner will not diagnose usages of these headers matched by
107+
/// these regexes.
108+
struct {
109+
std::vector<std::function<bool(llvm::StringRef)>> IgnoreHeader;
110+
} Includes;
103111
} Diagnostics;
104112

105113
/// Style of the codebase.

clang-tools-extra/clangd/ConfigCompile.cpp

+38
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@
4545
#include "llvm/Support/SMLoc.h"
4646
#include "llvm/Support/SourceMgr.h"
4747
#include <algorithm>
48+
#include <memory>
4849
#include <string>
50+
#include <vector>
4951

5052
namespace clang {
5153
namespace clangd {
@@ -432,6 +434,7 @@ struct FragmentCompiler {
432434
Out.Apply.push_back([Val](const Params &, Config &C) {
433435
C.Diagnostics.UnusedIncludes = *Val;
434436
});
437+
compile(std::move(F.Includes));
435438

436439
compile(std::move(F.ClangTidy));
437440
}
@@ -507,6 +510,41 @@ struct FragmentCompiler {
507510
}
508511
}
509512

513+
void compile(Fragment::DiagnosticsBlock::IncludesBlock &&F) {
514+
#ifdef CLANGD_PATH_CASE_INSENSITIVE
515+
static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase;
516+
#else
517+
static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags;
518+
#endif
519+
auto Filters = std::make_shared<std::vector<llvm::Regex>>();
520+
for (auto &HeaderPattern : F.IgnoreHeader) {
521+
// Anchor on the right.
522+
std::string AnchoredPattern = "(" + *HeaderPattern + ")$";
523+
llvm::Regex CompiledRegex(AnchoredPattern, Flags);
524+
std::string RegexError;
525+
if (!CompiledRegex.isValid(RegexError)) {
526+
diag(Warning,
527+
llvm::formatv("Invalid regular expression '{0}': {1}",
528+
*HeaderPattern, RegexError)
529+
.str(),
530+
HeaderPattern.Range);
531+
continue;
532+
}
533+
Filters->push_back(std::move(CompiledRegex));
534+
}
535+
if (Filters->empty())
536+
return;
537+
auto Filter = [Filters](llvm::StringRef Path) {
538+
for (auto &Regex : *Filters)
539+
if (Regex.match(Path))
540+
return true;
541+
return false;
542+
};
543+
Out.Apply.push_back([Filter](const Params &, Config &C) {
544+
C.Diagnostics.Includes.IgnoreHeader.emplace_back(Filter);
545+
});
546+
}
547+
510548
void compile(Fragment::CompletionBlock &&F) {
511549
if (F.AllScopes) {
512550
Out.Apply.push_back(

clang-tools-extra/clangd/ConfigFragment.h

+9
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,15 @@ struct Fragment {
232232
/// - None
233233
llvm::Optional<Located<std::string>> UnusedIncludes;
234234

235+
/// Controls IncludeCleaner diagnostics.
236+
struct IncludesBlock {
237+
/// Regexes that will be used to avoid diagnosing certain includes as
238+
/// unused or missing. These can match any suffix of the header file in
239+
/// question.
240+
std::vector<Located<std::string>> IgnoreHeader;
241+
};
242+
IncludesBlock Includes;
243+
235244
/// Controls how clang-tidy will run over the code base.
236245
///
237246
/// The settings are merged with any settings found in .clang-tidy

clang-tools-extra/clangd/IncludeCleaner.cpp

+17-2
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,14 @@
2323
#include "clang/Lex/HeaderSearch.h"
2424
#include "clang/Lex/Preprocessor.h"
2525
#include "clang/Tooling/Syntax/Tokens.h"
26+
#include "llvm/ADT/ArrayRef.h"
2627
#include "llvm/ADT/STLFunctionalExtras.h"
28+
#include "llvm/ADT/SmallString.h"
2729
#include "llvm/ADT/StringSet.h"
2830
#include "llvm/Support/FormatVariadic.h"
2931
#include "llvm/Support/Path.h"
32+
#include "llvm/Support/Regex.h"
33+
#include <functional>
3034

3135
namespace clang {
3236
namespace clangd {
@@ -233,7 +237,8 @@ void findReferencedMacros(const SourceManager &SM, Preprocessor &PP,
233237
}
234238
}
235239

236-
static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST) {
240+
static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST,
241+
const Config &Cfg) {
237242
if (Inc.BehindPragmaKeep)
238243
return false;
239244

@@ -258,6 +263,15 @@ static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST) {
258263
FE->getName());
259264
return false;
260265
}
266+
for (auto &Filter : Cfg.Diagnostics.Includes.IgnoreHeader) {
267+
// Convert the path to Unix slashes and try to match aginast the fiilter.
268+
llvm::SmallString<64> Path(Inc.Resolved);
269+
llvm::sys::path::native(Path, llvm::sys::path::Style::posix);
270+
if (Filter(Inc.Resolved)) {
271+
dlog("{0} header is filtered out by the configuration", FE->getName());
272+
return false;
273+
}
274+
}
261275
return true;
262276
}
263277

@@ -369,6 +383,7 @@ getUnused(ParsedAST &AST,
369383
const llvm::DenseSet<IncludeStructure::HeaderID> &ReferencedFiles,
370384
const llvm::StringSet<> &ReferencedPublicHeaders) {
371385
trace::Span Tracer("IncludeCleaner::getUnused");
386+
const Config &Cfg = Config::current();
372387
std::vector<const Inclusion *> Unused;
373388
for (const Inclusion &MFI : AST.getIncludeStructure().MainFileIncludes) {
374389
if (!MFI.HeaderID)
@@ -377,7 +392,7 @@ getUnused(ParsedAST &AST,
377392
continue;
378393
auto IncludeID = static_cast<IncludeStructure::HeaderID>(*MFI.HeaderID);
379394
bool Used = ReferencedFiles.contains(IncludeID);
380-
if (!Used && !mayConsiderUnused(MFI, AST)) {
395+
if (!Used && !mayConsiderUnused(MFI, AST, Cfg)) {
381396
dlog("{0} was not used, but is not eligible to be diagnosed as unused",
382397
MFI.Written);
383398
continue;

clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp

+18
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,24 @@ TEST_F(ConfigCompileTests, DiagnosticsIncludeCleaner) {
263263
EXPECT_TRUE(compileAndApply());
264264
EXPECT_EQ(Conf.Diagnostics.UnusedIncludes,
265265
Config::UnusedIncludesPolicy::Strict);
266+
267+
Frag = {};
268+
EXPECT_TRUE(Conf.Diagnostics.Includes.IgnoreHeader.empty())
269+
<< Conf.Diagnostics.Includes.IgnoreHeader.size();
270+
Frag.Diagnostics.Includes.IgnoreHeader.push_back(
271+
Located<std::string>("foo.h"));
272+
Frag.Diagnostics.Includes.IgnoreHeader.push_back(
273+
Located<std::string>(".*inc"));
274+
EXPECT_TRUE(compileAndApply());
275+
auto HeaderFilter = [this](llvm::StringRef Path) {
276+
for (auto &Filter : Conf.Diagnostics.Includes.IgnoreHeader) {
277+
if (Filter(Path))
278+
return true;
279+
}
280+
return false;
281+
};
282+
EXPECT_TRUE(HeaderFilter("foo.h"));
283+
EXPECT_FALSE(HeaderFilter("bar.h"));
266284
}
267285

268286
TEST_F(ConfigCompileTests, DiagnosticSuppression) {

clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -1738,6 +1738,8 @@ TEST(DiagnosticsTest, IncludeCleaner) {
17381738
]]
17391739
#include "used.h"
17401740
1741+
#include "ignore.h"
1742+
17411743
#include <system_header.h>
17421744
17431745
void foo() {
@@ -1754,12 +1756,19 @@ TEST(DiagnosticsTest, IncludeCleaner) {
17541756
#pragma once
17551757
void used() {}
17561758
)cpp";
1759+
TU.AdditionalFiles["ignore.h"] = R"cpp(
1760+
#pragma once
1761+
void ignore() {}
1762+
)cpp";
17571763
TU.AdditionalFiles["system/system_header.h"] = "";
17581764
TU.ExtraArgs = {"-isystem" + testPath("system")};
17591765
// Off by default.
17601766
EXPECT_THAT(*TU.build().getDiagnostics(), IsEmpty());
17611767
Config Cfg;
17621768
Cfg.Diagnostics.UnusedIncludes = Config::UnusedIncludesPolicy::Strict;
1769+
// Set filtering.
1770+
Cfg.Diagnostics.Includes.IgnoreHeader.emplace_back(
1771+
[](llvm::StringRef Header) { return Header.endswith("ignore.h"); });
17631772
WithContextValue WithCfg(Config::Key, std::move(Cfg));
17641773
EXPECT_THAT(
17651774
*TU.build().getDiagnostics(),

0 commit comments

Comments
 (0)