Skip to content

Commit 6628c8d

Browse files
authored
feat(analysis): add diagnostics syntax (#457)
1 parent 2665678 commit 6628c8d

File tree

9 files changed

+133
-11
lines changed

9 files changed

+133
-11
lines changed

analysis/src/Cli.ml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ API examples:
1010
./rescript-editor-analysis.exe hover src/MyFile.res 10 2
1111
./rescript-editor-analysis.exe references src/MyFile.res 10 2
1212
./rescript-editor-analysis.exe rename src/MyFile.res 10 2 foo
13+
./rescript-editor-analysis.exe diagnosticSyntax src/MyFile.res
1314

1415
Dev-time examples:
1516
./rescript-editor-analysis.exe dump src/MyFile.res src/MyFile2.res
@@ -60,6 +61,10 @@ Options:
6061

6162
./rescript-editor-analysis.exe format src/MyFile.res
6263

64+
diagnosticSyntax: print to stdout diagnostic for syntax
65+
66+
./rescript-editor-analysis.exe diagnosticSyntax src/MyFile.res
67+
6368
test: run tests specified by special comments in file src/MyFile.res
6469

6570
./rescript-editor-analysis.exe test src/src/MyFile.res
@@ -88,6 +93,8 @@ let main () =
8893
Commands.codeAction ~path
8994
~pos:(int_of_string line, int_of_string col)
9095
~currentFile ~debug:false
96+
| [_; "diagnosticSyntax"; path;] ->
97+
Commands.diagnosticSyntax ~path
9198
| _ :: "reanalyze" :: _ ->
9299
let len = Array.length Sys.argv in
93100
for i = 1 to len - 2 do

analysis/src/Commands.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,10 @@ let format ~path =
257257
signature
258258
else ""
259259

260+
let diagnosticSyntax ~path =
261+
print_endline
262+
(Diagnostics.document_syntax ~path |> Protocol.array)
263+
260264
let test ~path =
261265
Uri.stripPath := true;
262266
match Files.readFile path with
@@ -378,6 +382,7 @@ let test ~path =
378382
Printf.printf "%s\nnewText:\n%s<--here\n%s%s\n"
379383
(Protocol.stringifyRange range)
380384
indent indent newText)))
385+
| "dia" -> diagnosticSyntax ~path
381386
| _ -> ());
382387
print_newline ())
383388
in

analysis/src/Diagnostics.ml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
let document_syntax ~path =
2+
let get_diagnostics diagnostics =
3+
diagnostics
4+
|> List.map (fun diagnostic ->
5+
let _, startline, startcol =
6+
Location.get_pos_info (Res_diagnostics.getStartPos diagnostic)
7+
in
8+
let _, endline, endcol =
9+
Location.get_pos_info (Res_diagnostics.getEndPos diagnostic)
10+
in
11+
Protocol.stringifyDiagnostic
12+
{
13+
range =
14+
{
15+
start = {line = startline - 1; character = startcol};
16+
end_ = {line = endline - 1; character = endcol};
17+
};
18+
message = Res_diagnostics.explain diagnostic;
19+
severity = 1;
20+
})
21+
in
22+
if FindFiles.isImplementation path then
23+
let parseImplementation =
24+
Res_driver.parsingEngine.parseImplementation ~forPrinter:false
25+
~filename:path
26+
in
27+
get_diagnostics parseImplementation.diagnostics
28+
else if FindFiles.isInterface path then
29+
let parseInterface =
30+
Res_driver.parsingEngine.parseInterface ~forPrinter:false ~filename:path
31+
in
32+
get_diagnostics parseInterface.diagnostics
33+
else []

analysis/src/Protocol.ml

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ type completionItem = {
1010
documentation: markupContent option;
1111
}
1212

13-
type hover = string
14-
type location = {uri: string; range: range}
15-
type documentSymbolItem = {name: string; kind: int; location: location}
16-
type renameFile = {oldUri: string; newUri: string}
17-
type textEdit = {range: range; newText: string}
13+
type location = {uri : string; range : range}
14+
type documentSymbolItem = {name : string; kind : int; location : location}
15+
type renameFile = {oldUri : string; newUri : string}
16+
type textEdit = {range : range; newText : string}
17+
18+
type diagnostic = {
19+
range : range;
20+
message : string;
21+
severity : int;
22+
}
1823

1924
type optionalVersionedTextDocumentIdentifier = {
2025
version: int option;
@@ -89,7 +94,7 @@ let stringifyRenameFile {oldUri; newUri} =
8994
}|}
9095
(Json.escape oldUri) (Json.escape newUri)
9196

92-
let stringifyTextEdit te =
97+
let stringifyTextEdit (te : textEdit) =
9398
Printf.sprintf {|{
9499
"range": %s,
95100
"newText": "%s"
@@ -126,3 +131,14 @@ let stringifyCodeAction ca =
126131
Printf.sprintf {|{"title": "%s", "kind": "%s", "edit": %s}|} ca.title
127132
(codeActionKindToString ca.codeActionKind)
128133
(ca.edit |> stringifyCodeActionEdit)
134+
135+
(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic *)
136+
let stringifyDiagnostic d =
137+
Printf.sprintf {|{
138+
"range": %s,
139+
"message": "%s",
140+
"severity": %d,
141+
"source": "ReScript"
142+
}|}
143+
(stringifyRange d.range) (Json.escape d.message)
144+
d.severity
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
let = 1 + 1.0
2+
let add = =2
3+
lett a = 2
4+
5+
//^dia
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[{
2+
"range": {"start": {"line": 2, "character": 4}, "end": {"line": 2, "character": 6}},
3+
"message": "consecutive statements on a line must be separated by ';' or a newline",
4+
"severity": 1,
5+
"source": "ReScript"
6+
}, {
7+
"range": {"start": {"line": 1, "character": 9}, "end": {"line": 1, "character": 11}},
8+
"message": "This let-binding misses an expression",
9+
"severity": 1,
10+
"source": "ReScript"
11+
}, {
12+
"range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}},
13+
"message": "I was expecting a name for this let-binding. Example: `let message = \"hello\"`",
14+
"severity": 1,
15+
"source": "ReScript"
16+
}]
17+

analysis/tests/test.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ for file in src/*.{res,resi}; do
77
fi
88
done
99

10+
for file in not_compiled/*.res; do
11+
output="$(dirname $file)/expected/$(basename $file).txt"
12+
../rescript-editor-analysis.exe test $file &> $output
13+
# CI. We use LF, and the CI OCaml fork prints CRLF. Convert.
14+
if [ "$RUNNER_OS" == "Windows" ]; then
15+
perl -pi -e 's/\r\n/\n/g' -- $output
16+
fi
17+
done
18+
1019
warningYellow='\033[0;33m'
1120
successGreen='\033[0;32m'
1221
reset='\033[0m'

server/src/server.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ let projectsFiles: Map<
5757
let codeActionsFromDiagnostics: codeActions.filesCodeActions = {};
5858

5959
// will be properly defined later depending on the mode (stdio/node-rpc)
60-
let send: (msg: p.Message) => void = (_) => {};
60+
let send: (msg: p.Message) => void = (_) => { };
6161

6262
interface CreateInterfaceRequestParams {
6363
uri: string;
@@ -598,6 +598,34 @@ function format(msg: p.RequestMessage): Array<p.Message> {
598598
}
599599
}
600600

601+
const updateDiagnosticSyntax = (fileUri: string, fileContent: string) => {
602+
let filePath = fileURLToPath(fileUri);
603+
let extension = path.extname(filePath);
604+
let tmpname = utils.createFileInTempDir(extension);
605+
fs.writeFileSync(tmpname, fileContent, { encoding: "utf-8" });
606+
607+
const items: p.Diagnostic[] | [] = utils.runAnalysisAfterSanityCheck(
608+
filePath,
609+
[
610+
"diagnosticSyntax",
611+
tmpname
612+
],
613+
);
614+
615+
const notification: p.NotificationMessage = {
616+
jsonrpc: c.jsonrpcVersion,
617+
method: "textDocument/publishDiagnostics",
618+
params: {
619+
uri: fileUri,
620+
diagnostics: items
621+
}
622+
}
623+
624+
fs.unlink(tmpname, () => null);
625+
626+
send(notification)
627+
}
628+
601629
function createInterface(msg: p.RequestMessage): p.Message {
602630
let params = msg.params as CreateInterfaceRequestParams;
603631
let extension = path.extname(params.uri);
@@ -784,6 +812,7 @@ function onMessage(msg: p.Message) {
784812
} else if (msg.method === DidOpenTextDocumentNotification.method) {
785813
let params = msg.params as p.DidOpenTextDocumentParams;
786814
openedFile(params.textDocument.uri, params.textDocument.text);
815+
updateDiagnosticSyntax(params.textDocument.uri, params.textDocument.text);
787816
} else if (msg.method === DidChangeTextDocumentNotification.method) {
788817
let params = msg.params as p.DidChangeTextDocumentParams;
789818
let extName = path.extname(params.textDocument.uri);
@@ -797,6 +826,7 @@ function onMessage(msg: p.Message) {
797826
params.textDocument.uri,
798827
changes[changes.length - 1].text
799828
);
829+
updateDiagnosticSyntax(params.textDocument.uri, changes[changes.length - 1].text);
800830
}
801831
}
802832
} else if (msg.method === DidCloseTextDocumentNotification.method) {

server/src/utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -603,9 +603,9 @@ export let parseCompilerLogOutput = (
603603
// 10 ┆
604604
} else if (line.startsWith(" ")) {
605605
// part of the actual diagnostics message
606-
parsedDiagnostics[parsedDiagnostics.length - 1].content.push(
607-
line.slice(2)
608-
);
606+
parsedDiagnostics[parsedDiagnostics.length - 1].content.push(
607+
line.slice(2)
608+
);
609609
} else if (line.trim() != "") {
610610
// We'll assume that everything else is also part of the diagnostics too.
611611
// Most of these should have been indented 2 spaces; sadly, some of them
@@ -635,7 +635,7 @@ export let parseCompilerLogOutput = (
635635
range,
636636
source: "ReScript",
637637
// remove start and end whitespaces/newlines
638-
message: diagnosticMessage.join("\n").trim() + "\n",
638+
message: diagnosticMessage.join("\n").trim(),
639639
};
640640

641641
// Check for potential code actions

0 commit comments

Comments
 (0)