58llvm::StringRef configRelative(llvm::StringRef
Path,
59 llvm::StringRef FragmentDir) {
60 if (FragmentDir.empty())
62 if (!
Path.consume_front(FragmentDir))
63 return llvm::StringRef();
67struct CompiledFragmentImpl {
73 std::vector<llvm::unique_function<bool(
const Params &)
const>> Conditions;
76 std::vector<llvm::unique_function<void(
const Params &,
Config &)
const>>
80 for (
const auto &C : Conditions) {
82 dlog(
"Config fragment {0}: condition not met",
this);
86 dlog(
"Config fragment {0}: applying {1} rules",
this, Apply.size());
87 for (
const auto &A : Apply)
94struct FragmentCompiler {
98 CompiledFragmentImpl &Out;
100 llvm::SourceMgr *SourceMgr;
102 std::string FragmentDirectory;
103 bool Trusted =
false;
105 std::optional<llvm::Regex>
107 llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags) {
108 std::string Anchored =
"^(" + *
Text +
")$";
109 llvm::Regex Result(Anchored, Flags);
110 std::string RegexError;
111 if (!Result.isValid(RegexError)) {
112 diag(
Error,
"Invalid regex " + Anchored +
": " + RegexError,
Text.Range);
115 return std::move(Result);
119 llvm::StringLiteral Description,
120 llvm::sys::path::Style Style) {
121 if (llvm::sys::path::is_absolute(*
Path))
123 if (FragmentDirectory.empty()) {
126 "{0} must be an absolute path, because this fragment is not "
127 "associated with any directory.",
133 llvm::SmallString<256> AbsPath = llvm::StringRef(*
Path);
134 llvm::sys::fs::make_absolute(FragmentDirectory, AbsPath);
135 llvm::sys::path::native(AbsPath, Style);
136 return AbsPath.str().str();
140 template <
typename T>
class EnumSwitch {
141 FragmentCompiler &Outer;
142 llvm::StringRef EnumName;
144 std::optional<T> Result;
145 llvm::SmallVector<llvm::StringLiteral> ValidValues;
149 FragmentCompiler &Outer)
150 : Outer(Outer), EnumName(EnumName), Input(In) {}
152 EnumSwitch &map(llvm::StringLiteral Name, T
Value) {
153 assert(!llvm::is_contained(ValidValues, Name) &&
"Duplicate value!");
154 ValidValues.push_back(Name);
155 if (!Result && *Input == Name)
160 std::optional<T> value() {
164 llvm::formatv(
"Invalid {0} value '{1}'. Valid values are {2}.",
165 EnumName, *Input, llvm::join(ValidValues,
", "))
179 template <
typename T>
180 EnumSwitch<T> compileEnum(llvm::StringRef EnumName,
182 return EnumSwitch<T>(EnumName, In, *
this);
186 Trusted = F.Source.Trusted;
187 if (!F.Source.Directory.empty()) {
188 FragmentDirectory = llvm::sys::path::convert_to_slash(F.Source.Directory);
189 if (FragmentDirectory.back() !=
'/')
190 FragmentDirectory +=
'/';
192 compile(std::move(F.If));
193 compile(std::move(F.CompileFlags));
194 compile(std::move(F.Index));
195 compile(std::move(F.Diagnostics));
196 compile(std::move(F.Completion));
197 compile(std::move(F.Hover));
198 compile(std::move(F.InlayHints));
199 compile(std::move(F.SemanticTokens));
200 compile(std::move(F.Style));
201 compile(std::move(F.Documentation));
205 if (F.HasUnrecognizedCondition)
206 Out.Conditions.push_back([&](
const Params &) {
return false; });
208#ifdef CLANGD_PATH_CASE_INSENSITIVE
209 llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase;
211 llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags;
214 auto PathMatch = std::make_unique<std::vector<llvm::Regex>>();
215 for (
auto &
Entry : F.PathMatch) {
216 if (
auto RE = compileRegex(
Entry, Flags))
217 PathMatch->push_back(std::move(*RE));
219 if (!PathMatch->empty()) {
220 Out.Conditions.push_back(
221 [PathMatch(std::move(PathMatch)),
222 FragmentDir(FragmentDirectory)](
const Params &P) {
225 llvm::StringRef
Path = configRelative(P.Path, FragmentDir);
229 return llvm::any_of(*PathMatch, [&](
const llvm::Regex &RE) {
230 return RE.match(
Path);
235 auto PathExclude = std::make_unique<std::vector<llvm::Regex>>();
236 for (
auto &
Entry : F.PathExclude) {
237 if (
auto RE = compileRegex(
Entry, Flags))
238 PathExclude->push_back(std::move(*RE));
240 if (!PathExclude->empty()) {
241 Out.Conditions.push_back(
242 [PathExclude(std::move(PathExclude)),
243 FragmentDir(FragmentDirectory)](
const Params &P) {
246 llvm::StringRef
Path = configRelative(P.Path, FragmentDir);
250 return llvm::none_of(*PathExclude, [&](
const llvm::Regex &RE) {
251 return RE.match(
Path);
260 [Compiler(std::move(**F.Compiler))](
const Params &,
Config &C) {
261 C.CompileFlags.Edits.push_back(
262 [Compiler](std::vector<std::string> &Args) {
264 Args.front() = Compiler;
268 if (!F.Remove.empty()) {
269 auto Remove = std::make_shared<ArgStripper>();
270 for (
auto &A : F.Remove)
272 Out.Apply.push_back([Remove(std::shared_ptr<const ArgStripper>(
274 C.CompileFlags.Edits.push_back(
275 [Remove](std::vector<std::string> &Args) {
276 Remove->process(Args);
281 if (!F.Add.empty()) {
282 std::vector<std::string> Add;
283 for (
auto &A : F.Add)
284 Add.push_back(std::move(*A));
285 Out.Apply.push_back([Add(std::move(Add))](
const Params &,
Config &C) {
286 C.CompileFlags.Edits.push_back([Add](std::vector<std::string> &Args) {
288 auto It = llvm::find(Args,
"--");
289 Args.insert(It, Add.begin(), Add.end());
294 if (F.BuiltinHeaders) {
296 compileEnum<Config::BuiltinHeaderPolicy>(
"BuiltinHeaders",
302 C.CompileFlags.BuiltinHeaders = *Val;
306 if (F.CompilationDatabase) {
307 std::optional<Config::CDBSearchSpec> Spec;
308 if (**F.CompilationDatabase ==
"Ancestors") {
311 }
else if (**F.CompilationDatabase ==
"None") {
316 makeAbsolute(*F.CompilationDatabase,
"CompilationDatabase",
317 llvm::sys::path::Style::native)) {
320 llvm::StringRef Rel = llvm::sys::path::relative_path(*
Path);
321 if (!Rel.empty() && llvm::sys::path::is_separator(Rel.back()))
326 Spec->FixedCDBPath = std::move(
Path);
332 C.CompileFlags.CDBSearch = Spec;
340 compileEnum<Config::BackgroundPolicy>(
"Background", *F.Background)
345 [Val](
const Params &,
Config &C) { C.Index.Background = *Val; });
348 compile(std::move(**F.External), F.External->Range);
349 if (F.StandardLibrary)
352 C.Index.StandardLibrary = Val;
357 llvm::SMRange BlockRange) {
358 if (External.Server && !Trusted) {
360 "Remote index may not be specified by untrusted configuration. "
361 "Copy this into user config to use it.",
362 External.Server->Range);
365#ifndef CLANGD_ENABLE_REMOTE
366 if (External.Server) {
367 elog(
"Clangd isn't compiled with remote index support, ignoring Server: "
370 External.Server.reset();
374 unsigned SourceCount = External.File.has_value() +
375 External.Server.has_value() + *External.IsNone;
376 if (SourceCount != 1) {
377 diag(
Error,
"Exactly one of File, Server or None must be set.",
382 if (External.Server) {
384 Spec.
Location = std::move(**External.Server);
385 }
else if (External.File) {
387 auto AbsPath = makeAbsolute(std::move(*External.File),
"File",
388 llvm::sys::path::Style::native);
391 Spec.
Location = std::move(*AbsPath);
393 assert(*External.IsNone);
398 if (!External.MountPoint)
399 External.MountPoint.emplace(FragmentDirectory);
400 if ((**External.MountPoint).empty()) {
401 diag(
Error,
"A mountpoint is required.", BlockRange);
404 auto AbsPath = makeAbsolute(std::move(*External.MountPoint),
"MountPoint",
405 llvm::sys::path::Style::posix);
410 Out.Apply.push_back([Spec(std::move(Spec))](
const Params &P,
Config &C) {
412 C.Index.External = Spec;
416 llvm::sys::path::Style::posix))
418 C.Index.External = Spec;
427 std::vector<std::string> Normalized;
428 for (
const auto &Suppressed : F.Suppress) {
429 if (*Suppressed ==
"*") {
431 C.Diagnostics.SuppressAll =
true;
432 C.Diagnostics.Suppress.clear();
438 if (!Normalized.empty())
440 [Normalized(std::move(Normalized))](
const Params &,
Config &C) {
441 if (C.Diagnostics.SuppressAll)
443 C.Diagnostics.Suppress.insert_range(Normalized);
446 if (F.UnusedIncludes) {
447 auto Val = compileEnum<Config::IncludesPolicy>(
"UnusedIncludes",
452 if (!Val && **F.UnusedIncludes ==
"Experiment") {
454 "Experiment is deprecated for UnusedIncludes, use Strict instead.",
455 F.UnusedIncludes->Range);
460 C.Diagnostics.UnusedIncludes = *Val;
465 if (F.MissingIncludes)
466 if (
auto Val = compileEnum<Config::IncludesPolicy>(
"MissingIncludes",
472 C.Diagnostics.MissingIncludes = *Val;
475 compile(std::move(F.Includes));
476 compile(std::move(F.ClangTidy));
480 if (!F.FullyQualifiedNamespaces.empty()) {
481 std::vector<std::string> FullyQualifiedNamespaces;
482 for (
auto &N : F.FullyQualifiedNamespaces) {
487 FullyQualifiedNamespaces.push_back(
Namespace.str());
489 Out.Apply.push_back([FullyQualifiedNamespaces(
490 std::move(FullyQualifiedNamespaces))](
492 C.Style.FullyQualifiedNamespaces.insert(
493 C.Style.FullyQualifiedNamespaces.begin(),
494 FullyQualifiedNamespaces.begin(), FullyQualifiedNamespaces.end());
497 auto QuotedFilter = compileHeaderRegexes(F.QuotedHeaders);
498 if (QuotedFilter.has_value()) {
500 [QuotedFilter = *QuotedFilter](
const Params &,
Config &C) {
501 C.Style.QuotedHeaders.emplace_back(QuotedFilter);
504 auto AngledFilter = compileHeaderRegexes(F.AngledHeaders);
505 if (AngledFilter.has_value()) {
507 [AngledFilter = *AngledFilter](
const Params &,
Config &C) {
508 C.Style.AngledHeaders.emplace_back(AngledFilter);
514 -> std::optional<std::function<bool(llvm::StringRef)>> {
516#ifdef CLANGD_PATH_CASE_INSENSITIVE
517 static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase;
519 static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags;
521 auto Filters = std::make_shared<std::vector<llvm::Regex>>();
522 for (
auto &HeaderPattern : HeaderPatterns) {
524 std::string AnchoredPattern =
"(" + *HeaderPattern +
")$";
525 llvm::Regex CompiledRegex(AnchoredPattern, Flags);
526 std::string RegexError;
527 if (!CompiledRegex.isValid(RegexError)) {
529 llvm::formatv(
"Invalid regular expression '{0}': {1}",
530 *HeaderPattern, RegexError)
532 HeaderPattern.Range);
535 Filters->push_back(std::move(CompiledRegex));
537 if (Filters->empty())
539 auto Filter = [Filters = std::move(Filters)](llvm::StringRef
Path) {
540 for (
auto &Regex : *Filters)
541 if (Regex.match(
Path))
548 void appendTidyCheckSpec(std::string &CurSpec,
550 StringRef Str = StringRef(*Arg).trim();
553 if (Str.starts_with(
"-") || Str.contains(
',')) {
554 diag(
Error,
"Invalid clang-tidy check name", Arg.
Range);
557 if (!Str.contains(
'*')) {
560 llvm::formatv(
"clang-tidy check '{0}' was not found", Str).str(),
565 if (!Fast.has_value()) {
568 "Latency of clang-tidy check '{0}' is not known. "
569 "It will only run if ClangTidy.FastCheckFilter is Loose or None",
576 "clang-tidy check '{0}' is slow. "
577 "It will only run if ClangTidy.FastCheckFilter is None",
591 for (
auto &CheckGlob : F.Add)
592 appendTidyCheckSpec(
Checks, CheckGlob,
true);
594 for (
auto &CheckGlob : F.Remove)
595 appendTidyCheckSpec(
Checks, CheckGlob,
false);
600 C.Diagnostics.ClangTidy.Checks.append(
602 C.Diagnostics.ClangTidy.Checks.empty() ? 1 : 0,
605 if (!F.CheckOptions.empty()) {
606 std::vector<std::pair<std::string, std::string>> CheckOptions;
607 for (
auto &Opt : F.CheckOptions)
608 CheckOptions.emplace_back(std::move(*Opt.first),
609 std::move(*Opt.second));
611 [CheckOptions = std::move(CheckOptions)](
const Params &,
Config &C) {
612 for (
auto &StringPair : CheckOptions)
613 C.Diagnostics.ClangTidy.CheckOptions.insert_or_assign(
614 StringPair.first, StringPair.second);
617 if (F.FastCheckFilter.has_value())
618 if (
auto Val = compileEnum<Config::FastCheckPolicy>(
"FastCheckFilter",
625 C.Diagnostics.ClangTidy.FastCheckFilter = *Val;
630#ifdef CLANGD_PATH_CASE_INSENSITIVE
631 static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase;
633 static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags;
635 std::shared_ptr<std::vector<llvm::Regex>> Filters;
636 if (!F.IgnoreHeader.empty()) {
637 Filters = std::make_shared<std::vector<llvm::Regex>>();
638 for (
auto &HeaderPattern : F.IgnoreHeader) {
640 std::string AnchoredPattern =
"(" + *HeaderPattern +
")$";
641 llvm::Regex CompiledRegex(AnchoredPattern, Flags);
642 std::string RegexError;
643 if (!CompiledRegex.isValid(RegexError)) {
645 llvm::formatv(
"Invalid regular expression '{0}': {1}",
646 *HeaderPattern, RegexError)
648 HeaderPattern.Range);
651 Filters->push_back(std::move(CompiledRegex));
657 std::optional<bool> AnalyzeAngledIncludes;
658 if (F.AnalyzeAngledIncludes.has_value())
659 AnalyzeAngledIncludes = **F.AnalyzeAngledIncludes;
660 if (!Filters && !AnalyzeAngledIncludes.has_value())
662 Out.Apply.push_back([Filters = std::move(Filters),
665 auto Filter = [Filters](llvm::StringRef
Path) {
666 for (
auto &Regex : *Filters)
667 if (Regex.match(
Path))
671 C.Diagnostics.Includes.IgnoreHeader.emplace_back(std::move(Filter));
673 if (AnalyzeAngledIncludes.has_value())
674 C.Diagnostics.Includes.AnalyzeAngledIncludes = *AnalyzeAngledIncludes;
682 C.Completion.AllScopes = AllScopes;
685 if (F.ArgumentLists) {
687 compileEnum<Config::ArgumentListsPolicy>(
"ArgumentLists",
690 .map(
"OpenDelimiter",
693 .map(
"FullPlaceholders",
697 C.Completion.ArgumentLists = *Val;
700 if (F.HeaderInsertion) {
702 compileEnum<Config::HeaderInsertionPolicy>(
"HeaderInsertion",
708 C.Completion.HeaderInsertion = *Val;
712 if (F.CodePatterns) {
713 if (
auto Val = compileEnum<Config::CodePatternsPolicy>(
"CodePatterns",
719 C.Completion.CodePatterns = *Val;
726 Out.Apply.push_back([ShowAKA(**F.ShowAKA)](
const Params &,
Config &C) {
727 C.Hover.ShowAKA = ShowAKA;
730 if (F.MacroContentsLimit) {
732 [Limit(**F.MacroContentsLimit)](
const Params &,
Config &C) {
733 C.Hover.MacroContentsLimit = Limit;
741 C.InlayHints.Enabled = Value;
743 if (F.ParameterNames)
746 C.InlayHints.Parameters = Value;
750 C.InlayHints.DeducedTypes = Value;
754 C.InlayHints.Designators = Value;
758 C.InlayHints.BlockEnd = Value;
760 if (F.DefaultArguments)
763 C.InlayHints.DefaultArguments = Value;
768 C.InlayHints.TypeNameLimit = Value;
773 if (!F.DisabledKinds.empty()) {
774 std::vector<std::string> DisabledKinds;
775 for (
auto &Kind : F.DisabledKinds)
776 DisabledKinds.push_back(std::move(*Kind));
779 [DisabledKinds(std::move(DisabledKinds))](
const Params &,
Config &C) {
780 for (
auto &Kind : DisabledKinds) {
781 auto It = llvm::find(C.SemanticTokens.DisabledKinds, Kind);
782 if (It == C.SemanticTokens.DisabledKinds.end())
783 C.SemanticTokens.DisabledKinds.push_back(std::move(Kind));
787 if (!F.DisabledModifiers.empty()) {
788 std::vector<std::string> DisabledModifiers;
789 for (
auto &Kind : F.DisabledModifiers)
790 DisabledModifiers.push_back(std::move(*Kind));
792 Out.Apply.push_back([DisabledModifiers(std::move(DisabledModifiers))](
794 for (
auto &Kind : DisabledModifiers) {
795 auto It = llvm::find(C.SemanticTokens.DisabledModifiers, Kind);
796 if (It == C.SemanticTokens.DisabledModifiers.end())
797 C.SemanticTokens.DisabledModifiers.push_back(std::move(Kind));
804 if (F.CommentFormat) {
806 compileEnum<Config::CommentFormatPolicy>(
"CommentFormat",
813 C.Documentation.CommentFormat = *Val;
818 constexpr static llvm::SourceMgr::DiagKind
Error = llvm::SourceMgr::DK_Error;
819 constexpr static llvm::SourceMgr::DiagKind
Warning =
820 llvm::SourceMgr::DK_Warning;
821 void diag(llvm::SourceMgr::DiagKind Kind, llvm::StringRef Message,
822 llvm::SMRange
Range) {
823 if (
Range.isValid() && SourceMgr !=
nullptr)
826 Diagnostic(llvm::SMDiagnostic(
"", Kind, Message));
834 std::pair<unsigned, unsigned> LineCol = {0, 0};
835 if (
auto *SM =
Source.Manager.get()) {
836 unsigned BufID = SM->getMainFileID();
837 LineCol = SM->getLineAndColumn(
Source.Location, BufID);
838 ConfigFile = SM->getBufferInfo(BufID).Buffer->getBufferIdentifier();
842 auto Result = std::make_shared<CompiledFragmentImpl>();
843 vlog(
"Config fragment: compiling {0}:{1} -> {2} (trusted={3})",
ConfigFile,
844 LineCol.first, Result.get(),
Source.Trusted);
846 FragmentCompiler{*Result, D,
Source.Manager.get()}.compile(std::move(*
this));
848 return [Result(std::move(Result))](
const Params &P,
Config &C) {
849 return (*Result)(P, C);