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`.
This commit is contained in:
Jon Ross-Perkins
2025-01-29 21:31:10 +00:00
committed by GitHub
parent 4ecf914a07
commit 7d2958ad37
5 changed files with 85 additions and 56 deletions
+5 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -10,7 +10,7 @@
// --- STDIN
[[@LSP:initialize]]
[[@LSP:exit]]
[[@LSP-NOTIFY:exit]]
// --- AUTOUPDATE-SPLIT