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

Reply via email to