856 lines · cpp
1//===--- ConfigCompile.cpp - Translating Fragments into Config ------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// Fragments are applied to Configs in two steps:10//11// 1. (When the fragment is first loaded)12// FragmentCompiler::compile() traverses the Fragment and creates13// function objects that know how to apply the configuration.14// 2. (Every time a config is required)15// CompiledFragment() executes these functions to populate the Config.16//17// Work could be split between these steps in different ways. We try to18// do as much work as possible in the first step. For example, regexes are19// compiled in stage 1 and captured by the apply function. This is because:20//21// - it's more efficient, as the work done in stage 1 must only be done once22// - problems can be reported in stage 1, in stage 2 we must silently recover23//24//===----------------------------------------------------------------------===//25 26#include "CompileCommands.h"27#include "Config.h"28#include "ConfigFragment.h"29#include "ConfigProvider.h"30#include "Diagnostics.h"31#include "Feature.h"32#include "TidyProvider.h"33#include "support/Logger.h"34#include "support/Path.h"35#include "support/Trace.h"36#include "llvm/ADT/STLExtras.h"37#include "llvm/ADT/SmallString.h"38#include "llvm/ADT/StringExtras.h"39#include "llvm/ADT/StringRef.h"40#include "llvm/Support/FileSystem.h"41#include "llvm/Support/FormatVariadic.h"42#include "llvm/Support/Path.h"43#include "llvm/Support/Regex.h"44#include "llvm/Support/SMLoc.h"45#include "llvm/Support/SourceMgr.h"46#include <memory>47#include <optional>48#include <string>49#include <vector>50 51namespace clang {52namespace clangd {53namespace config {54namespace {55 56// Returns an empty stringref if Path is not under FragmentDir. Returns Path57// as-is when FragmentDir is empty.58llvm::StringRef configRelative(llvm::StringRef Path,59 llvm::StringRef FragmentDir) {60 if (FragmentDir.empty())61 return Path;62 if (!Path.consume_front(FragmentDir))63 return llvm::StringRef();64 return Path.empty() ? "." : Path;65}66 67struct CompiledFragmentImpl {68 // The independent conditions to check before using settings from this config.69 // The following fragment has *two* conditions:70 // If: { Platform: [mac, linux], PathMatch: foo/.* }71 // All of them must be satisfied: the platform and path conditions are ANDed.72 // The OR logic for the platform condition is implemented inside the function.73 std::vector<llvm::unique_function<bool(const Params &) const>> Conditions;74 // Mutations that this fragment will apply to the configuration.75 // These are invoked only if the conditions are satisfied.76 std::vector<llvm::unique_function<void(const Params &, Config &) const>>77 Apply;78 79 bool operator()(const Params &P, Config &C) const {80 for (const auto &C : Conditions) {81 if (!C(P)) {82 dlog("Config fragment {0}: condition not met", this);83 return false;84 }85 }86 dlog("Config fragment {0}: applying {1} rules", this, Apply.size());87 for (const auto &A : Apply)88 A(P, C);89 return true;90 }91};92 93// Wrapper around condition compile() functions to reduce arg-passing.94struct FragmentCompiler {95 FragmentCompiler(CompiledFragmentImpl &Out, DiagnosticCallback D,96 llvm::SourceMgr *SM)97 : Out(Out), Diagnostic(D), SourceMgr(SM) {}98 CompiledFragmentImpl &Out;99 DiagnosticCallback Diagnostic;100 llvm::SourceMgr *SourceMgr;101 // Normalized Fragment::SourceInfo::Directory.102 std::string FragmentDirectory;103 bool Trusted = false;104 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);113 return std::nullopt;114 }115 return std::move(Result);116 }117 118 std::optional<std::string> makeAbsolute(Located<std::string> Path,119 llvm::StringLiteral Description,120 llvm::sys::path::Style Style) {121 if (llvm::sys::path::is_absolute(*Path))122 return *Path;123 if (FragmentDirectory.empty()) {124 diag(Error,125 llvm::formatv(126 "{0} must be an absolute path, because this fragment is not "127 "associated with any directory.",128 Description)129 .str(),130 Path.Range);131 return std::nullopt;132 }133 llvm::SmallString<256> AbsPath = llvm::StringRef(*Path);134 llvm::sys::path::make_absolute(FragmentDirectory, AbsPath);135 llvm::sys::path::native(AbsPath, Style);136 return AbsPath.str().str();137 }138 139 // Helper with similar API to StringSwitch, for parsing enum values.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;146 147 public:148 EnumSwitch(llvm::StringRef EnumName, const Located<std::string> &In,149 FragmentCompiler &Outer)150 : Outer(Outer), EnumName(EnumName), Input(In) {}151 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)156 Result = Value;157 return *this;158 }159 160 std::optional<T> value() {161 if (!Result)162 Outer.diag(163 Warning,164 llvm::formatv("Invalid {0} value '{1}'. Valid values are {2}.",165 EnumName, *Input, llvm::join(ValidValues, ", "))166 .str(),167 Input.Range);168 return Result;169 };170 };171 172 // Attempt to parse a specified string into an enum.173 // Yields std::nullopt and produces a diagnostic on failure.174 //175 // std::optional<T> Value = compileEnum<En>("Foo", Frag.Foo)176 // .map("Foo", Enum::Foo)177 // .map("Bar", Enum::Bar)178 // .value();179 template <typename T>180 EnumSwitch<T> compileEnum(llvm::StringRef EnumName,181 const Located<std::string> &In) {182 return EnumSwitch<T>(EnumName, In, *this);183 }184 185 void compile(Fragment &&F) {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 += '/';191 }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));202 }203 204 void compile(Fragment::IfBlock &&F) {205 if (F.HasUnrecognizedCondition)206 Out.Conditions.push_back([&](const Params &) { return false; });207 208#ifdef CLANGD_PATH_CASE_INSENSITIVE209 llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase;210#else211 llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags;212#endif213 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));218 }219 if (!PathMatch->empty()) {220 Out.Conditions.push_back(221 [PathMatch(std::move(PathMatch)),222 FragmentDir(FragmentDirectory)](const Params &P) {223 if (P.Path.empty())224 return false;225 llvm::StringRef Path = configRelative(P.Path, FragmentDir);226 // Ignore the file if it is not nested under Fragment.227 if (Path.empty())228 return false;229 return llvm::any_of(*PathMatch, [&](const llvm::Regex &RE) {230 return RE.match(Path);231 });232 });233 }234 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));239 }240 if (!PathExclude->empty()) {241 Out.Conditions.push_back(242 [PathExclude(std::move(PathExclude)),243 FragmentDir(FragmentDirectory)](const Params &P) {244 if (P.Path.empty())245 return false;246 llvm::StringRef Path = configRelative(P.Path, FragmentDir);247 // Ignore the file if it is not nested under Fragment.248 if (Path.empty())249 return true;250 return llvm::none_of(*PathExclude, [&](const llvm::Regex &RE) {251 return RE.match(Path);252 });253 });254 }255 }256 257 void compile(Fragment::CompileFlagsBlock &&F) {258 if (F.Compiler)259 Out.Apply.push_back(260 [Compiler(std::move(**F.Compiler))](const Params &, Config &C) {261 C.CompileFlags.Edits.push_back(262 [Compiler](std::vector<std::string> &Args) {263 if (!Args.empty())264 Args.front() = Compiler;265 });266 });267 268 if (!F.Remove.empty()) {269 auto Remove = std::make_shared<ArgStripper>();270 for (auto &A : F.Remove)271 Remove->strip(*A);272 Out.Apply.push_back([Remove(std::shared_ptr<const ArgStripper>(273 std::move(Remove)))](const Params &, Config &C) {274 C.CompileFlags.Edits.push_back(275 [Remove](std::vector<std::string> &Args) {276 Remove->process(Args);277 });278 });279 }280 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) {287 // The point to insert at. Just append when `--` isn't present.288 auto It = llvm::find(Args, "--");289 Args.insert(It, Add.begin(), Add.end());290 });291 });292 }293 294 if (F.BuiltinHeaders) {295 if (auto Val =296 compileEnum<Config::BuiltinHeaderPolicy>("BuiltinHeaders",297 *F.BuiltinHeaders)298 .map("Clangd", Config::BuiltinHeaderPolicy::Clangd)299 .map("QueryDriver", Config::BuiltinHeaderPolicy::QueryDriver)300 .value())301 Out.Apply.push_back([Val](const Params &, Config &C) {302 C.CompileFlags.BuiltinHeaders = *Val;303 });304 }305 306 if (F.CompilationDatabase) {307 std::optional<Config::CDBSearchSpec> Spec;308 if (**F.CompilationDatabase == "Ancestors") {309 Spec.emplace();310 Spec->Policy = Config::CDBSearchSpec::Ancestors;311 } else if (**F.CompilationDatabase == "None") {312 Spec.emplace();313 Spec->Policy = Config::CDBSearchSpec::NoCDBSearch;314 } else {315 if (auto Path =316 makeAbsolute(*F.CompilationDatabase, "CompilationDatabase",317 llvm::sys::path::Style::native)) {318 // Drop trailing slash to put the path in canonical form.319 // Should makeAbsolute do this?320 llvm::StringRef Rel = llvm::sys::path::relative_path(*Path);321 if (!Rel.empty() && llvm::sys::path::is_separator(Rel.back()))322 Path->pop_back();323 324 Spec.emplace();325 Spec->Policy = Config::CDBSearchSpec::FixedDir;326 Spec->FixedCDBPath = std::move(Path);327 }328 }329 if (Spec)330 Out.Apply.push_back(331 [Spec(std::move(*Spec))](const Params &, Config &C) {332 C.CompileFlags.CDBSearch = Spec;333 });334 }335 }336 337 void compile(Fragment::IndexBlock &&F) {338 if (F.Background) {339 if (auto Val =340 compileEnum<Config::BackgroundPolicy>("Background", *F.Background)341 .map("Build", Config::BackgroundPolicy::Build)342 .map("Skip", Config::BackgroundPolicy::Skip)343 .value())344 Out.Apply.push_back(345 [Val](const Params &, Config &C) { C.Index.Background = *Val; });346 }347 if (F.External)348 compile(std::move(**F.External), F.External->Range);349 if (F.StandardLibrary)350 Out.Apply.push_back(351 [Val(**F.StandardLibrary)](const Params &, Config &C) {352 C.Index.StandardLibrary = Val;353 });354 }355 356 void compile(Fragment::IndexBlock::ExternalBlock &&External,357 llvm::SMRange BlockRange) {358 if (External.Server && !Trusted) {359 diag(Error,360 "Remote index may not be specified by untrusted configuration. "361 "Copy this into user config to use it.",362 External.Server->Range);363 return;364 }365#ifndef CLANGD_ENABLE_REMOTE366 if (External.Server) {367 elog("Clangd isn't compiled with remote index support, ignoring Server: "368 "{0}",369 *External.Server);370 External.Server.reset();371 }372#endif373 // Make sure exactly one of the Sources is set.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.",378 BlockRange);379 return;380 }381 Config::ExternalIndexSpec Spec;382 if (External.Server) {383 Spec.Kind = Config::ExternalIndexSpec::Server;384 Spec.Location = std::move(**External.Server);385 } else if (External.File) {386 Spec.Kind = Config::ExternalIndexSpec::File;387 auto AbsPath = makeAbsolute(std::move(*External.File), "File",388 llvm::sys::path::Style::native);389 if (!AbsPath)390 return;391 Spec.Location = std::move(*AbsPath);392 } else {393 assert(*External.IsNone);394 Spec.Kind = Config::ExternalIndexSpec::None;395 }396 if (Spec.Kind != Config::ExternalIndexSpec::None) {397 // Make sure MountPoint is an absolute path with forward slashes.398 if (!External.MountPoint)399 External.MountPoint.emplace(FragmentDirectory);400 if ((**External.MountPoint).empty()) {401 diag(Error, "A mountpoint is required.", BlockRange);402 return;403 }404 auto AbsPath = makeAbsolute(std::move(*External.MountPoint), "MountPoint",405 llvm::sys::path::Style::posix);406 if (!AbsPath)407 return;408 Spec.MountPoint = std::move(*AbsPath);409 }410 Out.Apply.push_back([Spec(std::move(Spec))](const Params &P, Config &C) {411 if (Spec.Kind == Config::ExternalIndexSpec::None) {412 C.Index.External = Spec;413 return;414 }415 if (P.Path.empty() || !pathStartsWith(Spec.MountPoint, P.Path,416 llvm::sys::path::Style::posix))417 return;418 C.Index.External = Spec;419 // Disable background indexing for the files under the mountpoint.420 // Note that this will overwrite statements in any previous fragments421 // (including the current one).422 C.Index.Background = Config::BackgroundPolicy::Skip;423 });424 }425 426 void compile(Fragment::DiagnosticsBlock &&F) {427 std::vector<std::string> Normalized;428 for (const auto &Suppressed : F.Suppress) {429 if (*Suppressed == "*") {430 Out.Apply.push_back([&](const Params &, Config &C) {431 C.Diagnostics.SuppressAll = true;432 C.Diagnostics.Suppress.clear();433 });434 return;435 }436 Normalized.push_back(normalizeSuppressedCode(*Suppressed).str());437 }438 if (!Normalized.empty())439 Out.Apply.push_back(440 [Normalized(std::move(Normalized))](const Params &, Config &C) {441 if (C.Diagnostics.SuppressAll)442 return;443 C.Diagnostics.Suppress.insert_range(Normalized);444 });445 446 if (F.UnusedIncludes) {447 auto Val = compileEnum<Config::IncludesPolicy>("UnusedIncludes",448 **F.UnusedIncludes)449 .map("Strict", Config::IncludesPolicy::Strict)450 .map("None", Config::IncludesPolicy::None)451 .value();452 if (!Val && **F.UnusedIncludes == "Experiment") {453 diag(Warning,454 "Experiment is deprecated for UnusedIncludes, use Strict instead.",455 F.UnusedIncludes->Range);456 Val = Config::IncludesPolicy::Strict;457 }458 if (Val) {459 Out.Apply.push_back([Val](const Params &, Config &C) {460 C.Diagnostics.UnusedIncludes = *Val;461 });462 }463 }464 465 if (F.MissingIncludes)466 if (auto Val = compileEnum<Config::IncludesPolicy>("MissingIncludes",467 **F.MissingIncludes)468 .map("Strict", Config::IncludesPolicy::Strict)469 .map("None", Config::IncludesPolicy::None)470 .value())471 Out.Apply.push_back([Val](const Params &, Config &C) {472 C.Diagnostics.MissingIncludes = *Val;473 });474 475 compile(std::move(F.Includes));476 compile(std::move(F.ClangTidy));477 }478 479 void compile(Fragment::StyleBlock &&F) {480 if (!F.FullyQualifiedNamespaces.empty()) {481 std::vector<std::string> FullyQualifiedNamespaces;482 for (auto &N : F.FullyQualifiedNamespaces) {483 // Normalize the data by dropping both leading and trailing ::484 StringRef Namespace(*N);485 Namespace.consume_front("::");486 Namespace.consume_back("::");487 FullyQualifiedNamespaces.push_back(Namespace.str());488 }489 Out.Apply.push_back([FullyQualifiedNamespaces(490 std::move(FullyQualifiedNamespaces))](491 const Params &, Config &C) {492 C.Style.FullyQualifiedNamespaces.insert(493 C.Style.FullyQualifiedNamespaces.begin(),494 FullyQualifiedNamespaces.begin(), FullyQualifiedNamespaces.end());495 });496 }497 auto QuotedFilter = compileHeaderRegexes(F.QuotedHeaders);498 if (QuotedFilter.has_value()) {499 Out.Apply.push_back(500 [QuotedFilter = *QuotedFilter](const Params &, Config &C) {501 C.Style.QuotedHeaders.emplace_back(QuotedFilter);502 });503 }504 auto AngledFilter = compileHeaderRegexes(F.AngledHeaders);505 if (AngledFilter.has_value()) {506 Out.Apply.push_back(507 [AngledFilter = *AngledFilter](const Params &, Config &C) {508 C.Style.AngledHeaders.emplace_back(AngledFilter);509 });510 }511 }512 513 auto compileHeaderRegexes(llvm::ArrayRef<Located<std::string>> HeaderPatterns)514 -> std::optional<std::function<bool(llvm::StringRef)>> {515 // TODO: Share this code with Diagnostics.Includes.IgnoreHeader516#ifdef CLANGD_PATH_CASE_INSENSITIVE517 static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase;518#else519 static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags;520#endif521 auto Filters = std::make_shared<std::vector<llvm::Regex>>();522 for (auto &HeaderPattern : HeaderPatterns) {523 // Anchor on the right.524 std::string AnchoredPattern = "(" + *HeaderPattern + ")$";525 llvm::Regex CompiledRegex(AnchoredPattern, Flags);526 std::string RegexError;527 if (!CompiledRegex.isValid(RegexError)) {528 diag(Warning,529 llvm::formatv("Invalid regular expression '{0}': {1}",530 *HeaderPattern, RegexError)531 .str(),532 HeaderPattern.Range);533 continue;534 }535 Filters->push_back(std::move(CompiledRegex));536 }537 if (Filters->empty())538 return std::nullopt;539 auto Filter = [Filters = std::move(Filters)](llvm::StringRef Path) {540 for (auto &Regex : *Filters)541 if (Regex.match(Path))542 return true;543 return false;544 };545 return Filter;546 }547 548 void appendTidyCheckSpec(std::string &CurSpec,549 const Located<std::string> &Arg, bool IsPositive) {550 StringRef Str = StringRef(*Arg).trim();551 // Don't support negating here, its handled if the item is in the Add or552 // Remove list.553 if (Str.starts_with("-") || Str.contains(',')) {554 diag(Error, "Invalid clang-tidy check name", Arg.Range);555 return;556 }557 if (!Str.contains('*')) {558 if (!isRegisteredTidyCheck(Str)) {559 diag(Warning,560 llvm::formatv("clang-tidy check '{0}' was not found", Str).str(),561 Arg.Range);562 return;563 }564 auto Fast = isFastTidyCheck(Str);565 if (!Fast.has_value()) {566 diag(Warning,567 llvm::formatv(568 "Latency of clang-tidy check '{0}' is not known. "569 "It will only run if ClangTidy.FastCheckFilter is Loose or None",570 Str)571 .str(),572 Arg.Range);573 } else if (!*Fast) {574 diag(Warning,575 llvm::formatv(576 "clang-tidy check '{0}' is slow. "577 "It will only run if ClangTidy.FastCheckFilter is None",578 Str)579 .str(),580 Arg.Range);581 }582 }583 CurSpec += ',';584 if (!IsPositive)585 CurSpec += '-';586 CurSpec += Str;587 }588 589 void compile(Fragment::DiagnosticsBlock::ClangTidyBlock &&F) {590 std::string Checks;591 for (auto &CheckGlob : F.Add)592 appendTidyCheckSpec(Checks, CheckGlob, true);593 594 for (auto &CheckGlob : F.Remove)595 appendTidyCheckSpec(Checks, CheckGlob, false);596 597 if (!Checks.empty())598 Out.Apply.push_back(599 [Checks = std::move(Checks)](const Params &, Config &C) {600 C.Diagnostics.ClangTidy.Checks.append(601 Checks,602 C.Diagnostics.ClangTidy.Checks.empty() ? /*skip comma*/ 1 : 0,603 std::string::npos);604 });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));610 Out.Apply.push_back(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);615 });616 }617 if (F.FastCheckFilter.has_value())618 if (auto Val = compileEnum<Config::FastCheckPolicy>("FastCheckFilter",619 *F.FastCheckFilter)620 .map("Strict", Config::FastCheckPolicy::Strict)621 .map("Loose", Config::FastCheckPolicy::Loose)622 .map("None", Config::FastCheckPolicy::None)623 .value())624 Out.Apply.push_back([Val](const Params &, Config &C) {625 C.Diagnostics.ClangTidy.FastCheckFilter = *Val;626 });627 }628 629 void compile(Fragment::DiagnosticsBlock::IncludesBlock &&F) {630#ifdef CLANGD_PATH_CASE_INSENSITIVE631 static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase;632#else633 static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags;634#endif635 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) {639 // Anchor on the right.640 std::string AnchoredPattern = "(" + *HeaderPattern + ")$";641 llvm::Regex CompiledRegex(AnchoredPattern, Flags);642 std::string RegexError;643 if (!CompiledRegex.isValid(RegexError)) {644 diag(Warning,645 llvm::formatv("Invalid regular expression '{0}': {1}",646 *HeaderPattern, RegexError)647 .str(),648 HeaderPattern.Range);649 continue;650 }651 Filters->push_back(std::move(CompiledRegex));652 }653 }654 // Optional to override the resulting AnalyzeAngledIncludes655 // only if it's explicitly set in the current fragment.656 // Otherwise it's inherited from parent fragment.657 std::optional<bool> AnalyzeAngledIncludes;658 if (F.AnalyzeAngledIncludes.has_value())659 AnalyzeAngledIncludes = **F.AnalyzeAngledIncludes;660 if (!Filters && !AnalyzeAngledIncludes.has_value())661 return;662 Out.Apply.push_back([Filters = std::move(Filters),663 AnalyzeAngledIncludes](const Params &, Config &C) {664 if (Filters) {665 auto Filter = [Filters](llvm::StringRef Path) {666 for (auto &Regex : *Filters)667 if (Regex.match(Path))668 return true;669 return false;670 };671 C.Diagnostics.Includes.IgnoreHeader.emplace_back(std::move(Filter));672 }673 if (AnalyzeAngledIncludes.has_value())674 C.Diagnostics.Includes.AnalyzeAngledIncludes = *AnalyzeAngledIncludes;675 });676 }677 678 void compile(Fragment::CompletionBlock &&F) {679 if (F.AllScopes) {680 Out.Apply.push_back(681 [AllScopes(**F.AllScopes)](const Params &, Config &C) {682 C.Completion.AllScopes = AllScopes;683 });684 }685 if (F.ArgumentLists) {686 if (auto Val =687 compileEnum<Config::ArgumentListsPolicy>("ArgumentLists",688 *F.ArgumentLists)689 .map("None", Config::ArgumentListsPolicy::None)690 .map("OpenDelimiter",691 Config::ArgumentListsPolicy::OpenDelimiter)692 .map("Delimiters", Config::ArgumentListsPolicy::Delimiters)693 .map("FullPlaceholders",694 Config::ArgumentListsPolicy::FullPlaceholders)695 .value())696 Out.Apply.push_back([Val](const Params &, Config &C) {697 C.Completion.ArgumentLists = *Val;698 });699 }700 if (F.HeaderInsertion) {701 if (auto Val =702 compileEnum<Config::HeaderInsertionPolicy>("HeaderInsertion",703 *F.HeaderInsertion)704 .map("IWYU", Config::HeaderInsertionPolicy::IWYU)705 .map("Never", Config::HeaderInsertionPolicy::NeverInsert)706 .value())707 Out.Apply.push_back([Val](const Params &, Config &C) {708 C.Completion.HeaderInsertion = *Val;709 });710 }711 712 if (F.CodePatterns) {713 if (auto Val = compileEnum<Config::CodePatternsPolicy>("CodePatterns",714 *F.CodePatterns)715 .map("All", Config::CodePatternsPolicy::All)716 .map("None", Config::CodePatternsPolicy::None)717 .value())718 Out.Apply.push_back([Val](const Params &, Config &C) {719 C.Completion.CodePatterns = *Val;720 });721 }722 }723 724 void compile(Fragment::HoverBlock &&F) {725 if (F.ShowAKA) {726 Out.Apply.push_back([ShowAKA(**F.ShowAKA)](const Params &, Config &C) {727 C.Hover.ShowAKA = ShowAKA;728 });729 }730 if (F.MacroContentsLimit) {731 Out.Apply.push_back(732 [Limit(**F.MacroContentsLimit)](const Params &, Config &C) {733 C.Hover.MacroContentsLimit = Limit;734 });735 }736 }737 738 void compile(Fragment::InlayHintsBlock &&F) {739 if (F.Enabled)740 Out.Apply.push_back([Value(**F.Enabled)](const Params &, Config &C) {741 C.InlayHints.Enabled = Value;742 });743 if (F.ParameterNames)744 Out.Apply.push_back(745 [Value(**F.ParameterNames)](const Params &, Config &C) {746 C.InlayHints.Parameters = Value;747 });748 if (F.DeducedTypes)749 Out.Apply.push_back([Value(**F.DeducedTypes)](const Params &, Config &C) {750 C.InlayHints.DeducedTypes = Value;751 });752 if (F.Designators)753 Out.Apply.push_back([Value(**F.Designators)](const Params &, Config &C) {754 C.InlayHints.Designators = Value;755 });756 if (F.BlockEnd)757 Out.Apply.push_back([Value(**F.BlockEnd)](const Params &, Config &C) {758 C.InlayHints.BlockEnd = Value;759 });760 if (F.DefaultArguments)761 Out.Apply.push_back(762 [Value(**F.DefaultArguments)](const Params &, Config &C) {763 C.InlayHints.DefaultArguments = Value;764 });765 if (F.TypeNameLimit)766 Out.Apply.push_back(767 [Value(**F.TypeNameLimit)](const Params &, Config &C) {768 C.InlayHints.TypeNameLimit = Value;769 });770 }771 772 void compile(Fragment::SemanticTokensBlock &&F) {773 if (!F.DisabledKinds.empty()) {774 std::vector<std::string> DisabledKinds;775 for (auto &Kind : F.DisabledKinds)776 DisabledKinds.push_back(std::move(*Kind));777 778 Out.Apply.push_back(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));784 }785 });786 }787 if (!F.DisabledModifiers.empty()) {788 std::vector<std::string> DisabledModifiers;789 for (auto &Kind : F.DisabledModifiers)790 DisabledModifiers.push_back(std::move(*Kind));791 792 Out.Apply.push_back([DisabledModifiers(std::move(DisabledModifiers))](793 const Params &, Config &C) {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));798 }799 });800 }801 }802 803 void compile(Fragment::DocumentationBlock &&F) {804 if (F.CommentFormat) {805 if (auto Val =806 compileEnum<Config::CommentFormatPolicy>("CommentFormat",807 *F.CommentFormat)808 .map("Plaintext", Config::CommentFormatPolicy::PlainText)809 .map("Markdown", Config::CommentFormatPolicy::Markdown)810 .map("Doxygen", Config::CommentFormatPolicy::Doxygen)811 .value())812 Out.Apply.push_back([Val](const Params &, Config &C) {813 C.Documentation.CommentFormat = *Val;814 });815 }816 }817 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)824 Diagnostic(SourceMgr->GetMessage(Range.Start, Kind, Message, Range));825 else826 Diagnostic(llvm::SMDiagnostic("", Kind, Message));827 }828};829 830} // namespace831 832CompiledFragment Fragment::compile(DiagnosticCallback D) && {833 llvm::StringRef ConfigFile = "<unknown>";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();839 }840 trace::Span Tracer("ConfigCompile");841 SPAN_ATTACH(Tracer, "ConfigFile", ConfigFile);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);845 846 FragmentCompiler{*Result, D, Source.Manager.get()}.compile(std::move(*this));847 // Return as cheaply-copyable wrapper.848 return [Result(std::move(Result))](const Params &P, Config &C) {849 return (*Result)(P, C);850 };851}852 853} // namespace config854} // namespace clangd855} // namespace clang856