blob: 742abb42670f67500d6ca6eca37b5f28479be388 [file] [log] [blame]
Sam McCalle9fb1502020-06-23 17:21:56 +02001//===--- ConfigYAML.cpp - Loading configuration fragments from YAML files -===//
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#include "ConfigFragment.h"
Sam McCalldbf486c2020-07-14 15:17:16 +020010#include "llvm/ADT/Optional.h"
Sam McCalle9fb1502020-06-23 17:21:56 +020011#include "llvm/ADT/SmallSet.h"
12#include "llvm/ADT/StringRef.h"
13#include "llvm/Support/MemoryBuffer.h"
14#include "llvm/Support/SourceMgr.h"
15#include "llvm/Support/YAMLParser.h"
16#include <system_error>
17
18namespace clang {
19namespace clangd {
20namespace config {
21namespace {
22using llvm::yaml::BlockScalarNode;
23using llvm::yaml::MappingNode;
24using llvm::yaml::Node;
25using llvm::yaml::ScalarNode;
26using llvm::yaml::SequenceNode;
27
28class Parser {
29 llvm::SourceMgr &SM;
Sam McCallf3651862020-07-09 00:13:54 +020030 bool HadError = false;
Sam McCalle9fb1502020-06-23 17:21:56 +020031
32public:
33 Parser(llvm::SourceMgr &SM) : SM(SM) {}
34
35 // Tries to parse N into F, returning false if it failed and we couldn't
Sam McCallf3651862020-07-09 00:13:54 +020036 // meaningfully recover (YAML syntax error, or hard semantic error).
Sam McCalle9fb1502020-06-23 17:21:56 +020037 bool parse(Fragment &F, Node &N) {
38 DictParser Dict("Config", this);
Sam McCallf3651862020-07-09 00:13:54 +020039 Dict.handle("If", [&](Node &N) { parse(F.If, N); });
40 Dict.handle("CompileFlags", [&](Node &N) { parse(F.CompileFlags, N); });
Adam Czachorowski7029e5d2020-09-15 20:13:00 +020041 Dict.handle("Index", [&](Node &N) { parse(F.Index, N); });
Adam Czachorowskic894bfd2020-09-15 19:47:50 +020042 Dict.handle("Style", [&](Node &N) { parse(F.Style, N); });
Nathan James20b69af2020-11-22 10:04:00 +000043 Dict.handle("ClangTidy", [&](Node &N) { parse(F.ClangTidy, N); });
Sam McCallf3651862020-07-09 00:13:54 +020044 Dict.parse(N);
45 return !(N.failed() || HadError);
Sam McCalle9fb1502020-06-23 17:21:56 +020046 }
47
48private:
Sam McCallf3651862020-07-09 00:13:54 +020049 void parse(Fragment::IfBlock &F, Node &N) {
Sam McCallf12cd992020-06-26 01:49:53 +020050 DictParser Dict("If", this);
Nathan James20b69af2020-11-22 10:04:00 +000051 Dict.unrecognized([&](Located<std::string>, Node &) {
52 F.HasUnrecognizedCondition = true;
53 return true; // Emit a warning for the unrecognized key.
54 });
Sam McCalle9fb1502020-06-23 17:21:56 +020055 Dict.handle("PathMatch", [&](Node &N) {
56 if (auto Values = scalarValues(N))
57 F.PathMatch = std::move(*Values);
Sam McCalle9fb1502020-06-23 17:21:56 +020058 });
Sam McCall86f13132020-07-09 23:33:46 +020059 Dict.handle("PathExclude", [&](Node &N) {
60 if (auto Values = scalarValues(N))
61 F.PathExclude = std::move(*Values);
62 });
Sam McCallf3651862020-07-09 00:13:54 +020063 Dict.parse(N);
Sam McCalle9fb1502020-06-23 17:21:56 +020064 }
65
Sam McCallf3651862020-07-09 00:13:54 +020066 void parse(Fragment::CompileFlagsBlock &F, Node &N) {
Sam McCalle9fb1502020-06-23 17:21:56 +020067 DictParser Dict("CompileFlags", this);
68 Dict.handle("Add", [&](Node &N) {
69 if (auto Values = scalarValues(N))
70 F.Add = std::move(*Values);
Sam McCalle9fb1502020-06-23 17:21:56 +020071 });
Sam McCall6c16fbd2020-07-13 20:37:54 +020072 Dict.handle("Remove", [&](Node &N) {
73 if (auto Values = scalarValues(N))
74 F.Remove = std::move(*Values);
75 });
Sam McCallf3651862020-07-09 00:13:54 +020076 Dict.parse(N);
Sam McCalle9fb1502020-06-23 17:21:56 +020077 }
78
Adam Czachorowskic894bfd2020-09-15 19:47:50 +020079 void parse(Fragment::StyleBlock &F, Node &N) {
80 DictParser Dict("Style", this);
81 Dict.handle("FullyQualifiedNamespaces", [&](Node &N) {
82 if (auto Values = scalarValues(N))
83 F.FullyQualifiedNamespaces = std::move(*Values);
84 });
85 Dict.parse(N);
86 }
87
Nathan James20b69af2020-11-22 10:04:00 +000088 void parse(Fragment::ClangTidyBlock &F, Node &N) {
89 DictParser Dict("ClangTidy", this);
90 Dict.handle("Add", [&](Node &N) {
91 if (auto Values = scalarValues(N))
92 F.Add = std::move(*Values);
93 });
94 Dict.handle("Remove", [&](Node &N) {
95 if (auto Values = scalarValues(N))
96 F.Remove = std::move(*Values);
97 });
98 Dict.handle("CheckOptions", [&](Node &N) {
99 DictParser CheckOptDict("CheckOptions", this);
100 CheckOptDict.unrecognized([&](Located<std::string> &&Key, Node &Val) {
101 if (auto Value = scalarValue(Val, *Key))
102 F.CheckOptions.emplace_back(std::move(Key), std::move(*Value));
103 return false; // Don't emit a warning
104 });
105 CheckOptDict.parse(N);
106 });
107 Dict.parse(N);
108 }
109
Sam McCalldbf486c2020-07-14 15:17:16 +0200110 void parse(Fragment::IndexBlock &F, Node &N) {
111 DictParser Dict("Index", this);
112 Dict.handle("Background",
113 [&](Node &N) { F.Background = scalarValue(N, "Background"); });
114 Dict.parse(N);
115 }
116
Sam McCalle9fb1502020-06-23 17:21:56 +0200117 // Helper for parsing mapping nodes (dictionaries).
118 // We don't use YamlIO as we want to control over unknown keys.
119 class DictParser {
120 llvm::StringRef Description;
Sam McCallf3651862020-07-09 00:13:54 +0200121 std::vector<std::pair<llvm::StringRef, std::function<void(Node &)>>> Keys;
Nathan James20b69af2020-11-22 10:04:00 +0000122 std::function<bool(Located<std::string>, Node &)> UnknownHandler;
Sam McCalle9fb1502020-06-23 17:21:56 +0200123 Parser *Outer;
124
125 public:
126 DictParser(llvm::StringRef Description, Parser *Outer)
127 : Description(Description), Outer(Outer) {}
128
129 // Parse is called when Key is encountered, and passed the associated value.
130 // It should emit diagnostics if the value is invalid (e.g. wrong type).
131 // If Key is seen twice, Parse runs only once and an error is reported.
Sam McCallf3651862020-07-09 00:13:54 +0200132 void handle(llvm::StringLiteral Key, std::function<void(Node &)> Parse) {
Tres Popp1a30eab2020-06-26 10:12:04 +0200133 for (const auto &Entry : Keys) {
134 (void) Entry;
Sam McCalle9fb1502020-06-23 17:21:56 +0200135 assert(Entry.first != Key && "duplicate key handler");
Tres Popp1a30eab2020-06-26 10:12:04 +0200136 }
Sam McCalle9fb1502020-06-23 17:21:56 +0200137 Keys.emplace_back(Key, std::move(Parse));
138 }
139
Nathan James20b69af2020-11-22 10:04:00 +0000140 // Handler is called when a Key is not matched by any handle().
141 // If this is unset or the Handler returns true, a warning is emitted for
142 // the unknown key.
143 void
144 unrecognized(std::function<bool(Located<std::string>, Node &)> Handler) {
145 UnknownHandler = std::move(Handler);
Sam McCalle9fb1502020-06-23 17:21:56 +0200146 }
147
148 // Process a mapping node and call handlers for each key/value pair.
Sam McCallf3651862020-07-09 00:13:54 +0200149 void parse(Node &N) const {
Sam McCalle9fb1502020-06-23 17:21:56 +0200150 if (N.getType() != Node::NK_Mapping) {
151 Outer->error(Description + " should be a dictionary", N);
Sam McCallf3651862020-07-09 00:13:54 +0200152 return;
Sam McCalle9fb1502020-06-23 17:21:56 +0200153 }
154 llvm::SmallSet<std::string, 8> Seen;
Sam McCallf3651862020-07-09 00:13:54 +0200155 // We *must* consume all items, even on error, or the parser will assert.
Sam McCalle9fb1502020-06-23 17:21:56 +0200156 for (auto &KV : llvm::cast<MappingNode>(N)) {
157 auto *K = KV.getKey();
158 if (!K) // YAMLParser emitted an error.
Sam McCallf3651862020-07-09 00:13:54 +0200159 continue;
Sam McCalle9fb1502020-06-23 17:21:56 +0200160 auto Key = Outer->scalarValue(*K, "Dictionary key");
161 if (!Key)
162 continue;
163 if (!Seen.insert(**Key).second) {
164 Outer->warning("Duplicate key " + **Key + " is ignored", *K);
Nathan James20b69af2020-11-22 10:04:00 +0000165 if (auto *Value = KV.getValue())
166 Value->skip();
Sam McCalle9fb1502020-06-23 17:21:56 +0200167 continue;
168 }
169 auto *Value = KV.getValue();
170 if (!Value) // YAMLParser emitted an error.
Sam McCallf3651862020-07-09 00:13:54 +0200171 continue;
Sam McCalle9fb1502020-06-23 17:21:56 +0200172 bool Matched = false;
173 for (const auto &Handler : Keys) {
174 if (Handler.first == **Key) {
Sam McCalle9fb1502020-06-23 17:21:56 +0200175 Matched = true;
Sam McCallf3651862020-07-09 00:13:54 +0200176 Handler.second(*Value);
Sam McCalle9fb1502020-06-23 17:21:56 +0200177 break;
178 }
179 }
180 if (!Matched) {
Nathan James20b69af2020-11-22 10:04:00 +0000181 bool Warn = !UnknownHandler;
182 if (UnknownHandler)
183 Warn = UnknownHandler(
184 Located<std::string>(**Key, K->getSourceRange()), *Value);
185 if (Warn)
186 Outer->warning("Unknown " + Description + " key " + **Key, *K);
Sam McCalle9fb1502020-06-23 17:21:56 +0200187 }
188 }
Sam McCalle9fb1502020-06-23 17:21:56 +0200189 }
190 };
191
192 // Try to parse a single scalar value from the node, warn on failure.
193 llvm::Optional<Located<std::string>> scalarValue(Node &N,
194 llvm::StringRef Desc) {
195 llvm::SmallString<256> Buf;
196 if (auto *S = llvm::dyn_cast<ScalarNode>(&N))
197 return Located<std::string>(S->getValue(Buf).str(), N.getSourceRange());
198 if (auto *BS = llvm::dyn_cast<BlockScalarNode>(&N))
199 return Located<std::string>(BS->getValue().str(), N.getSourceRange());
200 warning(Desc + " should be scalar", N);
201 return llvm::None;
202 }
203
204 // Try to parse a list of single scalar values, or just a single value.
205 llvm::Optional<std::vector<Located<std::string>>> scalarValues(Node &N) {
206 std::vector<Located<std::string>> Result;
207 if (auto *S = llvm::dyn_cast<ScalarNode>(&N)) {
208 llvm::SmallString<256> Buf;
209 Result.emplace_back(S->getValue(Buf).str(), N.getSourceRange());
210 } else if (auto *S = llvm::dyn_cast<BlockScalarNode>(&N)) {
211 Result.emplace_back(S->getValue().str(), N.getSourceRange());
212 } else if (auto *S = llvm::dyn_cast<SequenceNode>(&N)) {
Sam McCallf3651862020-07-09 00:13:54 +0200213 // We *must* consume all items, even on error, or the parser will assert.
Sam McCalle9fb1502020-06-23 17:21:56 +0200214 for (auto &Child : *S) {
215 if (auto Value = scalarValue(Child, "List item"))
216 Result.push_back(std::move(*Value));
217 }
218 } else {
219 warning("Expected scalar or list of scalars", N);
220 return llvm::None;
221 }
222 return Result;
223 }
224
225 // Report a "hard" error, reflecting a config file that can never be valid.
226 void error(const llvm::Twine &Msg, const Node &N) {
Sam McCallf3651862020-07-09 00:13:54 +0200227 HadError = true;
Sam McCalle9fb1502020-06-23 17:21:56 +0200228 SM.PrintMessage(N.getSourceRange().Start, llvm::SourceMgr::DK_Error, Msg,
229 N.getSourceRange());
230 }
231
232 // Report a "soft" error that could be caused by e.g. version skew.
233 void warning(const llvm::Twine &Msg, const Node &N) {
234 SM.PrintMessage(N.getSourceRange().Start, llvm::SourceMgr::DK_Warning, Msg,
235 N.getSourceRange());
236 }
237};
238
239} // namespace
240
241std::vector<Fragment> Fragment::parseYAML(llvm::StringRef YAML,
242 llvm::StringRef BufferName,
243 DiagnosticCallback Diags) {
244 // The YAML document may contain multiple conditional fragments.
245 // The SourceManager is shared for all of them.
246 auto SM = std::make_shared<llvm::SourceMgr>();
247 auto Buf = llvm::MemoryBuffer::getMemBufferCopy(YAML, BufferName);
248 // Adapt DiagnosticCallback to function-pointer interface.
249 // Callback receives both errors we emit and those from the YAML parser.
250 SM->setDiagHandler(
251 [](const llvm::SMDiagnostic &Diag, void *Ctx) {
252 (*reinterpret_cast<DiagnosticCallback *>(Ctx))(Diag);
253 },
254 &Diags);
255 std::vector<Fragment> Result;
256 for (auto &Doc : llvm::yaml::Stream(*Buf, *SM)) {
Sam McCallf3651862020-07-09 00:13:54 +0200257 if (Node *N = Doc.getRoot()) {
Sam McCalle9fb1502020-06-23 17:21:56 +0200258 Fragment Fragment;
259 Fragment.Source.Manager = SM;
260 Fragment.Source.Location = N->getSourceRange().Start;
261 if (Parser(*SM).parse(Fragment, *N))
262 Result.push_back(std::move(Fragment));
263 }
264 }
265 // Hack: stash the buffer in the SourceMgr to keep it alive.
266 // SM has two entries: "main" non-owning buffer, and ignored owning buffer.
267 SM->AddNewSourceBuffer(std::move(Buf), llvm::SMLoc());
268 return Result;
269}
270
271} // namespace config
272} // namespace clangd
273} // namespace clang