freqmod created this revision. Herald added subscribers: kadircet, arphaman. Herald added a project: All. freqmod requested review of this revision. Herald added subscribers: cfe-commits, MaskRay, ilya-biryukov. Herald added a project: clang-tools-extra.
This addresses the use case in https://github.com/clangd/clangd/issues/1038 where a user wants to make a .clangd file that is applicable for several subdirectories with relative paths to the .clangd file (instead of the individual c-files). It does this by replacing any occurrances of ${CURRENT_FILE_PATH} with the directory of the .clangd file. The syntax for the text to be replaced is up for discussion. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D142981 Files: clang-tools-extra/clangd/ConfigFragment.h clang-tools-extra/clangd/ConfigProvider.cpp clang-tools-extra/clangd/ConfigYAML.cpp clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp
Index: clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp +++ 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, Index: clang-tools-extra/clangd/ConfigYAML.cpp =================================================================== --- clang-tools-extra/clangd/ConfigYAML.cpp +++ 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<llvm::StringRef> ConfigFilePath; public: - Parser(llvm::SourceMgr &SM) : SM(SM) {} + Parser(llvm::SourceMgr &SM, std::optional<llvm::StringRef> 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<Located<std::string>> scalarValue(Node &N, llvm::StringRef Desc) { llvm::SmallString<256> Buf; - if (auto *S = llvm::dyn_cast<ScalarNode>(&N)) - return Located<std::string>(S->getValue(Buf).str(), N.getSourceRange()); - if (auto *BS = llvm::dyn_cast<BlockScalarNode>(&N)) - return Located<std::string>(BS->getValue().str(), N.getSourceRange()); + if (auto *S = llvm::dyn_cast<ScalarNode>(&N)) { + return Located<std::string>(keywordReplace(S->getValue(Buf).str()), + N.getSourceRange()); + } + if (auto *BS = llvm::dyn_cast<BlockScalarNode>(&N)) { + return Located<std::string>(keywordReplace(BS->getValue().str()), + N.getSourceRange()); + } warning(Desc + " should be scalar", N); return std::nullopt; } @@ -410,9 +433,10 @@ } // namespace -std::vector<Fragment> Fragment::parseYAML(llvm::StringRef YAML, - llvm::StringRef BufferName, - DiagnosticCallback Diags) { +std::vector<Fragment> +Fragment::parseYAML(llvm::StringRef YAML, llvm::StringRef BufferName, + std::optional<llvm::StringRef> configFilePath, + DiagnosticCallback Diags) { // The YAML document may contain multiple conditional fragments. // The SourceManager is shared for all of them. auto SM = std::make_shared<llvm::SourceMgr>(); @@ -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)); } } Index: clang-tools-extra/clangd/ConfigProvider.cpp =================================================================== --- clang-tools-extra/clangd/ConfigProvider.cpp +++ clang-tools-extra/clangd/ConfigProvider.cpp @@ -43,7 +43,8 @@ [&](std::optional<llvm::StringRef> 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)); Index: clang-tools-extra/clangd/ConfigFragment.h =================================================================== --- clang-tools-extra/clangd/ConfigFragment.h +++ 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<Fragment> 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<Fragment> + parseYAML(llvm::StringRef YAML, llvm::StringRef BufferName, + std::optional<llvm::StringRef> configFilePath, + DiagnosticCallback Diags); /// Analyzes and consumes this fragment, possibly yielding more diagnostics. /// This always produces a usable result (errors are recovered).
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits