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.BuiltinHeaders) { |
294 | if (auto Val = |
295 | compileEnum<Config::BuiltinHeaderPolicy>(EnumName: "BuiltinHeaders" , |
296 | In: *F.BuiltinHeaders) |
297 | .map(Name: "Clangd" , Value: Config::BuiltinHeaderPolicy::Clangd) |
298 | .map(Name: "QueryDriver" , Value: Config::BuiltinHeaderPolicy::QueryDriver) |
299 | .value()) |
300 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
301 | C.CompileFlags.BuiltinHeaders = *Val; |
302 | }); |
303 | } |
304 | |
305 | if (F.CompilationDatabase) { |
306 | std::optional<Config::CDBSearchSpec> Spec; |
307 | if (**F.CompilationDatabase == "Ancestors" ) { |
308 | Spec.emplace(); |
309 | Spec->Policy = Config::CDBSearchSpec::Ancestors; |
310 | } else if (**F.CompilationDatabase == "None" ) { |
311 | Spec.emplace(); |
312 | Spec->Policy = Config::CDBSearchSpec::NoCDBSearch; |
313 | } else { |
314 | if (auto Path = |
315 | makeAbsolute(Path: *F.CompilationDatabase, Description: "CompilationDatabase" , |
316 | Style: llvm::sys::path::Style::native)) { |
317 | // Drop trailing slash to put the path in canonical form. |
318 | // Should makeAbsolute do this? |
319 | llvm::StringRef Rel = llvm::sys::path::relative_path(path: *Path); |
320 | if (!Rel.empty() && llvm::sys::path::is_separator(value: Rel.back())) |
321 | Path->pop_back(); |
322 | |
323 | Spec.emplace(); |
324 | Spec->Policy = Config::CDBSearchSpec::FixedDir; |
325 | Spec->FixedCDBPath = std::move(Path); |
326 | } |
327 | } |
328 | if (Spec) |
329 | Out.Apply.push_back( |
330 | x: [Spec(std::move(*Spec))](const Params &, Config &C) { |
331 | C.CompileFlags.CDBSearch = Spec; |
332 | }); |
333 | } |
334 | } |
335 | |
336 | void compile(Fragment::IndexBlock &&F) { |
337 | if (F.Background) { |
338 | if (auto Val = |
339 | compileEnum<Config::BackgroundPolicy>(EnumName: "Background" , In: *F.Background) |
340 | .map(Name: "Build" , Value: Config::BackgroundPolicy::Build) |
341 | .map(Name: "Skip" , Value: Config::BackgroundPolicy::Skip) |
342 | .value()) |
343 | Out.Apply.push_back( |
344 | x: [Val](const Params &, Config &C) { C.Index.Background = *Val; }); |
345 | } |
346 | if (F.External) |
347 | compile(External: std::move(**F.External), BlockRange: F.External->Range); |
348 | if (F.StandardLibrary) |
349 | Out.Apply.push_back( |
350 | x: [Val(**F.StandardLibrary)](const Params &, Config &C) { |
351 | C.Index.StandardLibrary = Val; |
352 | }); |
353 | } |
354 | |
355 | void compile(Fragment::IndexBlock::ExternalBlock &&External, |
356 | llvm::SMRange BlockRange) { |
357 | if (External.Server && !Trusted) { |
358 | diag(Kind: Error, |
359 | Message: "Remote index may not be specified by untrusted configuration. " |
360 | "Copy this into user config to use it." , |
361 | Range: External.Server->Range); |
362 | return; |
363 | } |
364 | #ifndef CLANGD_ENABLE_REMOTE |
365 | if (External.Server) { |
366 | elog("Clangd isn't compiled with remote index support, ignoring Server: " |
367 | "{0}" , |
368 | *External.Server); |
369 | External.Server.reset(); |
370 | } |
371 | #endif |
372 | // Make sure exactly one of the Sources is set. |
373 | unsigned SourceCount = External.File.has_value() + |
374 | External.Server.has_value() + *External.IsNone; |
375 | if (SourceCount != 1) { |
376 | diag(Kind: Error, Message: "Exactly one of File, Server or None must be set." , |
377 | Range: BlockRange); |
378 | return; |
379 | } |
380 | Config::ExternalIndexSpec Spec; |
381 | if (External.Server) { |
382 | Spec.Kind = Config::ExternalIndexSpec::Server; |
383 | Spec.Location = std::move(**External.Server); |
384 | } else if (External.File) { |
385 | Spec.Kind = Config::ExternalIndexSpec::File; |
386 | auto AbsPath = makeAbsolute(Path: std::move(*External.File), Description: "File" , |
387 | Style: llvm::sys::path::Style::native); |
388 | if (!AbsPath) |
389 | return; |
390 | Spec.Location = std::move(*AbsPath); |
391 | } else { |
392 | assert(*External.IsNone); |
393 | Spec.Kind = Config::ExternalIndexSpec::None; |
394 | } |
395 | if (Spec.Kind != Config::ExternalIndexSpec::None) { |
396 | // Make sure MountPoint is an absolute path with forward slashes. |
397 | if (!External.MountPoint) |
398 | External.MountPoint.emplace(args&: FragmentDirectory); |
399 | if ((**External.MountPoint).empty()) { |
400 | diag(Kind: Error, Message: "A mountpoint is required." , Range: BlockRange); |
401 | return; |
402 | } |
403 | auto AbsPath = makeAbsolute(Path: std::move(*External.MountPoint), Description: "MountPoint" , |
404 | Style: llvm::sys::path::Style::posix); |
405 | if (!AbsPath) |
406 | return; |
407 | Spec.MountPoint = std::move(*AbsPath); |
408 | } |
409 | Out.Apply.push_back(x: [Spec(std::move(Spec))](const Params &P, Config &C) { |
410 | if (Spec.Kind == Config::ExternalIndexSpec::None) { |
411 | C.Index.External = Spec; |
412 | return; |
413 | } |
414 | if (P.Path.empty() || !pathStartsWith(Ancestor: Spec.MountPoint, Path: P.Path, |
415 | Style: llvm::sys::path::Style::posix)) |
416 | return; |
417 | C.Index.External = Spec; |
418 | // Disable background indexing for the files under the mountpoint. |
419 | // Note that this will overwrite statements in any previous fragments |
420 | // (including the current one). |
421 | C.Index.Background = Config::BackgroundPolicy::Skip; |
422 | }); |
423 | } |
424 | |
425 | void compile(Fragment::DiagnosticsBlock &&F) { |
426 | std::vector<std::string> Normalized; |
427 | for (const auto &Suppressed : F.Suppress) { |
428 | if (*Suppressed == "*" ) { |
429 | Out.Apply.push_back(x: [&](const Params &, Config &C) { |
430 | C.Diagnostics.SuppressAll = true; |
431 | C.Diagnostics.Suppress.clear(); |
432 | }); |
433 | return; |
434 | } |
435 | Normalized.push_back(x: normalizeSuppressedCode(*Suppressed).str()); |
436 | } |
437 | if (!Normalized.empty()) |
438 | Out.Apply.push_back( |
439 | x: [Normalized(std::move(Normalized))](const Params &, Config &C) { |
440 | if (C.Diagnostics.SuppressAll) |
441 | return; |
442 | C.Diagnostics.Suppress.insert_range(R: Normalized); |
443 | }); |
444 | |
445 | if (F.UnusedIncludes) { |
446 | auto Val = compileEnum<Config::IncludesPolicy>(EnumName: "UnusedIncludes" , |
447 | In: **F.UnusedIncludes) |
448 | .map(Name: "Strict" , Value: Config::IncludesPolicy::Strict) |
449 | .map(Name: "None" , Value: Config::IncludesPolicy::None) |
450 | .value(); |
451 | if (!Val && **F.UnusedIncludes == "Experiment" ) { |
452 | diag(Kind: Warning, |
453 | Message: "Experiment is deprecated for UnusedIncludes, use Strict instead." , |
454 | Range: F.UnusedIncludes->Range); |
455 | Val = Config::IncludesPolicy::Strict; |
456 | } |
457 | if (Val) { |
458 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
459 | C.Diagnostics.UnusedIncludes = *Val; |
460 | }); |
461 | } |
462 | } |
463 | |
464 | if (F.MissingIncludes) |
465 | if (auto Val = compileEnum<Config::IncludesPolicy>(EnumName: "MissingIncludes" , |
466 | In: **F.MissingIncludes) |
467 | .map(Name: "Strict" , Value: Config::IncludesPolicy::Strict) |
468 | .map(Name: "None" , Value: Config::IncludesPolicy::None) |
469 | .value()) |
470 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
471 | C.Diagnostics.MissingIncludes = *Val; |
472 | }); |
473 | |
474 | compile(F: std::move(F.Includes)); |
475 | compile(F: std::move(F.ClangTidy)); |
476 | } |
477 | |
478 | void compile(Fragment::StyleBlock &&F) { |
479 | if (!F.FullyQualifiedNamespaces.empty()) { |
480 | std::vector<std::string> FullyQualifiedNamespaces; |
481 | for (auto &N : F.FullyQualifiedNamespaces) { |
482 | // Normalize the data by dropping both leading and trailing :: |
483 | StringRef Namespace(*N); |
484 | Namespace.consume_front(Prefix: "::" ); |
485 | Namespace.consume_back(Suffix: "::" ); |
486 | FullyQualifiedNamespaces.push_back(x: Namespace.str()); |
487 | } |
488 | Out.Apply.push_back(x: [FullyQualifiedNamespaces( |
489 | std::move(FullyQualifiedNamespaces))]( |
490 | const Params &, Config &C) { |
491 | C.Style.FullyQualifiedNamespaces.insert( |
492 | position: C.Style.FullyQualifiedNamespaces.begin(), |
493 | first: FullyQualifiedNamespaces.begin(), last: FullyQualifiedNamespaces.end()); |
494 | }); |
495 | } |
496 | auto QuotedFilter = compileHeaderRegexes(HeaderPatterns: F.QuotedHeaders); |
497 | if (QuotedFilter.has_value()) { |
498 | Out.Apply.push_back( |
499 | x: [QuotedFilter = *QuotedFilter](const Params &, Config &C) { |
500 | C.Style.QuotedHeaders.emplace_back(args: QuotedFilter); |
501 | }); |
502 | } |
503 | auto AngledFilter = compileHeaderRegexes(HeaderPatterns: F.AngledHeaders); |
504 | if (AngledFilter.has_value()) { |
505 | Out.Apply.push_back( |
506 | x: [AngledFilter = *AngledFilter](const Params &, Config &C) { |
507 | C.Style.AngledHeaders.emplace_back(args: AngledFilter); |
508 | }); |
509 | } |
510 | } |
511 | |
512 | auto (llvm::ArrayRef<Located<std::string>> ) |
513 | -> std::optional<std::function<bool(llvm::StringRef)>> { |
514 | // TODO: Share this code with Diagnostics.Includes.IgnoreHeader |
515 | #ifdef CLANGD_PATH_CASE_INSENSITIVE |
516 | static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase; |
517 | #else |
518 | static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags; |
519 | #endif |
520 | auto Filters = std::make_shared<std::vector<llvm::Regex>>(); |
521 | for (auto & : HeaderPatterns) { |
522 | // Anchor on the right. |
523 | std::string AnchoredPattern = "(" + *HeaderPattern + ")$" ; |
524 | llvm::Regex CompiledRegex(AnchoredPattern, Flags); |
525 | std::string RegexError; |
526 | if (!CompiledRegex.isValid(Error&: RegexError)) { |
527 | diag(Kind: Warning, |
528 | Message: llvm::formatv(Fmt: "Invalid regular expression '{0}': {1}" , |
529 | Vals: *HeaderPattern, Vals&: RegexError) |
530 | .str(), |
531 | Range: HeaderPattern.Range); |
532 | continue; |
533 | } |
534 | Filters->push_back(x: std::move(CompiledRegex)); |
535 | } |
536 | if (Filters->empty()) |
537 | return std::nullopt; |
538 | auto Filter = [Filters = std::move(Filters)](llvm::StringRef Path) { |
539 | for (auto &Regex : *Filters) |
540 | if (Regex.match(String: Path)) |
541 | return true; |
542 | return false; |
543 | }; |
544 | return Filter; |
545 | } |
546 | |
547 | void appendTidyCheckSpec(std::string &CurSpec, |
548 | const Located<std::string> &Arg, bool IsPositive) { |
549 | StringRef Str = StringRef(*Arg).trim(); |
550 | // Don't support negating here, its handled if the item is in the Add or |
551 | // Remove list. |
552 | if (Str.starts_with(Prefix: "-" ) || Str.contains(C: ',')) { |
553 | diag(Kind: Error, Message: "Invalid clang-tidy check name" , Range: Arg.Range); |
554 | return; |
555 | } |
556 | if (!Str.contains(C: '*')) { |
557 | if (!isRegisteredTidyCheck(Check: Str)) { |
558 | diag(Kind: Warning, |
559 | Message: llvm::formatv(Fmt: "clang-tidy check '{0}' was not found" , Vals&: Str).str(), |
560 | Range: Arg.Range); |
561 | return; |
562 | } |
563 | auto Fast = isFastTidyCheck(Check: Str); |
564 | if (!Fast.has_value()) { |
565 | diag(Kind: Warning, |
566 | Message: llvm::formatv( |
567 | Fmt: "Latency of clang-tidy check '{0}' is not known. " |
568 | "It will only run if ClangTidy.FastCheckFilter is Loose or None" , |
569 | Vals&: Str) |
570 | .str(), |
571 | Range: Arg.Range); |
572 | } else if (!*Fast) { |
573 | diag(Kind: Warning, |
574 | Message: llvm::formatv( |
575 | Fmt: "clang-tidy check '{0}' is slow. " |
576 | "It will only run if ClangTidy.FastCheckFilter is None" , |
577 | Vals&: Str) |
578 | .str(), |
579 | Range: Arg.Range); |
580 | } |
581 | } |
582 | CurSpec += ','; |
583 | if (!IsPositive) |
584 | CurSpec += '-'; |
585 | CurSpec += Str; |
586 | } |
587 | |
588 | void compile(Fragment::DiagnosticsBlock::ClangTidyBlock &&F) { |
589 | std::string Checks; |
590 | for (auto &CheckGlob : F.Add) |
591 | appendTidyCheckSpec(CurSpec&: Checks, Arg: CheckGlob, IsPositive: true); |
592 | |
593 | for (auto &CheckGlob : F.Remove) |
594 | appendTidyCheckSpec(CurSpec&: Checks, Arg: CheckGlob, IsPositive: false); |
595 | |
596 | if (!Checks.empty()) |
597 | Out.Apply.push_back( |
598 | x: [Checks = std::move(Checks)](const Params &, Config &C) { |
599 | C.Diagnostics.ClangTidy.Checks.append( |
600 | str: Checks, |
601 | pos: C.Diagnostics.ClangTidy.Checks.empty() ? /*skip comma*/ 1 : 0, |
602 | n: std::string::npos); |
603 | }); |
604 | if (!F.CheckOptions.empty()) { |
605 | std::vector<std::pair<std::string, std::string>> CheckOptions; |
606 | for (auto &Opt : F.CheckOptions) |
607 | CheckOptions.emplace_back(args: std::move(*Opt.first), |
608 | args: std::move(*Opt.second)); |
609 | Out.Apply.push_back( |
610 | x: [CheckOptions = std::move(CheckOptions)](const Params &, Config &C) { |
611 | for (auto &StringPair : CheckOptions) |
612 | C.Diagnostics.ClangTidy.CheckOptions.insert_or_assign( |
613 | Key: StringPair.first, Val: StringPair.second); |
614 | }); |
615 | } |
616 | if (F.FastCheckFilter.has_value()) |
617 | if (auto Val = compileEnum<Config::FastCheckPolicy>(EnumName: "FastCheckFilter" , |
618 | In: *F.FastCheckFilter) |
619 | .map(Name: "Strict" , Value: Config::FastCheckPolicy::Strict) |
620 | .map(Name: "Loose" , Value: Config::FastCheckPolicy::Loose) |
621 | .map(Name: "None" , Value: Config::FastCheckPolicy::None) |
622 | .value()) |
623 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
624 | C.Diagnostics.ClangTidy.FastCheckFilter = *Val; |
625 | }); |
626 | } |
627 | |
628 | void compile(Fragment::DiagnosticsBlock::IncludesBlock &&F) { |
629 | #ifdef CLANGD_PATH_CASE_INSENSITIVE |
630 | static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase; |
631 | #else |
632 | static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags; |
633 | #endif |
634 | std::shared_ptr<std::vector<llvm::Regex>> Filters; |
635 | if (!F.IgnoreHeader.empty()) { |
636 | Filters = std::make_shared<std::vector<llvm::Regex>>(); |
637 | for (auto & : F.IgnoreHeader) { |
638 | // Anchor on the right. |
639 | std::string AnchoredPattern = "(" + *HeaderPattern + ")$" ; |
640 | llvm::Regex CompiledRegex(AnchoredPattern, Flags); |
641 | std::string RegexError; |
642 | if (!CompiledRegex.isValid(Error&: RegexError)) { |
643 | diag(Kind: Warning, |
644 | Message: llvm::formatv(Fmt: "Invalid regular expression '{0}': {1}" , |
645 | Vals&: *HeaderPattern, Vals&: RegexError) |
646 | .str(), |
647 | Range: HeaderPattern.Range); |
648 | continue; |
649 | } |
650 | Filters->push_back(x: std::move(CompiledRegex)); |
651 | } |
652 | } |
653 | // Optional to override the resulting AnalyzeAngledIncludes |
654 | // only if it's explicitly set in the current fragment. |
655 | // Otherwise it's inherited from parent fragment. |
656 | std::optional<bool> AnalyzeAngledIncludes; |
657 | if (F.AnalyzeAngledIncludes.has_value()) |
658 | AnalyzeAngledIncludes = **F.AnalyzeAngledIncludes; |
659 | if (!Filters && !AnalyzeAngledIncludes.has_value()) |
660 | return; |
661 | Out.Apply.push_back(x: [Filters = std::move(Filters), |
662 | AnalyzeAngledIncludes](const Params &, Config &C) { |
663 | if (Filters) { |
664 | auto Filter = [Filters](llvm::StringRef Path) { |
665 | for (auto &Regex : *Filters) |
666 | if (Regex.match(String: Path)) |
667 | return true; |
668 | return false; |
669 | }; |
670 | C.Diagnostics.Includes.IgnoreHeader.emplace_back(args: std::move(Filter)); |
671 | } |
672 | if (AnalyzeAngledIncludes.has_value()) |
673 | C.Diagnostics.Includes.AnalyzeAngledIncludes = *AnalyzeAngledIncludes; |
674 | }); |
675 | } |
676 | |
677 | void compile(Fragment::CompletionBlock &&F) { |
678 | if (F.AllScopes) { |
679 | Out.Apply.push_back( |
680 | x: [AllScopes(**F.AllScopes)](const Params &, Config &C) { |
681 | C.Completion.AllScopes = AllScopes; |
682 | }); |
683 | } |
684 | if (F.ArgumentLists) { |
685 | if (auto Val = |
686 | compileEnum<Config::ArgumentListsPolicy>(EnumName: "ArgumentLists" , |
687 | In: *F.ArgumentLists) |
688 | .map(Name: "None" , Value: Config::ArgumentListsPolicy::None) |
689 | .map(Name: "OpenDelimiter" , |
690 | Value: Config::ArgumentListsPolicy::OpenDelimiter) |
691 | .map(Name: "Delimiters" , Value: Config::ArgumentListsPolicy::Delimiters) |
692 | .map(Name: "FullPlaceholders" , |
693 | Value: Config::ArgumentListsPolicy::FullPlaceholders) |
694 | .value()) |
695 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
696 | C.Completion.ArgumentLists = *Val; |
697 | }); |
698 | } |
699 | if (F.HeaderInsertion) { |
700 | if (auto Val = |
701 | compileEnum<Config::HeaderInsertionPolicy>(EnumName: "HeaderInsertion" , |
702 | In: *F.HeaderInsertion) |
703 | .map(Name: "IWYU" , Value: Config::HeaderInsertionPolicy::IWYU) |
704 | .map(Name: "Never" , Value: Config::HeaderInsertionPolicy::NeverInsert) |
705 | .value()) |
706 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
707 | C.Completion.HeaderInsertion = *Val; |
708 | }); |
709 | } |
710 | |
711 | if (F.CodePatterns) { |
712 | if (auto Val = compileEnum<Config::CodePatternsPolicy>(EnumName: "CodePatterns" , |
713 | In: *F.CodePatterns) |
714 | .map(Name: "All" , Value: Config::CodePatternsPolicy::All) |
715 | .map(Name: "None" , Value: Config::CodePatternsPolicy::None) |
716 | .value()) |
717 | Out.Apply.push_back(x: [Val](const Params &, Config &C) { |
718 | C.Completion.CodePatterns = *Val; |
719 | }); |
720 | } |
721 | } |
722 | |
723 | void compile(Fragment::HoverBlock &&F) { |
724 | if (F.ShowAKA) { |
725 | Out.Apply.push_back(x: [ShowAKA(**F.ShowAKA)](const Params &, Config &C) { |
726 | C.Hover.ShowAKA = ShowAKA; |
727 | }); |
728 | } |
729 | } |
730 | |
731 | void compile(Fragment::InlayHintsBlock &&F) { |
732 | if (F.Enabled) |
733 | Out.Apply.push_back(x: [Value(**F.Enabled)](const Params &, Config &C) { |
734 | C.InlayHints.Enabled = Value; |
735 | }); |
736 | if (F.ParameterNames) |
737 | Out.Apply.push_back( |
738 | x: [Value(**F.ParameterNames)](const Params &, Config &C) { |
739 | C.InlayHints.Parameters = Value; |
740 | }); |
741 | if (F.DeducedTypes) |
742 | Out.Apply.push_back(x: [Value(**F.DeducedTypes)](const Params &, Config &C) { |
743 | C.InlayHints.DeducedTypes = Value; |
744 | }); |
745 | if (F.Designators) |
746 | Out.Apply.push_back(x: [Value(**F.Designators)](const Params &, Config &C) { |
747 | C.InlayHints.Designators = Value; |
748 | }); |
749 | if (F.BlockEnd) |
750 | Out.Apply.push_back(x: [Value(**F.BlockEnd)](const Params &, Config &C) { |
751 | C.InlayHints.BlockEnd = Value; |
752 | }); |
753 | if (F.DefaultArguments) |
754 | Out.Apply.push_back( |
755 | x: [Value(**F.DefaultArguments)](const Params &, Config &C) { |
756 | C.InlayHints.DefaultArguments = Value; |
757 | }); |
758 | if (F.TypeNameLimit) |
759 | Out.Apply.push_back( |
760 | x: [Value(**F.TypeNameLimit)](const Params &, Config &C) { |
761 | C.InlayHints.TypeNameLimit = Value; |
762 | }); |
763 | } |
764 | |
765 | void compile(Fragment::SemanticTokensBlock &&F) { |
766 | if (!F.DisabledKinds.empty()) { |
767 | std::vector<std::string> DisabledKinds; |
768 | for (auto &Kind : F.DisabledKinds) |
769 | DisabledKinds.push_back(x: std::move(*Kind)); |
770 | |
771 | Out.Apply.push_back( |
772 | x: [DisabledKinds(std::move(DisabledKinds))](const Params &, Config &C) { |
773 | for (auto &Kind : DisabledKinds) { |
774 | auto It = llvm::find(Range&: C.SemanticTokens.DisabledKinds, Val: Kind); |
775 | if (It == C.SemanticTokens.DisabledKinds.end()) |
776 | C.SemanticTokens.DisabledKinds.push_back(x: std::move(Kind)); |
777 | } |
778 | }); |
779 | } |
780 | if (!F.DisabledModifiers.empty()) { |
781 | std::vector<std::string> DisabledModifiers; |
782 | for (auto &Kind : F.DisabledModifiers) |
783 | DisabledModifiers.push_back(x: std::move(*Kind)); |
784 | |
785 | Out.Apply.push_back(x: [DisabledModifiers(std::move(DisabledModifiers))]( |
786 | const Params &, Config &C) { |
787 | for (auto &Kind : DisabledModifiers) { |
788 | auto It = llvm::find(Range&: C.SemanticTokens.DisabledModifiers, Val: Kind); |
789 | if (It == C.SemanticTokens.DisabledModifiers.end()) |
790 | C.SemanticTokens.DisabledModifiers.push_back(x: std::move(Kind)); |
791 | } |
792 | }); |
793 | } |
794 | } |
795 | |
796 | constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error; |
797 | constexpr static llvm::SourceMgr::DiagKind Warning = |
798 | llvm::SourceMgr::DK_Warning; |
799 | void diag(llvm::SourceMgr::DiagKind Kind, llvm::StringRef Message, |
800 | llvm::SMRange Range) { |
801 | if (Range.isValid() && SourceMgr != nullptr) |
802 | Diagnostic(SourceMgr->GetMessage(Loc: Range.Start, Kind, Msg: Message, Ranges: Range)); |
803 | else |
804 | Diagnostic(llvm::SMDiagnostic("" , Kind, Message)); |
805 | } |
806 | }; |
807 | |
808 | } // namespace |
809 | |
810 | CompiledFragment Fragment::compile(DiagnosticCallback D) && { |
811 | llvm::StringRef ConfigFile = "<unknown>" ; |
812 | std::pair<unsigned, unsigned> LineCol = {0, 0}; |
813 | if (auto *SM = Source.Manager.get()) { |
814 | unsigned BufID = SM->getMainFileID(); |
815 | LineCol = SM->getLineAndColumn(Loc: Source.Location, BufferID: BufID); |
816 | ConfigFile = SM->getBufferInfo(i: BufID).Buffer->getBufferIdentifier(); |
817 | } |
818 | trace::Span Tracer("ConfigCompile" ); |
819 | SPAN_ATTACH(Tracer, "ConfigFile" , ConfigFile); |
820 | auto Result = std::make_shared<CompiledFragmentImpl>(); |
821 | vlog(Fmt: "Config fragment: compiling {0}:{1} -> {2} (trusted={3})" , Vals&: ConfigFile, |
822 | Vals&: LineCol.first, Vals: Result.get(), Vals&: Source.Trusted); |
823 | |
824 | FragmentCompiler{*Result, D, Source.Manager.get()}.compile(F: std::move(*this)); |
825 | // Return as cheaply-copyable wrapper. |
826 | return [Result(std::move(Result))](const Params &P, Config &C) { |
827 | return (*Result)(P, C); |
828 | }; |
829 | } |
830 | |
831 | } // namespace config |
832 | } // namespace clangd |
833 | } // namespace clang |
834 | |