Skip to content

Commit 36c0bb8

Browse files
authored
fix(lsp): Add test harness, update named arg code action title, fix code action trigger points, do not print LSP warnings (#2134)
1 parent 1b3a9f0 commit 36c0bb8

File tree

8 files changed

+882
-19
lines changed

8 files changed

+882
-19
lines changed

compiler/src/language_server/code_action.re

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ let explicit_type_annotation = (range, uri, type_str) => {
5858

5959
let named_arg_label = (range, uri, arg_label) => {
6060
ResponseResult.{
61-
title: "Used named argument label",
62-
kind: "name-argument-label",
61+
title: "Use argument label",
62+
kind: "use-argument-label",
6363
edit: {
6464
document_changes: [
6565
{
@@ -79,23 +79,22 @@ let send_code_actions =
7979
Protocol.response(~id, ResponseResult.to_yojson(Some(code_actions)));
8080
};
8181

82-
let process_explicit_type_annotation = (uri, results: list(Sourcetree.node)) => {
82+
let rec process_explicit_type_annotation =
83+
(uri, results: list(Sourcetree.node)) => {
8384
switch (results) {
84-
| [Pattern({pattern})]
8585
| [
86-
Pattern({pattern: {pat_desc: TPatAlias({pat_desc: TPatAny}, _, _)}}),
87-
Pattern({pattern}),
86+
Pattern({pattern: {pat_extra: [], pat_desc: TPatVar(_)} as pattern}),
8887
..._,
89-
]
90-
when pattern.pat_extra == [] =>
88+
] =>
9189
let loc = {...pattern.pat_loc, loc_start: pattern.pat_loc.loc_end};
9290
let type_str = Printtyp.string_of_type_scheme(pattern.pat_type);
9391
Some(explicit_type_annotation(Utils.loc_to_range(loc), uri, type_str));
92+
| [_, ...rest] => process_explicit_type_annotation(uri, rest)
9493
| _ => None
9594
};
9695
};
9796

98-
let process_named_arg_label = (uri, results: list(Sourcetree.node)) => {
97+
let rec process_named_arg_label = (uri, results: list(Sourcetree.node)) => {
9998
switch (results) {
10099
| [Argument({arg_label, label_specified, loc}), ..._] when !label_specified =>
101100
let loc = {...loc, loc_end: loc.loc_start};
@@ -107,6 +106,7 @@ let process_named_arg_label = (uri, results: list(Sourcetree.node)) => {
107106
| Default({txt}) => txt
108107
};
109108
Some(named_arg_label(Utils.loc_to_range(loc), uri, arg_label));
109+
| [_, ...rest] => process_named_arg_label(uri, rest)
110110
| _ => None
111111
};
112112
};

compiler/src/language_server/code_file.re

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,16 @@ let compile_source = (uri, source) => {
5353
Trace.log("Compiling " ++ filename);
5454

5555
switch (
56-
Compile.compile_string(
57-
~is_root_file=true,
58-
~hook=stop_after_typed_well_formed,
59-
~name=filename,
60-
source,
61-
)
56+
Config.preserve_config(() => {
57+
// Warnings will be reported in diagnostics so no need to print them
58+
Config.print_warnings := false;
59+
Compile.compile_string(
60+
~is_root_file=true,
61+
~hook=stop_after_typed_well_formed,
62+
~name=filename,
63+
source,
64+
);
65+
})
6266
) {
6367
| exception exn =>
6468
switch (Grain_parsing.Location.error_of_exn(exn)) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[@deriving (enum, yojson)]
2+
type message_type =
3+
| [@value 1] Error
4+
| Warning
5+
| Info
6+
| Log
7+
| Debug;
8+
9+
[@deriving yojson]
10+
type t = {
11+
[@key "type"]
12+
type_: int,
13+
message: string,
14+
};
15+
16+
let log = (~message_type=Info, message) => {
17+
let type_ = message_type_to_enum(message_type);
18+
Protocol.notification(
19+
~method="window/logMessage",
20+
to_yojson({message, type_}),
21+
);
22+
};

compiler/src/language_server/utils.re

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
open Grain_utils;
22
open Grain_diagnostics;
33
let uri_to_filename = (uri: Uri.t): string => {
4-
Uri.path(uri);
4+
Uri.path(uri) |> Str.replace_first(Str.regexp("^/\\(.\\):"), "\\1:");
55
};
66

77
let filename_to_uri = (filename: string): Uri.t => {
8-
Uri.of_string(filename);
8+
Uri.make(~scheme="file", ~host="", ~path=filename, ());
99
};
1010

1111
let loc_to_range = (pos: Grain_parsing.Location.t): Protocol.range => {

compiler/test/runner.re

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ let extract_anf = ({cstate_desc}) =>
9595
let compile_string_to_final_anf = (name, s) =>
9696
extract_anf(compile_string(~hook=stop_after_optimization, ~name, s));
9797

98-
let open_process = args => {
98+
let open_process = (~stdin_input=?, args) => {
9999
// We need to run the tests in powershell on Windows to have the correct environment
100100
let program = Sys.win32 ? "powershell.exe" : "/usr/bin/env";
101101

@@ -112,6 +112,13 @@ let open_process = args => {
112112
Unix.environment(),
113113
);
114114

115+
switch (stdin_input) {
116+
| None => ()
117+
| Some(input) =>
118+
output_string(stdin, input);
119+
flush(stdin);
120+
};
121+
115122
let current_time = Unix.time();
116123

117124
let out_eof = ref(false);
@@ -200,6 +207,14 @@ let doc = (file, arguments) => {
200207
(out ++ err, code);
201208
};
202209

210+
let lsp = stdin_input => {
211+
let cmd = [|"grain", "lsp"|];
212+
213+
let (code, out, err) = open_process(~stdin_input, cmd);
214+
215+
(out ++ err, code);
216+
};
217+
203218
let module_header = "module Test; ";
204219

205220
let makeSnapshotRunner =
@@ -534,3 +549,155 @@ let makeGrainDocErrorRunner = (test, name, filename, expected, arguments) => {
534549
},
535550
);
536551
};
552+
553+
let lsp_request = json => {
554+
let str = Yojson.Safe.to_string(json);
555+
let request_len = String.length(str);
556+
"Content-Length: " ++ string_of_int(request_len) ++ "\r\n" ++ str ++ "\r\n";
557+
};
558+
559+
let lsp_expected_response = json => {
560+
let str = Yojson.Safe.to_string(json);
561+
let request_len = String.length(str);
562+
"Content-Length: " ++ string_of_int(request_len) ++ "\r\n\r\n" ++ str;
563+
};
564+
565+
let lsp_input = (method, params) => {
566+
`Assoc([
567+
("jsonrpc", `String("2.0")),
568+
("id", `Int(1)),
569+
("method", `String(method)),
570+
("params", params),
571+
]);
572+
};
573+
574+
let lsp_notification = (method, params) => {
575+
`Assoc([
576+
("jsonrpc", `String("2.0")),
577+
("method", `String(method)),
578+
("params", params),
579+
]);
580+
};
581+
582+
let lsp_success_response = result => {
583+
`Assoc([
584+
("jsonrpc", `String("2.0")),
585+
("id", `Int(1)),
586+
("result", result),
587+
("error", `Null),
588+
]);
589+
};
590+
591+
let lsp_setup_teardown_requests = (code_uri, code) => {
592+
let init_request =
593+
lsp_input(
594+
"initialize",
595+
Yojson.Safe.from_string(
596+
{|{"processId":1,"clientInfo":null,"locale":null,"rootUri":null,"trace":"off"}|},
597+
),
598+
);
599+
600+
let open_request =
601+
lsp_input(
602+
"textDocument/didOpen",
603+
`Assoc([
604+
(
605+
"textDocument",
606+
`Assoc([
607+
("uri", `String(code_uri)),
608+
("languageId", `String("grain")),
609+
("version", `Int(1)),
610+
("text", `String(code)),
611+
]),
612+
),
613+
]),
614+
);
615+
616+
let shutdown_request =
617+
Yojson.Safe.from_string({|{"jsonrpc":"2.0","id":1,"method":"shutdown"}|});
618+
619+
let exit_request =
620+
Yojson.Safe.from_string({|{"jsonrpc":"2.0","id":1,"method":"exit"}|});
621+
622+
(
623+
lsp_request(init_request) ++ lsp_request(open_request),
624+
lsp_request(shutdown_request) ++ lsp_request(exit_request),
625+
);
626+
};
627+
628+
let assert_lsp_responses =
629+
(expect, expected_open_diagnostics, ~expected_output=?, result) => {
630+
let expected_init_response =
631+
lsp_expected_response(
632+
lsp_success_response(
633+
Yojson.Safe.from_string(
634+
{|{"capabilities":{"documentFormattingProvider":true,"textDocumentSync":1,"hoverProvider":true,"definitionProvider":{"linkSupport":true},"typeDefinitionProvider":true,"referencesProvider":false,"documentSymbolProvider":false,"codeActionProvider":true,"codeLensProvider":{"resolveProvider":true},"documentHighlightProvider":false,"documentRangeFormattingProvider":false,"renameProvider":false,"inlayHintProvider":{"resolveProvider":false}}}|},
635+
),
636+
),
637+
);
638+
let expected_open_response =
639+
lsp_expected_response(
640+
lsp_notification(
641+
"textDocument/publishDiagnostics",
642+
expected_open_diagnostics,
643+
),
644+
);
645+
let expected_begin_response =
646+
expected_init_response ++ expected_open_response;
647+
648+
let expected_exit_response =
649+
lsp_expected_response(lsp_success_response(`Null));
650+
651+
let expected =
652+
switch (expected_output) {
653+
| None => expected_begin_response ++ expected_exit_response
654+
| Some(expected_output) =>
655+
let expected_response =
656+
lsp_expected_response(lsp_success_response(expected_output));
657+
expected_begin_response ++ expected_response ++ expected_exit_response;
658+
};
659+
660+
expect.string(result).toEqual(expected);
661+
};
662+
663+
let makeLspRunner =
664+
(test, name, code_uri, code, request_params, expected_output) => {
665+
test(
666+
name,
667+
({expect}) => {
668+
let (setup_request, teardown_request) =
669+
lsp_setup_teardown_requests(code_uri, code);
670+
671+
let (result, code) =
672+
lsp(
673+
setup_request ++ lsp_request(request_params) ++ teardown_request,
674+
);
675+
676+
assert_lsp_responses(
677+
expect,
678+
`Assoc([("uri", `String(code_uri)), ("diagnostics", `List([]))]),
679+
~expected_output,
680+
result,
681+
);
682+
683+
expect.int(code).toBe(0);
684+
},
685+
);
686+
};
687+
688+
let makeLspDiagnosticsRunner =
689+
(test, name, code_uri, code, expected_diagnostics) => {
690+
test(
691+
name,
692+
({expect}) => {
693+
let (setup_request, teardown_request) =
694+
lsp_setup_teardown_requests(code_uri, code);
695+
696+
let (result, code) = lsp(setup_request ++ teardown_request);
697+
698+
assert_lsp_responses(expect, expected_diagnostics, result);
699+
700+
expect.int(code).toBe(0);
701+
},
702+
);
703+
};

compiler/test/suites/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
(library
22
(name Grain_tests_suites)
33
(public_name grain-tests.suites)
4-
(libraries grain grain-tests.framework)
4+
(libraries grain grain-tests.framework uri)
55
(modules (:standard)))

0 commit comments

Comments
 (0)