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-exception |
6 | // |
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 creates |
13 | // 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 to |
18 | // do as much work as possible in the first step. For example, regexes are |
19 | // 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 once |
22 | // - problems can be reported in stage 1, in stage 2 we must silently recover |
23 | // |
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 <algorithm> |
47 | #include <memory> |
48 | #include <optional> |
49 | #include <string> |
50 | #include <vector> |
51 | |
52 | namespace clang { |
53 | namespace clangd { |
54 | namespace config { |
55 | namespace { |
56 | |
57 | // Returns an empty stringref if Path is not under FragmentDir. Returns Path |
58 | // as-is when FragmentDir is empty. |
59 | llvm::StringRef configRelative(llvm::StringRef Path, |
60 | llvm::StringRef FragmentDir) { |
61 | if (FragmentDir.empty()) |
62 | return Path; |
63 | if (!Path.consume_front(Prefix: FragmentDir)) |
64 | return llvm::StringRef(); |
65 | return Path.empty() ? "." : Path; |
66 | } |
67 | |
68 | struct CompiledFragmentImpl { |
69 | // The independent conditions to check before using settings from this config. |
70 | // The following fragment has *two* conditions: |
71 | // If: { Platform: [mac, linux], PathMatch: foo/.* } |
72 | // All of them must be satisfied: the platform and path conditions are ANDed. |
73 | // The OR logic for the platform condition is implemented inside the function. |
74 | std::vector<llvm::unique_function<bool(const Params &) const>> Conditions; |
75 | // Mutations that this fragment will apply to the configuration. |
76 | // These are invoked only if the conditions are satisfied. |
77 | std::vector<llvm::unique_function<void(const Params &, Config &) const>> |
78 | Apply; |
79 | |
80 | bool operator()(const Params &P, Config &C) const { |
81 | for (const auto &C : Conditions) { |
82 | if (!C(P)) { |
83 | dlog("Config fragment {0}: condition not met" , this); |
84 | return false; |
85 | } |
86 | } |
87 | dlog("Config fragment {0}: applying {1} rules" , this, Apply.size()); |
88 | for (const auto &A : Apply) |
89 | A(P, C); |
90 | return true; |
91 | } |
92 | }; |
93 | |
94 | // Wrapper around condition compile() functions to reduce arg-passing. |
95 | struct FragmentCompiler { |
96 | FragmentCompiler(CompiledFragmentImpl &Out, DiagnosticCallback D, |
97 | llvm::SourceMgr *SM) |
98 | : Out(Out), Diagnostic(D), SourceMgr(SM) {} |
99 | CompiledFragmentImpl &Out; |
100 | DiagnosticCallback Diagnostic; |
101 | llvm::SourceMgr *SourceMgr; |
102 | // Normalized Fragment::SourceInfo::Directory. |
103 | std::string FragmentDirectory; |
104 | bool Trusted = false; |
105 | |
106 | std::optional<llvm::Regex> |
107 | compileRegex(const Located<std::string> &Text, |
108 | llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags) { |
109 | std::string Anchored = "^(" + *Text + ")$" ; |
110 | llvm::Regex Result(Anchored, Flags); |
111 | std::string RegexError; |
112 | if (!Result.isValid(Error&: RegexError)) { |
113 | diag(Kind: Error, Message: "Invalid regex " + Anchored + ": " + RegexError, Range: Text.Range); |
114 | return std::nullopt; |
115 | } |
116 | return std::move(Result); |
117 | } |
118 | |
119 | std::optional<std::string> makeAbsolute(Located<std::string> Path, |
120 | llvm::StringLiteral Description, |
121 | llvm::sys::path::Style Style) { |
122 | if (llvm::sys::path::is_absolute(path: *Path)) |
123 | return *Path; |
124 | if (FragmentDirectory.empty()) { |
125 | diag(Kind: Error, |
126 | Message: llvm::formatv( |
127 | Fmt: "{0} must be an absolute path, because this fragment is not " |
128 | "associated with any directory." , |
129 | Vals&: Description) |
130 | .str(), |
131 | Range: Path.Range); |
132 | return std::nullopt; |
133 | } |
134 | llvm::SmallString<256> AbsPath = llvm::StringRef(*Path); |
135 | llvm::sys::fs::make_absolute(current_directory: FragmentDirectory, path&: AbsPath); |
136 | llvm::sys::path::native(path&: AbsPath, style: Style); |
137 | return AbsPath.str().str(); |
138 | } |
139 | |
140 | // Helper with similar API to StringSwitch, for parsing enum values. |
141 | template <typename T> class EnumSwitch { |
142 | FragmentCompiler &Outer; |
143 | llvm::StringRef EnumName; |
144 | const Located<std::string> &Input; |
145 | std::optional<T> Result; |
146 | llvm::SmallVector<llvm::StringLiteral> ValidValues; |
147 | |
148 | public: |
149 | EnumSwitch(llvm::StringRef EnumName, const Located<std::string> &In, |
150 | FragmentCompiler &Outer) |
151 | : Outer(Outer), EnumName(EnumName), Input(In) {} |
152 | |
153 | EnumSwitch &map(llvm::StringLiteral Name, T Value) { |
154 | assert(!llvm::is_contained(ValidValues, Name) && "Duplicate value!" ); |
155 | ValidValues.push_back(Elt: Name); |
156 | if (!Result && *Input == Name) |
157 | Result = Value; |
158 | return *this; |
159 | } |
160 | |
161 | std::optional<T> value() { |
162 | if (!Result) |
163 | Outer.diag( |
164 | Kind: Warning, |
165 | Message: llvm::formatv(Fmt: "Invalid {0} value '{1}'. Valid values are {2}." , |
166 | Vals&: EnumName, Vals: *Input, Vals: llvm::join(R&: ValidValues, Separator: ", " )) |
167 | .str(), |
168 | Range: Input.Range); |
169 | return Result; |
170 | }; |
171 | }; |
172 | |
173 | // Attempt to parse a specified string into an enum. |
174 | // Yields std::nullopt and produces a diagnostic on failure. |
175 | // |
176 | // std::optional<T> Value = compileEnum<En>("Foo", Frag.Foo) |
177 | // .map("Foo", Enum::Foo) |
178 | // .map("Bar", Enum::Bar) |
179 | // .value(); |
180 | template <typename T> |
181 | EnumSwitch<T> compileEnum(llvm::StringRef EnumName, |
182 | const Located<std::string> &In) { |
183 | return EnumSwitch<T>(EnumName, In, *this); |
184 | } |
185 | |
186 | void compile(Fragment &&F) { |
187 | Trusted = F.Source.Trusted; |
188 | if (!F.Source.Directory.empty()) { |
189 | FragmentDirectory = llvm::sys::path::convert_to_slash(path: F.Source.Directory); |
190 | if (FragmentDirectory.back() != '/') |
191 | FragmentDirectory += '/'; |
192 | } |
193 | compile(F: std::move(F.If)); |
194 | compile(F: std::move(F.CompileFlags)); |
195 | compile(F: std::move(F.Index)); |
196 | compile(F: std::move(F.Diagnostics)); |
197 | compile(F: std::move(F.Completion)); |
198 | compile(F: std::move(F.Hover)); |
199 | compile(F: std::move(F.InlayHints)); |
200 | compile(F: std::move(F.SemanticTokens)); |
201 | compile(F: std::move(F.Style)); |
202 | } |
203 | |
204 | void compile(Fragment::IfBlock &&F) { |
205 | if (F.HasUnrecognizedCondition) |
206 | Out.Conditions.push_back(x: [&](const Params &) { return false; }); |
207 | |
208 | #ifdef CLANGD_PATH_CASE_INSENSITIVE |
209 | llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase; |
210 | #else |
211 | llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags; |
212 | #endif |
213 | |
214 | auto PathMatch = std::make_unique<std::vector<llvm::Regex>>(); |
215 | for (auto &Entry : F.PathMatch) { |
216 | if (auto RE = compileRegex(Text: Entry, Flags)) |
217 | PathMatch->push_back(x: std::move(*RE)); |
218 | } |
219 | if (!PathMatch->empty()) { |
220 | Out.Conditions.push_back( |
221 | x: [PathMatch(std::move(PathMatch)), |
222 | FragmentDir(FragmentDirectory)](const Params &P) { |
223 | if (P.Path.empty()) |
224 | return false; |
225 | llvm::StringRef Path = configRelative(Path: 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(Range&: *PathMatch, P: [&](const llvm::Regex &RE) { |
230 | return RE.match(String: 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(Text: Entry, Flags)) |
238 | PathExclude->push_back(x: std::move(*RE)); |
239 | } |
240 | if (!PathExclude->empty()) { |
241 | Out.Conditions.push_back( |
242 | x: [PathExclude(std::move(PathExclude)), |
243 | FragmentDir(FragmentDirectory)](const Params &P) { |
244 | if (P.Path.empty()) |
245 | return false; |
246 | llvm::StringRef Path = configRelative(Path: 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(Range&: *PathExclude, P: [&](const llvm::Regex &RE) { |
251 | return RE.match(String: Path); |
252 | }); |
253 | }); |
254 | } |
255 | } |
256 | |
257 | void compile(Fragment::CompileFlagsBlock &&F) { |
258 | if (F.Compiler) |
259 | Out.Apply.push_back( |
260 | x: [Compiler(std::move(**F.Compiler))](const Params &, Config &C) { |
261 | C.CompileFlags.Edits.push_back( |
262 | x: [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(Arg: *A); |
272 | Out.Apply.push_back(x: [Remove(std::shared_ptr<const ArgStripper>( |
273 | std::move(Remove)))](const Params &, Config &C) { |
274 | C.CompileFlags.Edits.push_back( |
275 | x: [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(x: std::move(*A)); |
285 | Out.Apply.push_back(x: [Add(std::move(Add))](const Params &, Config &C) { |
286 | C.CompileFlags.Edits.push_back(x: [Add](std::vector<std::string> &Args) { |
287 | // The point to insert at. Just append when `--` isn't present. |
288 | auto It = llvm::find(Range&: Args, Val: "--" ); |
289 | Args.insert(position: It, first: Add.begin(), last: Add.end()); |
290 | }); |
291 | }); |
292 | } |
293 | |
294 | if (F.CompilationDatabase) { |
295 | std::optional<Config::CDBSearchSpec> Spec; |
296 | if (**F.CompilationDatabase == "Ancestors" ) { |
297 | Spec.emplace(); |
298 | Spec->Policy = Config::CDBSearchSpec::Ancestors; |
299 | } else if (**F.CompilationDatabase == "None" ) { |
300 | Spec.emplace(); |
301 | Spec->Policy = Config::CDBSearchSpec::NoCDBSearch; |
302 | } else { |
303 | if (auto Path = |
304 | makeAbsolute(Path: *F.CompilationDatabase, Description: "CompilationDatabase" , |
305 | Style: llvm::sys::path::Style::native)) { |
306 | // Drop trailing slash to put the path in canonical form. |
307 | // Should makeAbsolute do this? |
308 | llvm::StringRef Rel = llvm::sys::path::relative_path(path: *Path); |
309 | if (!Rel.empty() && llvm::sys::path::is_separator(value: Rel.back())) |
310 | Path->pop_back(); |
311 | |
312 | Spec.emplace(); |
313 | Spec->Policy = Config::CDBSearchSpec::FixedDir; |
314 | Spec->FixedCDBPath = std::move(Path); |
315 | } |
316 | } |
317 | if (Spec) |
318 | Out.Apply.push_back( |
319 | x: [Spec(std::move(*Spec))](const Params &, Config &C) { |
320 | C.CompileFlags.CDBSearch = Spec; |
321 | }); |
322 | } |
323 | } |
324 | |
325 | void compile(Fragment::IndexBlock &&F) { |
326 | if (F.Background) { |
327 | if (auto Val = |
328 | compileEnum<Config::BackgroundPolicy>(EnumName: "Background" , In: *F.Background) |
329 | .map(Name: "Build" , Value: Config::BackgroundPolicy::Build) |
330 | .map(Name: "Skip" , Value: Config::BackgroundPolicy::Skip) |
331 | .value()) |
332 | Out.Apply.push_back( |
333 | x: [Val](const Params &, Config &C) { C.Index.Background = *Val; }); |
334 | } |
335 | if (F.External) |
336 | compile(External: std::move(**F.External), BlockRange: F.External->Range); |
337 | if (F.StandardLibrary) |
338 | Out.Apply.push_back( |
339 | x: [Val(**F.StandardLibrary)](const Params &, Config &C) { |
340 | C.Index.StandardLibrary = Val; |
341 | }); |
342 | } |
343 | |
344 | void compile(Fragment::IndexBlock::ExternalBlock &&External, |
345 | llvm::SMRange BlockRange) { |
346 | if (External.Server && !Trusted) { |
347 | diag(Kind: Error, |
348 | Message: "Remote index may not be specified by untrusted configuration. " |
349 | "Copy this into user config to use it." , |
350 | Range: External.Server->Range); |
351 | return; |
352 | } |
353 | #ifndef CLANGD_ENABLE_REMOTE |
354 | if (External.Server) { |
355 | elog("Clangd isn't compiled with remote index support, ignoring Server: " |
356 | "{0}" , |
357 | *External.Server); |
358 | External.Server.reset(); |
359 | } |
360 | #endif |
361 | // Make sure exactly one of the Sources is set. |
362 | unsigned SourceCount = External.File.has_value() + |
363 | External.Server.has_value() + *External.IsNone; |
364 | if (SourceCount != 1) { |
365 | diag(Kind: Error, Message: "Exactly one of File, Server or None must be set." , |
366 | Range: BlockRange); |
367 | return; |
368 | } |
369 | Config::ExternalIndexSpec Spec; |
370 | if (External.Server) { |
371 | Spec.Kind = Config::ExternalIndexSpec::Server; |
372 | Spec.Location = std::move(**External.Server); |
373 | } else if (External.File) { |
374 | Spec.Kind = Config::ExternalIndexSpec::File; |
375 | auto AbsPath = makeAbsolute(Path: std::move(*External.File), Description: "File" , |
376 | Style: llvm::sys::path::Style::native); |
377 | if (!AbsPath) |
378 | return; |
379 | Spec.Location = std::move(*AbsPath); |
380 | } else { |
381 | assert(*External.IsNone); |
382 | Spec.Kind = Config::ExternalIndexSpec::None; |
383 | } |
384 | if (Spec.Kind != Config::ExternalIndexSpec::None) { |
385 | // Make sure MountPoint is an absolute path with forward slashes. |
386 | if (!External.MountPoint) |
387 | External.MountPoint.emplace(args&: FragmentDirectory); |
388 | if ((**External.MountPoint).empty()) { |
389 | diag(Kind: Error, Message: "A mountpoint is required." , Range: BlockRange); |
390 | return; |
391 | } |
392 | auto AbsPath = makeAbsolute(Path: std::move(*External.MountPoint), Description: "MountPoint" , |
393 | Style: llvm::sys::path::Style::posix); |
394 | if (!AbsPath) |
395 | return; |
396 | Spec.MountPoint = std::move(*AbsPath); |
397 | } |
398 | Out.Apply.push_back(x: [Spec(std::move(Spec))](const Params &P, Config &C) { |
399 | if (Spec.Kind == Config::ExternalIndexSpec::None) { |
400 | C.Index.External = Spec; |
401 | return; |
402 | } |
403 | if (P.Path.empty() || !pathStartsWith(Ancestor: Spec.MountPoint, Path: P.Path, |
404 | Style: llvm::sys::path::Style::posix)) |
405 | return; |
406 | C.Index.External = Spec; |
407 | // Disable background indexing for the files under the mountpoint. |
408 | // Note that this will overwrite statements in any previous fragments |
409 | // (including the current one). |
410 | C.Index.Background = Config::BackgroundPolicy::Skip; |
411 | }); |
412 | } |
413 | |
414 | void compile(Fragment::DiagnosticsBlock &&F) { |
415 | std::vector<std::string> Normalized; |
416 | for (const auto &Suppressed : F.Suppress) { |
417 | if (*Suppressed == "*" ) { |
418 | Out.Apply.push_back(x: [&](const Params &, Config &C) { |
419 | C.Diagnostics.SuppressAll = true; |
420 | C.Diagnostics.Suppress.clear(); |
421 | }); |
422 | return; |
423 | } |
424 | Normalized.push_back(x: normalizeSuppressedCode(*Suppressed).str()); |
425 | } |
426 | if (!Normalized.empty()) |
427 | Out.Apply.push_back( |
428 | x: [Normalized(std::move(Normalized))](const Params &, Config &C) { |
429 | if (C.Diagnostics.SuppressAll) |
430 | return; |
431 | for (llvm::StringRef N : Normalized) |
432 | C.Diagnostics.Suppress.insert(key: N); |
433 | }); |
434 | |
435 | if (F.UnusedIncludes) { |
436 | auto Val = compileEnum<Config::IncludesPolicy>(EnumName: "UnusedIncludes" , |
437 | In: **F.UnusedIncludes) |
438 | .map(Name: "Strict" , Value: Config::IncludesPolicy::Strict) |
439 | .map(Name: "None" , Value: Config::IncludesPolicy::None) |
440 | .value(); |
441 | if (!Val && **F.UnusedIncludes == "Experiment" ) { |
442 | diag(Kind: Warning, |
443 | Message: "Experiment is deprecated for UnusedIncludes, use Strict instead." , |
444 | Range: F.UnusedIncludes->Range); |
445 | Val = Config::IncludesPolicy::Strict; |
446 | } |
447 | if (Val) { |
448 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
449 | C.Diagnostics.UnusedIncludes = *Val; |
450 | }); |
451 | } |
452 | } |
453 | |
454 | if (F.MissingIncludes) |
455 | if (auto Val = compileEnum<Config::IncludesPolicy>(EnumName: "MissingIncludes" , |
456 | In: **F.MissingIncludes) |
457 | .map(Name: "Strict" , Value: Config::IncludesPolicy::Strict) |
458 | .map(Name: "None" , Value: Config::IncludesPolicy::None) |
459 | .value()) |
460 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
461 | C.Diagnostics.MissingIncludes = *Val; |
462 | }); |
463 | |
464 | compile(F: std::move(F.Includes)); |
465 | compile(F: std::move(F.ClangTidy)); |
466 | } |
467 | |
468 | void compile(Fragment::StyleBlock &&F) { |
469 | if (!F.FullyQualifiedNamespaces.empty()) { |
470 | std::vector<std::string> FullyQualifiedNamespaces; |
471 | for (auto &N : F.FullyQualifiedNamespaces) { |
472 | // Normalize the data by dropping both leading and trailing :: |
473 | StringRef Namespace(*N); |
474 | Namespace.consume_front(Prefix: "::" ); |
475 | Namespace.consume_back(Suffix: "::" ); |
476 | FullyQualifiedNamespaces.push_back(x: Namespace.str()); |
477 | } |
478 | Out.Apply.push_back(x: [FullyQualifiedNamespaces( |
479 | std::move(FullyQualifiedNamespaces))]( |
480 | const Params &, Config &C) { |
481 | C.Style.FullyQualifiedNamespaces.insert( |
482 | position: C.Style.FullyQualifiedNamespaces.begin(), |
483 | first: FullyQualifiedNamespaces.begin(), last: FullyQualifiedNamespaces.end()); |
484 | }); |
485 | } |
486 | } |
487 | |
488 | void appendTidyCheckSpec(std::string &CurSpec, |
489 | const Located<std::string> &Arg, bool IsPositive) { |
490 | StringRef Str = StringRef(*Arg).trim(); |
491 | // Don't support negating here, its handled if the item is in the Add or |
492 | // Remove list. |
493 | if (Str.starts_with(Prefix: "-" ) || Str.contains(C: ',')) { |
494 | diag(Kind: Error, Message: "Invalid clang-tidy check name" , Range: Arg.Range); |
495 | return; |
496 | } |
497 | if (!Str.contains(C: '*')) { |
498 | if (!isRegisteredTidyCheck(Check: Str)) { |
499 | diag(Kind: Warning, |
500 | Message: llvm::formatv(Fmt: "clang-tidy check '{0}' was not found" , Vals&: Str).str(), |
501 | Range: Arg.Range); |
502 | return; |
503 | } |
504 | auto Fast = isFastTidyCheck(Check: Str); |
505 | if (!Fast.has_value()) { |
506 | diag(Kind: Warning, |
507 | Message: llvm::formatv( |
508 | Fmt: "Latency of clang-tidy check '{0}' is not known. " |
509 | "It will only run if ClangTidy.FastCheckFilter is Loose or None" , |
510 | Vals&: Str) |
511 | .str(), |
512 | Range: Arg.Range); |
513 | } else if (!*Fast) { |
514 | diag(Kind: Warning, |
515 | Message: llvm::formatv( |
516 | Fmt: "clang-tidy check '{0}' is slow. " |
517 | "It will only run if ClangTidy.FastCheckFilter is None" , |
518 | Vals&: Str) |
519 | .str(), |
520 | Range: Arg.Range); |
521 | } |
522 | } |
523 | CurSpec += ','; |
524 | if (!IsPositive) |
525 | CurSpec += '-'; |
526 | CurSpec += Str; |
527 | } |
528 | |
529 | void compile(Fragment::DiagnosticsBlock::ClangTidyBlock &&F) { |
530 | std::string Checks; |
531 | for (auto &CheckGlob : F.Add) |
532 | appendTidyCheckSpec(CurSpec&: Checks, Arg: CheckGlob, IsPositive: true); |
533 | |
534 | for (auto &CheckGlob : F.Remove) |
535 | appendTidyCheckSpec(CurSpec&: Checks, Arg: CheckGlob, IsPositive: false); |
536 | |
537 | if (!Checks.empty()) |
538 | Out.Apply.push_back( |
539 | x: [Checks = std::move(Checks)](const Params &, Config &C) { |
540 | C.Diagnostics.ClangTidy.Checks.append( |
541 | str: Checks, |
542 | pos: C.Diagnostics.ClangTidy.Checks.empty() ? /*skip comma*/ 1 : 0, |
543 | n: std::string::npos); |
544 | }); |
545 | if (!F.CheckOptions.empty()) { |
546 | std::vector<std::pair<std::string, std::string>> CheckOptions; |
547 | for (auto &Opt : F.CheckOptions) |
548 | CheckOptions.emplace_back(args: std::move(*Opt.first), |
549 | args: std::move(*Opt.second)); |
550 | Out.Apply.push_back( |
551 | x: [CheckOptions = std::move(CheckOptions)](const Params &, Config &C) { |
552 | for (auto &StringPair : CheckOptions) |
553 | C.Diagnostics.ClangTidy.CheckOptions.insert_or_assign( |
554 | Key: StringPair.first, Val: StringPair.second); |
555 | }); |
556 | } |
557 | if (F.FastCheckFilter.has_value()) |
558 | if (auto Val = compileEnum<Config::FastCheckPolicy>(EnumName: "FastCheckFilter" , |
559 | In: *F.FastCheckFilter) |
560 | .map(Name: "Strict" , Value: Config::FastCheckPolicy::Strict) |
561 | .map(Name: "Loose" , Value: Config::FastCheckPolicy::Loose) |
562 | .map(Name: "None" , Value: Config::FastCheckPolicy::None) |
563 | .value()) |
564 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
565 | C.Diagnostics.ClangTidy.FastCheckFilter = *Val; |
566 | }); |
567 | } |
568 | |
569 | void compile(Fragment::DiagnosticsBlock::IncludesBlock &&F) { |
570 | #ifdef CLANGD_PATH_CASE_INSENSITIVE |
571 | static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase; |
572 | #else |
573 | static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags; |
574 | #endif |
575 | auto Filters = std::make_shared<std::vector<llvm::Regex>>(); |
576 | for (auto & : F.IgnoreHeader) { |
577 | // Anchor on the right. |
578 | std::string AnchoredPattern = "(" + *HeaderPattern + ")$" ; |
579 | llvm::Regex CompiledRegex(AnchoredPattern, Flags); |
580 | std::string RegexError; |
581 | if (!CompiledRegex.isValid(Error&: RegexError)) { |
582 | diag(Kind: Warning, |
583 | Message: llvm::formatv(Fmt: "Invalid regular expression '{0}': {1}" , |
584 | Vals&: *HeaderPattern, Vals&: RegexError) |
585 | .str(), |
586 | Range: HeaderPattern.Range); |
587 | continue; |
588 | } |
589 | Filters->push_back(x: std::move(CompiledRegex)); |
590 | } |
591 | if (Filters->empty()) |
592 | return; |
593 | auto Filter = [Filters](llvm::StringRef Path) { |
594 | for (auto &Regex : *Filters) |
595 | if (Regex.match(String: Path)) |
596 | return true; |
597 | return false; |
598 | }; |
599 | Out.Apply.push_back(x: [Filter](const Params &, Config &C) { |
600 | C.Diagnostics.Includes.IgnoreHeader.emplace_back(args: Filter); |
601 | }); |
602 | } |
603 | |
604 | void compile(Fragment::CompletionBlock &&F) { |
605 | if (F.AllScopes) { |
606 | Out.Apply.push_back( |
607 | x: [AllScopes(**F.AllScopes)](const Params &, Config &C) { |
608 | C.Completion.AllScopes = AllScopes; |
609 | }); |
610 | } |
611 | } |
612 | |
613 | void compile(Fragment::HoverBlock &&F) { |
614 | if (F.ShowAKA) { |
615 | Out.Apply.push_back(x: [ShowAKA(**F.ShowAKA)](const Params &, Config &C) { |
616 | C.Hover.ShowAKA = ShowAKA; |
617 | }); |
618 | } |
619 | } |
620 | |
621 | void compile(Fragment::InlayHintsBlock &&F) { |
622 | if (F.Enabled) |
623 | Out.Apply.push_back(x: [Value(**F.Enabled)](const Params &, Config &C) { |
624 | C.InlayHints.Enabled = Value; |
625 | }); |
626 | if (F.ParameterNames) |
627 | Out.Apply.push_back( |
628 | x: [Value(**F.ParameterNames)](const Params &, Config &C) { |
629 | C.InlayHints.Parameters = Value; |
630 | }); |
631 | if (F.DeducedTypes) |
632 | Out.Apply.push_back(x: [Value(**F.DeducedTypes)](const Params &, Config &C) { |
633 | C.InlayHints.DeducedTypes = Value; |
634 | }); |
635 | if (F.Designators) |
636 | Out.Apply.push_back(x: [Value(**F.Designators)](const Params &, Config &C) { |
637 | C.InlayHints.Designators = Value; |
638 | }); |
639 | if (F.BlockEnd) |
640 | Out.Apply.push_back(x: [Value(**F.BlockEnd)](const Params &, Config &C) { |
641 | C.InlayHints.BlockEnd = Value; |
642 | }); |
643 | if (F.TypeNameLimit) |
644 | Out.Apply.push_back( |
645 | x: [Value(**F.TypeNameLimit)](const Params &, Config &C) { |
646 | C.InlayHints.TypeNameLimit = Value; |
647 | }); |
648 | } |
649 | |
650 | void compile(Fragment::SemanticTokensBlock &&F) { |
651 | if (!F.DisabledKinds.empty()) { |
652 | std::vector<std::string> DisabledKinds; |
653 | for (auto &Kind : F.DisabledKinds) |
654 | DisabledKinds.push_back(x: std::move(*Kind)); |
655 | |
656 | Out.Apply.push_back( |
657 | x: [DisabledKinds(std::move(DisabledKinds))](const Params &, Config &C) { |
658 | for (auto &Kind : DisabledKinds) { |
659 | auto It = llvm::find(Range&: C.SemanticTokens.DisabledKinds, Val: Kind); |
660 | if (It == C.SemanticTokens.DisabledKinds.end()) |
661 | C.SemanticTokens.DisabledKinds.push_back(x: std::move(Kind)); |
662 | } |
663 | }); |
664 | } |
665 | if (!F.DisabledModifiers.empty()) { |
666 | std::vector<std::string> DisabledModifiers; |
667 | for (auto &Kind : F.DisabledModifiers) |
668 | DisabledModifiers.push_back(x: std::move(*Kind)); |
669 | |
670 | Out.Apply.push_back(x: [DisabledModifiers(std::move(DisabledModifiers))]( |
671 | const Params &, Config &C) { |
672 | for (auto &Kind : DisabledModifiers) { |
673 | auto It = llvm::find(Range&: C.SemanticTokens.DisabledModifiers, Val: Kind); |
674 | if (It == C.SemanticTokens.DisabledModifiers.end()) |
675 | C.SemanticTokens.DisabledModifiers.push_back(x: std::move(Kind)); |
676 | } |
677 | }); |
678 | } |
679 | } |
680 | |
681 | constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error; |
682 | constexpr static llvm::SourceMgr::DiagKind Warning = |
683 | llvm::SourceMgr::DK_Warning; |
684 | void diag(llvm::SourceMgr::DiagKind Kind, llvm::StringRef Message, |
685 | llvm::SMRange Range) { |
686 | if (Range.isValid() && SourceMgr != nullptr) |
687 | Diagnostic(SourceMgr->GetMessage(Loc: Range.Start, Kind, Msg: Message, Ranges: Range)); |
688 | else |
689 | Diagnostic(llvm::SMDiagnostic("" , Kind, Message)); |
690 | } |
691 | }; |
692 | |
693 | } // namespace |
694 | |
695 | CompiledFragment Fragment::compile(DiagnosticCallback D) && { |
696 | llvm::StringRef ConfigFile = "<unknown>" ; |
697 | std::pair<unsigned, unsigned> LineCol = {0, 0}; |
698 | if (auto *SM = Source.Manager.get()) { |
699 | unsigned BufID = SM->getMainFileID(); |
700 | LineCol = SM->getLineAndColumn(Loc: Source.Location, BufferID: BufID); |
701 | ConfigFile = SM->getBufferInfo(i: BufID).Buffer->getBufferIdentifier(); |
702 | } |
703 | trace::Span Tracer("ConfigCompile" ); |
704 | SPAN_ATTACH(Tracer, "ConfigFile" , ConfigFile); |
705 | auto Result = std::make_shared<CompiledFragmentImpl>(); |
706 | vlog(Fmt: "Config fragment: compiling {0}:{1} -> {2} (trusted={3})" , Vals&: ConfigFile, |
707 | Vals&: LineCol.first, Vals: Result.get(), Vals&: Source.Trusted); |
708 | |
709 | FragmentCompiler{*Result, D, Source.Manager.get()}.compile(F: std::move(*this)); |
710 | // Return as cheaply-copyable wrapper. |
711 | return [Result(std::move(Result))](const Params &P, Config &C) { |
712 | return (*Result)(P, C); |
713 | }; |
714 | } |
715 | |
716 | } // namespace config |
717 | } // namespace clangd |
718 | } // namespace clang |
719 | |