1 | //===--- 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 | #include "ConfigFragment.h" |
9 | #include "llvm/ADT/SmallSet.h" |
10 | #include "llvm/ADT/SmallString.h" |
11 | #include "llvm/ADT/StringRef.h" |
12 | #include "llvm/Support/MemoryBuffer.h" |
13 | #include "llvm/Support/SourceMgr.h" |
14 | #include "llvm/Support/YAMLParser.h" |
15 | #include <optional> |
16 | #include <string> |
17 | #include <system_error> |
18 | |
19 | namespace clang { |
20 | namespace clangd { |
21 | namespace config { |
22 | namespace { |
23 | using llvm::yaml::BlockScalarNode; |
24 | using llvm::yaml::MappingNode; |
25 | using llvm::yaml::Node; |
26 | using llvm::yaml::ScalarNode; |
27 | using llvm::yaml::SequenceNode; |
28 | |
29 | std::optional<llvm::StringRef> |
30 | bestGuess(llvm::StringRef Search, |
31 | llvm::ArrayRef<llvm::StringRef> AllowedValues) { |
32 | unsigned MaxEdit = (Search.size() + 1) / 3; |
33 | if (!MaxEdit) |
34 | return std::nullopt; |
35 | std::optional<llvm::StringRef> Result; |
36 | for (const auto &AllowedValue : AllowedValues) { |
37 | unsigned EditDistance = Search.edit_distance(Other: AllowedValue, AllowReplacements: true, MaxEditDistance: MaxEdit); |
38 | // We can't do better than an edit distance of 1, so just return this and |
39 | // save computing other values. |
40 | if (EditDistance == 1U) |
41 | return AllowedValue; |
42 | if (EditDistance == MaxEdit && !Result) { |
43 | Result = AllowedValue; |
44 | } else if (EditDistance < MaxEdit) { |
45 | Result = AllowedValue; |
46 | MaxEdit = EditDistance; |
47 | } |
48 | } |
49 | return Result; |
50 | } |
51 | |
52 | class Parser { |
53 | llvm::SourceMgr &SM; |
54 | bool HadError = false; |
55 | |
56 | public: |
57 | Parser(llvm::SourceMgr &SM) : SM(SM) {} |
58 | |
59 | // Tries to parse N into F, returning false if it failed and we couldn't |
60 | // meaningfully recover (YAML syntax error, or hard semantic error). |
61 | bool parse(Fragment &F, Node &N) { |
62 | DictParser Dict("Config" , this); |
63 | Dict.handle(Key: "If" , Parse: [&](Node &N) { parse(F&: F.If, N); }); |
64 | Dict.handle(Key: "CompileFlags" , Parse: [&](Node &N) { parse(F&: F.CompileFlags, N); }); |
65 | Dict.handle(Key: "Index" , Parse: [&](Node &N) { parse(F&: F.Index, N); }); |
66 | Dict.handle(Key: "Style" , Parse: [&](Node &N) { parse(F&: F.Style, N); }); |
67 | Dict.handle(Key: "Diagnostics" , Parse: [&](Node &N) { parse(F&: F.Diagnostics, N); }); |
68 | Dict.handle(Key: "Completion" , Parse: [&](Node &N) { parse(F&: F.Completion, N); }); |
69 | Dict.handle(Key: "Hover" , Parse: [&](Node &N) { parse(F&: F.Hover, N); }); |
70 | Dict.handle(Key: "InlayHints" , Parse: [&](Node &N) { parse(F&: F.InlayHints, N); }); |
71 | Dict.handle(Key: "SemanticTokens" , Parse: [&](Node &N) { parse(F&: F.SemanticTokens, N); }); |
72 | Dict.parse(N); |
73 | return !(N.failed() || HadError); |
74 | } |
75 | |
76 | private: |
77 | void parse(Fragment::IfBlock &F, Node &N) { |
78 | DictParser Dict("If" , this); |
79 | Dict.unrecognized(Handler: [&](Located<std::string>, Node &) { |
80 | F.HasUnrecognizedCondition = true; |
81 | return true; // Emit a warning for the unrecognized key. |
82 | }); |
83 | Dict.handle(Key: "PathMatch" , Parse: [&](Node &N) { |
84 | if (auto Values = scalarValues(N)) |
85 | F.PathMatch = std::move(*Values); |
86 | }); |
87 | Dict.handle(Key: "PathExclude" , Parse: [&](Node &N) { |
88 | if (auto Values = scalarValues(N)) |
89 | F.PathExclude = std::move(*Values); |
90 | }); |
91 | Dict.parse(N); |
92 | } |
93 | |
94 | void parse(Fragment::CompileFlagsBlock &F, Node &N) { |
95 | DictParser Dict("CompileFlags" , this); |
96 | Dict.handle(Key: "Compiler" , Parse: [&](Node &N) { |
97 | if (auto Value = scalarValue(N, Desc: "Compiler" )) |
98 | F.Compiler = std::move(*Value); |
99 | }); |
100 | Dict.handle(Key: "Add" , Parse: [&](Node &N) { |
101 | if (auto Values = scalarValues(N)) |
102 | F.Add = std::move(*Values); |
103 | }); |
104 | Dict.handle(Key: "Remove" , Parse: [&](Node &N) { |
105 | if (auto Values = scalarValues(N)) |
106 | F.Remove = std::move(*Values); |
107 | }); |
108 | Dict.handle(Key: "CompilationDatabase" , Parse: [&](Node &N) { |
109 | F.CompilationDatabase = scalarValue(N, Desc: "CompilationDatabase" ); |
110 | }); |
111 | Dict.parse(N); |
112 | } |
113 | |
114 | void parse(Fragment::StyleBlock &F, Node &N) { |
115 | DictParser Dict("Style" , this); |
116 | Dict.handle(Key: "FullyQualifiedNamespaces" , Parse: [&](Node &N) { |
117 | if (auto Values = scalarValues(N)) |
118 | F.FullyQualifiedNamespaces = std::move(*Values); |
119 | }); |
120 | Dict.parse(N); |
121 | } |
122 | |
123 | void parse(Fragment::DiagnosticsBlock &F, Node &N) { |
124 | DictParser Dict("Diagnostics" , this); |
125 | Dict.handle(Key: "Suppress" , Parse: [&](Node &N) { |
126 | if (auto Values = scalarValues(N)) |
127 | F.Suppress = std::move(*Values); |
128 | }); |
129 | Dict.handle(Key: "UnusedIncludes" , Parse: [&](Node &N) { |
130 | F.UnusedIncludes = scalarValue(N, Desc: "UnusedIncludes" ); |
131 | }); |
132 | Dict.handle(Key: "MissingIncludes" , Parse: [&](Node &N) { |
133 | F.MissingIncludes = scalarValue(N, Desc: "MissingIncludes" ); |
134 | }); |
135 | Dict.handle(Key: "Includes" , Parse: [&](Node &N) { parse(F&: F.Includes, N); }); |
136 | Dict.handle(Key: "ClangTidy" , Parse: [&](Node &N) { parse(F&: F.ClangTidy, N); }); |
137 | Dict.parse(N); |
138 | } |
139 | |
140 | void parse(Fragment::DiagnosticsBlock::ClangTidyBlock &F, Node &N) { |
141 | DictParser Dict("ClangTidy" , this); |
142 | Dict.handle(Key: "Add" , Parse: [&](Node &N) { |
143 | if (auto Values = scalarValues(N)) |
144 | F.Add = std::move(*Values); |
145 | }); |
146 | Dict.handle(Key: "Remove" , Parse: [&](Node &N) { |
147 | if (auto Values = scalarValues(N)) |
148 | F.Remove = std::move(*Values); |
149 | }); |
150 | Dict.handle(Key: "CheckOptions" , Parse: [&](Node &N) { |
151 | DictParser CheckOptDict("CheckOptions" , this); |
152 | CheckOptDict.unrecognized(Handler: [&](Located<std::string> &&Key, Node &Val) { |
153 | if (auto Value = scalarValue(N&: Val, Desc: *Key)) |
154 | F.CheckOptions.emplace_back(args: std::move(Key), args: std::move(*Value)); |
155 | return false; // Don't emit a warning |
156 | }); |
157 | CheckOptDict.parse(N); |
158 | }); |
159 | Dict.handle(Key: "FastCheckFilter" , Parse: [&](Node &N) { |
160 | if (auto FastCheckFilter = scalarValue(N, Desc: "FastCheckFilter" )) |
161 | F.FastCheckFilter = *FastCheckFilter; |
162 | }); |
163 | Dict.parse(N); |
164 | } |
165 | |
166 | void parse(Fragment::DiagnosticsBlock::IncludesBlock &F, Node &N) { |
167 | DictParser Dict("Includes" , this); |
168 | Dict.handle(Key: "IgnoreHeader" , Parse: [&](Node &N) { |
169 | if (auto Values = scalarValues(N)) |
170 | F.IgnoreHeader = std::move(*Values); |
171 | }); |
172 | Dict.parse(N); |
173 | } |
174 | |
175 | void parse(Fragment::IndexBlock &F, Node &N) { |
176 | DictParser Dict("Index" , this); |
177 | Dict.handle(Key: "Background" , |
178 | Parse: [&](Node &N) { F.Background = scalarValue(N, Desc: "Background" ); }); |
179 | Dict.handle(Key: "External" , Parse: [&](Node &N) { |
180 | Fragment::IndexBlock::ExternalBlock External; |
181 | // External block can either be a mapping or a scalar value. Dispatch |
182 | // accordingly. |
183 | if (N.getType() == Node::NK_Mapping) { |
184 | parse(F&: External, N); |
185 | } else if (N.getType() == Node::NK_Scalar || |
186 | N.getType() == Node::NK_BlockScalar) { |
187 | parse(F&: External, ExternalVal: *scalarValue(N, Desc: "External" )); |
188 | } else { |
189 | error(Msg: "External must be either a scalar or a mapping." , N); |
190 | return; |
191 | } |
192 | F.External.emplace(args: std::move(External)); |
193 | F.External->Range = N.getSourceRange(); |
194 | }); |
195 | Dict.handle(Key: "StandardLibrary" , Parse: [&](Node &N) { |
196 | if (auto StandardLibrary = boolValue(N, Desc: "StandardLibrary" )) |
197 | F.StandardLibrary = *StandardLibrary; |
198 | }); |
199 | Dict.parse(N); |
200 | } |
201 | |
202 | void parse(Fragment::IndexBlock::ExternalBlock &F, |
203 | Located<std::string> ExternalVal) { |
204 | if (!llvm::StringRef(*ExternalVal).equals_insensitive(RHS: "none" )) { |
205 | error(Msg: "Only scalar value supported for External is 'None'" , |
206 | Range: ExternalVal.Range); |
207 | return; |
208 | } |
209 | F.IsNone = true; |
210 | F.IsNone.Range = ExternalVal.Range; |
211 | } |
212 | |
213 | void parse(Fragment::IndexBlock::ExternalBlock &F, Node &N) { |
214 | DictParser Dict("External" , this); |
215 | Dict.handle(Key: "File" , Parse: [&](Node &N) { F.File = scalarValue(N, Desc: "File" ); }); |
216 | Dict.handle(Key: "Server" , |
217 | Parse: [&](Node &N) { F.Server = scalarValue(N, Desc: "Server" ); }); |
218 | Dict.handle(Key: "MountPoint" , |
219 | Parse: [&](Node &N) { F.MountPoint = scalarValue(N, Desc: "MountPoint" ); }); |
220 | Dict.parse(N); |
221 | } |
222 | |
223 | void parse(Fragment::CompletionBlock &F, Node &N) { |
224 | DictParser Dict("Completion" , this); |
225 | Dict.handle(Key: "AllScopes" , Parse: [&](Node &N) { |
226 | if (auto AllScopes = boolValue(N, Desc: "AllScopes" )) |
227 | F.AllScopes = *AllScopes; |
228 | }); |
229 | Dict.parse(N); |
230 | } |
231 | |
232 | void parse(Fragment::HoverBlock &F, Node &N) { |
233 | DictParser Dict("Hover" , this); |
234 | Dict.handle(Key: "ShowAKA" , Parse: [&](Node &N) { |
235 | if (auto ShowAKA = boolValue(N, Desc: "ShowAKA" )) |
236 | F.ShowAKA = *ShowAKA; |
237 | }); |
238 | Dict.parse(N); |
239 | } |
240 | |
241 | void parse(Fragment::InlayHintsBlock &F, Node &N) { |
242 | DictParser Dict("InlayHints" , this); |
243 | Dict.handle(Key: "Enabled" , Parse: [&](Node &N) { |
244 | if (auto Value = boolValue(N, Desc: "Enabled" )) |
245 | F.Enabled = *Value; |
246 | }); |
247 | Dict.handle(Key: "ParameterNames" , Parse: [&](Node &N) { |
248 | if (auto Value = boolValue(N, Desc: "ParameterNames" )) |
249 | F.ParameterNames = *Value; |
250 | }); |
251 | Dict.handle(Key: "DeducedTypes" , Parse: [&](Node &N) { |
252 | if (auto Value = boolValue(N, Desc: "DeducedTypes" )) |
253 | F.DeducedTypes = *Value; |
254 | }); |
255 | Dict.handle(Key: "Designators" , Parse: [&](Node &N) { |
256 | if (auto Value = boolValue(N, Desc: "Designators" )) |
257 | F.Designators = *Value; |
258 | }); |
259 | Dict.handle(Key: "BlockEnd" , Parse: [&](Node &N) { |
260 | if (auto Value = boolValue(N, Desc: "BlockEnd" )) |
261 | F.BlockEnd = *Value; |
262 | }); |
263 | Dict.handle(Key: "TypeNameLimit" , Parse: [&](Node &N) { |
264 | if (auto Value = uint32Value(N, Desc: "TypeNameLimit" )) |
265 | F.TypeNameLimit = *Value; |
266 | }); |
267 | Dict.parse(N); |
268 | } |
269 | |
270 | void parse(Fragment::SemanticTokensBlock &F, Node &N) { |
271 | DictParser Dict("SemanticTokens" , this); |
272 | Dict.handle(Key: "DisabledKinds" , Parse: [&](Node &N) { |
273 | if (auto Values = scalarValues(N)) |
274 | F.DisabledKinds = std::move(*Values); |
275 | }); |
276 | Dict.handle(Key: "DisabledModifiers" , Parse: [&](Node &N) { |
277 | if (auto Values = scalarValues(N)) |
278 | F.DisabledModifiers = std::move(*Values); |
279 | }); |
280 | Dict.parse(N); |
281 | } |
282 | |
283 | // Helper for parsing mapping nodes (dictionaries). |
284 | // We don't use YamlIO as we want to control over unknown keys. |
285 | class DictParser { |
286 | llvm::StringRef Description; |
287 | std::vector<std::pair<llvm::StringRef, std::function<void(Node &)>>> Keys; |
288 | std::function<bool(Located<std::string>, Node &)> UnknownHandler; |
289 | Parser *Outer; |
290 | |
291 | public: |
292 | DictParser(llvm::StringRef Description, Parser *Outer) |
293 | : Description(Description), Outer(Outer) {} |
294 | |
295 | // Parse is called when Key is encountered, and passed the associated value. |
296 | // It should emit diagnostics if the value is invalid (e.g. wrong type). |
297 | // If Key is seen twice, Parse runs only once and an error is reported. |
298 | void handle(llvm::StringLiteral Key, std::function<void(Node &)> Parse) { |
299 | for (const auto &Entry : Keys) { |
300 | (void)Entry; |
301 | assert(Entry.first != Key && "duplicate key handler" ); |
302 | } |
303 | Keys.emplace_back(args&: Key, args: std::move(Parse)); |
304 | } |
305 | |
306 | // Handler is called when a Key is not matched by any handle(). |
307 | // If this is unset or the Handler returns true, a warning is emitted for |
308 | // the unknown key. |
309 | void |
310 | unrecognized(std::function<bool(Located<std::string>, Node &)> Handler) { |
311 | UnknownHandler = std::move(Handler); |
312 | } |
313 | |
314 | // Process a mapping node and call handlers for each key/value pair. |
315 | void parse(Node &N) const { |
316 | if (N.getType() != Node::NK_Mapping) { |
317 | Outer->error(Msg: Description + " should be a dictionary" , N); |
318 | return; |
319 | } |
320 | llvm::SmallSet<std::string, 8> Seen; |
321 | llvm::SmallVector<Located<std::string>, 0> UnknownKeys; |
322 | // We *must* consume all items, even on error, or the parser will assert. |
323 | for (auto &KV : llvm::cast<MappingNode>(Val&: N)) { |
324 | auto *K = KV.getKey(); |
325 | if (!K) // YAMLParser emitted an error. |
326 | continue; |
327 | auto Key = Outer->scalarValue(N&: *K, Desc: "Dictionary key" ); |
328 | if (!Key) |
329 | continue; |
330 | if (!Seen.insert(V: **Key).second) { |
331 | Outer->warning(Msg: "Duplicate key " + **Key + " is ignored" , N: *K); |
332 | if (auto *Value = KV.getValue()) |
333 | Value->skip(); |
334 | continue; |
335 | } |
336 | auto *Value = KV.getValue(); |
337 | if (!Value) // YAMLParser emitted an error. |
338 | continue; |
339 | bool Matched = false; |
340 | for (const auto &Handler : Keys) { |
341 | if (Handler.first == **Key) { |
342 | Matched = true; |
343 | Handler.second(*Value); |
344 | break; |
345 | } |
346 | } |
347 | if (!Matched) { |
348 | bool Warn = !UnknownHandler; |
349 | if (UnknownHandler) |
350 | Warn = UnknownHandler( |
351 | Located<std::string>(**Key, K->getSourceRange()), *Value); |
352 | if (Warn) |
353 | UnknownKeys.push_back(Elt: std::move(*Key)); |
354 | } |
355 | } |
356 | if (!UnknownKeys.empty()) |
357 | warnUnknownKeys(UnknownKeys, SeenKeys: Seen); |
358 | } |
359 | |
360 | private: |
361 | void warnUnknownKeys(llvm::ArrayRef<Located<std::string>> UnknownKeys, |
362 | const llvm::SmallSet<std::string, 8> &SeenKeys) const { |
363 | llvm::SmallVector<llvm::StringRef> UnseenKeys; |
364 | for (const auto &KeyAndHandler : Keys) |
365 | if (!SeenKeys.count(V: KeyAndHandler.first.str())) |
366 | UnseenKeys.push_back(Elt: KeyAndHandler.first); |
367 | |
368 | for (const Located<std::string> &UnknownKey : UnknownKeys) |
369 | if (auto BestGuess = bestGuess(Search: *UnknownKey, AllowedValues: UnseenKeys)) |
370 | Outer->warning(Msg: "Unknown " + Description + " key '" + *UnknownKey + |
371 | "'; did you mean '" + *BestGuess + "'?" , |
372 | Range: UnknownKey.Range); |
373 | else |
374 | Outer->warning(Msg: "Unknown " + Description + " key '" + *UnknownKey + |
375 | "'" , |
376 | Range: UnknownKey.Range); |
377 | } |
378 | }; |
379 | |
380 | // Try to parse a single scalar value from the node, warn on failure. |
381 | std::optional<Located<std::string>> scalarValue(Node &N, |
382 | llvm::StringRef Desc) { |
383 | llvm::SmallString<256> Buf; |
384 | if (auto *S = llvm::dyn_cast<ScalarNode>(Val: &N)) |
385 | return Located<std::string>(S->getValue(Storage&: Buf).str(), N.getSourceRange()); |
386 | if (auto *BS = llvm::dyn_cast<BlockScalarNode>(Val: &N)) |
387 | return Located<std::string>(BS->getValue().str(), N.getSourceRange()); |
388 | warning(Msg: Desc + " should be scalar" , N); |
389 | return std::nullopt; |
390 | } |
391 | |
392 | std::optional<Located<bool>> boolValue(Node &N, llvm::StringRef Desc) { |
393 | if (auto Scalar = scalarValue(N, Desc)) { |
394 | if (auto Bool = llvm::yaml::parseBool(S: **Scalar)) |
395 | return Located<bool>(*Bool, Scalar->Range); |
396 | warning(Msg: Desc + " should be a boolean" , N); |
397 | } |
398 | return std::nullopt; |
399 | } |
400 | |
401 | std::optional<Located<uint32_t>> uint32Value(Node &N, llvm::StringRef Desc) { |
402 | if (auto Scalar = scalarValue(N, Desc)) { |
403 | unsigned long long Num; |
404 | if (!llvm::getAsUnsignedInteger(Str: **Scalar, Radix: 0, Result&: Num)) { |
405 | return Located<uint32_t>(Num, Scalar->Range); |
406 | } |
407 | } |
408 | warning(Msg: Desc + " invalid number" , N); |
409 | return std::nullopt; |
410 | } |
411 | |
412 | // Try to parse a list of single scalar values, or just a single value. |
413 | std::optional<std::vector<Located<std::string>>> scalarValues(Node &N) { |
414 | std::vector<Located<std::string>> Result; |
415 | if (auto *S = llvm::dyn_cast<ScalarNode>(Val: &N)) { |
416 | llvm::SmallString<256> Buf; |
417 | Result.emplace_back(args: S->getValue(Storage&: Buf).str(), args: N.getSourceRange()); |
418 | } else if (auto *S = llvm::dyn_cast<BlockScalarNode>(Val: &N)) { |
419 | Result.emplace_back(args: S->getValue().str(), args: N.getSourceRange()); |
420 | } else if (auto *S = llvm::dyn_cast<SequenceNode>(Val: &N)) { |
421 | // We *must* consume all items, even on error, or the parser will assert. |
422 | for (auto &Child : *S) { |
423 | if (auto Value = scalarValue(N&: Child, Desc: "List item" )) |
424 | Result.push_back(x: std::move(*Value)); |
425 | } |
426 | } else { |
427 | warning(Msg: "Expected scalar or list of scalars" , N); |
428 | return std::nullopt; |
429 | } |
430 | return Result; |
431 | } |
432 | |
433 | // Report a "hard" error, reflecting a config file that can never be valid. |
434 | void error(const llvm::Twine &Msg, llvm::SMRange Range) { |
435 | HadError = true; |
436 | SM.PrintMessage(Loc: Range.Start, Kind: llvm::SourceMgr::DK_Error, Msg, Ranges: Range); |
437 | } |
438 | void error(const llvm::Twine &Msg, const Node &N) { |
439 | return error(Msg, Range: N.getSourceRange()); |
440 | } |
441 | |
442 | // Report a "soft" error that could be caused by e.g. version skew. |
443 | void warning(const llvm::Twine &Msg, llvm::SMRange Range) { |
444 | SM.PrintMessage(Loc: Range.Start, Kind: llvm::SourceMgr::DK_Warning, Msg, Ranges: Range); |
445 | } |
446 | void warning(const llvm::Twine &Msg, const Node &N) { |
447 | return warning(Msg, Range: N.getSourceRange()); |
448 | } |
449 | }; |
450 | |
451 | } // namespace |
452 | |
453 | std::vector<Fragment> Fragment::parseYAML(llvm::StringRef YAML, |
454 | llvm::StringRef BufferName, |
455 | DiagnosticCallback Diags) { |
456 | // The YAML document may contain multiple conditional fragments. |
457 | // The SourceManager is shared for all of them. |
458 | auto SM = std::make_shared<llvm::SourceMgr>(); |
459 | auto Buf = llvm::MemoryBuffer::getMemBufferCopy(InputData: YAML, BufferName); |
460 | // Adapt DiagnosticCallback to function-pointer interface. |
461 | // Callback receives both errors we emit and those from the YAML parser. |
462 | SM->setDiagHandler( |
463 | DH: [](const llvm::SMDiagnostic &Diag, void *Ctx) { |
464 | (*reinterpret_cast<DiagnosticCallback *>(Ctx))(Diag); |
465 | }, |
466 | Ctx: &Diags); |
467 | std::vector<Fragment> Result; |
468 | for (auto &Doc : llvm::yaml::Stream(*Buf, *SM)) { |
469 | if (Node *N = Doc.getRoot()) { |
470 | Fragment Fragment; |
471 | Fragment.Source.Manager = SM; |
472 | Fragment.Source.Location = N->getSourceRange().Start; |
473 | SM->PrintMessage(Loc: Fragment.Source.Location, Kind: llvm::SourceMgr::DK_Note, |
474 | Msg: "Parsing config fragment" ); |
475 | if (Parser(*SM).parse(F&: Fragment, N&: *N)) |
476 | Result.push_back(x: std::move(Fragment)); |
477 | } |
478 | } |
479 | SM->PrintMessage(Loc: SM->FindLocForLineAndColumn(BufferID: SM->getMainFileID(), LineNo: 0, ColNo: 0), |
480 | Kind: llvm::SourceMgr::DK_Note, |
481 | Msg: "Parsed " + llvm::Twine(Result.size()) + |
482 | " fragments from file" ); |
483 | // Hack: stash the buffer in the SourceMgr to keep it alive. |
484 | // SM has two entries: "main" non-owning buffer, and ignored owning buffer. |
485 | SM->AddNewSourceBuffer(F: std::move(Buf), IncludeLoc: llvm::SMLoc()); |
486 | return Result; |
487 | } |
488 | |
489 | } // namespace config |
490 | } // namespace clangd |
491 | } // namespace clang |
492 | |