#[path = "common/lsp.rs"] #[allow(dead_code)] mod support; use serde_json::{Value, json}; use support::{Server, backend, files, position_at}; fn signature(server: &mut Server, path: &str, text: &str, offset: usize) -> Value { server .call( "textDocument/signatureHelp", json!({ "textDocument": {"uri": server.uri(path)}, "position": position_at(text, offset), "context": {"triggerKind": 1, "isRetrigger": false, "triggerCharacter": null, "activeSignatureHelp": null} }), ) .unwrap() } #[test] fn call_signature_help() { let Some(backend) = backend() else { eprintln!("skipping: AVRIL_TEST_TYPESCRIPT is not set"); return; }; let dir = tempfile::tempdir().unwrap(); let root = dir.path(); let main = [ "export function add(a: number, b?: string, ...rest: boolean[]): boolean { return true; }", "export function pick(x: number): number;", "export function pick(x: string): string;", "export function pick(x: unknown): unknown { return x; }", "export class Box { constructor(public w: number, public h?: number) {} }", "const r1 = add(1, \"hi\");", "const r2 = pick(\"a\");", "const made = new Box(3);", "const nested = add(pick(1), true);", "const mixed = add(\"x,y\", [true], \"s\");", "add(2,", ] .join("\n"); files(root, &[("main.ts", &main), ("avril.conf", "{}")]); let mut server = Server::start(root, &backend, &["--offline"]); let initialize = server .call("initialize", json!({"capabilities": {}})) .unwrap(); assert_eq!( initialize["capabilities"]["signatureHelpProvider"]["triggerCharacters"], json!(["(", ","]) ); assert_eq!( initialize["capabilities"]["signatureHelpProvider"]["retriggerCharacters"], json!([")"]) ); server.notify("initialized", json!({})); server.open("main.ts", &main); // First and second arguments select the active parameter. let call = main.find("add(1, \"hi\")").unwrap(); let help = signature(&mut server, "main.ts", &main, call + "add(".len()); assert_eq!(help["activeSignature"], json!(0)); assert_eq!(help["activeParameter"], json!(0)); assert_eq!( help["signatures"][0]["label"], json!("add(a: number, b?: string, ...rest: boolean[]): boolean") ); let help = signature(&mut server, "main.ts", &main, call + "add(1,".len()); assert_eq!(help["activeParameter"], json!(1)); // Overloads list every signature with the resolved one active. let call = main.find("pick(\"a\")").unwrap(); let help = signature(&mut server, "main.ts", &main, call + "pick(".len()); assert_eq!(help["signatures"].as_array().unwrap().len(), 2); assert_eq!(help["activeSignature"], json!(1)); assert_eq!( help["signatures"][1]["label"], json!("pick(x: string): string") ); assert_eq!(help["activeParameter"], json!(0)); // Construct expressions use the constructor signature. let call = main.find("new Box(3)").unwrap(); let help = signature(&mut server, "main.ts", &main, call + "new Box(".len()); assert_eq!(help["activeParameter"], json!(0)); let label = help["signatures"][0]["label"].as_str().unwrap(); assert!(label.starts_with("Box(w: number, h?: number"), "{label}"); assert!(label.ends_with(": Box"), "{label}"); // Nested calls resolve to the innermost argument list. let outer = main.find("add(pick(1), true)").unwrap(); let help = signature(&mut server, "main.ts", &main, outer + "add(pick(1),".len()); assert!( help["signatures"][0]["label"] .as_str() .unwrap() .starts_with("add(") ); assert_eq!(help["activeParameter"], json!(1)); let inner = main.find("pick(1)").unwrap(); let help = signature(&mut server, "main.ts", &main, inner + "pick(1".len()); assert_eq!(help["signatures"].as_array().unwrap().len(), 2); assert_eq!(help["activeSignature"], json!(0)); // Commas inside strings and brackets do not advance the parameter. let call = main.find("add(\"x,y\", [true], \"s\")").unwrap(); let help = signature( &mut server, "main.ts", &main, call + "add(\"x,y\", [true],".len(), ); assert_eq!(help["activeParameter"], json!(2)); // An unclosed call still reports the argument index. let call = main.rfind("add(2,").unwrap(); let help = signature(&mut server, "main.ts", &main, call + "add(2,".len()); assert_eq!(help["activeParameter"], json!(1)); // The callee name and positions past the call have no signature. assert_eq!( signature(&mut server, "main.ts", &main, call + "add".len()), Value::Null ); let closed = main.find("add(1, \"hi\")").unwrap() + "add(1, \"hi\")".len(); assert_eq!( signature(&mut server, "main.ts", &main, closed), Value::Null ); }