blob: 3f4dcd3c036d822b2a830e4919e847ee56cc59da [file] [log] [blame]
Sam McCallf12cd992020-06-26 01:49:53 +02001//===--- 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
Sam McCall6c16fbd2020-07-13 20:37:54 +020026#include "CompileCommands.h"
Sam McCallf12cd992020-06-26 01:49:53 +020027#include "Config.h"
28#include "ConfigFragment.h"
Kadir Cetinkaya7f059a22020-10-27 23:03:41 +010029#include "ConfigProvider.h"
Sam McCallf12cd992020-06-26 01:49:53 +020030#include "support/Logger.h"
31#include "support/Trace.h"
Sam McCalldbf486c2020-07-14 15:17:16 +020032#include "llvm/ADT/STLExtras.h"
Sam McCallf12cd992020-06-26 01:49:53 +020033#include "llvm/ADT/StringRef.h"
Sam McCalldbf486c2020-07-14 15:17:16 +020034#include "llvm/ADT/StringSwitch.h"
Kadir Cetinkaya7f059a22020-10-27 23:03:41 +010035#include "llvm/Support/Path.h"
Sam McCallf12cd992020-06-26 01:49:53 +020036#include "llvm/Support/Regex.h"
37#include "llvm/Support/SMLoc.h"
38#include "llvm/Support/SourceMgr.h"
Kadir Cetinkaya7f059a22020-10-27 23:03:41 +010039#include <string>
Sam McCallf12cd992020-06-26 01:49:53 +020040
41namespace clang {
42namespace clangd {
43namespace config {
44namespace {
45
Kadir Cetinkaya7f059a22020-10-27 23:03:41 +010046// Returns an empty stringref if Path is not under FragmentDir. Returns Path
47// as-is when FragmentDir is empty.
48llvm::StringRef configRelative(llvm::StringRef Path,
49 llvm::StringRef FragmentDir) {
50 if (FragmentDir.empty())
51 return Path;
52 if (!Path.consume_front(FragmentDir))
53 return llvm::StringRef();
54 return Path.empty() ? "." : Path;
55}
56
Sam McCallf12cd992020-06-26 01:49:53 +020057struct CompiledFragmentImpl {
58 // The independent conditions to check before using settings from this config.
59 // The following fragment has *two* conditions:
60 // If: { Platform: [mac, linux], PathMatch: foo/.* }
61 // All of them must be satisfied: the platform and path conditions are ANDed.
62 // The OR logic for the platform condition is implemented inside the function.
63 std::vector<llvm::unique_function<bool(const Params &) const>> Conditions;
64 // Mutations that this fragment will apply to the configuration.
65 // These are invoked only if the conditions are satisfied.
Kadir Cetinkayaa57550d2020-10-30 12:06:16 +010066 std::vector<llvm::unique_function<void(const Params &, Config &) const>>
67 Apply;
Sam McCallf12cd992020-06-26 01:49:53 +020068
69 bool operator()(const Params &P, Config &C) const {
70 for (const auto &C : Conditions) {
71 if (!C(P)) {
72 dlog("Config fragment {0}: condition not met", this);
73 return false;
74 }
75 }
76 dlog("Config fragment {0}: applying {1} rules", this, Apply.size());
77 for (const auto &A : Apply)
Kadir Cetinkayaa57550d2020-10-30 12:06:16 +010078 A(P, C);
Sam McCallf12cd992020-06-26 01:49:53 +020079 return true;
80 }
81};
82
83// Wrapper around condition compile() functions to reduce arg-passing.
84struct FragmentCompiler {
Kadir Cetinkaya7f059a22020-10-27 23:03:41 +010085 FragmentCompiler(CompiledFragmentImpl &Out, DiagnosticCallback D,
86 llvm::SourceMgr *SM)
87 : Out(Out), Diagnostic(D), SourceMgr(SM) {}
Sam McCallf12cd992020-06-26 01:49:53 +020088 CompiledFragmentImpl &Out;
89 DiagnosticCallback Diagnostic;
90 llvm::SourceMgr *SourceMgr;
Kadir Cetinkaya7f059a22020-10-27 23:03:41 +010091 // Normalized Fragment::SourceInfo::Directory.
92 std::string FragmentDirectory;
Sam McCallf12cd992020-06-26 01:49:53 +020093
94 llvm::Optional<llvm::Regex> compileRegex(const Located<std::string> &Text) {
95 std::string Anchored = "^(" + *Text + ")$";
96 llvm::Regex Result(Anchored);
97 std::string RegexError;
98 if (!Result.isValid(RegexError)) {
99 diag(Error, "Invalid regex " + Anchored + ": " + RegexError, Text.Range);
100 return llvm::None;
101 }
102 return Result;
103 }
104
Sam McCalldbf486c2020-07-14 15:17:16 +0200105 // Helper with similar API to StringSwitch, for parsing enum values.
106 template <typename T> class EnumSwitch {
107 FragmentCompiler &Outer;
108 llvm::StringRef EnumName;
109 const Located<std::string> &Input;
110 llvm::Optional<T> Result;
111 llvm::SmallVector<llvm::StringLiteral, 8> ValidValues;
112
113 public:
114 EnumSwitch(llvm::StringRef EnumName, const Located<std::string> &In,
115 FragmentCompiler &Outer)
116 : Outer(Outer), EnumName(EnumName), Input(In) {}
117
118 EnumSwitch &map(llvm::StringLiteral Name, T Value) {
119 assert(!llvm::is_contained(ValidValues, Name) && "Duplicate value!");
120 ValidValues.push_back(Name);
121 if (!Result && *Input == Name)
122 Result = Value;
123 return *this;
124 }
125
126 llvm::Optional<T> value() {
127 if (!Result)
128 Outer.diag(
129 Warning,
130 llvm::formatv("Invalid {0} value '{1}'. Valid values are {2}.",
131 EnumName, *Input, llvm::join(ValidValues, ", "))
132 .str(),
133 Input.Range);
134 return Result;
135 };
136 };
137
138 // Attempt to parse a specified string into an enum.
139 // Yields llvm::None and produces a diagnostic on failure.
140 //
141 // Optional<T> Value = compileEnum<En>("Foo", Frag.Foo)
142 // .map("Foo", Enum::Foo)
143 // .map("Bar", Enum::Bar)
144 // .value();
145 template <typename T>
146 EnumSwitch<T> compileEnum(llvm::StringRef EnumName,
147 const Located<std::string> &In) {
148 return EnumSwitch<T>(EnumName, In, *this);
149 }
150
Sam McCallf12cd992020-06-26 01:49:53 +0200151 void compile(Fragment &&F) {
Kadir Cetinkaya7f059a22020-10-27 23:03:41 +0100152 if (!F.Source.Directory.empty()) {
153 FragmentDirectory = llvm::sys::path::convert_to_slash(F.Source.Directory);
154 if (FragmentDirectory.back() != '/')
155 FragmentDirectory += '/';
156 }
Sam McCallf12cd992020-06-26 01:49:53 +0200157 compile(std::move(F.If));
158 compile(std::move(F.CompileFlags));
Sam McCalldbf486c2020-07-14 15:17:16 +0200159 compile(std::move(F.Index));
Nathan James20b69af2020-11-22 10:04:00 +0000160 compile(std::move(F.ClangTidy));
Sam McCallf12cd992020-06-26 01:49:53 +0200161 }
162
163 void compile(Fragment::IfBlock &&F) {
164 if (F.HasUnrecognizedCondition)
165 Out.Conditions.push_back([&](const Params &) { return false; });
166
167 auto PathMatch = std::make_unique<std::vector<llvm::Regex>>();
168 for (auto &Entry : F.PathMatch) {
169 if (auto RE = compileRegex(Entry))
170 PathMatch->push_back(std::move(*RE));
171 }
172 if (!PathMatch->empty()) {
173 Out.Conditions.push_back(
Kadir Cetinkaya7f059a22020-10-27 23:03:41 +0100174 [PathMatch(std::move(PathMatch)),
175 FragmentDir(FragmentDirectory)](const Params &P) {
Sam McCallf12cd992020-06-26 01:49:53 +0200176 if (P.Path.empty())
177 return false;
Kadir Cetinkaya7f059a22020-10-27 23:03:41 +0100178 llvm::StringRef Path = configRelative(P.Path, FragmentDir);
179 // Ignore the file if it is not nested under Fragment.
180 if (Path.empty())
181 return false;
Sam McCallf12cd992020-06-26 01:49:53 +0200182 return llvm::any_of(*PathMatch, [&](const llvm::Regex &RE) {
Kadir Cetinkaya7f059a22020-10-27 23:03:41 +0100183 return RE.match(Path);
Sam McCallf12cd992020-06-26 01:49:53 +0200184 });
185 });
186 }
Sam McCall86f13132020-07-09 23:33:46 +0200187
188 auto PathExclude = std::make_unique<std::vector<llvm::Regex>>();
189 for (auto &Entry : F.PathExclude) {
190 if (auto RE = compileRegex(Entry))
191 PathExclude->push_back(std::move(*RE));
192 }
193 if (!PathExclude->empty()) {
194 Out.Conditions.push_back(
Kadir Cetinkaya7f059a22020-10-27 23:03:41 +0100195 [PathExclude(std::move(PathExclude)),
196 FragmentDir(FragmentDirectory)](const Params &P) {
Sam McCall86f13132020-07-09 23:33:46 +0200197 if (P.Path.empty())
198 return false;
Kadir Cetinkaya7f059a22020-10-27 23:03:41 +0100199 llvm::StringRef Path = configRelative(P.Path, FragmentDir);
200 // Ignore the file if it is not nested under Fragment.
201 if (Path.empty())
202 return true;
Sam McCall86f13132020-07-09 23:33:46 +0200203 return llvm::none_of(*PathExclude, [&](const llvm::Regex &RE) {
Kadir Cetinkaya7f059a22020-10-27 23:03:41 +0100204 return RE.match(Path);
Sam McCall86f13132020-07-09 23:33:46 +0200205 });
206 });
207 }
Sam McCallf12cd992020-06-26 01:49:53 +0200208 }
209
210 void compile(Fragment::CompileFlagsBlock &&F) {
Sam McCall6c16fbd2020-07-13 20:37:54 +0200211 if (!F.Remove.empty()) {
212 auto Remove = std::make_shared<ArgStripper>();
213 for (auto &A : F.Remove)
214 Remove->strip(*A);
215 Out.Apply.push_back([Remove(std::shared_ptr<const ArgStripper>(
Kadir Cetinkayaa57550d2020-10-30 12:06:16 +0100216 std::move(Remove)))](const Params &, Config &C) {
Sam McCall6c16fbd2020-07-13 20:37:54 +0200217 C.CompileFlags.Edits.push_back(
218 [Remove](std::vector<std::string> &Args) {
219 Remove->process(Args);
220 });
221 });
222 }
223
Sam McCallf12cd992020-06-26 01:49:53 +0200224 if (!F.Add.empty()) {
225 std::vector<std::string> Add;
226 for (auto &A : F.Add)
227 Add.push_back(std::move(*A));
Kadir Cetinkayaa57550d2020-10-30 12:06:16 +0100228 Out.Apply.push_back([Add(std::move(Add))](const Params &, Config &C) {
Sam McCallf12cd992020-06-26 01:49:53 +0200229 C.CompileFlags.Edits.push_back([Add](std::vector<std::string> &Args) {
230 Args.insert(Args.end(), Add.begin(), Add.end());
231 });
232 });
233 }
234 }
235
Sam McCalldbf486c2020-07-14 15:17:16 +0200236 void compile(Fragment::IndexBlock &&F) {
237 if (F.Background) {
238 if (auto Val = compileEnum<Config::BackgroundPolicy>("Background",
239 **F.Background)
240 .map("Build", Config::BackgroundPolicy::Build)
241 .map("Skip", Config::BackgroundPolicy::Skip)
242 .value())
Kadir Cetinkayaa57550d2020-10-30 12:06:16 +0100243 Out.Apply.push_back(
244 [Val](const Params &, Config &C) { C.Index.Background = *Val; });
Sam McCalldbf486c2020-07-14 15:17:16 +0200245 }
246 }
247
Adam Czachorowskic894bfd2020-09-15 19:47:50 +0200248 void compile(Fragment::StyleBlock &&F) {
249 if (!F.FullyQualifiedNamespaces.empty()) {
250 std::vector<std::string> FullyQualifiedNamespaces;
251 for (auto &N : F.FullyQualifiedNamespaces) {
252 // Normalize the data by dropping both leading and trailing ::
253 StringRef Namespace(*N);
254 Namespace.consume_front("::");
255 Namespace.consume_back("::");
256 FullyQualifiedNamespaces.push_back(Namespace.str());
257 }
258 Out.Apply.push_back([FullyQualifiedNamespaces(
Kadir Cetinkayaa57550d2020-10-30 12:06:16 +0100259 std::move(FullyQualifiedNamespaces))](
260 const Params &, Config &C) {
Adam Czachorowskic894bfd2020-09-15 19:47:50 +0200261 C.Style.FullyQualifiedNamespaces.insert(
262 C.Style.FullyQualifiedNamespaces.begin(),
263 FullyQualifiedNamespaces.begin(), FullyQualifiedNamespaces.end());
264 });
265 }
266 }
267
Nathan James20b69af2020-11-22 10:04:00 +0000268 void appendTidyCheckSpec(std::string &CurSpec,
269 const Located<std::string> &Arg, bool IsPositive) {
270 StringRef Str = *Arg;
271 // Don't support negating here, its handled if the item is in the Add or
272 // Remove list.
273 if (Str.startswith("-") || Str.contains(',')) {
274 diag(Error, "Invalid clang-tidy check name", Arg.Range);
275 return;
276 }
277 CurSpec += ',';
278 if (!IsPositive)
279 CurSpec += '-';
280 CurSpec += Str;
281 }
282
283 void compile(Fragment::ClangTidyBlock &&F) {
284 std::string Checks;
285 for (auto &CheckGlob : F.Add)
286 appendTidyCheckSpec(Checks, CheckGlob, true);
287
288 for (auto &CheckGlob : F.Remove)
289 appendTidyCheckSpec(Checks, CheckGlob, false);
290
291 if (!Checks.empty())
292 Out.Apply.push_back(
293 [Checks = std::move(Checks)](const Params &, Config &C) {
294 C.ClangTidy.Checks.append(
Nathan James82c22f12020-11-22 10:48:48 +0000295 Checks, C.ClangTidy.Checks.empty() ? /*skip comma*/ 1 : 0,
296 std::string::npos);
Nathan James20b69af2020-11-22 10:04:00 +0000297 });
298 if (!F.CheckOptions.empty()) {
299 std::vector<std::pair<std::string, std::string>> CheckOptions;
300 for (auto &Opt : F.CheckOptions)
301 CheckOptions.emplace_back(std::move(*Opt.first),
302 std::move(*Opt.second));
303 Out.Apply.push_back(
304 [CheckOptions = std::move(CheckOptions)](const Params &, Config &C) {
305 for (auto &StringPair : CheckOptions)
306 C.ClangTidy.CheckOptions.insert_or_assign(StringPair.first,
307 StringPair.second);
308 });
309 }
310 }
311
Sam McCallf12cd992020-06-26 01:49:53 +0200312 constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error;
Sam McCalldbf486c2020-07-14 15:17:16 +0200313 constexpr static llvm::SourceMgr::DiagKind Warning =
314 llvm::SourceMgr::DK_Warning;
Sam McCallf12cd992020-06-26 01:49:53 +0200315 void diag(llvm::SourceMgr::DiagKind Kind, llvm::StringRef Message,
316 llvm::SMRange Range) {
317 if (Range.isValid() && SourceMgr != nullptr)
318 Diagnostic(SourceMgr->GetMessage(Range.Start, Kind, Message, Range));
319 else
320 Diagnostic(llvm::SMDiagnostic("", Kind, Message));
321 }
322};
323
324} // namespace
325
326CompiledFragment Fragment::compile(DiagnosticCallback D) && {
327 llvm::StringRef ConfigFile = "<unknown>";
328 std::pair<unsigned, unsigned> LineCol = {0, 0};
329 if (auto *SM = Source.Manager.get()) {
330 unsigned BufID = SM->getMainFileID();
331 LineCol = SM->getLineAndColumn(Source.Location, BufID);
332 ConfigFile = SM->getBufferInfo(BufID).Buffer->getBufferIdentifier();
333 }
334 trace::Span Tracer("ConfigCompile");
335 SPAN_ATTACH(Tracer, "ConfigFile", ConfigFile);
336 auto Result = std::make_shared<CompiledFragmentImpl>();
337 vlog("Config fragment: compiling {0}:{1} -> {2}", ConfigFile, LineCol.first,
338 Result.get());
339
340 FragmentCompiler{*Result, D, Source.Manager.get()}.compile(std::move(*this));
341 // Return as cheaply-copyable wrapper.
342 return [Result(std::move(Result))](const Params &P, Config &C) {
343 return (*Result)(P, C);
344 };
345}
346
347} // namespace config
348} // namespace clangd
349} // namespace clang