Skip to content

Commit 67971ca

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 66c710e commit 67971ca

13 files changed

+244
-28
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 matcher functions 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: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,23 @@ 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+
/// Matching is performed against the header text, not its absolute path
306+
/// within the project.
307+
std::vector<Located<std::string>> QuotedHeaders;
308+
/// List of regexes for headers that should always be included with a
309+
/// <>-style include. By default, and in case of a conflict with
310+
/// AngledHeaders (i.e. a header matches a regex in both QuotedHeaders and
311+
/// AngledHeaders), system headers use <> and non-system headers use "".
312+
/// These can match any suffix of the header file in question.
313+
/// Matching is performed against the header text, not its absolute path
314+
/// within the project.
315+
std::vector<Located<std::string>> AngledHeaders;
299316
};
300317
StyleBlock Style;
301318

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: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
namespace clang {
3434
namespace clangd {
3535

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

@@ -211,10 +213,12 @@ class IncludeInserter {
211213
// include path of non-verbatim header will not be shortened.
212214
IncludeInserter(StringRef FileName, StringRef Code,
213215
const format::FormatStyle &Style, StringRef BuildDir,
214-
HeaderSearch *HeaderSearchInfo)
216+
HeaderSearch *HeaderSearchInfo, HeaderFilter QuotedHeaders,
217+
HeaderFilter AngledHeaders)
215218
: FileName(FileName), Code(Code), BuildDir(BuildDir),
216219
HeaderSearchInfo(HeaderSearchInfo),
217-
Inserter(FileName, Code, Style.IncludeStyle) {}
220+
Inserter(FileName, Code, Style.IncludeStyle),
221+
QuotedHeaders(QuotedHeaders), AngledHeaders(AngledHeaders) {}
218222

219223
void addExisting(const Inclusion &Inc);
220224

@@ -258,6 +262,8 @@ class IncludeInserter {
258262
HeaderSearch *HeaderSearchInfo = nullptr;
259263
llvm::StringSet<> IncludedHeaders; // Both written and resolved.
260264
tooling::HeaderIncludes Inserter; // Computers insertion replacement.
265+
HeaderFilter QuotedHeaders;
266+
HeaderFilter AngledHeaders;
261267
};
262268

263269
} // 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/CodeCompleteTests.cpp

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "ClangdServer.h"
1212
#include "CodeComplete.h"
1313
#include "Compiler.h"
14+
#include "Config.h"
1415
#include "Feature.h"
1516
#include "Matchers.h"
1617
#include "Protocol.h"
@@ -916,6 +917,41 @@ TEST(CompletionTest, NoIncludeInsertionWhenDeclFoundInFile) {
916917
AllOf(named("Y"), Not(insertInclude()))));
917918
}
918919

920+
TEST(CompletionTest, IncludeInsertionRespectsQuotedAngledConfig) {
921+
TestTU TU;
922+
TU.ExtraArgs.push_back("-I" + testPath("sub"));
923+
TU.AdditionalFiles["sub/bar.h"] = "";
924+
auto BarURI = URI::create(testPath("sub/bar.h")).toString();
925+
926+
Symbol Sym = cls("ns::X");
927+
Sym.CanonicalDeclaration.FileURI = BarURI.c_str();
928+
Sym.IncludeHeaders.emplace_back(BarURI, 1, Symbol::Include);
929+
Annotations Test("int main() { ns::^ }");
930+
TU.Code = Test.code().str();
931+
auto Results = completions(TU, Test.point(), {Sym});
932+
// Default for a local path is quoted include
933+
EXPECT_THAT(Results.Completions,
934+
ElementsAre(AllOf(named("X"), insertInclude("\"bar.h\""))));
935+
{
936+
Config C;
937+
C.Style.AngledHeaders.push_back(
938+
[](auto header) { return header == "bar.h"; });
939+
WithContextValue WithCfg(Config::Key, std::move(C));
940+
Results = completions(TU, Test.point(), {Sym});
941+
EXPECT_THAT(Results.Completions,
942+
ElementsAre(AllOf(named("X"), insertInclude("<bar.h>"))));
943+
}
944+
{
945+
Config C;
946+
C.Style.QuotedHeaders.push_back(
947+
[](auto header) { return header == "bar.h"; });
948+
WithContextValue WithCfg(Config::Key, std::move(C));
949+
Results = completions(TU, Test.point(), {Sym});
950+
EXPECT_THAT(Results.Completions,
951+
ElementsAre(AllOf(named("X"), insertInclude("\"bar.h\""))));
952+
}
953+
}
954+
919955
TEST(CompletionTest, IndexSuppressesPreambleCompletions) {
920956
Annotations Test(R"cpp(
921957
#include "bar.h"
@@ -1134,8 +1170,8 @@ TEST(CodeCompleteTest, NoColonColonAtTheEnd) {
11341170
}
11351171

11361172
TEST(CompletionTests, EmptySnippetDoesNotCrash) {
1137-
// See https://github.com/clangd/clangd/issues/1216
1138-
auto Results = completions(R"cpp(
1173+
// See https://github.com/clangd/clangd/issues/1216
1174+
auto Results = completions(R"cpp(
11391175
int main() {
11401176
auto w = [&](auto &&f) { return f(f); };
11411177
auto f = w([&](auto &&f) {
@@ -1151,18 +1187,18 @@ TEST(CompletionTests, EmptySnippetDoesNotCrash) {
11511187
}
11521188

11531189
TEST(CompletionTest, Issue1427Crash) {
1154-
// Need to provide main file signals to ensure that the branch in
1155-
// SymbolRelevanceSignals::computeASTSignals() that tries to
1156-
// compute a symbol ID is taken.
1157-
ASTSignals MainFileSignals;
1158-
CodeCompleteOptions Opts;
1159-
Opts.MainFileSignals = &MainFileSignals;
1160-
completions(R"cpp(
1190+
// Need to provide main file signals to ensure that the branch in
1191+
// SymbolRelevanceSignals::computeASTSignals() that tries to
1192+
// compute a symbol ID is taken.
1193+
ASTSignals MainFileSignals;
1194+
CodeCompleteOptions Opts;
1195+
Opts.MainFileSignals = &MainFileSignals;
1196+
completions(R"cpp(
11611197
auto f = []() {
11621198
1.0_^
11631199
};
11641200
)cpp",
1165-
{}, Opts);
1201+
{}, Opts);
11661202
}
11671203

11681204
TEST(CompletionTest, BacktrackCrashes) {

0 commit comments

Comments
 (0)