Просмотр исходного кода

Broaden file_test support for LSP requests (#4854)

Adds `@LSP-NOTIFY` and `@LSP-REPLY` to capture the slightly different
formats versus a typical call. Removes special-casing for `exit`.
Jon Ross-Perkins 1 год назад
Родитель
Сommit
7d2958ad37

+ 5 - 1
testing/file_test/README.md

@@ -88,9 +88,13 @@ Some keywords can be inserted for content:
 
 -   ```
     [[@LSP:<method>:<extra content>]]
+    [[@LSP-NOTIFY:<method>:<extra content>]]
+    [[@LSP-REPLY:<id>:<extra content>]]
     ```
 
-    Produces JSON for an LSP method call, complete with `Content-Length` header.
+    Produces JSON for an LSP method call, notification, or reply. Each includes
+    the `Content-Length` header. The `:<extra content>` is optional, and may be
+    omitted.
 
 -   ```
     [[@TEST_NAME]]

+ 67 - 52
testing/file_test/file_test_base.cpp

@@ -517,10 +517,62 @@ struct SplitState {
   int file_index = 0;
 };
 
+// Reformats `[[@LSP:` and `[[LSP-NOTIFY:` as an LSP call with headers. For
+// notifications, `lsp_call_id` is null.
+static auto ReplaceLspKeywordAt(std::string* content, size_t keyword_pos,
+                                int* lsp_call_id, llvm::StringLiteral keyword,
+                                llvm::StringLiteral method_or_id_label)
+    -> ErrorOr<size_t> {
+  auto method_or_id_start = keyword_pos + keyword.size();
+
+  static constexpr llvm::StringLiteral LspEnd = "]]";
+  auto keyword_end = content->find("]]", method_or_id_start);
+  if (keyword_end == std::string::npos) {
+    return ErrorBuilder() << "Missing `" << LspEnd << "` after `" << keyword
+                          << "`";
+  }
+
+  auto method_or_id_end = content->find(":", method_or_id_start);
+  auto extra_content_start = method_or_id_end + 1;
+  if (method_or_id_end == std::string::npos || method_or_id_end > keyword_end) {
+    method_or_id_end = keyword_end;
+    extra_content_start = keyword_end;
+  }
+  auto method_or_id =
+      llvm::StringRef(*content).slice(method_or_id_start, method_or_id_end);
+
+  auto extra_content =
+      llvm::StringRef(*content).slice(extra_content_start, keyword_end);
+  std::string extra_content_sep;
+  if (!extra_content.empty()) {
+    extra_content_sep = ",";
+    if (!extra_content.starts_with("\n")) {
+      extra_content_sep += " ";
+    }
+  }
+
+  // Form the JSON.
+  std::string json = R"({"jsonrpc": "2.0", )";
+  if (lsp_call_id) {
+    json += llvm::formatv(R"("id": "{0}", )", ++(*lsp_call_id));
+  }
+  json += llvm::formatv(R"("{0}": "{1}"{2}{3}})", method_or_id_label,
+                        method_or_id, extra_content_sep, extra_content);
+
+  // Add the Content-Length header. The `2` accounts for extra newlines.
+  auto json_with_header =
+      llvm::formatv("Content-Length: {0}\n\n{1}\n", json.size() + 2, json)
+          .str();
+  // Insert the content.
+  content->replace(keyword_pos, keyword_end + 2 - keyword_pos,
+                   json_with_header);
+  return keyword_pos + json_with_header.size();
+}
+
 // Replaces the keyword at the given position. Returns the position to start a
 // find for the next keyword.
 static auto ReplaceContentKeywordAt(std::string* content, size_t keyword_pos,
-                                    llvm::StringRef test_name, int* lsp_id)
+                                    llvm::StringRef test_name, int* lsp_call_id)
     -> ErrorOr<size_t> {
   auto keyword = llvm::StringRef(*content).substr(keyword_pos);
 
@@ -538,57 +590,20 @@ static auto ReplaceContentKeywordAt(std::string* content, size_t keyword_pos,
     return keyword_pos + test_name.size();
   }
 
-  // Reformatted as an LSP call with headers.
   static constexpr llvm::StringLiteral Lsp = "[[@LSP:";
   if (keyword.starts_with(Lsp)) {
-    auto method_start = keyword_pos + Lsp.size();
-
-    static constexpr llvm::StringLiteral LspEnd = "]]";
-    auto keyword_end = content->find("]]", method_start);
-    if (keyword_end == std::string::npos) {
-      return ErrorBuilder()
-             << "Missing `" << LspEnd << "` after `" << Lsp << "`";
-    }
-
-    auto method_end = content->find(":", method_start);
-    auto extra_content_start = method_end + 1;
-    if (method_end == std::string::npos || method_end > keyword_end) {
-      method_end = keyword_end;
-      extra_content_start = keyword_end;
-    }
-    auto method = content->substr(method_start, method_end - method_start);
-
-    auto extra_content =
-        content->substr(extra_content_start, keyword_end - extra_content_start);
-    std::string extra_content_sep;
-    if (!extra_content.empty()) {
-      extra_content_sep = ",";
-      if (!extra_content.starts_with("\n")) {
-        extra_content_sep += " ";
-      }
-    }
-
-    // Form the JSON.
-    std::string json;
-    if (method == "exit") {
-      if (!extra_content.empty()) {
-        return Error("`[[@LSP:exit:` cannot include extra content");
-      }
-      json = R"({"jsonrpc": "2.0", "method": "exit"})";
-    } else {
-      json = llvm::formatv(
-                 R"({{"jsonrpc": "2.0", "id": "{0}", "method": "{1}"{2}{3}})",
-                 ++(*lsp_id), method, extra_content_sep, extra_content)
-                 .str();
-    }
-    // Add the Content-Length header. The `2` accounts for extra newlines.
-    auto json_with_header =
-        llvm::formatv("Content-Length: {0}\n\n{1}\n", json.size() + 2, json)
-            .str();
-    // Insert the content.
-    content->replace(keyword_pos, keyword_end + 2 - keyword_pos,
-                     json_with_header);
-    return keyword_pos + json_with_header.size();
+    return ReplaceLspKeywordAt(content, keyword_pos, lsp_call_id, Lsp,
+                               "method");
+  }
+  static constexpr llvm::StringLiteral LspNotify = "[[@LSP-NOTIFY:";
+  if (keyword.starts_with(LspNotify)) {
+    return ReplaceLspKeywordAt(content, keyword_pos,
+                               /*lsp_call_id=*/nullptr, LspNotify, "method");
+  }
+  static constexpr llvm::StringLiteral LspReply = "[[@LSP-REPLY:";
+  if (keyword.starts_with(LspReply)) {
+    return ReplaceLspKeywordAt(content, keyword_pos,
+                               /*lsp_call_id=*/nullptr, LspReply, "id");
   }
 
   return ErrorBuilder() << "Unexpected use of `[[@` at `"
@@ -625,11 +640,11 @@ static auto ReplaceContentKeywords(llvm::StringRef filename,
   test_name.consume_front("todo_");
 
   // A counter for LSP calls.
-  int lsp_id = 0;
+  int lsp_call_id = 0;
   while (keyword_pos != std::string::npos) {
     CARBON_ASSIGN_OR_RETURN(
         auto keyword_end,
-        ReplaceContentKeywordAt(content, keyword_pos, test_name, &lsp_id));
+        ReplaceContentKeywordAt(content, keyword_pos, test_name, &lsp_call_id));
     keyword_pos = content->find(Prefix, keyword_end);
   }
   return Success();

+ 11 - 1
testing/file_test/testdata/lsp_calls.carbon

@@ -16,7 +16,9 @@
 multi
 line
 ]]
-[[@LSP:exit]]
+[[@LSP-REPLY:7]]
+[[@LSP-REPLY:8:bar]]
+[[@LSP-NOTIFY:exit]]
 
 // --- AUTOUPDATE-SPLIT
 
@@ -40,6 +42,14 @@ line
 // CHECK:STDERR: line
 // CHECK:STDERR: }
 // CHECK:STDERR:
+// CHECK:STDERR: Content-Length: 31
+// CHECK:STDERR:
+// CHECK:STDERR: {"jsonrpc": "2.0", "id": "7"}
+// CHECK:STDERR:
+// CHECK:STDERR: Content-Length: 36
+// CHECK:STDERR:
+// CHECK:STDERR: {"jsonrpc": "2.0", "id": "8", bar}
+// CHECK:STDERR:
 // CHECK:STDERR: Content-Length: 38
 // CHECK:STDERR:
 // CHECK:STDERR: {"jsonrpc": "2.0", "method": "exit"}

+ 1 - 1
toolchain/language_server/testdata/exit.carbon

@@ -9,7 +9,7 @@
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/exit.carbon
 
 // --- STDIN
-[[@LSP:exit]]
+[[@LSP-NOTIFY:exit]]
 
 // --- AUTOUPDATE-SPLIT
 

+ 1 - 1
toolchain/language_server/testdata/initialize.carbon

@@ -10,7 +10,7 @@
 
 // --- STDIN
 [[@LSP:initialize]]
-[[@LSP:exit]]
+[[@LSP-NOTIFY:exit]]
 
 // --- AUTOUPDATE-SPLIT