Skip to content

Commit 0cd3cf8

Browse files
authored
nixd/test: specify textDocument/didOpen via nix code blocks (#679)
This makes the test cases less verbose and enables syntax highlighting but most importantly makes multiline documents readable so that test definitions don't contain lines such as: "text":"{\n x = 1;\n anonymousLambda = { a }: a;\n namedLambda = a: a;\n numbers = 1 + 2.0;\n bool = true;\n bool2= false;\n string = \"1\";\n string2 = \"${builtins.foo}\";\n undefined = x;\n list = [ 1 2 3 ];\n}\n" Before multiline documents were duplicated in another nix code block (that was ignored by the test runner) for readability but these duplicated code blocks could (and did) of course get out of sync with the actual test code.
2 parents 4e103b9 + 0649c9e commit 0cd3cf8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+211
-764
lines changed

nixd/lspserver/include/lspserver/Connection.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,13 @@ class InboundPort {
4040

4141
JSONStreamStyle StreamStyle = JSONStreamStyle::Standard;
4242

43-
bool readStandardMessage(std::string &JSONString);
43+
/// Read one message as specified in the LSP standard.
44+
/// A Language Server Protocol message starts with a set of
45+
/// HTTP headers, delimited by \r\n, and terminated by an empty line (\r\n).
46+
llvm::Expected<llvm::json::Value> readStandardMessage(std::string &Buffer);
4447

45-
bool readDelimitedMessage(std::string &JSONString);
48+
/// Read one message, expecting the input to be one of our Markdown lit-tests.
49+
llvm::Expected<llvm::json::Value> readDelimitedMessage(std::string &Buffer);
4650

4751
/// \brief Notify the inbound port to close the connection
4852
void close() { Close = true; }
@@ -51,11 +55,9 @@ class InboundPort {
5155
JSONStreamStyle StreamStyle = JSONStreamStyle::Standard)
5256
: Close(false), In(In), StreamStyle(StreamStyle) {};
5357

54-
/// Read messages specified in LSP standard, and collect standard json string
55-
/// into \p JSONString.
56-
/// A Language Server Protocol message starts with a set of
57-
/// HTTP headers, delimited by \r\n, and terminated by an empty line (\r\n).
58-
bool readMessage(std::string &JSONString);
58+
/// Read one LSP message depending on the configured StreamStyle.
59+
/// The parsed value is returned. The given \p Buffer is only used internally.
60+
llvm::Expected<llvm::json::Value> readMessage(std::string &Buffer);
5961

6062
/// Dispatch messages to on{Notify,Call,Reply} ( \p Handlers)
6163
/// Return values should be forwarded from \p Handlers

nixd/lspserver/src/Connection.cpp

Lines changed: 92 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,28 @@ llvm::cl::opt<int> ClientProcessID{
2424
"Client process ID, if this PID died, the server should exit."),
2525
llvm::cl::init(getppid())};
2626

27+
std::string jsonToString(llvm::json::Value &Message) {
28+
std::string Result;
29+
llvm::raw_string_ostream OS(Result);
30+
OS << Message;
31+
return Result;
32+
}
33+
34+
class ReadEOF : public llvm::ErrorInfo<ReadEOF> {
35+
public:
36+
static char ID;
37+
ReadEOF() = default;
38+
39+
/// No need to implement this, we don't need to log this error.
40+
void log(llvm::raw_ostream &OS) const override { __builtin_unreachable(); }
41+
42+
std::error_code convertToErrorCode() const override {
43+
return std::make_error_code(std::errc::io_error);
44+
}
45+
};
46+
47+
char ReadEOF::ID;
48+
2749
} // namespace
2850

2951
namespace lspserver {
@@ -184,12 +206,13 @@ bool readLine(int fd, const std::atomic<bool> &Close,
184206
}
185207
}
186208

187-
bool InboundPort::readStandardMessage(std::string &JSONString) {
209+
llvm::Expected<llvm::json::Value>
210+
InboundPort::readStandardMessage(std::string &Buffer) {
188211
unsigned long long ContentLength = 0;
189212
llvm::SmallString<128> Line;
190213
while (true) {
191214
if (!readLine(In, Close, Line))
192-
return false;
215+
return llvm::make_error<ReadEOF>(); // EOF
193216

194217
llvm::StringRef LineRef = Line;
195218

@@ -205,76 +228,112 @@ bool InboundPort::readStandardMessage(std::string &JSONString) {
205228
// It's another header, ignore it.
206229
}
207230

208-
JSONString.resize(ContentLength);
231+
Buffer.resize(ContentLength);
209232
for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) {
210233

211-
Read = read(In, JSONString.data() + Pos, ContentLength - Pos);
234+
Read = read(In, Buffer.data() + Pos, ContentLength - Pos);
212235

213236
if (Read == 0) {
214237
elog("Input was aborted. Read only {0} bytes of expected {1}.", Pos,
215238
ContentLength);
216-
return false;
239+
return llvm::make_error<ReadEOF>();
217240
}
218241
}
219-
return true;
242+
return llvm::json::parse(Buffer);
220243
}
221244

222-
bool InboundPort::readDelimitedMessage(std::string &JSONString) {
223-
JSONString.clear();
245+
llvm::Expected<llvm::json::Value>
246+
InboundPort::readDelimitedMessage(std::string &Buffer) {
247+
enum class State { Prose, JSONBlock, NixBlock };
248+
State State = State::Prose;
249+
Buffer.clear();
224250
llvm::SmallString<128> Line;
225-
bool IsInputBlock = false;
251+
std::string NixDocURI;
226252
while (readLine(In, Close, Line)) {
227253
auto LineRef = Line.str().trim();
228-
if (IsInputBlock) {
229-
// We are in input blocks, read lines and append JSONString.
254+
if (State == State::Prose) {
255+
if (LineRef.starts_with("```json"))
256+
State = State::JSONBlock;
257+
else if (LineRef.consume_front("```nix ")) {
258+
State = State::NixBlock;
259+
NixDocURI = LineRef.str();
260+
}
261+
} else if (State == State::JSONBlock) {
262+
// We are in a JSON block, read lines and append JSONString.
230263
if (LineRef.starts_with("#")) // comment
231264
continue;
232265

233266
// End of the block
234267
if (LineRef.starts_with("```")) {
235-
IsInputBlock = false;
236-
break;
268+
return llvm::json::parse(Buffer);
237269
}
238270

239-
JSONString += Line;
271+
Buffer += Line;
272+
} else if (State == State::NixBlock) {
273+
// We are in a Nix block. (This was implemented to make the .md test
274+
// files more readable, particularly regarding multiline Nix documents,
275+
// so that the newlines don't have to be \n escaped.)
276+
277+
if (LineRef.starts_with("```")) {
278+
return llvm::json::Object{
279+
{"jsonrpc", "2.0"},
280+
{"method", "textDocument/didOpen"},
281+
{
282+
"params",
283+
llvm::json::Object{
284+
{
285+
"textDocument",
286+
llvm::json::Object{
287+
{"uri", NixDocURI},
288+
{"languageId", "nix"},
289+
{"version", 1},
290+
{"text", llvm::StringRef(Buffer).rtrim().str()},
291+
},
292+
},
293+
},
294+
}};
295+
}
296+
Buffer += Line;
297+
Buffer += "\n";
240298
} else {
241-
if (LineRef.starts_with("```json"))
242-
IsInputBlock = true;
299+
assert(false && "unreachable");
243300
}
244301
}
245-
return true; // Including at EOF
302+
return llvm::make_error<ReadEOF>(); // EOF
246303
}
247304

248-
bool InboundPort::readMessage(std::string &JSONString) {
305+
llvm::Expected<llvm::json::Value>
306+
InboundPort::readMessage(std::string &Buffer) {
249307
switch (StreamStyle) {
250308

251309
case JSONStreamStyle::Standard:
252-
return readStandardMessage(JSONString);
310+
return readStandardMessage(Buffer);
253311
case JSONStreamStyle::Delimited:
254-
return readDelimitedMessage(JSONString);
255-
break;
312+
return readDelimitedMessage(Buffer);
256313
}
257314
assert(false && "Invalid stream style");
258315
__builtin_unreachable();
259316
}
260317

261318
void InboundPort::loop(MessageHandler &Handler) {
262-
std::string JSONString;
263-
llvm::SmallString<128> Line;
319+
std::string Buffer;
264320

265321
for (;;) {
266-
if (readMessage(JSONString)) {
267-
vlog("<<< {0}", JSONString);
268-
if (auto ExpectedParsedJSON = llvm::json::parse(JSONString)) {
269-
if (!dispatch(*ExpectedParsedJSON, Handler))
270-
return;
271-
} else {
272-
auto Err = ExpectedParsedJSON.takeError();
273-
elog("The received json cannot be parsed, reason: {0}", Err);
322+
if (auto Message = readMessage(Buffer)) {
323+
vlog("<<< {0}", jsonToString(*Message));
324+
if (!dispatch(*Message, Handler))
274325
return;
275-
}
276326
} else {
277-
return;
327+
// Handle error while reading message.
328+
return [&]() {
329+
auto Err = Message.takeError();
330+
if (Err.isA<ReadEOF>())
331+
return; // Stop reading.
332+
else if (Err.isA<llvm::json::ParseError>())
333+
elog("The received json cannot be parsed, reason: {0}", Err);
334+
else
335+
elog("Error reading message: {0}", llvm::toString(std::move(Err)));
336+
}();
278337
}
279338
}
280339
}

nixd/tools/nixd/test/code-action/quick-fix.md

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,8 @@
2121

2222
<-- textDocument/didOpen
2323

24-
```json
25-
{
26-
"jsonrpc":"2.0",
27-
"method":"textDocument/didOpen",
28-
"params":{
29-
"textDocument":{
30-
"uri":"file:///basic.nix",
31-
"languageId":"nix",
32-
"version":1,
33-
"text":"/*"
34-
}
35-
}
36-
}
24+
```nix file:///basic.nix
25+
/*
3726
```
3827

3928
<-- textDocument/codeAction(2)

nixd/tools/nixd/test/completion/comment.md

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,8 @@
2323
<-- textDocument/didOpen
2424

2525

26-
```json
27-
{
28-
"jsonrpc":"2.0",
29-
"method":"textDocument/didOpen",
30-
"params":{
31-
"textDocument":{
32-
"uri":"file:///completion.nix",
33-
"languageId":"nix",
34-
"version":1,
35-
"text":"{ x = 1; y = /* */2;}"
36-
}
37-
}
38-
}
26+
```nix file:///completion.nix
27+
{ x = 1; y = /* */2;}
3928
```
4029

4130
```json

nixd/tools/nixd/test/completion/dot-select.md

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,8 @@
2323
<-- textDocument/didOpen
2424

2525

26-
```json
27-
{
28-
"jsonrpc":"2.0",
29-
"method":"textDocument/didOpen",
30-
"params":{
31-
"textDocument":{
32-
"uri":"file:///completion.nix",
33-
"languageId":"nix",
34-
"version":1,
35-
"text":"{ bar = 1; foo = foo.foo.; }"
36-
}
37-
}
38-
}
26+
```nix file:///completion.nix
27+
{ bar = 1; foo = foo.foo.; }
3928
```
4029

4130
```json

nixd/tools/nixd/test/completion/dot.md

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,8 @@
2323
<-- textDocument/didOpen
2424

2525

26-
```json
27-
{
28-
"jsonrpc":"2.0",
29-
"method":"textDocument/didOpen",
30-
"params":{
31-
"textDocument":{
32-
"uri":"file:///completion.nix",
33-
"languageId":"nix",
34-
"version":1,
35-
"text":"{ bar = 1; foo. }"
36-
}
37-
}
38-
}
26+
```nix file:///completion.nix
27+
{ bar = 1; foo. }
3928
```
4029

4130
```json

nixd/tools/nixd/test/completion/nested.md

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,8 @@
2323
<-- textDocument/didOpen
2424

2525

26-
```json
27-
{
28-
"jsonrpc":"2.0",
29-
"method":"textDocument/didOpen",
30-
"params":{
31-
"textDocument":{
32-
"uri":"file:///completion.nix",
33-
"languageId":"nix",
34-
"version":1,
35-
"text":"{ bar = 1; foo = { ba = }; }"
36-
}
37-
}
38-
}
26+
```nix file:///completion.nix
27+
{ bar = 1; foo = { ba = }; }
3928
```
4029

4130
```json

nixd/tools/nixd/test/completion/nixpkgs.md

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,8 @@
2121
<-- textDocument/didOpen
2222

2323

24-
```json
25-
{
26-
"jsonrpc":"2.0",
27-
"method":"textDocument/didOpen",
28-
"params":{
29-
"textDocument":{
30-
"uri":"file:///completion.nix",
31-
"languageId":"nix",
32-
"version":1,
33-
"text":"with pkgs; [ a ]"
34-
}
35-
}
36-
}
24+
```nix file:///completion.nix
25+
with pkgs; [ a ]
3726
```
3827

3928
```json

nixd/tools/nixd/test/completion/option-stop.md

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,8 @@
2323
<-- textDocument/didOpen
2424

2525

26-
```json
27-
{
28-
"jsonrpc":"2.0",
29-
"method":"textDocument/didOpen",
30-
"params":{
31-
"textDocument":{
32-
"uri":"file:///completion.nix",
33-
"languageId":"nix",
34-
"version":1,
35-
"text":"{ bar = 1; foo.bar. }"
36-
}
37-
}
38-
}
26+
```nix file:///completion.nix
27+
{ bar = 1; foo.bar. }
3928
```
4029

4130
```json

nixd/tools/nixd/test/completion/options-in-config.md

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,8 @@
2323
<-- textDocument/didOpen
2424

2525

26-
```json
27-
{
28-
"jsonrpc":"2.0",
29-
"method":"textDocument/didOpen",
30-
"params":{
31-
"textDocument":{
32-
"uri":"file:///completion.nix",
33-
"languageId":"nix",
34-
"version":1,
35-
"text":"{ config = { fo }; }"
36-
}
37-
}
38-
}
26+
```nix file:///completion.nix
27+
{ config = { foo }; }
3928
```
4029

4130
```json

0 commit comments

Comments
 (0)