sammccall created this revision.
sammccall added a reviewer: kadircet.
Herald added subscribers: cfe-commits, usaxena95, arphaman, jkorous, MaskRay, 
ilya-biryukov.
Herald added a project: clang.

We support sending fragments in initialize and workspace/didChangeConfiguration.
Sadly no workspace/configuration yet, we'd want to send that request on
didOpen and we'd have to block on getting the answer, which is much more
invasive.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D83822

Files:
  clang-tools-extra/clangd/ClangdLSPServer.cpp
  clang-tools-extra/clangd/ClangdLSPServer.h
  clang-tools-extra/clangd/ConfigFragment.h
  clang-tools-extra/clangd/ConfigYAML.cpp
  clang-tools-extra/clangd/Protocol.cpp
  clang-tools-extra/clangd/Protocol.h
  clang-tools-extra/clangd/test/config.test
  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
@@ -119,6 +119,25 @@
   ASSERT_THAT(Results, IsEmpty());
 }
 
+TEST(ParseYAML, JSON) {
+  CapturedDiags Diags;
+  llvm::json::Object Config{
+      {"If", llvm::json::Object{{"UnknownCondition", "foo"}}},
+      {"CompileFlags", llvm::json::Object{{"Add", "first"}}},
+  };
+  auto Result =
+      Fragment::parseJSON(std::move(Config), "config.json", Diags.callback());
+
+  ASSERT_THAT(Diags.Diagnostics,
+              ElementsAre(AllOf(DiagMessage("Unknown If key UnknownCondition"),
+                                DiagKind(llvm::SourceMgr::DK_Warning))));
+  EXPECT_EQ(Diags.Diagnostics.front().Rng, llvm::None);
+
+  ASSERT_NE(Result, llvm::None);
+  EXPECT_TRUE(Result->If.HasUnrecognizedCondition);
+  EXPECT_THAT(Result->CompileFlags.Add, ElementsAre(Val("first")));
+}
+
 } // namespace
 } // namespace config
 } // namespace clangd
Index: clang-tools-extra/clangd/test/config.test
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/test/config.test
@@ -0,0 +1,65 @@
+# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s
+# Set some config fragments on startup.
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","initializationOptions": {
+  "fragments": {
+    "!global": {
+      "CompileFlags": {"Add": "-DFOO=A"}
+    },
+    "bar": {
+      "If": {"PathMatch":".*/bar.c"},
+      "CompileFlags": {"Add": "-DFOO=B"}
+    }
+  }
+}}}
+---
+# foo.c gets the global configuration.
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c",
+  "text":"int main() { return FOO; }"
+}}}
+#      CHECK:  "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT:  "params": {
+# CHECK-NEXT:    "diagnostics": [
+# CHECK-NEXT:      {
+# CHECK-NEXT:        "code": "undeclared_var_use",
+# CHECK-NEXT:        "message": "Use of undeclared identifier 'A'",
+#      CHECK:      }
+#      CHECK:    ],
+# CHECK-NEXT:    "uri": "file://{{.*}}/foo.c",
+# CHECK-NEXT:    "version": 0
+# CHECK-NEXT:  }
+---
+# bar.c (also) gets the conditional configuration.
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///bar.c","languageId":"c",
+  "text":"int main() { return FOO; }"
+}}}
+#      CHECK:  "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT:  "params": {
+# CHECK-NEXT:    "diagnostics": [
+# CHECK-NEXT:      {
+# CHECK-NEXT:        "code": "undeclared_var_use",
+# CHECK-NEXT:        "message": "Use of undeclared identifier 'B'",
+#      CHECK:      }
+#      CHECK:    ],
+# CHECK-NEXT:    "uri": "file://{{.*}}/bar.c",
+# CHECK-NEXT:    "version": 0
+# CHECK-NEXT:  }
+---
+# Replace global configuration.
+{"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{
+  "fragments": {
+    "!global": {
+      "CompileFlags": {"Add": "-DFOO=C"}
+    }
+  }
+}}}
+# Both files get rebuilt, and use the new config.
+#      CHECK: "method": "textDocument/publishDiagnostics",
+#  CHECK-DAG: "message": "Use of undeclared identifier 'C'",
+#  CHECK-DAG: "message": "Use of undeclared identifier 'B'",
+---
+{"jsonrpc":"2.0","id":5,"method":"shutdown"}
+---
+{"jsonrpc":"2.0","method":"exit"}
+
+
+
Index: clang-tools-extra/clangd/Protocol.h
===================================================================
--- clang-tools-extra/clangd/Protocol.h
+++ clang-tools-extra/clangd/Protocol.h
@@ -488,6 +488,11 @@
   // Changes to the in-memory compilation database.
   // The key of the map is a file name.
   std::map<std::string, ClangdCompileCommand> compilationDatabaseChanges;
+
+  // clangd configuration fragments: see https://clangd.llvm.org/config.html.
+  // Each value is an Object or null, and replaces any fragment with that key.
+  // Fragments are used in key order ("!foo" is low-priority, "~foo" is high).
+  std::map<std::string, llvm::json::Value> fragments;
 };
 bool fromJSON(const llvm::json::Value &, ConfigurationSettings &);
 
@@ -527,9 +532,6 @@
   /// `rootUri` wins.
   llvm::Optional<URIForFile> rootUri;
 
-  // User provided initialization options.
-  // initializationOptions?: any;
-
   /// The capabilities provided by the client (editor or tool)
   ClientCapabilities capabilities;
 
@@ -537,6 +539,7 @@
   llvm::Optional<TraceLevel> trace;
 
   /// User-provided initialization options.
+  /// LSP defines this type as `any`.
   InitializationOptions initializationOptions;
 };
 bool fromJSON(const llvm::json::Value &, InitializeParams &);
Index: clang-tools-extra/clangd/Protocol.cpp
===================================================================
--- clang-tools-extra/clangd/Protocol.cpp
+++ clang-tools-extra/clangd/Protocol.cpp
@@ -1077,6 +1077,9 @@
   if (!O)
     return true; // 'any' type in LSP.
   O.map("compilationDatabaseChanges", S.compilationDatabaseChanges);
+  if (auto *Clangd = Params.getAsObject()->getObject("fragments"))
+    for (const auto &Entry : *Clangd)
+      S.fragments.emplace(Entry.first.str(), llvm::json::Value(Entry.second));
   return true;
 }
 
Index: clang-tools-extra/clangd/ConfigYAML.cpp
===================================================================
--- clang-tools-extra/clangd/ConfigYAML.cpp
+++ clang-tools-extra/clangd/ConfigYAML.cpp
@@ -11,6 +11,7 @@
 #include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/ScopedPrinter.h"
 #include "llvm/Support/SourceMgr.h"
 #include "llvm/Support/YAMLParser.h"
 #include <system_error>
@@ -225,6 +226,22 @@
   return Result;
 }
 
+llvm::Optional<Fragment> Fragment::parseJSON(const llvm::json::Value &Value,
+                                             llvm::StringRef Source,
+                                             DiagnosticCallback Diags) {
+  // This is a horrible hack: JSON is a subset of YAML, so use the YAML parser.
+  // FIXME: The diagnostics produced will use YAML terms etc. Do this properly.
+  auto NoLocDiags = [&](const llvm::SMDiagnostic &D) {
+    Diags(llvm::SMDiagnostic(Source, D.getKind(), D.getMessage()));
+  };
+  auto Result = parseYAML(llvm::to_string(Value), "", NoLocDiags);
+
+  assert(Result.size() <= 1 && "JSON yielded multiple YAML documents?");
+  if (Result.empty())
+    return llvm::None;
+  return std::move(Result.front());
+}
+
 } // namespace config
 } // namespace clangd
 } // namespace clang
Index: clang-tools-extra/clangd/ConfigFragment.h
===================================================================
--- clang-tools-extra/clangd/ConfigFragment.h
+++ clang-tools-extra/clangd/ConfigFragment.h
@@ -36,6 +36,7 @@
 #include "llvm/ADT/Optional.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
 #include "llvm/Support/SMLoc.h"
 #include "llvm/Support/SourceMgr.h"
 #include <string>
@@ -69,6 +70,13 @@
                                          llvm::StringRef BufferName,
                                          DiagnosticCallback);
 
+  /// Parses a fragment from a JSON value (an Object).
+  /// Returns None if the value contained a fatal error and could not be parsed.
+  /// Locations are not reported with the diagnostics.
+  static llvm::Optional<Fragment> parseJSON(const llvm::json::Value &JSON,
+                                            llvm::StringRef SourceName,
+                                            DiagnosticCallback);
+
   /// Analyzes and consumes this fragment, possibly yielding more diagnostics.
   /// This always produces a usable result (errors are recovered).
   ///
Index: clang-tools-extra/clangd/ClangdLSPServer.h
===================================================================
--- clang-tools-extra/clangd/ClangdLSPServer.h
+++ clang-tools-extra/clangd/ClangdLSPServer.h
@@ -10,6 +10,7 @@
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDLSPSERVER_H
 
 #include "ClangdServer.h"
+#include "ConfigProvider.h"
 #include "DraftStore.h"
 #include "Features.inc"
 #include "FindSymbols.h"
@@ -209,6 +210,22 @@
     notify("$/progress", Params);
   }
 
+  /// Stores clangd config set over LSP.
+  class LSPConfigProvider : public config::Provider {
+    mutable std::mutex Mu;
+    mutable std::map<std::string, config::CompiledFragment> Cached;
+    llvm::StringMap<llvm::json::Value> Pending;
+
+    std::vector<config::CompiledFragment>
+    getFragments(const config::Params &,
+                 config::DiagnosticCallback) const override;
+
+  public:
+    void update(const std::map<std::string, llvm::json::Value> &);
+  };
+  LSPConfigProvider LSPConfig;
+  std::unique_ptr<config::Provider> ConfigProviderStorage;
+
   const ThreadsafeFS &TFS;
   /// Options used for code completion
   clangd::CodeCompleteOptions CCOpts;
Index: clang-tools-extra/clangd/ClangdLSPServer.cpp
===================================================================
--- clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -8,6 +8,7 @@
 
 #include "ClangdLSPServer.h"
 #include "CodeComplete.h"
+#include "ConfigFragment.h"
 #include "Diagnostics.h"
 #include "DraftStore.h"
 #include "GlobalCompilationDatabase.h"
@@ -1199,6 +1200,12 @@
 
 void ClangdLSPServer::applyConfiguration(
     const ConfigurationSettings &Settings) {
+  bool ReparseAllFiles = false;
+  if (!Settings.fragments.empty()) {
+    LSPConfig.update(Settings.fragments);
+    ReparseAllFiles = true; // Config may affect any open file.
+  }
+
   // Per-file update to the compilation database.
   llvm::StringSet<> ModifiedFiles;
   for (auto &Entry : Settings.compilationDatabaseChanges) {
@@ -1214,8 +1221,9 @@
     }
   }
 
-  reparseOpenFilesIfNeeded(
-      [&](llvm::StringRef File) { return ModifiedFiles.count(File) != 0; });
+  reparseOpenFilesIfNeeded([&](llvm::StringRef File) {
+    return ReparseAllFiles || ModifiedFiles.count(File) != 0;
+  });
 }
 
 void ClangdLSPServer::publishTheiaSemanticHighlighting(
@@ -1234,6 +1242,35 @@
   applyConfiguration(Params.settings);
 }
 
+std::vector<config::CompiledFragment>
+ClangdLSPServer::LSPConfigProvider::getFragments(
+    const config::Params &P, config::DiagnosticCallback DC) const {
+  std::lock_guard<std::mutex> Lock(Mu);
+  // Compile any pending configs, and merge them into the cache.
+  for (const auto &Entry : Pending) {
+    llvm::Optional<config::Fragment> Fragment;
+    if (!Entry.second.getAsNull())
+      Fragment = config::Fragment::parseJSON(
+          Entry.second, ("LSP:" + Entry.first()).str(), DC);
+    if (Fragment)
+      Cached[Entry.first().str()] = std::move(*Fragment).compile(DC);
+    else
+      Cached.erase(Entry.first().str());
+  }
+  // Now extract all fragments from the cache.
+  std::vector<config::CompiledFragment> Results;
+  for (const auto &Entry : Cached) // Sorted by key, as desired.
+    Results.push_back(Entry.second);
+  return Results;
+}
+
+void ClangdLSPServer::LSPConfigProvider::update(
+    const std::map<std::string, llvm::json::Value> &M) {
+  std::lock_guard<std::mutex> Lock(Mu);
+  for (const auto &Entry : M)
+    Pending.insert_or_assign(Entry.first, Entry.second);
+}
+
 void ClangdLSPServer::onReference(const ReferenceParams &Params,
                                   Callback<std::vector<Location>> Reply) {
   Server->findReferences(Params.textDocument.uri.file(), Params.position,
@@ -1405,6 +1442,13 @@
   if (Opts.FoldingRanges)
     MsgHandler->bind("textDocument/foldingRange", &ClangdLSPServer::onFoldingRange);
   // clang-format on
+  if (Opts.ConfigProvider) {
+    ConfigProviderStorage =
+        config::Provider::combine({&LSPConfig, Opts.ConfigProvider});
+    ClangdServerOpts.ConfigProvider = ConfigProviderStorage.get();
+  } else {
+    ClangdServerOpts.ConfigProvider = &LSPConfig;
+  }
 }
 
 ClangdLSPServer::~ClangdLSPServer() {
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to