clang-tools 24.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::path::make_absolute(FragmentDirectory, AbsPath);
135 llvm::sys::path::native(AbsPath, Style);
136 return AbsPath.str().str();
137 }
138
139 // Helper with similar API to StringSwitch, for parsing enum values.
140 template <typename T> class EnumSwitch {
141 FragmentCompiler &Outer;
142 llvm::StringRef EnumName;
143 const Located<std::string> &Input;
144 std::optional<T> Result;
145 llvm::SmallVector<llvm::StringLiteral> ValidValues;
146
147 public:
148 EnumSwitch(llvm::StringRef EnumName, const Located<std::string> &In,
149 FragmentCompiler &Outer)
150 : Outer(Outer), EnumName(EnumName), Input(In) {}
151
152 EnumSwitch &map(llvm::StringLiteral Name, T Value) {
153 assert(!llvm::is_contained(ValidValues, Name) && "Duplicate value!");
154 ValidValues.push_back(Name);
155 if (!Result && *Input == Name)
156 Result = Value;
157 return *this;
158 }
159
160 std::optional<T> value() {
161 if (!Result)
162 Outer.diag(
163 Warning,
164 llvm::formatv("Invalid {0} value '{1}'. Valid values are {2}.",
165 EnumName, *Input, llvm::join(ValidValues, ", "))
166 .str(),
167 Input.Range);
168 return Result;
169 };
170 };
171
172 // Attempt to parse a specified string into an enum.
173 // Yields std::nullopt and produces a diagnostic on failure.
174 //
175 // std::optional<T> Value = compileEnum<En>("Foo", Frag.Foo)
176 // .map("Foo", Enum::Foo)
177 // .map("Bar", Enum::Bar)
178 // .value();
179 template <typename T>
180 EnumSwitch<T> compileEnum(llvm::StringRef EnumName,
181 const Located<std::string> &In) {
182 return EnumSwitch<T>(EnumName, In, *this);
183 }
184
185 void compile(Fragment &&F) {
186 Trusted = F.Source.Trusted;
187 if (!F.Source.Directory.empty()) {
188 FragmentDirectory = llvm::sys::path::convert_to_slash(F.Source.Directory);
189 if (FragmentDirectory.back() != '/')
190 FragmentDirectory += '/';
191 }
192 compile(std::move(F.If));
193 compile(std::move(F.CompileFlags));
194 compile(std::move(F.Index));
195 compile(std::move(F.Diagnostics));
196 compile(std::move(F.Completion));
197 compile(std::move(F.Hover));
198 compile(std::move(F.InlayHints));
199 compile(std::move(F.SemanticTokens));
200 compile(std::move(F.Style));
201 compile(std::move(F.Documentation));
202 }
203
204 void compile(Fragment::IfBlock &&F) {
205 if (F.HasUnrecognizedCondition)
206 Out.Conditions.push_back([&](const Params &) { return false; });
207
208#ifdef CLANGD_PATH_CASE_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("Latency of clang-tidy check '{0}' is not known. "
568 "It will only run if ClangTidy.FastCheckFilter is "
569 "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 if (F.ExperimentalCustomChecks)
628 Out.Apply.push_back(
629 [Enabled = **F.ExperimentalCustomChecks](const Params &, Config &C) {
630 C.Diagnostics.ClangTidy.ExperimentalCustomChecks = Enabled;
631 });
632 }
633
635#ifdef CLANGD_PATH_CASE_INSENSITIVE
636 static llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase;
637#else
638 static llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags;
639#endif
640 std::shared_ptr<std::vector<llvm::Regex>> Filters;
641 if (!F.IgnoreHeader.empty()) {
642 Filters = std::make_shared<std::vector<llvm::Regex>>();
643 for (auto &HeaderPattern : F.IgnoreHeader) {
644 // Anchor on the right.
645 std::string AnchoredPattern = "(" + *HeaderPattern + ")$";
646 llvm::Regex CompiledRegex(AnchoredPattern, Flags);
647 std::string RegexError;
648 if (!CompiledRegex.isValid(RegexError)) {
649 diag(Warning,
650 llvm::formatv("Invalid regular expression '{0}': {1}",
651 *HeaderPattern, RegexError)
652 .str(),
653 HeaderPattern.Range);
654 continue;
655 }
656 Filters->push_back(std::move(CompiledRegex));
657 }
658 }
659 // Optional to override the resulting AnalyzeAngledIncludes
660 // only if it's explicitly set in the current fragment.
661 // Otherwise it's inherited from parent fragment.
662 std::optional<bool> AnalyzeAngledIncludes;
663 if (F.AnalyzeAngledIncludes.has_value())
664 AnalyzeAngledIncludes = **F.AnalyzeAngledIncludes;
665 if (!Filters && !AnalyzeAngledIncludes.has_value())
666 return;
667 Out.Apply.push_back([Filters = std::move(Filters),
668 AnalyzeAngledIncludes](const Params &, Config &C) {
669 if (Filters) {
670 auto Filter = [Filters](llvm::StringRef Path) {
671 for (auto &Regex : *Filters)
672 if (Regex.match(Path))
673 return true;
674 return false;
675 };
676 C.Diagnostics.Includes.IgnoreHeader.emplace_back(std::move(Filter));
677 }
678 if (AnalyzeAngledIncludes.has_value())
679 C.Diagnostics.Includes.AnalyzeAngledIncludes = *AnalyzeAngledIncludes;
680 });
681 }
682
683 void compile(Fragment::CompletionBlock &&F) {
684 if (F.AllScopes) {
685 Out.Apply.push_back(
686 [AllScopes(**F.AllScopes)](const Params &, Config &C) {
687 C.Completion.AllScopes = AllScopes;
688 });
689 }
690 if (F.ArgumentLists) {
691 if (auto Val =
692 compileEnum<Config::ArgumentListsPolicy>("ArgumentLists",
693 *F.ArgumentLists)
695 .map("OpenDelimiter",
698 .map("FullPlaceholders",
700 .value())
701 Out.Apply.push_back([Val](const Params &, Config &C) {
702 C.Completion.ArgumentLists = *Val;
703 });
704 }
705 if (F.HeaderInsertion) {
706 if (auto Val =
707 compileEnum<Config::HeaderInsertionPolicy>("HeaderInsertion",
708 *F.HeaderInsertion)
711 .value())
712 Out.Apply.push_back([Val](const Params &, Config &C) {
713 C.Completion.HeaderInsertion = *Val;
714 });
715 }
716
717 if (F.CodePatterns) {
718 if (auto Val = compileEnum<Config::CodePatternsPolicy>("CodePatterns",
719 *F.CodePatterns)
722 .value())
723 Out.Apply.push_back([Val](const Params &, Config &C) {
724 C.Completion.CodePatterns = *Val;
725 });
726 }
727
728 if (F.MacroFilter) {
729 if (auto Val =
730 compileEnum<Config::MacroFilterPolicy>("MacroFilter",
731 *F.MacroFilter)
732 .map("ExactPrefix", Config::MacroFilterPolicy::ExactPrefix)
733 .map("FuzzyMatch", Config::MacroFilterPolicy::FuzzyMatch)
734 .value())
735 Out.Apply.push_back([Val](const Params &, Config &C) {
736 C.Completion.MacroFilter = *Val;
737 });
738 }
739 }
740
741 void compile(Fragment::HoverBlock &&F) {
742 if (F.ShowAKA) {
743 Out.Apply.push_back([ShowAKA(**F.ShowAKA)](const Params &, Config &C) {
744 C.Hover.ShowAKA = ShowAKA;
745 });
746 }
747 if (F.MacroContentsLimit) {
748 Out.Apply.push_back(
749 [Limit(**F.MacroContentsLimit)](const Params &, Config &C) {
750 C.Hover.MacroContentsLimit = Limit;
751 });
752 }
753 }
754
755 void compile(Fragment::InlayHintsBlock &&F) {
756 if (F.Enabled)
757 Out.Apply.push_back([Value(**F.Enabled)](const Params &, Config &C) {
758 C.InlayHints.Enabled = Value;
759 });
760 if (F.ParameterNames)
761 Out.Apply.push_back(
762 [Value(**F.ParameterNames)](const Params &, Config &C) {
763 C.InlayHints.Parameters = Value;
764 });
765 if (F.DeducedTypes)
766 Out.Apply.push_back([Value(**F.DeducedTypes)](const Params &, Config &C) {
767 C.InlayHints.DeducedTypes = Value;
768 });
769 if (F.Designators)
770 Out.Apply.push_back([Value(**F.Designators)](const Params &, Config &C) {
771 C.InlayHints.Designators = Value;
772 });
773 if (F.BlockEnd)
774 Out.Apply.push_back([Value(**F.BlockEnd)](const Params &, Config &C) {
775 C.InlayHints.BlockEnd = Value;
776 });
777 if (F.DefaultArguments)
778 Out.Apply.push_back(
779 [Value(**F.DefaultArguments)](const Params &, Config &C) {
780 C.InlayHints.DefaultArguments = Value;
781 });
782 if (F.TypeNameLimit)
783 Out.Apply.push_back(
784 [Value(**F.TypeNameLimit)](const Params &, Config &C) {
785 C.InlayHints.TypeNameLimit = Value;
786 });
787 }
788
789 void compile(Fragment::SemanticTokensBlock &&F) {
790 if (!F.DisabledKinds.empty()) {
791 std::vector<std::string> DisabledKinds;
792 for (auto &Kind : F.DisabledKinds)
793 DisabledKinds.push_back(std::move(*Kind));
794
795 Out.Apply.push_back(
796 [DisabledKinds(std::move(DisabledKinds))](const Params &, Config &C) {
797 for (auto &Kind : DisabledKinds) {
798 auto It = llvm::find(C.SemanticTokens.DisabledKinds, Kind);
799 if (It == C.SemanticTokens.DisabledKinds.end())
800 C.SemanticTokens.DisabledKinds.push_back(std::move(Kind));
801 }
802 });
803 }
804 if (!F.DisabledModifiers.empty()) {
805 std::vector<std::string> DisabledModifiers;
806 for (auto &Kind : F.DisabledModifiers)
807 DisabledModifiers.push_back(std::move(*Kind));
808
809 Out.Apply.push_back([DisabledModifiers(std::move(DisabledModifiers))](
810 const Params &, Config &C) {
811 for (auto &Kind : DisabledModifiers) {
812 auto It = llvm::find(C.SemanticTokens.DisabledModifiers, Kind);
813 if (It == C.SemanticTokens.DisabledModifiers.end())
814 C.SemanticTokens.DisabledModifiers.push_back(std::move(Kind));
815 }
816 });
817 }
818 }
819
820 void compile(Fragment::DocumentationBlock &&F) {
821 if (F.CommentFormat) {
822 if (auto Val =
823 compileEnum<Config::CommentFormatPolicy>("CommentFormat",
824 *F.CommentFormat)
828 .value())
829 Out.Apply.push_back([Val](const Params &, Config &C) {
830 C.Documentation.CommentFormat = *Val;
831 });
832 }
833 }
834
835 constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error;
836 constexpr static llvm::SourceMgr::DiagKind Warning =
837 llvm::SourceMgr::DK_Warning;
838 void diag(llvm::SourceMgr::DiagKind Kind, llvm::StringRef Message,
839 llvm::SMRange Range) {
840 if (Range.isValid() && SourceMgr != nullptr)
841 Diagnostic(SourceMgr->GetMessage(Range.Start, Kind, Message, Range));
842 else
843 Diagnostic(llvm::SMDiagnostic("", Kind, Message));
844 }
845};
846
847} // namespace
848
850 llvm::StringRef ConfigFile = "<unknown>";
851 std::pair<unsigned, unsigned> LineCol = {0, 0};
852 if (auto *SM = Source.Manager.get()) {
853 unsigned BufID = SM->getMainFileID();
854 LineCol = SM->getLineAndColumn(Source.Location, BufID);
855 ConfigFile = SM->getBufferInfo(BufID).Buffer->getBufferIdentifier();
856 }
857 trace::Span Tracer("ConfigCompile");
858 SPAN_ATTACH(Tracer, "ConfigFile", ConfigFile);
859 auto Result = std::make_shared<CompiledFragmentImpl>();
860 vlog("Config fragment: compiling {0}:{1} -> {2} (trusted={3})", ConfigFile,
861 LineCol.first, Result.get(), Source.Trusted);
862
863 FragmentCompiler{*Result, D, Source.Manager.get()}.compile(std::move(*this));
864 // Return as cheaply-copyable wrapper.
865 return [Result(std::move(Result))](const Params &P, Config &C) {
866 return (*Result)(P, C);
867 };
868}
869
870} // namespace config
871} // namespace clangd
872} // 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:44
@ Warning
A warning message.
Definition Protocol.h:753
@ Error
An error message.
Definition Protocol.h:751
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:79
std::string Location
This is one of:
Definition Config.h:84
enum clang::clangd::Config::ExternalIndexSpec::@312203371027245331141003236204003120156040237037 Kind
std::string MountPoint
Absolute path to source root this index is associated with, uses forward-slashes.
Definition Config.h:87
Settings that express user/project preferences and control clangd behavior.
Definition Config.h:45
@ None
nothing, no argument list and also NO Delimiters "()" or "<>".
Definition Config.h:143
@ Delimiters
empty pair of delimiters "()" or "<>".
Definition Config.h:147
@ OpenDelimiter
open, only opening delimiter "(" or "<".
Definition Config.h:145
@ FullPlaceholders
full name of both type and variable.
Definition Config.h:149
@ Strict
Diagnose missing and unused includes.
Definition Config.h:99
@ Markdown
Treat comments as Markdown.
Definition Config.h:217
@ Doxygen
Treat comments as doxygen.
Definition Config.h:219
@ PlainText
Treat comments as plain text.
Definition Config.h:215
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.