Skip to content

Commit 8a2b22e

Browse files
[clangd] Allow specifying what headers are always included via "" or <>
Projects can now add config fragments like this to their .clangd: ```yaml Style: QuotedHeaders: "src/.*" AngledHeaders: ["path/sdk/.*", "third-party/.*"] ``` to force headers inserted via the --header-insertion=iwyu mode matching at least one of the regexes to have <> (AngledHeaders) or "" (QuotedHeaders) around them, respectively. For other headers (and in conflicting cases where both styles have a matching regex), the current system header detection remains. Ref clangd/clangd#1247 Based on https://reviews.llvm.org/D145843 This solution does not affect other clang tools like clang-format.
1 parent 5b38ecf commit 8a2b22e

File tree

12 files changed

+193
-18
lines changed

12 files changed

+193
-18
lines changed

clang-tools-extra/clangd/CodeComplete.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "AST.h"
2222
#include "CodeCompletionStrings.h"
2323
#include "Compiler.h"
24+
#include "Config.h"
2425
#include "ExpectedTypes.h"
2526
#include "Feature.h"
2627
#include "FileDistance.h"
@@ -786,8 +787,8 @@ SpecifiedScope getQueryScopes(CodeCompletionContext &CCContext,
786787
llvm::StringRef SpelledSpecifier = Lexer::getSourceText(
787788
CharSourceRange::getCharRange(SemaSpecifier->getRange()),
788789
CCSema.SourceMgr, clang::LangOptions());
789-
if (SpelledSpecifier.consume_front("::"))
790-
Scopes.QueryScopes = {""};
790+
if (SpelledSpecifier.consume_front("::"))
791+
Scopes.QueryScopes = {""};
791792
Scopes.UnresolvedQualifier = std::string(SpelledSpecifier);
792793
// Sema excludes the trailing "::".
793794
if (!Scopes.UnresolvedQualifier->empty())
@@ -1580,7 +1581,7 @@ class CodeCompleteFlow {
15801581
CompletionPrefix HeuristicPrefix;
15811582
std::optional<FuzzyMatcher> Filter; // Initialized once Sema runs.
15821583
Range ReplacedRange;
1583-
std::vector<std::string> QueryScopes; // Initialized once Sema runs.
1584+
std::vector<std::string> QueryScopes; // Initialized once Sema runs.
15841585
std::vector<std::string> AccessibleScopes; // Initialized once Sema runs.
15851586
// Initialized once QueryScopes is initialized, if there are scopes.
15861587
std::optional<ScopeDistance> ScopeProximity;
@@ -1639,7 +1640,9 @@ class CodeCompleteFlow {
16391640
Inserter.emplace(
16401641
SemaCCInput.FileName, SemaCCInput.ParseInput.Contents, Style,
16411642
SemaCCInput.ParseInput.CompileCommand.Directory,
1642-
&Recorder->CCSema->getPreprocessor().getHeaderSearchInfo());
1643+
&Recorder->CCSema->getPreprocessor().getHeaderSearchInfo(),
1644+
Config::current().Style.QuotedHeaders,
1645+
Config::current().Style.AngledHeaders);
16431646
for (const auto &Inc : Includes.MainFileIncludes)
16441647
Inserter->addExisting(Inc);
16451648

@@ -1722,7 +1725,9 @@ class CodeCompleteFlow {
17221725
auto Style = getFormatStyleForFile(FileName, Content, TFS);
17231726
// This will only insert verbatim headers.
17241727
Inserter.emplace(FileName, Content, Style,
1725-
/*BuildDir=*/"", /*HeaderSearchInfo=*/nullptr);
1728+
/*BuildDir=*/"", /*HeaderSearchInfo=*/nullptr,
1729+
Config::current().Style.QuotedHeaders,
1730+
Config::current().Style.AngledHeaders);
17261731

17271732
auto Identifiers = collectIdentifiers(Content, Style);
17281733
std::vector<RawIdentifier> IdentifierResults;

clang-tools-extra/clangd/Config.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ struct Config {
123123
// declarations, always spell out the whole name (with or without leading
124124
// ::). All nested namespaces are affected as well.
125125
std::vector<std::string> FullyQualifiedNamespaces;
126+
127+
// List of regexes for inserting certain headers with <> or "".
128+
std::vector<std::function<bool(llvm::StringRef)>> QuotedHeaders;
129+
std::vector<std::function<bool(llvm::StringRef)>> AngledHeaders;
126130
} Style;
127131

128132
/// Configures code completion feature.

clang-tools-extra/clangd/ConfigCompile.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,55 @@ struct FragmentCompiler {
483483
FullyQualifiedNamespaces.begin(), FullyQualifiedNamespaces.end());
484484
});
485485
}
486+
auto QuotedFilter = compileHeaderRegexes(F.QuotedHeaders);
487+
if (QuotedFilter.has_value()) {
488+
Out.Apply.push_back(
489+
[QuotedFilter = *QuotedFilter](const Params &, Config &C) {
490+
C.Style.QuotedHeaders.emplace_back(QuotedFilter);
491+
});
492+
}
493+
auto AngledFilter = compileHeaderRegexes(F.AngledHeaders);
494+
if (AngledFilter.has_value()) {
495+
Out.Apply.push_back(
496+
[AngledFilter = *AngledFilter](const Params &, Config &C) {
497+
C.Style.AngledHeaders.emplace_back(AngledFilter);
498+
});
499+
}
500+
}
501+
502+
auto compileHeaderRegexes(llvm::ArrayRef<Located<std::string>> HeaderPatterns)
503+
-> std::optional<std::function<bool(llvm::StringRef)>> {
504+
// TODO: Share this code with Diagnostics.Includes.IgnoreHeader
505+
#ifdef CLANGD_PATH_CASE_INSENSITIVE
506+
static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase;
507+
#else
508+
static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags;
509+
#endif
510+
auto Filters = std::make_shared<std::vector<llvm::Regex>>();
511+
for (auto &HeaderPattern : HeaderPatterns) {
512+
// Anchor on the right.
513+
std::string AnchoredPattern = "(" + *HeaderPattern + ")$";
514+
llvm::Regex CompiledRegex(AnchoredPattern, Flags);
515+
std::string RegexError;
516+
if (!CompiledRegex.isValid(RegexError)) {
517+
diag(Warning,
518+
llvm::formatv("Invalid regular expression '{0}': {1}",
519+
*HeaderPattern, RegexError)
520+
.str(),
521+
HeaderPattern.Range);
522+
continue;
523+
}
524+
Filters->push_back(std::move(CompiledRegex));
525+
}
526+
if (Filters->empty())
527+
return std::nullopt;
528+
auto Filter = [Filters](llvm::StringRef Path) {
529+
for (auto &Regex : *Filters)
530+
if (Regex.match(Path))
531+
return true;
532+
return false;
533+
};
534+
return Filter;
486535
}
487536

488537
void appendTidyCheckSpec(std::string &CurSpec,

clang-tools-extra/clangd/ConfigFragment.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,19 @@ struct Fragment {
296296
// ::). All nested namespaces are affected as well.
297297
// Affects availability of the AddUsing tweak.
298298
std::vector<Located<std::string>> FullyQualifiedNamespaces;
299+
300+
/// List of regexes for headers that should always be included with a
301+
/// ""-style include. By default, and in case of a conflict with
302+
/// AngledHeaders (i.e. a header matches a regex in both QuotedHeaders and
303+
/// AngledHeaders), system headers use <> and non-system headers use "".
304+
/// These can match any suffix of the header file in question.
305+
std::vector<Located<std::string>> QuotedHeaders;
306+
/// List of regexes for headers that should always be included with a
307+
/// <>-style include. By default, and in case of a conflict with
308+
/// AngledHeaders (i.e. a header matches a regex in both QuotedHeaders and
309+
/// AngledHeaders), system headers use <> and non-system headers use "".
310+
/// These can match any suffix of the header file in question.
311+
std::vector<Located<std::string>> AngledHeaders;
299312
};
300313
StyleBlock Style;
301314

clang-tools-extra/clangd/ConfigYAML.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ class Parser {
117117
if (auto Values = scalarValues(N))
118118
F.FullyQualifiedNamespaces = std::move(*Values);
119119
});
120+
Dict.handle("QuotedHeaders", [&](Node &N) {
121+
if (auto Values = scalarValues(N))
122+
F.QuotedHeaders = std::move(*Values);
123+
});
124+
Dict.handle("AngledHeaders", [&](Node &N) {
125+
if (auto Values = scalarValues(N))
126+
F.AngledHeaders = std::move(*Values);
127+
});
120128
Dict.parse(N);
121129
}
122130

clang-tools-extra/clangd/Headers.cpp

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "Headers.h"
1010
#include "Preamble.h"
1111
#include "SourceCode.h"
12+
#include "support/Logger.h"
1213
#include "clang/Basic/SourceLocation.h"
1314
#include "clang/Basic/SourceManager.h"
1415
#include "clang/Frontend/CompilerInstance.h"
@@ -30,8 +31,7 @@ namespace clangd {
3031
class IncludeStructure::RecordHeaders : public PPCallbacks {
3132
public:
3233
RecordHeaders(const CompilerInstance &CI, IncludeStructure *Out)
33-
: SM(CI.getSourceManager()),
34-
Out(Out) {}
34+
: SM(CI.getSourceManager()), Out(Out) {}
3535

3636
// Record existing #includes - both written and resolved paths. Only #includes
3737
// in the main file are collected.
@@ -286,11 +286,11 @@ IncludeInserter::calculateIncludePath(const HeaderFile &InsertedHeader,
286286
assert(InsertedHeader.valid());
287287
if (InsertedHeader.Verbatim)
288288
return InsertedHeader.File;
289-
bool IsAngled = false;
289+
bool IsAngledByDefault = false;
290290
std::string Suggested;
291291
if (HeaderSearchInfo) {
292292
Suggested = HeaderSearchInfo->suggestPathToFileForDiagnostics(
293-
InsertedHeader.File, BuildDir, IncludingFile, &IsAngled);
293+
InsertedHeader.File, BuildDir, IncludingFile, &IsAngledByDefault);
294294
} else {
295295
// Calculate include relative to including file only.
296296
StringRef IncludingDir = llvm::sys::path::parent_path(IncludingFile);
@@ -303,9 +303,33 @@ IncludeInserter::calculateIncludePath(const HeaderFile &InsertedHeader,
303303
// FIXME: should we allow (some limited number of) "../header.h"?
304304
if (llvm::sys::path::is_absolute(Suggested))
305305
return std::nullopt;
306+
bool IsAngled = false;
307+
for (auto Filter : AngledHeaders) {
308+
if (Filter(Suggested)) {
309+
IsAngled = true;
310+
break;
311+
}
312+
}
313+
bool IsQuoted = false;
314+
for (auto Filter : QuotedHeaders) {
315+
if (Filter(Suggested)) {
316+
IsQuoted = true;
317+
break;
318+
}
319+
}
320+
// No filters apply, or both filters apply (a bug), use system default.
321+
if (IsAngled == IsQuoted) {
322+
// Probably a bug in the config regex.
323+
if (IsAngled && IsQuoted) {
324+
elog("Header '{0}' matches both quoted and angled regexes, default will "
325+
"be used.",
326+
Suggested);
327+
}
328+
IsAngled = IsAngledByDefault;
329+
}
306330
if (IsAngled)
307331
Suggested = "<" + Suggested + ">";
308-
else
332+
else // if (IsQuoted)
309333
Suggested = "\"" + Suggested + "\"";
310334
return Suggested;
311335
}

clang-tools-extra/clangd/Headers.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H
1010
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H
1111

12+
#include "Config.h"
1213
#include "Protocol.h"
1314
#include "SourceCode.h"
1415
#include "index/Symbol.h"
@@ -33,6 +34,8 @@
3334
namespace clang {
3435
namespace clangd {
3536

37+
using HeaderFilter = llvm::ArrayRef<std::function<bool(llvm::StringRef)>>;
38+
3639
/// Returns true if \p Include is literal include like "path" or <path>.
3740
bool isLiteralInclude(llvm::StringRef Include);
3841

@@ -211,10 +214,12 @@ class IncludeInserter {
211214
// include path of non-verbatim header will not be shortened.
212215
IncludeInserter(StringRef FileName, StringRef Code,
213216
const format::FormatStyle &Style, StringRef BuildDir,
214-
HeaderSearch *HeaderSearchInfo)
217+
HeaderSearch *HeaderSearchInfo, HeaderFilter QuotedHeaders,
218+
HeaderFilter AngledHeaders)
215219
: FileName(FileName), Code(Code), BuildDir(BuildDir),
216220
HeaderSearchInfo(HeaderSearchInfo),
217-
Inserter(FileName, Code, Style.IncludeStyle) {}
221+
Inserter(FileName, Code, Style.IncludeStyle),
222+
QuotedHeaders(QuotedHeaders), AngledHeaders(AngledHeaders) {}
218223

219224
void addExisting(const Inclusion &Inc);
220225

@@ -258,6 +263,8 @@ class IncludeInserter {
258263
HeaderSearch *HeaderSearchInfo = nullptr;
259264
llvm::StringSet<> IncludedHeaders; // Both written and resolved.
260265
tooling::HeaderIncludes Inserter; // Computers insertion replacement.
266+
HeaderFilter QuotedHeaders;
267+
HeaderFilter AngledHeaders;
261268
};
262269

263270
} // namespace clangd

clang-tools-extra/clangd/IncludeCleaner.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ struct IncludeCleanerFindings {
5555

5656
IncludeCleanerFindings computeIncludeCleanerFindings(ParsedAST &AST);
5757

58-
using HeaderFilter = llvm::ArrayRef<std::function<bool(llvm::StringRef)>>;
5958
std::vector<Diag>
6059
issueIncludeCleanerDiagnostics(ParsedAST &AST, llvm::StringRef Code,
6160
const IncludeCleanerFindings &Findings,

clang-tools-extra/clangd/ParsedAST.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,8 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs,
628628
getFormatStyleForFile(Filename, Inputs.Contents, *Inputs.TFS);
629629
auto Inserter = std::make_shared<IncludeInserter>(
630630
Filename, Inputs.Contents, Style, BuildDir.get(),
631-
&Clang->getPreprocessor().getHeaderSearchInfo());
631+
&Clang->getPreprocessor().getHeaderSearchInfo(),
632+
Cfg.Style.QuotedHeaders, Cfg.Style.AngledHeaders);
632633
ArrayRef<Inclusion> MainFileIncludes;
633634
if (Preamble) {
634635
MainFileIncludes = Preamble->Includes.MainFileIncludes;

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,42 @@ TEST_F(ConfigCompileTests, Style) {
539539
Frag.Style.FullyQualifiedNamespaces.push_back(std::string("bar"));
540540
EXPECT_TRUE(compileAndApply());
541541
EXPECT_THAT(Conf.Style.FullyQualifiedNamespaces, ElementsAre("foo", "bar"));
542+
543+
{
544+
Frag = {};
545+
EXPECT_TRUE(Conf.Style.QuotedHeaders.empty())
546+
<< Conf.Style.QuotedHeaders.size();
547+
Frag.Style.QuotedHeaders.push_back(Located<std::string>("foo.h"));
548+
Frag.Style.QuotedHeaders.push_back(Located<std::string>(".*inc"));
549+
EXPECT_TRUE(compileAndApply());
550+
auto HeaderFilter = [this](llvm::StringRef Path) {
551+
for (auto &Filter : Conf.Style.QuotedHeaders) {
552+
if (Filter(Path))
553+
return true;
554+
}
555+
return false;
556+
};
557+
EXPECT_TRUE(HeaderFilter("foo.h"));
558+
EXPECT_FALSE(HeaderFilter("bar.h"));
559+
}
560+
561+
{
562+
Frag = {};
563+
EXPECT_TRUE(Conf.Style.AngledHeaders.empty())
564+
<< Conf.Style.AngledHeaders.size();
565+
Frag.Style.AngledHeaders.push_back(Located<std::string>("foo.h"));
566+
Frag.Style.AngledHeaders.push_back(Located<std::string>(".*inc"));
567+
EXPECT_TRUE(compileAndApply());
568+
auto HeaderFilter = [this](llvm::StringRef Path) {
569+
for (auto &Filter : Conf.Style.AngledHeaders) {
570+
if (Filter(Path))
571+
return true;
572+
}
573+
return false;
574+
};
575+
EXPECT_TRUE(HeaderFilter("foo.h"));
576+
EXPECT_FALSE(HeaderFilter("bar.h"));
577+
}
542578
}
543579
} // namespace
544580
} // namespace config

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,13 +282,19 @@ TEST(ParseYAML, Style) {
282282
CapturedDiags Diags;
283283
Annotations YAML(R"yaml(
284284
Style:
285-
FullyQualifiedNamespaces: [foo, bar])yaml");
285+
FullyQualifiedNamespaces: [foo, bar]
286+
AngledHeaders: ["foo", "bar"]
287+
QuotedHeaders: ["baz", "baar"])yaml");
286288
auto Results =
287289
Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
288290
ASSERT_THAT(Diags.Diagnostics, IsEmpty());
289291
ASSERT_EQ(Results.size(), 1u);
290292
EXPECT_THAT(Results[0].Style.FullyQualifiedNamespaces,
291293
ElementsAre(val("foo"), val("bar")));
294+
EXPECT_THAT(Results[0].Style.AngledHeaders,
295+
ElementsAre(val("foo"), val("bar")));
296+
EXPECT_THAT(Results[0].Style.QuotedHeaders,
297+
ElementsAre(val("baz"), val("baar")));
292298
}
293299
} // namespace
294300
} // namespace config

0 commit comments

Comments
 (0)