36 #include "llvm/ADT/STLExtras.h"
37 #include "llvm/ADT/SmallString.h"
38 #include "llvm/ADT/StringRef.h"
39 #include "llvm/Support/FileSystem.h"
40 #include "llvm/Support/FormatVariadic.h"
41 #include "llvm/Support/Path.h"
42 #include "llvm/Support/Regex.h"
43 #include "llvm/Support/SMLoc.h"
44 #include "llvm/Support/SourceMgr.h"
58 llvm::StringRef configRelative(llvm::StringRef
Path,
59 llvm::StringRef FragmentDir) {
60 if (FragmentDir.empty())
62 if (!
Path.consume_front(FragmentDir))
63 return llvm::StringRef();
67 struct CompiledFragmentImpl {
73 std::vector<llvm::unique_function<bool(
const Params &)
const>>
Conditions;
76 std::vector<llvm::unique_function<void(
const Params &,
Config &)
const>>
79 bool operator()(
const Params &P,
Config &
C)
const {
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)
94 struct FragmentCompiler {
98 CompiledFragmentImpl &
Out;
105 std::optional<llvm::Regex>
106 compileRegex(
const Located<std::string> &Text,
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);
118 std::optional<std::string> makeAbsolute(Located<std::string>
Path,
120 llvm::sys::path::Style Style) {
121 if (llvm::sys::path::is_absolute(*
Path))
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);
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;
143 const Located<std::string> &Input;
144 std::optional<T> Result;
145 llvm::SmallVector<llvm::StringLiteral> ValidValues;
148 EnumSwitch(llvm::StringRef EnumName,
const Located<std::string> &In,
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,
181 const Located<std::string> &In) {
182 return EnumSwitch<T>(EnumName, In, *
this);
185 void compile(Fragment &&F) {
187 if (!F.Source.Directory.empty()) {
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.Style));
202 void compile(Fragment::IfBlock &&F) {
203 if (F.HasUnrecognizedCondition)
204 Out.Conditions.push_back([&](
const Params &) {
return false; });
206 #ifdef CLANGD_PATH_CASE_INSENSITIVE
207 llvm::Regex::RegexFlags
Flags = llvm::Regex::IgnoreCase;
209 llvm::Regex::RegexFlags
Flags = llvm::Regex::NoFlags;
212 auto PathMatch = std::make_unique<std::vector<llvm::Regex>>();
213 for (
auto &
Entry : F.PathMatch) {
215 PathMatch->push_back(std::move(*RE));
217 if (!PathMatch->empty()) {
218 Out.Conditions.push_back(
219 [PathMatch(std::move(PathMatch)),
223 llvm::StringRef
Path = configRelative(P.Path, FragmentDir);
227 return llvm::any_of(*PathMatch, [&](
const llvm::Regex &RE) {
228 return RE.match(
Path);
233 auto PathExclude = std::make_unique<std::vector<llvm::Regex>>();
234 for (
auto &
Entry : F.PathExclude) {
236 PathExclude->push_back(std::move(*RE));
238 if (!PathExclude->empty()) {
239 Out.Conditions.push_back(
240 [PathExclude(std::move(PathExclude)),
244 llvm::StringRef
Path = configRelative(P.Path, FragmentDir);
248 return llvm::none_of(*PathExclude, [&](
const llvm::Regex &RE) {
249 return RE.match(
Path);
255 void compile(Fragment::CompileFlagsBlock &&F) {
258 [Compiler(std::move(**F.Compiler))](
const Params &,
Config &
C) {
259 C.CompileFlags.Edits.push_back(
260 [Compiler](std::vector<std::string> &Args) {
262 Args.front() = Compiler;
266 if (!F.Remove.empty()) {
267 auto Remove = std::make_shared<ArgStripper>();
268 for (
auto &
A : F.Remove)
270 Out.Apply.push_back([Remove(std::shared_ptr<const ArgStripper>(
271 std::move(Remove)))](
const Params &,
Config &
C) {
272 C.CompileFlags.Edits.push_back(
273 [Remove](std::vector<std::string> &
Args) {
274 Remove->process(
Args);
279 if (!F.Add.empty()) {
280 std::vector<std::string> Add;
281 for (
auto &
A : F.Add)
282 Add.push_back(std::move(*
A));
283 Out.Apply.push_back([Add(std::move(Add))](
const Params &,
Config &
C) {
284 C.CompileFlags.Edits.push_back([Add](std::vector<std::string> &
Args) {
286 auto It = llvm::find(
Args,
"--");
287 Args.insert(It, Add.begin(), Add.end());
292 if (F.CompilationDatabase) {
293 std::optional<Config::CDBSearchSpec> Spec;
294 if (**F.CompilationDatabase ==
"Ancestors") {
297 }
else if (**F.CompilationDatabase ==
"None") {
302 makeAbsolute(*F.CompilationDatabase,
"CompilationDatabase",
303 llvm::sys::path::Style::native)) {
306 llvm::StringRef Rel = llvm::sys::path::relative_path(*
Path);
307 if (!Rel.empty() && llvm::sys::path::is_separator(Rel.back()))
312 Spec->FixedCDBPath = std::move(
Path);
317 [Spec(std::move(*Spec))](
const Params &,
Config &
C) {
318 C.CompileFlags.CDBSearch = Spec;
323 void compile(Fragment::IndexBlock &&F) {
325 if (
auto Val = compileEnum<Config::BackgroundPolicy>(
"Background",
331 [Val](
const Params &,
Config &
C) {
C.Index.Background = *Val; });
334 compile(std::move(**F.External), F.External->Range);
335 if (F.StandardLibrary)
337 [Val(**F.StandardLibrary)](
const Params &,
Config &
C) {
338 C.Index.StandardLibrary = Val;
342 void compile(Fragment::IndexBlock::ExternalBlock &&External,
343 llvm::SMRange BlockRange) {
344 if (External.Server && !
Trusted) {
346 "Remote index may not be specified by untrusted configuration. "
347 "Copy this into user config to use it.",
348 External.Server->Range);
351 #ifndef CLANGD_ENABLE_REMOTE
352 if (External.Server) {
353 elog(
"Clangd isn't compiled with remote index support, ignoring Server: "
356 External.Server.reset();
360 unsigned SourceCount = External.File.has_value() +
361 External.Server.has_value() + *External.IsNone;
362 if (SourceCount != 1) {
363 diag(Error,
"Exactly one of File, Server or None must be set.",
367 Config::ExternalIndexSpec Spec;
368 if (External.Server) {
370 Spec.Location = std::move(**External.Server);
371 }
else if (External.File) {
373 auto AbsPath = makeAbsolute(std::move(*External.File),
"File",
374 llvm::sys::path::Style::native);
377 Spec.Location = std::move(*AbsPath);
379 assert(*External.IsNone);
384 if (!External.MountPoint)
386 if ((**External.MountPoint).empty()) {
387 diag(Error,
"A mountpoint is required.", BlockRange);
390 auto AbsPath = makeAbsolute(std::move(*External.MountPoint),
"MountPoint",
391 llvm::sys::path::Style::posix);
394 Spec.MountPoint = std::move(*AbsPath);
396 Out.Apply.push_back([Spec(std::move(Spec))](
const Params &P,
Config &
C) {
398 C.Index.External = Spec;
402 llvm::sys::path::Style::posix))
404 C.Index.External = Spec;
412 void compile(Fragment::DiagnosticsBlock &&F) {
413 std::vector<std::string> Normalized;
414 for (
const auto &Suppressed : F.Suppress) {
415 if (*Suppressed ==
"*") {
416 Out.Apply.push_back([&](
const Params &,
Config &
C) {
417 C.Diagnostics.SuppressAll =
true;
418 C.Diagnostics.Suppress.clear();
424 if (!Normalized.empty())
426 [Normalized(std::move(Normalized))](
const Params &,
Config &
C) {
427 if (
C.Diagnostics.SuppressAll)
429 for (llvm::StringRef N : Normalized)
430 C.Diagnostics.Suppress.insert(N);
433 if (F.UnusedIncludes)
435 compileEnum<Config::UnusedIncludesPolicy>(
"UnusedIncludes",
437 .map(
"Strict", Config::UnusedIncludesPolicy::Strict)
438 .map(
"Experiment", Config::UnusedIncludesPolicy::Experiment)
439 .map(
"None", Config::UnusedIncludesPolicy::None)
441 Out.Apply.push_back([Val](
const Params &,
Config &
C) {
442 C.Diagnostics.UnusedIncludes = *Val;
444 compile(std::move(F.Includes));
446 compile(std::move(F.ClangTidy));
449 void compile(Fragment::StyleBlock &&F) {
450 if (!F.FullyQualifiedNamespaces.empty()) {
451 std::vector<std::string> FullyQualifiedNamespaces;
452 for (
auto &N : F.FullyQualifiedNamespaces) {
457 FullyQualifiedNamespaces.push_back(
Namespace.str());
459 Out.Apply.push_back([FullyQualifiedNamespaces(
460 std::move(FullyQualifiedNamespaces))](
462 C.Style.FullyQualifiedNamespaces.insert(
463 C.Style.FullyQualifiedNamespaces.begin(),
464 FullyQualifiedNamespaces.begin(), FullyQualifiedNamespaces.end());
469 void appendTidyCheckSpec(std::string &CurSpec,
470 const Located<std::string> &Arg,
bool IsPositive) {
471 StringRef Str = StringRef(*Arg).trim();
474 if (Str.startswith(
"-") || Str.contains(
',')) {
475 diag(Error,
"Invalid clang-tidy check name", Arg.Range);
480 llvm::formatv(
"clang-tidy check '{0}' was not found", Str).str(),
490 void compile(Fragment::DiagnosticsBlock::ClangTidyBlock &&F) {
492 for (
auto &CheckGlob : F.Add)
493 appendTidyCheckSpec(
Checks, CheckGlob,
true);
495 for (
auto &CheckGlob : F.Remove)
496 appendTidyCheckSpec(
Checks, CheckGlob,
false);
501 C.Diagnostics.ClangTidy.Checks.append(
503 C.Diagnostics.ClangTidy.Checks.empty() ? 1 : 0,
506 if (!F.CheckOptions.empty()) {
507 std::vector<std::pair<std::string, std::string>> CheckOptions;
508 for (
auto &Opt : F.CheckOptions)
509 CheckOptions.emplace_back(std::move(*Opt.first),
510 std::move(*Opt.second));
512 [CheckOptions = std::move(CheckOptions)](
const Params &,
Config &
C) {
513 for (
auto &StringPair : CheckOptions)
514 C.Diagnostics.ClangTidy.CheckOptions.insert_or_assign(
515 StringPair.first, StringPair.second);
520 void compile(Fragment::DiagnosticsBlock::IncludesBlock &&F) {
521 #ifdef CLANGD_PATH_CASE_INSENSITIVE
522 static llvm::Regex::RegexFlags
Flags = llvm::Regex::IgnoreCase;
524 static llvm::Regex::RegexFlags
Flags = llvm::Regex::NoFlags;
526 auto Filters = std::make_shared<std::vector<llvm::Regex>>();
527 for (
auto &HeaderPattern : F.IgnoreHeader) {
529 std::string AnchoredPattern =
"(" + *HeaderPattern +
")$";
530 llvm::Regex CompiledRegex(AnchoredPattern,
Flags);
531 std::string RegexError;
532 if (!CompiledRegex.isValid(RegexError)) {
534 llvm::formatv(
"Invalid regular expression '{0}': {1}",
535 *HeaderPattern, RegexError)
537 HeaderPattern.Range);
540 Filters->push_back(std::move(CompiledRegex));
542 if (Filters->empty())
544 auto Filter = [Filters](llvm::StringRef
Path) {
545 for (
auto &Regex : *Filters)
546 if (Regex.match(
Path))
550 Out.Apply.push_back([Filter](
const Params &,
Config &
C) {
551 C.Diagnostics.Includes.IgnoreHeader.emplace_back(Filter);
555 void compile(Fragment::CompletionBlock &&F) {
558 [AllScopes(**F.AllScopes)](
const Params &,
Config &
C) {
559 C.Completion.AllScopes = AllScopes;
564 void compile(Fragment::HoverBlock &&F) {
566 Out.Apply.push_back([ShowAKA(**F.ShowAKA)](
const Params &,
Config &
C) {
567 C.Hover.ShowAKA = ShowAKA;
572 void compile(Fragment::InlayHintsBlock &&F) {
574 Out.Apply.push_back([Value(**F.Enabled)](
const Params &,
Config &
C) {
575 C.InlayHints.Enabled = Value;
577 if (F.ParameterNames)
579 [Value(**F.ParameterNames)](
const Params &,
Config &
C) {
580 C.InlayHints.Parameters = Value;
583 Out.Apply.push_back([Value(**F.DeducedTypes)](
const Params &,
Config &
C) {
584 C.InlayHints.DeducedTypes = Value;
587 Out.Apply.push_back([Value(**F.Designators)](
const Params &,
Config &
C) {
588 C.InlayHints.Designators = Value;
592 constexpr
static llvm::SourceMgr::DiagKind
Error = llvm::SourceMgr::DK_Error;
593 constexpr
static llvm::SourceMgr::DiagKind
Warning =
594 llvm::SourceMgr::DK_Warning;
595 void diag(llvm::SourceMgr::DiagKind
Kind, llvm::StringRef
Message,
596 llvm::SMRange
Range) {
608 std::pair<unsigned, unsigned> LineCol = {0, 0};
609 if (
auto *SM = Source.Manager.get()) {
610 unsigned BufID = SM->getMainFileID();
611 LineCol = SM->getLineAndColumn(Source.Location, BufID);
612 ConfigFile = SM->getBufferInfo(BufID).Buffer->getBufferIdentifier();
616 auto Result = std::make_shared<CompiledFragmentImpl>();
617 vlog(
"Config fragment: compiling {0}:{1} -> {2} (trusted={3})",
ConfigFile,
618 LineCol.first, Result.get(), Source.Trusted);
620 FragmentCompiler{*Result,
D, Source.Manager.get()}.compile(std::move(*
this));
622 return [Result(std::move(Result))](
const Params &P,
Config &
C) {
623 return (*Result)(P,
C);