diff --git a/analysis/src/Cli.ml b/analysis/src/Cli.ml index a49326d92..7e0f1bb77 100644 --- a/analysis/src/Cli.ml +++ b/analysis/src/Cli.ml @@ -10,6 +10,7 @@ API examples: ./rescript-editor-analysis.exe hover src/MyFile.res 10 2 ./rescript-editor-analysis.exe references src/MyFile.res 10 2 ./rescript-editor-analysis.exe rename src/MyFile.res 10 2 foo + ./rescript-editor-analysis.exe diagnosticSyntax src/MyFile.res Dev-time examples: ./rescript-editor-analysis.exe dump src/MyFile.res src/MyFile2.res @@ -60,6 +61,10 @@ Options: ./rescript-editor-analysis.exe format src/MyFile.res + diagnosticSyntax: print to stdout diagnostic for syntax + + ./rescript-editor-analysis.exe diagnosticSyntax src/MyFile.res + test: run tests specified by special comments in file src/MyFile.res ./rescript-editor-analysis.exe test src/src/MyFile.res @@ -88,6 +93,8 @@ let main () = Commands.codeAction ~path ~pos:(int_of_string line, int_of_string col) ~currentFile ~debug:false + | [_; "diagnosticSyntax"; path;] -> + Commands.diagnosticSyntax ~path | _ :: "reanalyze" :: _ -> let len = Array.length Sys.argv in for i = 1 to len - 2 do diff --git a/analysis/src/Commands.ml b/analysis/src/Commands.ml index 65066f8a8..48c4d6338 100644 --- a/analysis/src/Commands.ml +++ b/analysis/src/Commands.ml @@ -257,6 +257,10 @@ let format ~path = signature else "" +let diagnosticSyntax ~path = + print_endline + (Diagnostics.document_syntax ~path |> Protocol.array) + let test ~path = Uri.stripPath := true; match Files.readFile path with @@ -378,6 +382,7 @@ let test ~path = Printf.printf "%s\nnewText:\n%s<--here\n%s%s\n" (Protocol.stringifyRange range) indent indent newText))) + | "dia" -> diagnosticSyntax ~path | _ -> ()); print_newline ()) in diff --git a/analysis/src/Diagnostics.ml b/analysis/src/Diagnostics.ml new file mode 100644 index 000000000..5ef3673dd --- /dev/null +++ b/analysis/src/Diagnostics.ml @@ -0,0 +1,33 @@ +let document_syntax ~path = + let get_diagnostics diagnostics = + diagnostics + |> List.map (fun diagnostic -> + let _, startline, startcol = + Location.get_pos_info (Res_diagnostics.getStartPos diagnostic) + in + let _, endline, endcol = + Location.get_pos_info (Res_diagnostics.getEndPos diagnostic) + in + Protocol.stringifyDiagnostic + { + range = + { + start = {line = startline - 1; character = startcol}; + end_ = {line = endline - 1; character = endcol}; + }; + message = Res_diagnostics.explain diagnostic; + severity = 1; + }) + in + if FindFiles.isImplementation path then + let parseImplementation = + Res_driver.parsingEngine.parseImplementation ~forPrinter:false + ~filename:path + in + get_diagnostics parseImplementation.diagnostics + else if FindFiles.isInterface path then + let parseInterface = + Res_driver.parsingEngine.parseInterface ~forPrinter:false ~filename:path + in + get_diagnostics parseInterface.diagnostics + else [] \ No newline at end of file diff --git a/analysis/src/Protocol.ml b/analysis/src/Protocol.ml index 076137842..4a03b1508 100644 --- a/analysis/src/Protocol.ml +++ b/analysis/src/Protocol.ml @@ -10,11 +10,16 @@ type completionItem = { documentation: markupContent option; } -type hover = string -type location = {uri: string; range: range} -type documentSymbolItem = {name: string; kind: int; location: location} -type renameFile = {oldUri: string; newUri: string} -type textEdit = {range: range; newText: string} +type location = {uri : string; range : range} +type documentSymbolItem = {name : string; kind : int; location : location} +type renameFile = {oldUri : string; newUri : string} +type textEdit = {range : range; newText : string} + +type diagnostic = { + range : range; + message : string; + severity : int; +} type optionalVersionedTextDocumentIdentifier = { version: int option; @@ -89,7 +94,7 @@ let stringifyRenameFile {oldUri; newUri} = }|} (Json.escape oldUri) (Json.escape newUri) -let stringifyTextEdit te = +let stringifyTextEdit (te : textEdit) = Printf.sprintf {|{ "range": %s, "newText": "%s" @@ -126,3 +131,14 @@ let stringifyCodeAction ca = Printf.sprintf {|{"title": "%s", "kind": "%s", "edit": %s}|} ca.title (codeActionKindToString ca.codeActionKind) (ca.edit |> stringifyCodeActionEdit) + +(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic *) +let stringifyDiagnostic d = + Printf.sprintf {|{ + "range": %s, + "message": "%s", + "severity": %d, + "source": "ReScript" +}|} + (stringifyRange d.range) (Json.escape d.message) + d.severity \ No newline at end of file diff --git a/analysis/tests/not_compiled/Diagnostics.res b/analysis/tests/not_compiled/Diagnostics.res new file mode 100644 index 000000000..c81f1e63e --- /dev/null +++ b/analysis/tests/not_compiled/Diagnostics.res @@ -0,0 +1,5 @@ +let = 1 + 1.0 +let add = =2 +lett a = 2 + +//^dia \ No newline at end of file diff --git a/analysis/tests/not_compiled/expected/Diagnostics.res.txt b/analysis/tests/not_compiled/expected/Diagnostics.res.txt new file mode 100644 index 000000000..a5df33b71 --- /dev/null +++ b/analysis/tests/not_compiled/expected/Diagnostics.res.txt @@ -0,0 +1,17 @@ +[{ + "range": {"start": {"line": 2, "character": 4}, "end": {"line": 2, "character": 6}}, + "message": "consecutive statements on a line must be separated by ';' or a newline", + "severity": 1, + "source": "ReScript" +}, { + "range": {"start": {"line": 1, "character": 9}, "end": {"line": 1, "character": 11}}, + "message": "This let-binding misses an expression", + "severity": 1, + "source": "ReScript" +}, { + "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, + "message": "I was expecting a name for this let-binding. Example: `let message = \"hello\"`", + "severity": 1, + "source": "ReScript" +}] + diff --git a/analysis/tests/test.sh b/analysis/tests/test.sh index 27a98ffb2..c931641e9 100755 --- a/analysis/tests/test.sh +++ b/analysis/tests/test.sh @@ -7,6 +7,15 @@ for file in src/*.{res,resi}; do fi done +for file in not_compiled/*.res; do + output="$(dirname $file)/expected/$(basename $file).txt" + ../rescript-editor-analysis.exe test $file &> $output + # CI. We use LF, and the CI OCaml fork prints CRLF. Convert. + if [ "$RUNNER_OS" == "Windows" ]; then + perl -pi -e 's/\r\n/\n/g' -- $output + fi +done + warningYellow='\033[0;33m' successGreen='\033[0;32m' reset='\033[0m' diff --git a/server/src/server.ts b/server/src/server.ts index c5683387b..f99ac82d3 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -57,7 +57,7 @@ let projectsFiles: Map< let codeActionsFromDiagnostics: codeActions.filesCodeActions = {}; // will be properly defined later depending on the mode (stdio/node-rpc) -let send: (msg: p.Message) => void = (_) => {}; +let send: (msg: p.Message) => void = (_) => { }; interface CreateInterfaceRequestParams { uri: string; @@ -598,6 +598,34 @@ function format(msg: p.RequestMessage): Array