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