Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(analysis): add diagnostics syntax #457

Merged
merged 13 commits into from
Jun 29, 2022
7 changes: 7 additions & 0 deletions analysis/src/Cli.ml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions analysis/src/Commands.ml
Original file line number Diff line number Diff line change
@@ -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
33 changes: 33 additions & 0 deletions analysis/src/Diagnostics.ml
Original file line number Diff line number Diff line change
@@ -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 []
28 changes: 22 additions & 6 deletions analysis/src/Protocol.ml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions analysis/tests/not_compiled/Diagnostics.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
let = 1 + 1.0
let add = =2
lett a = 2

//^dia
17 changes: 17 additions & 0 deletions analysis/tests/not_compiled/expected/Diagnostics.res.txt
Original file line number Diff line number Diff line change
@@ -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"
}]

9 changes: 9 additions & 0 deletions analysis/tests/test.sh
Original file line number Diff line number Diff line change
@@ -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'
32 changes: 31 additions & 1 deletion server/src/server.ts
Original file line number Diff line number Diff line change
@@ -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<p.Message> {
}
}

const updateDiagnosticSyntax = (fileUri: string, fileContent: string) => {
let filePath = fileURLToPath(fileUri);
let extension = path.extname(filePath);
let tmpname = utils.createFileInTempDir(extension);
fs.writeFileSync(tmpname, fileContent, { encoding: "utf-8" });

const items: p.Diagnostic[] | [] = utils.runAnalysisAfterSanityCheck(
filePath,
[
"diagnosticSyntax",
tmpname
],
);

const notification: p.NotificationMessage = {
jsonrpc: c.jsonrpcVersion,
method: "textDocument/publishDiagnostics",
params: {
uri: fileUri,
diagnostics: items
}
}

fs.unlink(tmpname, () => null);

send(notification)
}

function createInterface(msg: p.RequestMessage): p.Message {
let params = msg.params as CreateInterfaceRequestParams;
let extension = path.extname(params.uri);
@@ -784,6 +812,7 @@ function onMessage(msg: p.Message) {
} else if (msg.method === DidOpenTextDocumentNotification.method) {
let params = msg.params as p.DidOpenTextDocumentParams;
openedFile(params.textDocument.uri, params.textDocument.text);
updateDiagnosticSyntax(params.textDocument.uri, params.textDocument.text);
} else if (msg.method === DidChangeTextDocumentNotification.method) {
let params = msg.params as p.DidChangeTextDocumentParams;
let extName = path.extname(params.textDocument.uri);
@@ -797,6 +826,7 @@ function onMessage(msg: p.Message) {
params.textDocument.uri,
changes[changes.length - 1].text
);
updateDiagnosticSyntax(params.textDocument.uri, changes[changes.length - 1].text);
}
}
} else if (msg.method === DidCloseTextDocumentNotification.method) {
8 changes: 4 additions & 4 deletions server/src/utils.ts
Original file line number Diff line number Diff line change
@@ -603,9 +603,9 @@ export let parseCompilerLogOutput = (
// 10 ┆
} else if (line.startsWith(" ")) {
// part of the actual diagnostics message
parsedDiagnostics[parsedDiagnostics.length - 1].content.push(
line.slice(2)
);
parsedDiagnostics[parsedDiagnostics.length - 1].content.push(
line.slice(2)
);
} else if (line.trim() != "") {
// We'll assume that everything else is also part of the diagnostics too.
// Most of these should have been indented 2 spaces; sadly, some of them
@@ -635,7 +635,7 @@ export let parseCompilerLogOutput = (
range,
source: "ReScript",
// remove start and end whitespaces/newlines
message: diagnosticMessage.join("\n").trim() + "\n",
message: diagnosticMessage.join("\n").trim(),
};

// Check for potential code actions