//===--- ConfigYAML.cpp - Loading configuration fragments from YAML files -===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "ConfigFragment.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/YAMLParser.h" #include namespace clang { namespace clangd { namespace config { namespace { using llvm::yaml::BlockScalarNode; using llvm::yaml::MappingNode; using llvm::yaml::Node; using llvm::yaml::ScalarNode; using llvm::yaml::SequenceNode; class Parser { llvm::SourceMgr &SM; bool HadError = false; public: Parser(llvm::SourceMgr &SM) : SM(SM) {} // Tries to parse N into F, returning false if it failed and we couldn't // meaningfully recover (YAML syntax error, or hard semantic error). bool parse(Fragment &F, Node &N) { DictParser Dict("Config", this); Dict.handle("If", [&](Node &N) { parse(F.If, N); }); Dict.handle("CompileFlags", [&](Node &N) { parse(F.CompileFlags, N); }); Dict.handle("Index", [&](Node &N) { parse(F.Index, N); }); Dict.handle("Style", [&](Node &N) { parse(F.Style, N); }); Dict.handle("ClangTidy", [&](Node &N) { parse(F.ClangTidy, N); }); Dict.parse(N); return !(N.failed() || HadError); } private: void parse(Fragment::IfBlock &F, Node &N) { DictParser Dict("If", this); Dict.unrecognized([&](Located, Node &) { F.HasUnrecognizedCondition = true; return true; // Emit a warning for the unrecognized key. }); Dict.handle("PathMatch", [&](Node &N) { if (auto Values = scalarValues(N)) F.PathMatch = std::move(*Values); }); Dict.handle("PathExclude", [&](Node &N) { if (auto Values = scalarValues(N)) F.PathExclude = std::move(*Values); }); Dict.parse(N); } void parse(Fragment::CompileFlagsBlock &F, Node &N) { DictParser Dict("CompileFlags", this); Dict.handle("Add", [&](Node &N) { if (auto Values = scalarValues(N)) F.Add = std::move(*Values); }); Dict.handle("Remove", [&](Node &N) { if (auto Values = scalarValues(N)) F.Remove = std::move(*Values); }); Dict.parse(N); } void parse(Fragment::StyleBlock &F, Node &N) { DictParser Dict("Style", this); Dict.handle("FullyQualifiedNamespaces", [&](Node &N) { if (auto Values = scalarValues(N)) F.FullyQualifiedNamespaces = std::move(*Values); }); Dict.parse(N); } void parse(Fragment::ClangTidyBlock &F, Node &N) { DictParser Dict("ClangTidy", this); Dict.handle("Add", [&](Node &N) { if (auto Values = scalarValues(N)) F.Add = std::move(*Values); }); Dict.handle("Remove", [&](Node &N) { if (auto Values = scalarValues(N)) F.Remove = std::move(*Values); }); Dict.handle("CheckOptions", [&](Node &N) { DictParser CheckOptDict("CheckOptions", this); CheckOptDict.unrecognized([&](Located &&Key, Node &Val) { if (auto Value = scalarValue(Val, *Key)) F.CheckOptions.emplace_back(std::move(Key), std::move(*Value)); return false; // Don't emit a warning }); CheckOptDict.parse(N); }); Dict.parse(N); } void parse(Fragment::IndexBlock &F, Node &N) { DictParser Dict("Index", this); Dict.handle("Background", [&](Node &N) { F.Background = scalarValue(N, "Background"); }); Dict.handle("External", [&](Node &N) { Fragment::IndexBlock::ExternalBlock External; parse(External, N); F.External.emplace(std::move(External)); F.External->Range = N.getSourceRange(); }); Dict.parse(N); } void parse(Fragment::IndexBlock::ExternalBlock &F, Node &N) { DictParser Dict("External", this); Dict.handle("File", [&](Node &N) { F.File = scalarValue(N, "File"); }); Dict.handle("Server", [&](Node &N) { F.Server = scalarValue(N, "Server"); }); Dict.handle("MountPoint", [&](Node &N) { F.MountPoint = scalarValue(N, "MountPoint"); }); Dict.parse(N); } // Helper for parsing mapping nodes (dictionaries). // We don't use YamlIO as we want to control over unknown keys. class DictParser { llvm::StringRef Description; std::vector>> Keys; std::function, Node &)> UnknownHandler; Parser *Outer; public: DictParser(llvm::StringRef Description, Parser *Outer) : Description(Description), Outer(Outer) {} // Parse is called when Key is encountered, and passed the associated value. // It should emit diagnostics if the value is invalid (e.g. wrong type). // If Key is seen twice, Parse runs only once and an error is reported. void handle(llvm::StringLiteral Key, std::function Parse) { for (const auto &Entry : Keys) { (void) Entry; assert(Entry.first != Key && "duplicate key handler"); } Keys.emplace_back(Key, std::move(Parse)); } // Handler is called when a Key is not matched by any handle(). // If this is unset or the Handler returns true, a warning is emitted for // the unknown key. void unrecognized(std::function, Node &)> Handler) { UnknownHandler = std::move(Handler); } // Process a mapping node and call handlers for each key/value pair. void parse(Node &N) const { if (N.getType() != Node::NK_Mapping) { Outer->error(Description + " should be a dictionary", N); return; } llvm::SmallSet Seen; // We *must* consume all items, even on error, or the parser will assert. for (auto &KV : llvm::cast(N)) { auto *K = KV.getKey(); if (!K) // YAMLParser emitted an error. continue; auto Key = Outer->scalarValue(*K, "Dictionary key"); if (!Key) continue; if (!Seen.insert(**Key).second) { Outer->warning("Duplicate key " + **Key + " is ignored", *K); if (auto *Value = KV.getValue()) Value->skip(); continue; } auto *Value = KV.getValue(); if (!Value) // YAMLParser emitted an error. continue; bool Matched = false; for (const auto &Handler : Keys) { if (Handler.first == **Key) { Matched = true; Handler.second(*Value); break; } } if (!Matched) { bool Warn = !UnknownHandler; if (UnknownHandler) Warn = UnknownHandler( Located(**Key, K->getSourceRange()), *Value); if (Warn) Outer->warning("Unknown " + Description + " key " + **Key, *K); } } } }; // Try to parse a single scalar value from the node, warn on failure. llvm::Optional> scalarValue(Node &N, llvm::StringRef Desc) { llvm::SmallString<256> Buf; if (auto *S = llvm::dyn_cast(&N)) return Located(S->getValue(Buf).str(), N.getSourceRange()); if (auto *BS = llvm::dyn_cast(&N)) return Located(BS->getValue().str(), N.getSourceRange()); warning(Desc + " should be scalar", N); return llvm::None; } // Try to parse a list of single scalar values, or just a single value. llvm::Optional>> scalarValues(Node &N) { std::vector> Result; if (auto *S = llvm::dyn_cast(&N)) { llvm::SmallString<256> Buf; Result.emplace_back(S->getValue(Buf).str(), N.getSourceRange()); } else if (auto *S = llvm::dyn_cast(&N)) { Result.emplace_back(S->getValue().str(), N.getSourceRange()); } else if (auto *S = llvm::dyn_cast(&N)) { // We *must* consume all items, even on error, or the parser will assert. for (auto &Child : *S) { if (auto Value = scalarValue(Child, "List item")) Result.push_back(std::move(*Value)); } } else { warning("Expected scalar or list of scalars", N); return llvm::None; } return Result; } // Report a "hard" error, reflecting a config file that can never be valid. void error(const llvm::Twine &Msg, const Node &N) { HadError = true; SM.PrintMessage(N.getSourceRange().Start, llvm::SourceMgr::DK_Error, Msg, N.getSourceRange()); } // Report a "soft" error that could be caused by e.g. version skew. void warning(const llvm::Twine &Msg, const Node &N) { SM.PrintMessage(N.getSourceRange().Start, llvm::SourceMgr::DK_Warning, Msg, N.getSourceRange()); } }; } // namespace std::vector Fragment::parseYAML(llvm::StringRef YAML, llvm::StringRef BufferName, DiagnosticCallback Diags) { // The YAML document may contain multiple conditional fragments. // The SourceManager is shared for all of them. auto SM = std::make_shared(); auto Buf = llvm::MemoryBuffer::getMemBufferCopy(YAML, BufferName); // Adapt DiagnosticCallback to function-pointer interface. // Callback receives both errors we emit and those from the YAML parser. SM->setDiagHandler( [](const llvm::SMDiagnostic &Diag, void *Ctx) { (*reinterpret_cast(Ctx))(Diag); }, &Diags); std::vector Result; for (auto &Doc : llvm::yaml::Stream(*Buf, *SM)) { if (Node *N = Doc.getRoot()) { Fragment Fragment; Fragment.Source.Manager = SM; Fragment.Source.Location = N->getSourceRange().Start; SM->PrintMessage(Fragment.Source.Location, llvm::SourceMgr::DK_Note, "Parsing config fragment"); if (Parser(*SM).parse(Fragment, *N)) Result.push_back(std::move(Fragment)); } } SM->PrintMessage(SM->FindLocForLineAndColumn(SM->getMainFileID(), 0, 0), llvm::SourceMgr::DK_Note, "Parsed " + llvm::Twine(Result.size()) + " fragments from file"); // Hack: stash the buffer in the SourceMgr to keep it alive. // SM has two entries: "main" non-owning buffer, and ignored owning buffer. SM->AddNewSourceBuffer(std::move(Buf), llvm::SMLoc()); return Result; } } // namespace config } // namespace clangd } // namespace clang