clang-tools 22.0.0git
ConfigCompile.cpp
Go to the documentation of this file.
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
51namespace clang {
52namespace clangd {
53namespace config {
54namespace {
55
56// Returns an empty stringref if Path is not under FragmentDir. Returns Path
57// as-is when FragmentDir is empty.
58llvm::StringRef configRelative(llvm::StringRef Path,
59 llvm::StringRef FragmentDir) {
60 if (FragmentDir.empty())
61 return Path;
62 if (!Path.consume_front(FragmentDir))
63 return llvm::StringRef();
64 return Path.empty() ? "." : Path;
65}
66
67struct CompiledFragmentImpl {
68 // The independent conditions to check before using settings from this config.
69 // The following fragment has *two* conditions:
70 // If: { Platform: [mac, linux], PathMatch: foo/.* }
71 // All of them must be satisfied: the platform and path conditions are ANDed.
72 // The OR logic for the platform condition is implemented inside the function.
73 std::vector<llvm::unique_function<bool(const Params &) const>> Conditions;
74 // Mutations that this fragment will apply to the configuration.
75 // These are invoked only if the conditions are satisfied.
76 std::vector<llvm::unique_function<void(const Params &, Config &) const>>
77 Apply;
78
79 bool operator()(const Params &P, Config &C) const {
80 for (const auto &C : Conditions) {
81 if (!C(P)) {
82 dlog("Config fragment {0}: condition not met", this);
83 return false;
84 }
85 }
86 dlog("Config fragment {0}: applying {1} rules", this, Apply.size());
87 for (const auto &A : Apply)
88 A(P, C);
89 return true;
90 }
91};
92
93// Wrapper around condition compile() functions to reduce arg-passing.
94struct FragmentCompiler {
95 FragmentCompiler(CompiledFragmentImpl &Out, DiagnosticCallback D,
96 llvm::SourceMgr *SM)
97 : Out(Out), Diagnostic(D), SourceMgr(SM) {}
98 CompiledFragmentImpl &Out;
100 llvm::SourceMgr *SourceMgr;
101 // Normalized Fragment::SourceInfo::Directory.
102 std::string FragmentDirectory;
103 bool Trusted = false;
104
105 std::optional<llvm::Regex>
106 compileRegex(const Located<std::string> &Text,
107 llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags) {
108 std::string Anchored = "^(" + *Text + ")$";
109 llvm::Regex Result(Anchored, Flags);
110 std::string RegexError;
111 if (!Result.isValid(RegexError)) {
112 diag(Error, "Invalid regex " + Anchored + ": " + RegexError, Text.Range);
113 return std::nullopt;
114 }
115 return std::move(Result);
116 }
117
118 std::optional<std::string> makeAbsolute(Located<std::string> Path,
119 llvm::StringLiteral Description,
120 llvm::sys::path::Style Style) {
121 if (llvm::sys::path::is_absolute(*Path))
122 return *Path;
123 if (FragmentDirectory.empty()) {
124 diag(Error,
125 llvm::formatv(
126 "{0} must be an absolute path, because this fragment is not "
127 "associated with any directory.",
128 Description)
129 .str(),
130 Path.Range);
131 return std::nullopt;
132 }
133 llvm::SmallString<256> AbsPath = llvm::StringRef(*Path);
134 llvm::sys::fs::make_absolute(FragmentDirectory, AbsPath);
135 llvm::sys::path::native(AbsPath, Style);
136 return AbsPath.str().str();
137 }
138
139 // Helper with similar API to StringSwitch, for parsing enum values.
140 template <typename T> class EnumSwitch {
141 FragmentCompiler &Outer;
142 llvm::StringRef EnumName;
143 const Located<std::string> &Input;
144 std::optional<T> Result;
145 llvm::SmallVector<llvm::StringLiteral> ValidValues;
146
147 public:
148 EnumSwitch(llvm::StringRef EnumName, const Located<std::string> &In,
149 FragmentCompiler &Outer)
150 : Outer(Outer), EnumName(EnumName), Input(In) {}
151
152 EnumSwitch &map(llvm::StringLiteral Name, T Value) {
153 assert(!llvm::is_contained(ValidValues, Name) && "Duplicate value!");
154 ValidValues.push_back(Name);
155 if (!Result && *Input == Name)
156 Result = Value;
157 return *this;
158 }
159
160 std::optional<T> value() {
161 if (!Result)
162 Outer.diag(
163 Warning,
164 llvm::formatv("Invalid {0} value '{1}'. Valid values are {2}.",
165 EnumName, *Input, llvm::join(ValidValues, ", "))
166 .str(),
167 Input.Range);
168 return Result;
169 };
170 };
171
172 // Attempt to parse a specified string into an enum.
173 // Yields std::nullopt and produces a diagnostic on failure.
174 //
175 // std::optional<T> Value = compileEnum<En>("Foo", Frag.Foo)
176 // .map("Foo", Enum::Foo)
177 // .map("Bar", Enum::Bar)
178 // .value();
179 template <typename T>
180 EnumSwitch<T> compileEnum(llvm::StringRef EnumName,
181 const Located<std::string> &In) {
182 return EnumSwitch<T>(EnumName, In, *this);
183 }
184
185 void compile(Fragment &&F) {
186 Trusted = F.Source.Trusted;
187 if (!F.Source.Directory.empty()) {
188 FragmentDirectory = llvm::sys::path::convert_to_slash(F.Source.Directory);
189 if (FragmentDirectory.back() != '/')
190 FragmentDirectory += '/';
191 }
192 compile(std::move(F.If));
193 compile(std::move(F.CompileFlags));
194 compile(std::move(F.Index));
195 compile(std::move(F.Diagnostics));
196 compile(std::move(F.Completion));
197 compile(std::move(F.Hover));
198 compile(std::move(F.InlayHints));
199 compile(std::move(F.SemanticTokens));
200 compile(std::move(F.Style));
201 compile(std::move(F.Documentation));
202 }
203
204 void compile(Fragment::IfBlock &&F) {
205 if (F.HasUnrecognizedCondition)
206 Out.Conditions.push_back([&](const Params &) { return false; });
207
208#ifdef CLANGD_PATH_CASE_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(Entry, Flags))
217 PathMatch->push_back(std::move(*RE));
218 }
219 if (!PathMatch->empty()) {
220 Out.Conditions.push_back(
221 [PathMatch(std::move(PathMatch)),
222 FragmentDir(FragmentDirectory)](const Params &P) {
223 if (P.Path.empty())
224 return false;
225 llvm::StringRef Path = configRelative(P.Path, FragmentDir);
226 // Ignore the file if it is not nested under Fragment.
227 if (Path.empty())
228 return false;
229 return llvm::any_of(*PathMatch, [&](const llvm::Regex &RE) {
230 return RE.match(Path);
231 });
232 });
233 }
234
235 auto PathExclude = std::make_unique<std::vector<llvm::Regex>>();
236 for (auto &Entry : F.PathExclude) {
237 if (auto RE = compileRegex(Entry, Flags))
238 PathExclude->push_back(std::move(*RE));
239 }
240 if (!PathExclude->empty()) {
241 Out.Conditions.push_back(
242 [PathExclude(std::move(PathExclude)),
243 FragmentDir(FragmentDirectory)](const Params &P) {
244 if (P.Path.empty())
245 return false;
246 llvm::StringRef Path = configRelative(P.Path, FragmentDir);
247 // Ignore the file if it is not nested under Fragment.
248 if (Path.empty())
249 return true;
250 return llvm::none_of(*PathExclude, [&](const llvm::Regex &RE) {
251 return RE.match(Path);
252 });
253 });
254 }
255 }
256
257 void compile(Fragment::CompileFlagsBlock &&F) {
258 if (F.Compiler)
259 Out.Apply.push_back(
260 [Compiler(std::move(**F.Compiler))](const Params &, Config &C) {
261 C.CompileFlags.Edits.push_back(
262 [Compiler](std::vector<std::string> &Args) {
263 if (!Args.empty())
264 Args.front() = Compiler;
265 });
266 });
267
268 if (!F.Remove.empty()) {
269 auto Remove = std::make_shared<ArgStripper>();
270 for (auto &A : F.Remove)
271 Remove->strip(*A);
272 Out.Apply.push_back([Remove(std::shared_ptr<const ArgStripper>(
273 std::move(Remove)))](const Params &, Config &C) {
274 C.CompileFlags.Edits.push_back(
275 [Remove](std::vector<std::string> &Args) {
276 Remove->process(Args);
277 });
278 });
279 }
280
281 if (!F.Add.empty()) {
282 std::vector<std::string> Add;
283 for (auto &A : F.Add)
284 Add.push_back(std::move(*A));
285 Out.Apply.push_back([Add(std::move(Add))](const Params &, Config &C) {
286 C.CompileFlags.Edits.push_back([Add](std::vector<std::string> &Args) {
287 // The point to insert at. Just append when `--` isn't present.
288 auto It = llvm::find(Args, "--");
289 Args.insert(It, Add.begin(), Add.end());
290 });
291 });
292 }
293
294 if (F.BuiltinHeaders) {
295 if (auto Val =
296 compileEnum<Config::BuiltinHeaderPolicy>("BuiltinHeaders",
297 *F.BuiltinHeaders)
300 .value())
301 Out.Apply.push_back([Val](const Params &, Config &C) {
302 C.CompileFlags.BuiltinHeaders = *Val;
303 });
304 }
305
306 if (F.CompilationDatabase) {
307 std::optional<Config::CDBSearchSpec> Spec;
308 if (**F.CompilationDatabase == "Ancestors") {
309 Spec.emplace();
311 } else if (**F.CompilationDatabase == "None") {
312 Spec.emplace();
314 } else {
315 if (auto Path =
316 makeAbsolute(*F.CompilationDatabase, "CompilationDatabase",
317 llvm::sys::path::Style::native)) {
318 // Drop trailing slash to put the path in canonical form.
319 // Should makeAbsolute do this?
320 llvm::StringRef Rel = llvm::sys::path::relative_path(*Path);
321 if (!Rel.empty() && llvm::sys::path::is_separator(Rel.back()))
322 Path->pop_back();
323
324 Spec.emplace();
325 Spec->Policy = Config::CDBSearchSpec::FixedDir;
326 Spec->FixedCDBPath = std::move(Path);
327 }
328 }
329 if (Spec)
330 Out.Apply.push_back(
331 [Spec(std::move(*Spec))](const Params &, Config &C) {
332 C.CompileFlags.CDBSearch = Spec;
333 });
334 }
335 }
336
337 void compile(Fragment::IndexBlock &&F) {
338 if (F.Background) {
339 if (auto Val =
340 compileEnum<Config::BackgroundPolicy>("Background", *F.Background)
343 .value())
344 Out.Apply.push_back(
345 [Val](const Params &, Config &C) { C.Index.Background = *Val; });
346 }
347 if (F.External)
348 compile(std::move(**F.External), F.External->Range);
349 if (F.StandardLibrary)
350 Out.Apply.push_back(
351 [Val(**F.StandardLibrary)](const Params &, Config &C) {
352 C.Index.StandardLibrary = Val;
353 });
354 }
355
356 void compile(Fragment::IndexBlock::ExternalBlock &&External,
357 llvm::SMRange BlockRange) {
358 if (External.Server && !Trusted) {
359 diag(Error,
360 "Remote index may not be specified by untrusted configuration. "
361 "Copy this into user config to use it.",
362 External.Server->Range);
363 return;
364 }
365#ifndef CLANGD_ENABLE_REMOTE
366 if (External.Server) {
367 elog("Clangd isn't compiled with remote index support, ignoring Server: "
368 "{0}",
369 *External.Server);
370 External.Server.reset();
371 }
372#endif
373 // Make sure exactly one of the Sources is set.
374 unsigned SourceCount = External.File.has_value() +
375 External.Server.has_value() + *External.IsNone;
376 if (SourceCount != 1) {
377 diag(Error, "Exactly one of File, Server or None must be set.",
378 BlockRange);
379 return;
380 }
382 if (External.Server) {
384 Spec.Location = std::move(**External.Server);
385 } else if (External.File) {
387 auto AbsPath = makeAbsolute(std::move(*External.File), "File",
388 llvm::sys::path::Style::native);
389 if (!AbsPath)
390 return;
391 Spec.Location = std::move(*AbsPath);
392 } else {
393 assert(*External.IsNone);
395 }
397 // Make sure MountPoint is an absolute path with forward slashes.
398 if (!External.MountPoint)
399 External.MountPoint.emplace(FragmentDirectory);
400 if ((**External.MountPoint).empty()) {
401 diag(Error, "A mountpoint is required.", BlockRange);
402 return;
403 }
404 auto AbsPath = makeAbsolute(std::move(*External.MountPoint), "MountPoint",
405 llvm::sys::path::Style::posix);
406 if (!AbsPath)
407 return;
408 Spec.MountPoint = std::move(*AbsPath);
409 }
410 Out.Apply.push_back([Spec(std::move(Spec))](const Params &P, Config &C) {
412 C.Index.External = Spec;
413 return;
414 }
415 if (P.Path.empty() || !pathStartsWith(Spec.MountPoint, P.Path,
416 llvm::sys::path::Style::posix))
417 return;
418 C.Index.External = Spec;
419 // Disable background indexing for the files under the mountpoint.
420 // Note that this will overwrite statements in any previous fragments
421 // (including the current one).
422 C.Index.Background = Config::BackgroundPolicy::Skip;
423 });
424 }
425
426 void compile(Fragment::DiagnosticsBlock &&F) {
427 std::vector<std::string> Normalized;
428 for (const auto &Suppressed : F.Suppress) {
429 if (*Suppressed == "*") {
430 Out.Apply.push_back([&](const Params &, Config &C) {
431 C.Diagnostics.SuppressAll = true;
432 C.Diagnostics.Suppress.clear();
433 });
434 return;
435 }
436 Normalized.push_back(normalizeSuppressedCode(*Suppressed).str());
437 }
438 if (!Normalized.empty())
439 Out.Apply.push_back(
440 [Normalized(std::move(Normalized))](const Params &, Config &C) {
441 if (C.Diagnostics.SuppressAll)
442 return;
443 C.Diagnostics.Suppress.insert_range(Normalized);
444 });
445
446 if (F.UnusedIncludes) {
447 auto Val = compileEnum<Config::IncludesPolicy>("UnusedIncludes",
448 **F.UnusedIncludes)
449 .map("Strict", Config::IncludesPolicy::Strict)
450 .map("None", Config::IncludesPolicy::None)
451 .value();
452 if (!Val && **F.UnusedIncludes == "Experiment") {
453 diag(Warning,
454 "Experiment is deprecated for UnusedIncludes, use Strict instead.",
455 F.UnusedIncludes->Range);
457 }
458 if (Val) {
459 Out.Apply.push_back([Val](const Params &, Config &C) {
460 C.Diagnostics.UnusedIncludes = *Val;
461 });
462 }
463 }
464
465 if (F.MissingIncludes)
466 if (auto Val = compileEnum<Config::IncludesPolicy>("MissingIncludes",
467 **F.MissingIncludes)
468 .map("Strict", Config::IncludesPolicy::Strict)
469 .map("None", Config::IncludesPolicy::None)
470 .value())
471 Out.Apply.push_back([Val](const Params &, Config &C) {
472 C.Diagnostics.MissingIncludes = *Val;
473 });
474
475 compile(std::move(F.Includes));
476 compile(std::move(F.ClangTidy));
477 }
478
479 void compile(Fragment::StyleBlock &&F) {
480 if (!F.FullyQualifiedNamespaces.empty()) {
481 std::vector<std::string> FullyQualifiedNamespaces;
482 for (auto &N : F.FullyQualifiedNamespaces) {
483 // Normalize the data by dropping both leading and trailing ::
484 StringRef Namespace(*N);
485 Namespace.consume_front("::");
486 Namespace.consume_back("::");
487 FullyQualifiedNamespaces.push_back(Namespace.str());
488 }
489 Out.Apply.push_back([FullyQualifiedNamespaces(
490 std::move(FullyQualifiedNamespaces))](
491 const Params &, Config &C) {
492 C.Style.FullyQualifiedNamespaces.insert(
493 C.Style.FullyQualifiedNamespaces.begin(),
494 FullyQualifiedNamespaces.begin(), FullyQualifiedNamespaces.end());
495 });
496 }
497 auto QuotedFilter = compileHeaderRegexes(F.QuotedHeaders);
498 if (QuotedFilter.has_value()) {
499 Out.Apply.push_back(
500 [QuotedFilter = *QuotedFilter](const Params &, Config &C) {
501 C.Style.QuotedHeaders.emplace_back(QuotedFilter);
502 });
503 }
504 auto AngledFilter = compileHeaderRegexes(F.AngledHeaders);
505 if (AngledFilter.has_value()) {
506 Out.Apply.push_back(
507 [AngledFilter = *AngledFilter](const Params &, Config &C) {
508 C.Style.AngledHeaders.emplace_back(AngledFilter);
509 });
510 }
511 }
512
513 auto compileHeaderRegexes(llvm::ArrayRef<Located<std::string>> HeaderPatterns)
514 -> std::optional<std::function<bool(llvm::StringRef)>> {
515 // TODO: Share this code with Diagnostics.Includes.IgnoreHeader
516#ifdef CLANGD_PATH_CASE_INSENSITIVE
517 static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase;
518#else
519 static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags;
520#endif
521 auto Filters = std::make_shared<std::vector<llvm::Regex>>();
522 for (auto &HeaderPattern : HeaderPatterns) {
523 // Anchor on the right.
524 std::string AnchoredPattern = "(" + *HeaderPattern + ")$";
525 llvm::Regex CompiledRegex(AnchoredPattern, Flags);
526 std::string RegexError;
527 if (!CompiledRegex.isValid(RegexError)) {
528 diag(Warning,
529 llvm::formatv("Invalid regular expression '{0}': {1}",
530 *HeaderPattern, RegexError)
531 .str(),
532 HeaderPattern.Range);
533 continue;
534 }
535 Filters->push_back(std::move(CompiledRegex));
536 }
537 if (Filters->empty())
538 return std::nullopt;
539 auto Filter = [Filters = std::move(Filters)](llvm::StringRef Path) {
540 for (auto &Regex : *Filters)
541 if (Regex.match(Path))
542 return true;
543 return false;
544 };
545 return Filter;
546 }
547
548 void appendTidyCheckSpec(std::string &CurSpec,
549 const Located<std::string> &Arg, bool IsPositive) {
550 StringRef Str = StringRef(*Arg).trim();
551 // Don't support negating here, its handled if the item is in the Add or
552 // Remove list.
553 if (Str.starts_with("-") || Str.contains(',')) {
554 diag(Error, "Invalid clang-tidy check name", Arg.Range);
555 return;
556 }
557 if (!Str.contains('*')) {
558 if (!isRegisteredTidyCheck(Str)) {
559 diag(Warning,
560 llvm::formatv("clang-tidy check '{0}' was not found", Str).str(),
561 Arg.Range);
562 return;
563 }
564 auto Fast = isFastTidyCheck(Str);
565 if (!Fast.has_value()) {
566 diag(Warning,
567 llvm::formatv(
568 "Latency of clang-tidy check '{0}' is not known. "
569 "It will only run if ClangTidy.FastCheckFilter is Loose or None",
570 Str)
571 .str(),
572 Arg.Range);
573 } else if (!*Fast) {
574 diag(Warning,
575 llvm::formatv(
576 "clang-tidy check '{0}' is slow. "
577 "It will only run if ClangTidy.FastCheckFilter is None",
578 Str)
579 .str(),
580 Arg.Range);
581 }
582 }
583 CurSpec += ',';
584 if (!IsPositive)
585 CurSpec += '-';
586 CurSpec += Str;
587 }
588
590 std::string Checks;
591 for (auto &CheckGlob : F.Add)
592 appendTidyCheckSpec(Checks, CheckGlob, true);
593
594 for (auto &CheckGlob : F.Remove)
595 appendTidyCheckSpec(Checks, CheckGlob, false);
596
597 if (!Checks.empty())
598 Out.Apply.push_back(
599 [Checks = std::move(Checks)](const Params &, Config &C) {
600 C.Diagnostics.ClangTidy.Checks.append(
601 Checks,
602 C.Diagnostics.ClangTidy.Checks.empty() ? /*skip comma*/ 1 : 0,
603 std::string::npos);
604 });
605 if (!F.CheckOptions.empty()) {
606 std::vector<std::pair<std::string, std::string>> CheckOptions;
607 for (auto &Opt : F.CheckOptions)
608 CheckOptions.emplace_back(std::move(*Opt.first),
609 std::move(*Opt.second));
610 Out.Apply.push_back(
611 [CheckOptions = std::move(CheckOptions)](const Params &, Config &C) {
612 for (auto &StringPair : CheckOptions)
613 C.Diagnostics.ClangTidy.CheckOptions.insert_or_assign(
614 StringPair.first, StringPair.second);
615 });
616 }
617 if (F.FastCheckFilter.has_value())
618 if (auto Val = compileEnum<Config::FastCheckPolicy>("FastCheckFilter",
619 *F.FastCheckFilter)
620 .map("Strict", Config::FastCheckPolicy::Strict)
621 .map("Loose", Config::FastCheckPolicy::Loose)
623 .value())
624 Out.Apply.push_back([Val](const Params &, Config &C) {
625 C.Diagnostics.ClangTidy.FastCheckFilter = *Val;
626 });
627 }
628
630#ifdef CLANGD_PATH_CASE_INSENSITIVE
631 static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase;
632#else
633 static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags;
634#endif
635 std::shared_ptr<std::vector<llvm::Regex>> Filters;
636 if (!F.IgnoreHeader.empty()) {
637 Filters = std::make_shared<std::vector<llvm::Regex>>();
638 for (auto &HeaderPattern : F.IgnoreHeader) {
639 // Anchor on the right.
640 std::string AnchoredPattern = "(" + *HeaderPattern + ")$";
641 llvm::Regex CompiledRegex(AnchoredPattern, Flags);
642 std::string RegexError;
643 if (!CompiledRegex.isValid(RegexError)) {
644 diag(Warning,
645 llvm::formatv("Invalid regular expression '{0}': {1}",
646 *HeaderPattern, RegexError)
647 .str(),
648 HeaderPattern.Range);
649 continue;
650 }
651 Filters->push_back(std::move(CompiledRegex));
652 }
653 }
654 // Optional to override the resulting AnalyzeAngledIncludes
655 // only if it's explicitly set in the current fragment.
656 // Otherwise it's inherited from parent fragment.
657 std::optional<bool> AnalyzeAngledIncludes;
658 if (F.AnalyzeAngledIncludes.has_value())
659 AnalyzeAngledIncludes = **F.AnalyzeAngledIncludes;
660 if (!Filters && !AnalyzeAngledIncludes.has_value())
661 return;
662 Out.Apply.push_back([Filters = std::move(Filters),
663 AnalyzeAngledIncludes](const Params &, Config &C) {
664 if (Filters) {
665 auto Filter = [Filters](llvm::StringRef Path) {
666 for (auto &Regex : *Filters)
667 if (Regex.match(Path))
668 return true;
669 return false;
670 };
671 C.Diagnostics.Includes.IgnoreHeader.emplace_back(std::move(Filter));
672 }
673 if (AnalyzeAngledIncludes.has_value())
674 C.Diagnostics.Includes.AnalyzeAngledIncludes = *AnalyzeAngledIncludes;
675 });
676 }
677
678 void compile(Fragment::CompletionBlock &&F) {
679 if (F.AllScopes) {
680 Out.Apply.push_back(
681 [AllScopes(**F.AllScopes)](const Params &, Config &C) {
682 C.Completion.AllScopes = AllScopes;
683 });
684 }
685 if (F.ArgumentLists) {
686 if (auto Val =
687 compileEnum<Config::ArgumentListsPolicy>("ArgumentLists",
688 *F.ArgumentLists)
690 .map("OpenDelimiter",
693 .map("FullPlaceholders",
695 .value())
696 Out.Apply.push_back([Val](const Params &, Config &C) {
697 C.Completion.ArgumentLists = *Val;
698 });
699 }
700 if (F.HeaderInsertion) {
701 if (auto Val =
702 compileEnum<Config::HeaderInsertionPolicy>("HeaderInsertion",
703 *F.HeaderInsertion)
706 .value())
707 Out.Apply.push_back([Val](const Params &, Config &C) {
708 C.Completion.HeaderInsertion = *Val;
709 });
710 }
711
712 if (F.CodePatterns) {
713 if (auto Val = compileEnum<Config::CodePatternsPolicy>("CodePatterns",
714 *F.CodePatterns)
717 .value())
718 Out.Apply.push_back([Val](const Params &, Config &C) {
719 C.Completion.CodePatterns = *Val;
720 });
721 }
722 }
723
724 void compile(Fragment::HoverBlock &&F) {
725 if (F.ShowAKA) {
726 Out.Apply.push_back([ShowAKA(**F.ShowAKA)](const Params &, Config &C) {
727 C.Hover.ShowAKA = ShowAKA;
728 });
729 }
730 if (F.MacroContentsLimit) {
731 Out.Apply.push_back(
732 [Limit(**F.MacroContentsLimit)](const Params &, Config &C) {
733 C.Hover.MacroContentsLimit = Limit;
734 });
735 }
736 }
737
738 void compile(Fragment::InlayHintsBlock &&F) {
739 if (F.Enabled)
740 Out.Apply.push_back([Value(**F.Enabled)](const Params &, Config &C) {
741 C.InlayHints.Enabled = Value;
742 });
743 if (F.ParameterNames)
744 Out.Apply.push_back(
745 [Value(**F.ParameterNames)](const Params &, Config &C) {
746 C.InlayHints.Parameters = Value;
747 });
748 if (F.DeducedTypes)
749 Out.Apply.push_back([Value(**F.DeducedTypes)](const Params &, Config &C) {
750 C.InlayHints.DeducedTypes = Value;
751 });
752 if (F.Designators)
753 Out.Apply.push_back([Value(**F.Designators)](const Params &, Config &C) {
754 C.InlayHints.Designators = Value;
755 });
756 if (F.BlockEnd)
757 Out.Apply.push_back([Value(**F.BlockEnd)](const Params &, Config &C) {
758 C.InlayHints.BlockEnd = Value;
759 });
760 if (F.DefaultArguments)
761 Out.Apply.push_back(
762 [Value(**F.DefaultArguments)](const Params &, Config &C) {
763 C.InlayHints.DefaultArguments = Value;
764 });
765 if (F.TypeNameLimit)
766 Out.Apply.push_back(
767 [Value(**F.TypeNameLimit)](const Params &, Config &C) {
768 C.InlayHints.TypeNameLimit = Value;
769 });
770 }
771
772 void compile(Fragment::SemanticTokensBlock &&F) {
773 if (!F.DisabledKinds.empty()) {
774 std::vector<std::string> DisabledKinds;
775 for (auto &Kind : F.DisabledKinds)
776 DisabledKinds.push_back(std::move(*Kind));
777
778 Out.Apply.push_back(
779 [DisabledKinds(std::move(DisabledKinds))](const Params &, Config &C) {
780 for (auto &Kind : DisabledKinds) {
781 auto It = llvm::find(C.SemanticTokens.DisabledKinds, Kind);
782 if (It == C.SemanticTokens.DisabledKinds.end())
783 C.SemanticTokens.DisabledKinds.push_back(std::move(Kind));
784 }
785 });
786 }
787 if (!F.DisabledModifiers.empty()) {
788 std::vector<std::string> DisabledModifiers;
789 for (auto &Kind : F.DisabledModifiers)
790 DisabledModifiers.push_back(std::move(*Kind));
791
792 Out.Apply.push_back([DisabledModifiers(std::move(DisabledModifiers))](
793 const Params &, Config &C) {
794 for (auto &Kind : DisabledModifiers) {
795 auto It = llvm::find(C.SemanticTokens.DisabledModifiers, Kind);
796 if (It == C.SemanticTokens.DisabledModifiers.end())
797 C.SemanticTokens.DisabledModifiers.push_back(std::move(Kind));
798 }
799 });
800 }
801 }
802
803 void compile(Fragment::DocumentationBlock &&F) {
804 if (F.CommentFormat) {
805 if (auto Val =
806 compileEnum<Config::CommentFormatPolicy>("CommentFormat",
807 *F.CommentFormat)
811 .value())
812 Out.Apply.push_back([Val](const Params &, Config &C) {
813 C.Documentation.CommentFormat = *Val;
814 });
815 }
816 }
817
818 constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error;
819 constexpr static llvm::SourceMgr::DiagKind Warning =
820 llvm::SourceMgr::DK_Warning;
821 void diag(llvm::SourceMgr::DiagKind Kind, llvm::StringRef Message,
822 llvm::SMRange Range) {
823 if (Range.isValid() && SourceMgr != nullptr)
824 Diagnostic(SourceMgr->GetMessage(Range.Start, Kind, Message, Range));
825 else
826 Diagnostic(llvm::SMDiagnostic("", Kind, Message));
827 }
828};
829
830} // namespace
831
833 llvm::StringRef ConfigFile = "<unknown>";
834 std::pair<unsigned, unsigned> LineCol = {0, 0};
835 if (auto *SM = Source.Manager.get()) {
836 unsigned BufID = SM->getMainFileID();
837 LineCol = SM->getLineAndColumn(Source.Location, BufID);
838 ConfigFile = SM->getBufferInfo(BufID).Buffer->getBufferIdentifier();
839 }
840 trace::Span Tracer("ConfigCompile");
841 SPAN_ATTACH(Tracer, "ConfigFile", ConfigFile);
842 auto Result = std::make_shared<CompiledFragmentImpl>();
843 vlog("Config fragment: compiling {0}:{1} -> {2} (trusted={3})", ConfigFile,
844 LineCol.first, Result.get(), Source.Trusted);
845
846 FragmentCompiler{*Result, D, Source.Manager.get()}.compile(std::move(*this));
847 // Return as cheaply-copyable wrapper.
848 return [Result(std::move(Result))](const Params &P, Config &C) {
849 return (*Result)(P, C);
850 };
851}
852
853} // namespace config
854} // namespace clangd
855} // namespace clang
static cl::opt< std::string > ConfigFile("config-file", desc(R"( Specify the path of .clang-tidy or custom config file: e.g. --config-file=/some/path/myTidyConfigFile This option internally works exactly the same way as --config option after reading specified config file. Use either --config-file or --config, not both. )"), cl::init(""), cl::cat(ClangTidyCategory))
static cl::opt< std::string > Checks("checks", desc(R"( Comma-separated list of globs with optional '-' prefix. Globs are processed in order of appearance in the list. Globs without '-' prefix add checks with matching names to the set, globs with the '-' prefix remove checks with matching names from the set of enabled checks. This option's value is appended to the value of the 'Checks' option in .clang-tidy file, if any. )"), cl::init(""), cl::cat(ClangTidyCategory))
#define dlog(...)
Definition Logger.h:101
#define SPAN_ATTACH(S, Name, Expr)
Attach a key-value pair to a Span event.
Definition Trace.h:164
Records an event whose duration is the lifetime of the Span object.
Definition Trace.h:143
std::function< bool(const Params &, Config &)> CompiledFragment
A chunk of configuration that has been fully analyzed and is ready to apply.
llvm::function_ref< void(const llvm::SMDiagnostic &)> DiagnosticCallback
Used to report problems in parsing or interpreting a config.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:45
@ Warning
A warning message.
Definition Protocol.h:736
@ Error
An error message.
Definition Protocol.h:734
bool isRegisteredTidyCheck(llvm::StringRef Check)
Returns if Check is a registered clang-tidy check.
void vlog(const char *Fmt, Ts &&... Vals)
Definition Logger.h:72
bool pathStartsWith(PathRef Ancestor, PathRef Path, llvm::sys::path::Style Style)
Checks if Ancestor is a proper ancestor of Path.
Definition Path.cpp:36
std::optional< bool > isFastTidyCheck(llvm::StringRef Check)
Returns if Check is known-fast, known-slow, or its speed is unknown.
llvm::StringRef normalizeSuppressedCode(llvm::StringRef Code)
Take a user-specified diagnostic code, and convert it to a normalized form stored in the config and c...
std::string Path
A typedef to represent a file path.
Definition Path.h:26
void elog(const char *Fmt, Ts &&... Vals)
Definition Logger.h:61
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Describes an external index configuration.
Definition Config.h:78
enum clang::clangd::Config::ExternalIndexSpec::@216221346246105351024304277277130164055373233215 Kind
std::string Location
This is one of:
Definition Config.h:83
std::string MountPoint
Absolute path to source root this index is associated with, uses forward-slashes.
Definition Config.h:86
Settings that express user/project preferences and control clangd behavior.
Definition Config.h:44
@ None
nothing, no argument list and also NO Delimiters "()" or "<>".
Definition Config.h:141
@ Delimiters
empty pair of delimiters "()" or "<>".
Definition Config.h:145
@ OpenDelimiter
open, only opening delimiter "(" or "<".
Definition Config.h:143
@ FullPlaceholders
full name of both type and variable.
Definition Config.h:147
@ Strict
Diagnose missing and unused includes.
Definition Config.h:98
@ Markdown
Treat comments as Markdown.
Definition Config.h:207
@ Doxygen
Treat comments as doxygen.
Definition Config.h:209
@ PlainText
Treat comments as plain text.
Definition Config.h:205
Conditions in the CompileFlags block affect how a file is parsed.
Describes code completion preferences.
Controls how clang-tidy will run over the code base.
Controls behavior of diagnostics (errors and warnings).
Configures documentation style and behaviour.
Conditions in the If block restrict when a Fragment applies.
An external index uses data source outside of clangd itself.
Controls how clangd understands code outside the current file.
Configures labels shown inline with the code.
Configures semantic tokens that are produced by clangd.
A chunk of configuration obtained from a config file, LSP, or elsewhere.
CompiledFragment compile(DiagnosticCallback) &&
Analyzes and consumes this fragment, possibly yielding more diagnostics.
An entity written in config along, with its optional location in the file.
Describes the context used to evaluate configuration fragments.