mirror of
https://github.com/c-cube/linol.git
synced 2025-12-10 05:04:00 -05:00
493 lines
11 KiB
TypeScript
493 lines
11 KiB
TypeScript
import outdent from "outdent";
|
|
import type * as rpc from "vscode-jsonrpc/node";
|
|
import * as Protocol from "vscode-languageserver-protocol";
|
|
import * as Types from "vscode-languageserver-types";
|
|
import * as LanguageServer from "../src/LanguageServer";
|
|
|
|
describe("textDocument/diagnostics", () => {
|
|
let languageServer: rpc.MessageConnection;
|
|
|
|
function openDocument(source: string) {
|
|
languageServer.sendNotification(
|
|
Protocol.DidOpenTextDocumentNotification.type,
|
|
{
|
|
textDocument: Types.TextDocumentItem.create(
|
|
"file:///test.ml",
|
|
"ocaml",
|
|
0,
|
|
source,
|
|
),
|
|
},
|
|
);
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
languageServer = await LanguageServer.startAndInitialize();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await LanguageServer.exit(languageServer);
|
|
});
|
|
|
|
it("has related diagnostics", async () => {
|
|
const receivedDiganostics = new Promise((resolve, _reject) =>
|
|
languageServer.onNotification((method, params) => {
|
|
expect(method).toMatchInlineSnapshot(
|
|
`"textDocument/publishDiagnostics"`,
|
|
);
|
|
expect(params).toMatchInlineSnapshot(`
|
|
{
|
|
"diagnostics": [
|
|
{
|
|
"message": "This comment contains an unterminated string literal",
|
|
"range": {
|
|
"end": {
|
|
"character": 2,
|
|
"line": 0,
|
|
},
|
|
"start": {
|
|
"character": 0,
|
|
"line": 0,
|
|
},
|
|
},
|
|
"relatedInformation": [
|
|
{
|
|
"location": {
|
|
"range": {
|
|
"end": {
|
|
"character": 4,
|
|
"line": 0,
|
|
},
|
|
"start": {
|
|
"character": 3,
|
|
"line": 0,
|
|
},
|
|
},
|
|
"uri": "file:///test.ml",
|
|
},
|
|
"message": "String literal begins here",
|
|
},
|
|
],
|
|
"severity": 1,
|
|
"source": "ocamllsp",
|
|
},
|
|
],
|
|
"uri": "file:///test.ml",
|
|
}
|
|
`);
|
|
resolve(null);
|
|
}),
|
|
);
|
|
openDocument(outdent`
|
|
(* " *)
|
|
`);
|
|
await receivedDiganostics;
|
|
});
|
|
|
|
it("unused values have diagnostic tags", async () => {
|
|
const receivedDiganostics = new Promise((resolve, _reject) =>
|
|
languageServer.onNotification((method, params) => {
|
|
expect(method).toMatchInlineSnapshot(
|
|
`"textDocument/publishDiagnostics"`,
|
|
);
|
|
expect(params).toMatchInlineSnapshot(`
|
|
{
|
|
"diagnostics": [
|
|
{
|
|
"message": "Warning 26: unused variable x.",
|
|
"range": {
|
|
"end": {
|
|
"character": 7,
|
|
"line": 1,
|
|
},
|
|
"start": {
|
|
"character": 6,
|
|
"line": 1,
|
|
},
|
|
},
|
|
"severity": 2,
|
|
"source": "ocamllsp",
|
|
},
|
|
],
|
|
"uri": "file:///test.ml",
|
|
}
|
|
`);
|
|
resolve(null);
|
|
}),
|
|
);
|
|
openDocument(outdent`
|
|
let () =
|
|
let x = 123 in
|
|
()
|
|
`);
|
|
await receivedDiganostics;
|
|
});
|
|
|
|
it("deprecated values have diganostic tags", async () => {
|
|
const receivedDiganostics = new Promise((resolve, _reject) =>
|
|
languageServer.onNotification((method, params) => {
|
|
expect(method).toMatchInlineSnapshot(
|
|
`"textDocument/publishDiagnostics"`,
|
|
);
|
|
expect(params).toMatchInlineSnapshot(`
|
|
{
|
|
"diagnostics": [
|
|
{
|
|
"message": "Alert deprecated: X.x
|
|
do not use",
|
|
"range": {
|
|
"end": {
|
|
"character": 19,
|
|
"line": 6,
|
|
},
|
|
"start": {
|
|
"character": 16,
|
|
"line": 6,
|
|
},
|
|
},
|
|
"severity": 2,
|
|
"source": "ocamllsp",
|
|
},
|
|
],
|
|
"uri": "file:///test.ml",
|
|
}
|
|
`);
|
|
resolve(null);
|
|
}),
|
|
);
|
|
openDocument(outdent`
|
|
module X : sig
|
|
val x : unit
|
|
[@@ocaml.deprecated "do not use"]
|
|
end = struct
|
|
let x = ()
|
|
end
|
|
let () = ignore X.x
|
|
`);
|
|
await receivedDiganostics;
|
|
});
|
|
|
|
it("related diagnostics for mismatched signatures", async () => {
|
|
const receivedDiganostics = new Promise((resolve, _reject) =>
|
|
languageServer.onNotification((method, params) => {
|
|
expect(method).toMatchInlineSnapshot(
|
|
`"textDocument/publishDiagnostics"`,
|
|
);
|
|
expect(params).toMatchInlineSnapshot(`
|
|
{
|
|
"diagnostics": [
|
|
{
|
|
"message": "Signature mismatch:
|
|
Modules do not match:
|
|
sig val x : int end
|
|
is not included in
|
|
sig val x : unit end
|
|
Values do not match: val x : int is not included in val x : unit
|
|
The type int is not compatible with the type unit",
|
|
"range": {
|
|
"end": {
|
|
"character": 3,
|
|
"line": 4,
|
|
},
|
|
"start": {
|
|
"character": 6,
|
|
"line": 2,
|
|
},
|
|
},
|
|
"relatedInformation": [
|
|
{
|
|
"location": {
|
|
"range": {
|
|
"end": {
|
|
"character": 14,
|
|
"line": 2,
|
|
},
|
|
"start": {
|
|
"character": 2,
|
|
"line": 2,
|
|
},
|
|
},
|
|
"uri": "file:///test.ml",
|
|
},
|
|
"message": "Expected declaration",
|
|
},
|
|
{
|
|
"location": {
|
|
"range": {
|
|
"end": {
|
|
"character": 7,
|
|
"line": 4,
|
|
},
|
|
"start": {
|
|
"character": 6,
|
|
"line": 4,
|
|
},
|
|
},
|
|
"uri": "file:///test.ml",
|
|
},
|
|
"message": "Actual declaration",
|
|
},
|
|
],
|
|
"severity": 1,
|
|
"source": "ocamllsp",
|
|
},
|
|
],
|
|
"uri": "file:///test.ml",
|
|
}
|
|
`);
|
|
resolve(null);
|
|
}),
|
|
);
|
|
openDocument(outdent`
|
|
module X : sig
|
|
val x : unit
|
|
end = struct
|
|
let x = 123
|
|
end
|
|
`);
|
|
await receivedDiganostics;
|
|
});
|
|
|
|
it("no diagnostics for valid files", async () => {
|
|
const receivedDiganostics = new Promise((resolve, _reject) =>
|
|
languageServer.onNotification((method, params) => {
|
|
expect(method).toMatchInlineSnapshot(
|
|
`"textDocument/publishDiagnostics"`,
|
|
);
|
|
expect(params).toMatchInlineSnapshot(`
|
|
{
|
|
"diagnostics": [],
|
|
"uri": "file:///test.ml",
|
|
}
|
|
`);
|
|
resolve(null);
|
|
}),
|
|
);
|
|
|
|
openDocument(outdent`
|
|
let num = 42
|
|
`);
|
|
|
|
await receivedDiganostics;
|
|
});
|
|
|
|
it("should have diagnostics for a hole only", async () => {
|
|
const receivedDiganostics = new Promise((resolve, _reject) =>
|
|
languageServer.onNotification((method, params) => {
|
|
expect(method).toMatchInlineSnapshot(
|
|
`"textDocument/publishDiagnostics"`,
|
|
);
|
|
expect(params).toMatchInlineSnapshot(`
|
|
{
|
|
"diagnostics": [
|
|
{
|
|
"code": "hole",
|
|
"message": "This typed hole should be replaced with an expression of type int",
|
|
"range": {
|
|
"end": {
|
|
"character": 15,
|
|
"line": 0,
|
|
},
|
|
"start": {
|
|
"character": 14,
|
|
"line": 0,
|
|
},
|
|
},
|
|
"severity": 1,
|
|
"source": "ocamllsp",
|
|
},
|
|
],
|
|
"uri": "file:///test.ml",
|
|
}
|
|
`);
|
|
resolve(null);
|
|
}),
|
|
);
|
|
|
|
openDocument(outdent`
|
|
let a : int = _
|
|
`);
|
|
|
|
await receivedDiganostics;
|
|
});
|
|
|
|
it("should have diagnostics for holes only", async () => {
|
|
const receivedDiganostics = new Promise((resolve, _reject) =>
|
|
languageServer.onNotification((method, params) => {
|
|
expect(method).toMatchInlineSnapshot(
|
|
`"textDocument/publishDiagnostics"`,
|
|
);
|
|
expect(params).toMatchInlineSnapshot(`
|
|
{
|
|
"diagnostics": [
|
|
{
|
|
"code": "hole",
|
|
"message": "This typed hole should be replaced with an expression of type int",
|
|
"range": {
|
|
"end": {
|
|
"character": 16,
|
|
"line": 0,
|
|
},
|
|
"start": {
|
|
"character": 15,
|
|
"line": 0,
|
|
},
|
|
},
|
|
"severity": 1,
|
|
"source": "ocamllsp",
|
|
},
|
|
{
|
|
"message": "Warning 26: unused variable b.",
|
|
"range": {
|
|
"end": {
|
|
"character": 5,
|
|
"line": 1,
|
|
},
|
|
"start": {
|
|
"character": 4,
|
|
"line": 1,
|
|
},
|
|
},
|
|
"severity": 2,
|
|
"source": "ocamllsp",
|
|
},
|
|
{
|
|
"code": "hole",
|
|
"message": "This typed hole should be replaced with an expression of type string",
|
|
"range": {
|
|
"end": {
|
|
"character": 44,
|
|
"line": 1,
|
|
},
|
|
"start": {
|
|
"character": 43,
|
|
"line": 1,
|
|
},
|
|
},
|
|
"severity": 1,
|
|
"source": "ocamllsp",
|
|
},
|
|
{
|
|
"code": "hole",
|
|
"message": "This typed hole should be replaced with an expression of type string",
|
|
"range": {
|
|
"end": {
|
|
"character": 58,
|
|
"line": 1,
|
|
},
|
|
"start": {
|
|
"character": 57,
|
|
"line": 1,
|
|
},
|
|
},
|
|
"severity": 1,
|
|
"source": "ocamllsp",
|
|
},
|
|
],
|
|
"uri": "file:///test.ml",
|
|
}
|
|
`);
|
|
resolve(null);
|
|
}),
|
|
);
|
|
|
|
openDocument(outdent`
|
|
let _a : int = _ in
|
|
let b : string = match Some 1 with None -> _ | Some _ -> _ in
|
|
()
|
|
`);
|
|
|
|
await receivedDiganostics;
|
|
});
|
|
|
|
it("different diagnostics, including holes, sorted by range", async () => {
|
|
const receivedDiganostics = new Promise((resolve, _reject) =>
|
|
languageServer.onNotification((method, params) => {
|
|
expect(method).toMatchInlineSnapshot(
|
|
`"textDocument/publishDiagnostics"`,
|
|
);
|
|
expect(params).toMatchInlineSnapshot(`
|
|
{
|
|
"diagnostics": [
|
|
{
|
|
"message": "The constructor () has type unit but an expression was expected of type int",
|
|
"range": {
|
|
"end": {
|
|
"character": 42,
|
|
"line": 1,
|
|
},
|
|
"start": {
|
|
"character": 40,
|
|
"line": 1,
|
|
},
|
|
},
|
|
"severity": 1,
|
|
"source": "ocamllsp",
|
|
},
|
|
{
|
|
"code": "hole",
|
|
"message": "This typed hole should be replaced with an expression of type 'a",
|
|
"range": {
|
|
"end": {
|
|
"character": 12,
|
|
"line": 3,
|
|
},
|
|
"start": {
|
|
"character": 11,
|
|
"line": 3,
|
|
},
|
|
},
|
|
"severity": 1,
|
|
"source": "ocamllsp",
|
|
},
|
|
{
|
|
"code": "hole",
|
|
"message": "This typed hole should be replaced with an expression of type 'a",
|
|
"range": {
|
|
"end": {
|
|
"character": 12,
|
|
"line": 4,
|
|
},
|
|
"start": {
|
|
"character": 11,
|
|
"line": 4,
|
|
},
|
|
},
|
|
"severity": 1,
|
|
"source": "ocamllsp",
|
|
},
|
|
{
|
|
"message": "This constant has type string but an expression was expected of type int",
|
|
"range": {
|
|
"end": {
|
|
"character": 9,
|
|
"line": 5,
|
|
},
|
|
"start": {
|
|
"character": 6,
|
|
"line": 5,
|
|
},
|
|
},
|
|
"severity": 1,
|
|
"source": "ocamllsp",
|
|
},
|
|
],
|
|
"uri": "file:///test.ml",
|
|
}
|
|
`);
|
|
resolve(null);
|
|
}),
|
|
);
|
|
|
|
openDocument(outdent`
|
|
let _u =
|
|
let _i = List.map (fun i -> i) [1; 2; ()] in
|
|
let b = 234 in
|
|
let _k = _ in
|
|
let _c = _ in
|
|
b + "a"
|
|
`);
|
|
|
|
await receivedDiganostics;
|
|
});
|
|
});
|