diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h --- a/clang-tools-extra/clangd/ConfigFragment.h +++ b/clang-tools-extra/clangd/ConfigFragment.h @@ -63,9 +63,12 @@ /// Parses fragments from a YAML file (one from each --- delimited document). /// Documents that contained fatal errors are omitted from the results. /// BufferName is used for the SourceMgr and diagnostics. - static std::vector parseYAML(llvm::StringRef YAML, - llvm::StringRef BufferName, - DiagnosticCallback); + /// configFilePath is used for variable replacement in the configuration if + /// present, otherwise it is treated as "" + static std::vector + parseYAML(llvm::StringRef YAML, llvm::StringRef BufferName, + std::optional configFilePath, + DiagnosticCallback Diags); /// Analyzes and consumes this fragment, possibly yielding more diagnostics. /// This always produces a usable result (errors are recovered). diff --git a/clang-tools-extra/clangd/ConfigProvider.cpp b/clang-tools-extra/clangd/ConfigProvider.cpp --- a/clang-tools-extra/clangd/ConfigProvider.cpp +++ b/clang-tools-extra/clangd/ConfigProvider.cpp @@ -43,7 +43,8 @@ [&](std::optional Data) { CachedValue.clear(); if (Data) - for (auto &Fragment : Fragment::parseYAML(*Data, path(), DC)) { + for (auto &Fragment : + Fragment::parseYAML(*Data, path(), Directory, DC)) { Fragment.Source.Directory = Directory; Fragment.Source.Trusted = Trusted; CachedValue.push_back(std::move(Fragment).compile(DC)); diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp --- a/clang-tools-extra/clangd/ConfigYAML.cpp +++ b/clang-tools-extra/clangd/ConfigYAML.cpp @@ -49,12 +49,26 @@ return Result; } +// Copied from OpDefinitionsGen +// Replaces all occurrences of `match` in `str` with `substitute`. +static std::string replaceAllSubstrs(std::string str, const std::string &match, + const std::string &substitute) { + std::string::size_type scanLoc = 0, matchLoc = std::string::npos; + while ((matchLoc = str.find(match, scanLoc)) != std::string::npos) { + str = str.replace(matchLoc, match.size(), substitute); + scanLoc = matchLoc + substitute.size(); + } + return str; +} + class Parser { llvm::SourceMgr &SM; bool HadError = false; + std::optional ConfigFilePath; public: - Parser(llvm::SourceMgr &SM) : SM(SM) {} + Parser(llvm::SourceMgr &SM, std::optional configFilePath) + : SM(SM), ConfigFilePath(configFilePath) {} // Tries to parse N into F, returning false if it failed and we couldn't // meaningfully recover (YAML syntax error, or hard semantic error). @@ -348,14 +362,23 @@ } }; + std::string keywordReplace(std::string value) { + return replaceAllSubstrs( + value, "${CURRENT_FILE_PATH}", + this->ConfigFilePath.value_or(llvm::StringRef()).str()); + } // Try to parse a single scalar value from the node, warn on failure. std::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()); + if (auto *S = llvm::dyn_cast(&N)) { + return Located(keywordReplace(S->getValue(Buf).str()), + N.getSourceRange()); + } + if (auto *BS = llvm::dyn_cast(&N)) { + return Located(keywordReplace(BS->getValue().str()), + N.getSourceRange()); + } warning(Desc + " should be scalar", N); return std::nullopt; } @@ -410,9 +433,10 @@ } // namespace -std::vector Fragment::parseYAML(llvm::StringRef YAML, - llvm::StringRef BufferName, - DiagnosticCallback Diags) { +std::vector +Fragment::parseYAML(llvm::StringRef YAML, llvm::StringRef BufferName, + std::optional configFilePath, + DiagnosticCallback Diags) { // The YAML document may contain multiple conditional fragments. // The SourceManager is shared for all of them. auto SM = std::make_shared(); @@ -432,7 +456,7 @@ Fragment.Source.Location = N->getSourceRange().Start; SM->PrintMessage(Fragment.Source.Location, llvm::SourceMgr::DK_Note, "Parsing config fragment"); - if (Parser(*SM).parse(Fragment, *N)) + if (Parser(*SM, configFilePath).parse(Fragment, *N)) Result.push_back(std::move(Fragment)); } } diff --git a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp --- a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp +++ b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp @@ -70,7 +70,7 @@ example-check.ExampleOption: 0 UnusedIncludes: Strict )yaml"; - auto Results = Fragment::parseYAML(YAML, "config.yaml", Diags.callback()); + auto Results = Fragment::parseYAML(YAML, "config.yaml", "", Diags.callback()); EXPECT_THAT(Diags.Diagnostics, IsEmpty()); EXPECT_THAT(Diags.Files, ElementsAre("config.yaml")); ASSERT_EQ(Results.size(), 4u); @@ -89,6 +89,22 @@ EXPECT_EQ("Strict", **Results[3].Diagnostics.UnusedIncludes); } +TEST(ParseYAML, IncludesFileRelative) { + CapturedDiags Diags; + /* Annotations cannot be used here as it interprets the $ and {} special + * characters */ + StringRef RawYAML(R"yaml( + CompileFlags: + Add: ["-I${CURRENT_FILE_PATH}test.h"] + )yaml"); + auto Results = Fragment::parseYAML(RawYAML, "config.yaml", "/tmp/testpath/", + Diags.callback()); + ASSERT_THAT(Diags.Diagnostics, IsEmpty()); + ASSERT_EQ(Results.size(), 1u); + EXPECT_THAT(Results[0].CompileFlags.Add, + ElementsAre(val("-I/tmp/testpath/test.h"))); +} + TEST(ParseYAML, Locations) { CapturedDiags Diags; Annotations YAML(R"yaml( @@ -96,7 +112,7 @@ PathMatch: [['???bad***regex(((']] )yaml"); auto Results = - Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); + Fragment::parseYAML(YAML.code(), "config.yaml", "", Diags.callback()); EXPECT_THAT(Diags.Diagnostics, IsEmpty()); ASSERT_EQ(Results.size(), 1u); ASSERT_NE(Results.front().Source.Manager, nullptr); @@ -116,7 +132,7 @@ CompileFlags: {$unexpected^ )yaml"); auto Results = - Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); + Fragment::parseYAML(YAML.code(), "config.yaml", "", Diags.callback()); ASSERT_THAT( Diags.Diagnostics, @@ -144,7 +160,7 @@ --- - 1 )yaml"; - auto Results = Fragment::parseYAML(YAML, "config.yaml", Diags.callback()); + auto Results = Fragment::parseYAML(YAML, "config.yaml", "", Diags.callback()); EXPECT_THAT(Diags.Diagnostics, ElementsAre(diagMessage("If should be a dictionary"), diagMessage("Config should be a dictionary"))); @@ -158,7 +174,7 @@ External: None )yaml"); auto Results = - Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); + Fragment::parseYAML(YAML.code(), "config.yaml", "", Diags.callback()); ASSERT_THAT(Diags.Diagnostics, IsEmpty()); ASSERT_EQ(Results.size(), 1u); ASSERT_TRUE(Results[0].Index.External); @@ -178,7 +194,7 @@ MountPoint: "baz" )yaml"); auto Results = - Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); + Fragment::parseYAML(YAML.code(), "config.yaml", "", Diags.callback()); ASSERT_EQ(Results.size(), 1u); ASSERT_TRUE(Results[0].Index.External); EXPECT_THAT(*(*Results[0].Index.External)->File, val("foo")); @@ -194,7 +210,7 @@ AllScopes: True )yaml"); auto Results = - Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); + Fragment::parseYAML(YAML.code(), "config.yaml", "", Diags.callback()); ASSERT_THAT(Diags.Diagnostics, IsEmpty()); ASSERT_EQ(Results.size(), 1u); EXPECT_THAT(Results[0].Completion.AllScopes, llvm::ValueIs(val(true))); @@ -207,7 +223,7 @@ AllScopes: $diagrange[[Truex]] )yaml"); auto Results = - Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); + Fragment::parseYAML(YAML.code(), "config.yaml", "", Diags.callback()); EXPECT_THAT(Diags.Diagnostics, ElementsAre(AllOf(diagMessage("AllScopes should be a boolean"), diagKind(llvm::SourceMgr::DK_Warning), @@ -224,7 +240,7 @@ ShowAKA: True )yaml"); auto Results = - Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); + Fragment::parseYAML(YAML.code(), "config.yaml", "", Diags.callback()); ASSERT_THAT(Diags.Diagnostics, IsEmpty()); ASSERT_EQ(Results.size(), 1u); EXPECT_THAT(Results[0].Hover.ShowAKA, llvm::ValueIs(val(true))); @@ -238,7 +254,7 @@ ParameterNames: Yes )yaml"); auto Results = - Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); + Fragment::parseYAML(YAML.code(), "config.yaml", "", Diags.callback()); ASSERT_THAT(Diags.Diagnostics, IsEmpty()); ASSERT_EQ(Results.size(), 1u); EXPECT_THAT(Results[0].InlayHints.Enabled, llvm::ValueIs(val(false))); @@ -254,7 +270,7 @@ IgnoreHeader: [foo, bar] )yaml"); auto Results = - Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); + Fragment::parseYAML(YAML.code(), "config.yaml", "", Diags.callback()); ASSERT_THAT(Diags.Diagnostics, IsEmpty()); ASSERT_EQ(Results.size(), 1u); EXPECT_THAT(Results[0].Diagnostics.Includes.IgnoreHeader, @@ -267,7 +283,7 @@ Style: FullyQualifiedNamespaces: [foo, bar])yaml"); auto Results = - Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); + Fragment::parseYAML(YAML.code(), "config.yaml", "", Diags.callback()); ASSERT_THAT(Diags.Diagnostics, IsEmpty()); ASSERT_EQ(Results.size(), 1u); EXPECT_THAT(Results[0].Style.FullyQualifiedNamespaces,