char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somexperiment: use native typescript lsp supportc7a9569

main
4.9 KiB130 linesraw
1#[path = "common/lsp.rs"]
2#[allow(dead_code)]
3mod support;
4
5use serde_json::{Value, json};
6use support::{Server, backend, files, position_at};
7
8fn signature(server: &mut Server, path: &str, text: &str, offset: usize) -> Value {
9    server
10        .call(
11            "textDocument/signatureHelp",
12            json!({
13                "textDocument": {"uri": server.uri(path)},
14                "position": position_at(text, offset),
15                "context": {"triggerKind": 1, "isRetrigger": false, "triggerCharacter": null, "activeSignatureHelp": null}
16            }),
17        )
18        .unwrap()
19}
20
21#[test]
22fn call_signature_help() {
23    let Some(backend) = backend() else {
24        eprintln!("skipping: AVRIL_TEST_TYPESCRIPT is not set");
25        return;
26    };
27    let dir = tempfile::tempdir().unwrap();
28    let root = dir.path();
29    let main = [
30        "export function add(a: number, b?: string, ...rest: boolean[]): boolean { return true; }",
31        "export function pick(x: number): number;",
32        "export function pick(x: string): string;",
33        "export function pick(x: unknown): unknown { return x; }",
34        "export class Box { constructor(public w: number, public h?: number) {} }",
35        "const r1 = add(1, \"hi\");",
36        "const r2 = pick(\"a\");",
37        "const made = new Box(3);",
38        "const nested = add(pick(1), true);",
39        "const mixed = add(\"x,y\", [true], \"s\");",
40        "add(2,",
41    ]
42    .join("\n");
43    files(root, &[("main.ts", &main), ("avril.conf", "{}")]);
44    let mut server = Server::start(root, &backend, &["--offline"]);
45    let initialize = server
46        .call("initialize", json!({"capabilities": {}}))
47        .unwrap();
48    assert_eq!(
49        initialize["capabilities"]["signatureHelpProvider"]["triggerCharacters"],
50        json!(["(", ","])
51    );
52    assert_eq!(
53        initialize["capabilities"]["signatureHelpProvider"]["retriggerCharacters"],
54        json!([")"])
55    );
56    server.notify("initialized", json!({}));
57    server.open("main.ts", &main);
58
59    // First and second arguments select the active parameter.
60    let call = main.find("add(1, \"hi\")").unwrap();
61    let help = signature(&mut server, "main.ts", &main, call + "add(".len());
62    assert_eq!(help["activeSignature"], json!(0));
63    assert_eq!(help["activeParameter"], json!(0));
64    assert_eq!(
65        help["signatures"][0]["label"],
66        json!("add(a: number, b?: string, ...rest: boolean[]): boolean")
67    );
68    let help = signature(&mut server, "main.ts", &main, call + "add(1,".len());
69    assert_eq!(help["activeParameter"], json!(1));
70
71    // Overloads list every signature with the resolved one active.
72    let call = main.find("pick(\"a\")").unwrap();
73    let help = signature(&mut server, "main.ts", &main, call + "pick(".len());
74    assert_eq!(help["signatures"].as_array().unwrap().len(), 2);
75    assert_eq!(help["activeSignature"], json!(1));
76    assert_eq!(
77        help["signatures"][1]["label"],
78        json!("pick(x: string): string")
79    );
80    assert_eq!(help["activeParameter"], json!(0));
81
82    // Construct expressions use the constructor signature.
83    let call = main.find("new Box(3)").unwrap();
84    let help = signature(&mut server, "main.ts", &main, call + "new Box(".len());
85    assert_eq!(help["activeParameter"], json!(0));
86    let label = help["signatures"][0]["label"].as_str().unwrap();
87    assert!(label.starts_with("Box(w: number, h?: number"), "{label}");
88    assert!(label.ends_with(": Box"), "{label}");
89
90    // Nested calls resolve to the innermost argument list.
91    let outer = main.find("add(pick(1), true)").unwrap();
92    let help = signature(&mut server, "main.ts", &main, outer + "add(pick(1),".len());
93    assert!(
94        help["signatures"][0]["label"]
95            .as_str()
96            .unwrap()
97            .starts_with("add(")
98    );
99    assert_eq!(help["activeParameter"], json!(1));
100    let inner = main.find("pick(1)").unwrap();
101    let help = signature(&mut server, "main.ts", &main, inner + "pick(1".len());
102    assert_eq!(help["signatures"].as_array().unwrap().len(), 2);
103    assert_eq!(help["activeSignature"], json!(0));
104
105    // Commas inside strings and brackets do not advance the parameter.
106    let call = main.find("add(\"x,y\", [true], \"s\")").unwrap();
107    let help = signature(
108        &mut server,
109        "main.ts",
110        &main,
111        call + "add(\"x,y\", [true],".len(),
112    );
113    assert_eq!(help["activeParameter"], json!(2));
114
115    // An unclosed call still reports the argument index.
116    let call = main.rfind("add(2,").unwrap();
117    let help = signature(&mut server, "main.ts", &main, call + "add(2,".len());
118    assert_eq!(help["activeParameter"], json!(1));
119
120    // The callee name and positions past the call have no signature.
121    assert_eq!(
122        signature(&mut server, "main.ts", &main, call + "add".len()),
123        Value::Null
124    );
125    let closed = main.find("add(1, \"hi\")").unwrap() + "add(1, \"hi\")".len();
126    assert_eq!(
127        signature(&mut server, "main.ts", &main, closed),
128        Value::Null
129    );
130}