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 <memory> |
47 | #include <optional> |
48 | #include <string> |
49 | #include <vector> |
50 | |
51 | namespace clang { |
52 | namespace clangd { |
53 | namespace config { |
54 | namespace { |
55 | |
56 | // Returns an empty stringref if Path is not under FragmentDir. Returns Path |
57 | // as-is when FragmentDir is empty. |
58 | llvm::StringRef configRelative(llvm::StringRef Path, |
59 | llvm::StringRef FragmentDir) { |
60 | if (FragmentDir.empty()) |
61 | return Path; |
62 | if (!Path.consume_front(Prefix: FragmentDir)) |
63 | return llvm::StringRef(); |
64 | return Path.empty() ? "." : Path; |
65 | } |
66 | |
67 | struct 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. |
94 | struct 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(Error&: RegexError)) { |
112 | diag(Kind: Error, Message: "Invalid regex " + Anchored + ": " + RegexError, Range: 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: *Path)) |
122 | return *Path; |
123 | if (FragmentDirectory.empty()) { |
124 | diag(Kind: Error, |
125 | Message: llvm::formatv( |
126 | Fmt: "{0} must be an absolute path, because this fragment is not " |
127 | "associated with any directory." , |
128 | Vals&: Description) |
129 | .str(), |
130 | Range: Path.Range); |
131 | return std::nullopt; |
132 | } |
133 | llvm::SmallString<256> AbsPath = llvm::StringRef(*Path); |
134 | llvm::sys::fs::make_absolute(current_directory: FragmentDirectory, path&: AbsPath); |
135 | llvm::sys::path::native(path&: AbsPath, style: 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(Elt: 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 | Kind: Warning, |
164 | Message: llvm::formatv(Fmt: "Invalid {0} value '{1}'. Valid values are {2}." , |
165 | Vals&: EnumName, Vals: *Input, Vals: llvm::join(R&: ValidValues, Separator: ", " )) |
166 | .str(), |
167 | Range: 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(path: F.Source.Directory); |
189 | if (FragmentDirectory.back() != '/') |
190 | FragmentDirectory += '/'; |
191 | } |
192 | compile(F: std::move(F.If)); |
193 | compile(F: std::move(F.CompileFlags)); |
194 | compile(F: std::move(F.Index)); |
195 | compile(F: std::move(F.Diagnostics)); |
196 | compile(F: std::move(F.Completion)); |
197 | compile(F: std::move(F.Hover)); |
198 | compile(F: std::move(F.InlayHints)); |
199 | compile(F: std::move(F.SemanticTokens)); |
200 | compile(F: std::move(F.Style)); |
201 | } |
202 | |
203 | void compile(Fragment::IfBlock &&F) { |
204 | if (F.HasUnrecognizedCondition) |
205 | Out.Conditions.push_back(x: [&](const Params &) { return false; }); |
206 | |
207 | #ifdef CLANGD_PATH_CASE_INSENSITIVE |
208 | llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase; |
209 | #else |
210 | llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags; |
211 | #endif |
212 | |
213 | auto PathMatch = std::make_unique<std::vector<llvm::Regex>>(); |
214 | for (auto &Entry : F.PathMatch) { |
215 | if (auto RE = compileRegex(Text: Entry, Flags)) |
216 | PathMatch->push_back(x: std::move(*RE)); |
217 | } |
218 | if (!PathMatch->empty()) { |
219 | Out.Conditions.push_back( |
220 | x: [PathMatch(std::move(PathMatch)), |
221 | FragmentDir(FragmentDirectory)](const Params &P) { |
222 | if (P.Path.empty()) |
223 | return false; |
224 | llvm::StringRef Path = configRelative(Path: P.Path, FragmentDir); |
225 | // Ignore the file if it is not nested under Fragment. |
226 | if (Path.empty()) |
227 | return false; |
228 | return llvm::any_of(Range&: *PathMatch, P: [&](const llvm::Regex &RE) { |
229 | return RE.match(String: Path); |
230 | }); |
231 | }); |
232 | } |
233 | |
234 | auto PathExclude = std::make_unique<std::vector<llvm::Regex>>(); |
235 | for (auto &Entry : F.PathExclude) { |
236 | if (auto RE = compileRegex(Text: Entry, Flags)) |
237 | PathExclude->push_back(x: std::move(*RE)); |
238 | } |
239 | if (!PathExclude->empty()) { |
240 | Out.Conditions.push_back( |
241 | x: [PathExclude(std::move(PathExclude)), |
242 | FragmentDir(FragmentDirectory)](const Params &P) { |
243 | if (P.Path.empty()) |
244 | return false; |
245 | llvm::StringRef Path = configRelative(Path: P.Path, FragmentDir); |
246 | // Ignore the file if it is not nested under Fragment. |
247 | if (Path.empty()) |
248 | return true; |
249 | return llvm::none_of(Range&: *PathExclude, P: [&](const llvm::Regex &RE) { |
250 | return RE.match(String: Path); |
251 | }); |
252 | }); |
253 | } |
254 | } |
255 | |
256 | void compile(Fragment::CompileFlagsBlock &&F) { |
257 | if (F.Compiler) |
258 | Out.Apply.push_back( |
259 | x: [Compiler(std::move(**F.Compiler))](const Params &, Config &C) { |
260 | C.CompileFlags.Edits.push_back( |
261 | x: [Compiler](std::vector<std::string> &Args) { |
262 | if (!Args.empty()) |
263 | Args.front() = Compiler; |
264 | }); |
265 | }); |
266 | |
267 | if (!F.Remove.empty()) { |
268 | auto Remove = std::make_shared<ArgStripper>(); |
269 | for (auto &A : F.Remove) |
270 | Remove->strip(Arg: *A); |
271 | Out.Apply.push_back(x: [Remove(std::shared_ptr<const ArgStripper>( |
272 | std::move(Remove)))](const Params &, Config &C) { |
273 | C.CompileFlags.Edits.push_back( |
274 | x: [Remove](std::vector<std::string> &Args) { |
275 | Remove->process(Args); |
276 | }); |
277 | }); |
278 | } |
279 | |
280 | if (!F.Add.empty()) { |
281 | std::vector<std::string> Add; |
282 | for (auto &A : F.Add) |
283 | Add.push_back(x: std::move(*A)); |
284 | Out.Apply.push_back(x: [Add(std::move(Add))](const Params &, Config &C) { |
285 | C.CompileFlags.Edits.push_back(x: [Add](std::vector<std::string> &Args) { |
286 | // The point to insert at. Just append when `--` isn't present. |
287 | auto It = llvm::find(Range&: Args, Val: "--" ); |
288 | Args.insert(position: It, first: Add.begin(), last: Add.end()); |
289 | }); |
290 | }); |
291 | } |
292 | |
293 | if (F.CompilationDatabase) { |
294 | std::optional<Config::CDBSearchSpec> Spec; |
295 | if (**F.CompilationDatabase == "Ancestors" ) { |
296 | Spec.emplace(); |
297 | Spec->Policy = Config::CDBSearchSpec::Ancestors; |
298 | } else if (**F.CompilationDatabase == "None" ) { |
299 | Spec.emplace(); |
300 | Spec->Policy = Config::CDBSearchSpec::NoCDBSearch; |
301 | } else { |
302 | if (auto Path = |
303 | makeAbsolute(Path: *F.CompilationDatabase, Description: "CompilationDatabase" , |
304 | Style: llvm::sys::path::Style::native)) { |
305 | // Drop trailing slash to put the path in canonical form. |
306 | // Should makeAbsolute do this? |
307 | llvm::StringRef Rel = llvm::sys::path::relative_path(path: *Path); |
308 | if (!Rel.empty() && llvm::sys::path::is_separator(value: Rel.back())) |
309 | Path->pop_back(); |
310 | |
311 | Spec.emplace(); |
312 | Spec->Policy = Config::CDBSearchSpec::FixedDir; |
313 | Spec->FixedCDBPath = std::move(Path); |
314 | } |
315 | } |
316 | if (Spec) |
317 | Out.Apply.push_back( |
318 | x: [Spec(std::move(*Spec))](const Params &, Config &C) { |
319 | C.CompileFlags.CDBSearch = Spec; |
320 | }); |
321 | } |
322 | } |
323 | |
324 | void compile(Fragment::IndexBlock &&F) { |
325 | if (F.Background) { |
326 | if (auto Val = |
327 | compileEnum<Config::BackgroundPolicy>(EnumName: "Background" , In: *F.Background) |
328 | .map(Name: "Build" , Value: Config::BackgroundPolicy::Build) |
329 | .map(Name: "Skip" , Value: Config::BackgroundPolicy::Skip) |
330 | .value()) |
331 | Out.Apply.push_back( |
332 | x: [Val](const Params &, Config &C) { C.Index.Background = *Val; }); |
333 | } |
334 | if (F.External) |
335 | compile(External: std::move(**F.External), BlockRange: F.External->Range); |
336 | if (F.StandardLibrary) |
337 | Out.Apply.push_back( |
338 | x: [Val(**F.StandardLibrary)](const Params &, Config &C) { |
339 | C.Index.StandardLibrary = Val; |
340 | }); |
341 | } |
342 | |
343 | void compile(Fragment::IndexBlock::ExternalBlock &&External, |
344 | llvm::SMRange BlockRange) { |
345 | if (External.Server && !Trusted) { |
346 | diag(Kind: Error, |
347 | Message: "Remote index may not be specified by untrusted configuration. " |
348 | "Copy this into user config to use it." , |
349 | Range: External.Server->Range); |
350 | return; |
351 | } |
352 | #ifndef CLANGD_ENABLE_REMOTE |
353 | if (External.Server) { |
354 | elog("Clangd isn't compiled with remote index support, ignoring Server: " |
355 | "{0}" , |
356 | *External.Server); |
357 | External.Server.reset(); |
358 | } |
359 | #endif |
360 | // Make sure exactly one of the Sources is set. |
361 | unsigned SourceCount = External.File.has_value() + |
362 | External.Server.has_value() + *External.IsNone; |
363 | if (SourceCount != 1) { |
364 | diag(Kind: Error, Message: "Exactly one of File, Server or None must be set." , |
365 | Range: BlockRange); |
366 | return; |
367 | } |
368 | Config::ExternalIndexSpec Spec; |
369 | if (External.Server) { |
370 | Spec.Kind = Config::ExternalIndexSpec::Server; |
371 | Spec.Location = std::move(**External.Server); |
372 | } else if (External.File) { |
373 | Spec.Kind = Config::ExternalIndexSpec::File; |
374 | auto AbsPath = makeAbsolute(Path: std::move(*External.File), Description: "File" , |
375 | Style: llvm::sys::path::Style::native); |
376 | if (!AbsPath) |
377 | return; |
378 | Spec.Location = std::move(*AbsPath); |
379 | } else { |
380 | assert(*External.IsNone); |
381 | Spec.Kind = Config::ExternalIndexSpec::None; |
382 | } |
383 | if (Spec.Kind != Config::ExternalIndexSpec::None) { |
384 | // Make sure MountPoint is an absolute path with forward slashes. |
385 | if (!External.MountPoint) |
386 | External.MountPoint.emplace(args&: FragmentDirectory); |
387 | if ((**External.MountPoint).empty()) { |
388 | diag(Kind: Error, Message: "A mountpoint is required." , Range: BlockRange); |
389 | return; |
390 | } |
391 | auto AbsPath = makeAbsolute(Path: std::move(*External.MountPoint), Description: "MountPoint" , |
392 | Style: llvm::sys::path::Style::posix); |
393 | if (!AbsPath) |
394 | return; |
395 | Spec.MountPoint = std::move(*AbsPath); |
396 | } |
397 | Out.Apply.push_back(x: [Spec(std::move(Spec))](const Params &P, Config &C) { |
398 | if (Spec.Kind == Config::ExternalIndexSpec::None) { |
399 | C.Index.External = Spec; |
400 | return; |
401 | } |
402 | if (P.Path.empty() || !pathStartsWith(Ancestor: Spec.MountPoint, Path: P.Path, |
403 | Style: llvm::sys::path::Style::posix)) |
404 | return; |
405 | C.Index.External = Spec; |
406 | // Disable background indexing for the files under the mountpoint. |
407 | // Note that this will overwrite statements in any previous fragments |
408 | // (including the current one). |
409 | C.Index.Background = Config::BackgroundPolicy::Skip; |
410 | }); |
411 | } |
412 | |
413 | void compile(Fragment::DiagnosticsBlock &&F) { |
414 | std::vector<std::string> Normalized; |
415 | for (const auto &Suppressed : F.Suppress) { |
416 | if (*Suppressed == "*" ) { |
417 | Out.Apply.push_back(x: [&](const Params &, Config &C) { |
418 | C.Diagnostics.SuppressAll = true; |
419 | C.Diagnostics.Suppress.clear(); |
420 | }); |
421 | return; |
422 | } |
423 | Normalized.push_back(x: normalizeSuppressedCode(*Suppressed).str()); |
424 | } |
425 | if (!Normalized.empty()) |
426 | Out.Apply.push_back( |
427 | x: [Normalized(std::move(Normalized))](const Params &, Config &C) { |
428 | if (C.Diagnostics.SuppressAll) |
429 | return; |
430 | for (llvm::StringRef N : Normalized) |
431 | C.Diagnostics.Suppress.insert(key: N); |
432 | }); |
433 | |
434 | if (F.UnusedIncludes) { |
435 | auto Val = compileEnum<Config::IncludesPolicy>(EnumName: "UnusedIncludes" , |
436 | In: **F.UnusedIncludes) |
437 | .map(Name: "Strict" , Value: Config::IncludesPolicy::Strict) |
438 | .map(Name: "None" , Value: Config::IncludesPolicy::None) |
439 | .value(); |
440 | if (!Val && **F.UnusedIncludes == "Experiment" ) { |
441 | diag(Kind: Warning, |
442 | Message: "Experiment is deprecated for UnusedIncludes, use Strict instead." , |
443 | Range: F.UnusedIncludes->Range); |
444 | Val = Config::IncludesPolicy::Strict; |
445 | } |
446 | if (Val) { |
447 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
448 | C.Diagnostics.UnusedIncludes = *Val; |
449 | }); |
450 | } |
451 | } |
452 | |
453 | if (F.MissingIncludes) |
454 | if (auto Val = compileEnum<Config::IncludesPolicy>(EnumName: "MissingIncludes" , |
455 | In: **F.MissingIncludes) |
456 | .map(Name: "Strict" , Value: Config::IncludesPolicy::Strict) |
457 | .map(Name: "None" , Value: Config::IncludesPolicy::None) |
458 | .value()) |
459 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
460 | C.Diagnostics.MissingIncludes = *Val; |
461 | }); |
462 | |
463 | compile(F: std::move(F.Includes)); |
464 | compile(F: std::move(F.ClangTidy)); |
465 | } |
466 | |
467 | void compile(Fragment::StyleBlock &&F) { |
468 | if (!F.FullyQualifiedNamespaces.empty()) { |
469 | std::vector<std::string> FullyQualifiedNamespaces; |
470 | for (auto &N : F.FullyQualifiedNamespaces) { |
471 | // Normalize the data by dropping both leading and trailing :: |
472 | StringRef Namespace(*N); |
473 | Namespace.consume_front(Prefix: "::" ); |
474 | Namespace.consume_back(Suffix: "::" ); |
475 | FullyQualifiedNamespaces.push_back(x: Namespace.str()); |
476 | } |
477 | Out.Apply.push_back(x: [FullyQualifiedNamespaces( |
478 | std::move(FullyQualifiedNamespaces))]( |
479 | const Params &, Config &C) { |
480 | C.Style.FullyQualifiedNamespaces.insert( |
481 | position: C.Style.FullyQualifiedNamespaces.begin(), |
482 | first: FullyQualifiedNamespaces.begin(), last: FullyQualifiedNamespaces.end()); |
483 | }); |
484 | } |
485 | auto QuotedFilter = compileHeaderRegexes(HeaderPatterns: F.QuotedHeaders); |
486 | if (QuotedFilter.has_value()) { |
487 | Out.Apply.push_back( |
488 | x: [QuotedFilter = *QuotedFilter](const Params &, Config &C) { |
489 | C.Style.QuotedHeaders.emplace_back(args: QuotedFilter); |
490 | }); |
491 | } |
492 | auto AngledFilter = compileHeaderRegexes(HeaderPatterns: F.AngledHeaders); |
493 | if (AngledFilter.has_value()) { |
494 | Out.Apply.push_back( |
495 | x: [AngledFilter = *AngledFilter](const Params &, Config &C) { |
496 | C.Style.AngledHeaders.emplace_back(args: AngledFilter); |
497 | }); |
498 | } |
499 | } |
500 | |
501 | auto (llvm::ArrayRef<Located<std::string>> ) |
502 | -> std::optional<std::function<bool(llvm::StringRef)>> { |
503 | // TODO: Share this code with Diagnostics.Includes.IgnoreHeader |
504 | #ifdef CLANGD_PATH_CASE_INSENSITIVE |
505 | static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase; |
506 | #else |
507 | static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags; |
508 | #endif |
509 | auto Filters = std::make_shared<std::vector<llvm::Regex>>(); |
510 | for (auto & : HeaderPatterns) { |
511 | // Anchor on the right. |
512 | std::string AnchoredPattern = "(" + *HeaderPattern + ")$" ; |
513 | llvm::Regex CompiledRegex(AnchoredPattern, Flags); |
514 | std::string RegexError; |
515 | if (!CompiledRegex.isValid(Error&: RegexError)) { |
516 | diag(Kind: Warning, |
517 | Message: llvm::formatv(Fmt: "Invalid regular expression '{0}': {1}" , |
518 | Vals: *HeaderPattern, Vals&: RegexError) |
519 | .str(), |
520 | Range: HeaderPattern.Range); |
521 | continue; |
522 | } |
523 | Filters->push_back(x: std::move(CompiledRegex)); |
524 | } |
525 | if (Filters->empty()) |
526 | return std::nullopt; |
527 | auto Filter = [Filters](llvm::StringRef Path) { |
528 | for (auto &Regex : *Filters) |
529 | if (Regex.match(String: Path)) |
530 | return true; |
531 | return false; |
532 | }; |
533 | return Filter; |
534 | } |
535 | |
536 | void appendTidyCheckSpec(std::string &CurSpec, |
537 | const Located<std::string> &Arg, bool IsPositive) { |
538 | StringRef Str = StringRef(*Arg).trim(); |
539 | // Don't support negating here, its handled if the item is in the Add or |
540 | // Remove list. |
541 | if (Str.starts_with(Prefix: "-" ) || Str.contains(C: ',')) { |
542 | diag(Kind: Error, Message: "Invalid clang-tidy check name" , Range: Arg.Range); |
543 | return; |
544 | } |
545 | if (!Str.contains(C: '*')) { |
546 | if (!isRegisteredTidyCheck(Check: Str)) { |
547 | diag(Kind: Warning, |
548 | Message: llvm::formatv(Fmt: "clang-tidy check '{0}' was not found" , Vals&: Str).str(), |
549 | Range: Arg.Range); |
550 | return; |
551 | } |
552 | auto Fast = isFastTidyCheck(Check: Str); |
553 | if (!Fast.has_value()) { |
554 | diag(Kind: Warning, |
555 | Message: llvm::formatv( |
556 | Fmt: "Latency of clang-tidy check '{0}' is not known. " |
557 | "It will only run if ClangTidy.FastCheckFilter is Loose or None" , |
558 | Vals&: Str) |
559 | .str(), |
560 | Range: Arg.Range); |
561 | } else if (!*Fast) { |
562 | diag(Kind: Warning, |
563 | Message: llvm::formatv( |
564 | Fmt: "clang-tidy check '{0}' is slow. " |
565 | "It will only run if ClangTidy.FastCheckFilter is None" , |
566 | Vals&: Str) |
567 | .str(), |
568 | Range: Arg.Range); |
569 | } |
570 | } |
571 | CurSpec += ','; |
572 | if (!IsPositive) |
573 | CurSpec += '-'; |
574 | CurSpec += Str; |
575 | } |
576 | |
577 | void compile(Fragment::DiagnosticsBlock::ClangTidyBlock &&F) { |
578 | std::string Checks; |
579 | for (auto &CheckGlob : F.Add) |
580 | appendTidyCheckSpec(CurSpec&: Checks, Arg: CheckGlob, IsPositive: true); |
581 | |
582 | for (auto &CheckGlob : F.Remove) |
583 | appendTidyCheckSpec(CurSpec&: Checks, Arg: CheckGlob, IsPositive: false); |
584 | |
585 | if (!Checks.empty()) |
586 | Out.Apply.push_back( |
587 | x: [Checks = std::move(Checks)](const Params &, Config &C) { |
588 | C.Diagnostics.ClangTidy.Checks.append( |
589 | str: Checks, |
590 | pos: C.Diagnostics.ClangTidy.Checks.empty() ? /*skip comma*/ 1 : 0, |
591 | n: std::string::npos); |
592 | }); |
593 | if (!F.CheckOptions.empty()) { |
594 | std::vector<std::pair<std::string, std::string>> CheckOptions; |
595 | for (auto &Opt : F.CheckOptions) |
596 | CheckOptions.emplace_back(args: std::move(*Opt.first), |
597 | args: std::move(*Opt.second)); |
598 | Out.Apply.push_back( |
599 | x: [CheckOptions = std::move(CheckOptions)](const Params &, Config &C) { |
600 | for (auto &StringPair : CheckOptions) |
601 | C.Diagnostics.ClangTidy.CheckOptions.insert_or_assign( |
602 | Key: StringPair.first, Val: StringPair.second); |
603 | }); |
604 | } |
605 | if (F.FastCheckFilter.has_value()) |
606 | if (auto Val = compileEnum<Config::FastCheckPolicy>(EnumName: "FastCheckFilter" , |
607 | In: *F.FastCheckFilter) |
608 | .map(Name: "Strict" , Value: Config::FastCheckPolicy::Strict) |
609 | .map(Name: "Loose" , Value: Config::FastCheckPolicy::Loose) |
610 | .map(Name: "None" , Value: Config::FastCheckPolicy::None) |
611 | .value()) |
612 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
613 | C.Diagnostics.ClangTidy.FastCheckFilter = *Val; |
614 | }); |
615 | } |
616 | |
617 | void compile(Fragment::DiagnosticsBlock::IncludesBlock &&F) { |
618 | #ifdef CLANGD_PATH_CASE_INSENSITIVE |
619 | static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase; |
620 | #else |
621 | static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags; |
622 | #endif |
623 | std::shared_ptr<std::vector<llvm::Regex>> Filters; |
624 | if (!F.IgnoreHeader.empty()) { |
625 | Filters = std::make_shared<std::vector<llvm::Regex>>(); |
626 | for (auto & : F.IgnoreHeader) { |
627 | // Anchor on the right. |
628 | std::string AnchoredPattern = "(" + *HeaderPattern + ")$" ; |
629 | llvm::Regex CompiledRegex(AnchoredPattern, Flags); |
630 | std::string RegexError; |
631 | if (!CompiledRegex.isValid(Error&: RegexError)) { |
632 | diag(Kind: Warning, |
633 | Message: llvm::formatv(Fmt: "Invalid regular expression '{0}': {1}" , |
634 | Vals&: *HeaderPattern, Vals&: RegexError) |
635 | .str(), |
636 | Range: HeaderPattern.Range); |
637 | continue; |
638 | } |
639 | Filters->push_back(x: std::move(CompiledRegex)); |
640 | } |
641 | } |
642 | // Optional to override the resulting AnalyzeAngledIncludes |
643 | // only if it's explicitly set in the current fragment. |
644 | // Otherwise it's inherited from parent fragment. |
645 | std::optional<bool> AnalyzeAngledIncludes; |
646 | if (F.AnalyzeAngledIncludes.has_value()) |
647 | AnalyzeAngledIncludes = **F.AnalyzeAngledIncludes; |
648 | if (!Filters && !AnalyzeAngledIncludes.has_value()) |
649 | return; |
650 | Out.Apply.push_back(x: [Filters = std::move(Filters), |
651 | AnalyzeAngledIncludes](const Params &, Config &C) { |
652 | if (Filters) { |
653 | auto Filter = [Filters](llvm::StringRef Path) { |
654 | for (auto &Regex : *Filters) |
655 | if (Regex.match(String: Path)) |
656 | return true; |
657 | return false; |
658 | }; |
659 | C.Diagnostics.Includes.IgnoreHeader.emplace_back(args: std::move(Filter)); |
660 | } |
661 | if (AnalyzeAngledIncludes.has_value()) |
662 | C.Diagnostics.Includes.AnalyzeAngledIncludes = *AnalyzeAngledIncludes; |
663 | }); |
664 | } |
665 | |
666 | void compile(Fragment::CompletionBlock &&F) { |
667 | if (F.AllScopes) { |
668 | Out.Apply.push_back( |
669 | x: [AllScopes(**F.AllScopes)](const Params &, Config &C) { |
670 | C.Completion.AllScopes = AllScopes; |
671 | }); |
672 | } |
673 | if (F.ArgumentLists) { |
674 | if (auto Val = |
675 | compileEnum<Config::ArgumentListsPolicy>(EnumName: "ArgumentLists" , |
676 | In: *F.ArgumentLists) |
677 | .map(Name: "None" , Value: Config::ArgumentListsPolicy::None) |
678 | .map(Name: "OpenDelimiter" , |
679 | Value: Config::ArgumentListsPolicy::OpenDelimiter) |
680 | .map(Name: "Delimiters" , Value: Config::ArgumentListsPolicy::Delimiters) |
681 | .map(Name: "FullPlaceholders" , |
682 | Value: Config::ArgumentListsPolicy::FullPlaceholders) |
683 | .value()) |
684 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
685 | C.Completion.ArgumentLists = *Val; |
686 | }); |
687 | } |
688 | } |
689 | |
690 | void compile(Fragment::HoverBlock &&F) { |
691 | if (F.ShowAKA) { |
692 | Out.Apply.push_back(x: [ShowAKA(**F.ShowAKA)](const Params &, Config &C) { |
693 | C.Hover.ShowAKA = ShowAKA; |
694 | }); |
695 | } |
696 | } |
697 | |
698 | void compile(Fragment::InlayHintsBlock &&F) { |
699 | if (F.Enabled) |
700 | Out.Apply.push_back(x: [Value(**F.Enabled)](const Params &, Config &C) { |
701 | C.InlayHints.Enabled = Value; |
702 | }); |
703 | if (F.ParameterNames) |
704 | Out.Apply.push_back( |
705 | x: [Value(**F.ParameterNames)](const Params &, Config &C) { |
706 | C.InlayHints.Parameters = Value; |
707 | }); |
708 | if (F.DeducedTypes) |
709 | Out.Apply.push_back(x: [Value(**F.DeducedTypes)](const Params &, Config &C) { |
710 | C.InlayHints.DeducedTypes = Value; |
711 | }); |
712 | if (F.Designators) |
713 | Out.Apply.push_back(x: [Value(**F.Designators)](const Params &, Config &C) { |
714 | C.InlayHints.Designators = Value; |
715 | }); |
716 | if (F.BlockEnd) |
717 | Out.Apply.push_back(x: [Value(**F.BlockEnd)](const Params &, Config &C) { |
718 | C.InlayHints.BlockEnd = Value; |
719 | }); |
720 | if (F.DefaultArguments) |
721 | Out.Apply.push_back( |
722 | x: [Value(**F.DefaultArguments)](const Params &, Config &C) { |
723 | C.InlayHints.DefaultArguments = Value; |
724 | }); |
725 | if (F.TypeNameLimit) |
726 | Out.Apply.push_back( |
727 | x: [Value(**F.TypeNameLimit)](const Params &, Config &C) { |
728 | C.InlayHints.TypeNameLimit = Value; |
729 | }); |
730 | } |
731 | |
732 | void compile(Fragment::SemanticTokensBlock &&F) { |
733 | if (!F.DisabledKinds.empty()) { |
734 | std::vector<std::string> DisabledKinds; |
735 | for (auto &Kind : F.DisabledKinds) |
736 | DisabledKinds.push_back(x: std::move(*Kind)); |
737 | |
738 | Out.Apply.push_back( |
739 | x: [DisabledKinds(std::move(DisabledKinds))](const Params &, Config &C) { |
740 | for (auto &Kind : DisabledKinds) { |
741 | auto It = llvm::find(Range&: C.SemanticTokens.DisabledKinds, Val: Kind); |
742 | if (It == C.SemanticTokens.DisabledKinds.end()) |
743 | C.SemanticTokens.DisabledKinds.push_back(x: std::move(Kind)); |
744 | } |
745 | }); |
746 | } |
747 | if (!F.DisabledModifiers.empty()) { |
748 | std::vector<std::string> DisabledModifiers; |
749 | for (auto &Kind : F.DisabledModifiers) |
750 | DisabledModifiers.push_back(x: std::move(*Kind)); |
751 | |
752 | Out.Apply.push_back(x: [DisabledModifiers(std::move(DisabledModifiers))]( |
753 | const Params &, Config &C) { |
754 | for (auto &Kind : DisabledModifiers) { |
755 | auto It = llvm::find(Range&: C.SemanticTokens.DisabledModifiers, Val: Kind); |
756 | if (It == C.SemanticTokens.DisabledModifiers.end()) |
757 | C.SemanticTokens.DisabledModifiers.push_back(x: std::move(Kind)); |
758 | } |
759 | }); |
760 | } |
761 | } |
762 | |
763 | constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error; |
764 | constexpr static llvm::SourceMgr::DiagKind Warning = |
765 | llvm::SourceMgr::DK_Warning; |
766 | void diag(llvm::SourceMgr::DiagKind Kind, llvm::StringRef Message, |
767 | llvm::SMRange Range) { |
768 | if (Range.isValid() && SourceMgr != nullptr) |
769 | Diagnostic(SourceMgr->GetMessage(Loc: Range.Start, Kind, Msg: Message, Ranges: Range)); |
770 | else |
771 | Diagnostic(llvm::SMDiagnostic("" , Kind, Message)); |
772 | } |
773 | }; |
774 | |
775 | } // namespace |
776 | |
777 | CompiledFragment Fragment::compile(DiagnosticCallback D) && { |
778 | llvm::StringRef ConfigFile = "<unknown>" ; |
779 | std::pair<unsigned, unsigned> LineCol = {0, 0}; |
780 | if (auto *SM = Source.Manager.get()) { |
781 | unsigned BufID = SM->getMainFileID(); |
782 | LineCol = SM->getLineAndColumn(Loc: Source.Location, BufferID: BufID); |
783 | ConfigFile = SM->getBufferInfo(i: BufID).Buffer->getBufferIdentifier(); |
784 | } |
785 | trace::Span Tracer("ConfigCompile" ); |
786 | SPAN_ATTACH(Tracer, "ConfigFile" , ConfigFile); |
787 | auto Result = std::make_shared<CompiledFragmentImpl>(); |
788 | vlog(Fmt: "Config fragment: compiling {0}:{1} -> {2} (trusted={3})" , Vals&: ConfigFile, |
789 | Vals&: LineCol.first, Vals: Result.get(), Vals&: Source.Trusted); |
790 | |
791 | FragmentCompiler{*Result, D, Source.Manager.get()}.compile(F: std::move(*this)); |
792 | // Return as cheaply-copyable wrapper. |
793 | return [Result(std::move(Result))](const Params &P, Config &C) { |
794 | return (*Result)(P, C); |
795 | }; |
796 | } |
797 | |
798 | } // namespace config |
799 | } // namespace clangd |
800 | } // namespace clang |
801 | |