#[path = "common/lsp.rs"] #[allow(dead_code)] mod common; use common::{Server, backend, files, write_frame}; use serde_json::{Value, json}; use std::{fs, process::Command}; use tempfile::TempDir; #[test] fn queued_document_changes_use_the_latest_full_sync_version() { let Some(backend) = backend() else { return; }; let root = TempDir::new().unwrap(); files( root.path(), &[ ("tsconfig.json", r#"{"compilerOptions":{"strict":true}}"#), ("main.ts", "const value: string = 'first';\nvalue;"), ], ); let mut server = Server::start(root.path(), &backend, &[]); server.initialize(); server.open_version("main.ts", "const value: string = 'first';\nvalue;", 1); server.change("main.ts", "const value: number = 42;\nvalue;", 3); server.change("main.ts", "const value: boolean = true;\nvalue;", 2); let hover = server .call( "textDocument/hover", json!({ "textDocument":{"uri":server.uri("main.ts")}, "position":{"line":1,"character":2} }), ) .unwrap(); assert!( common::hover_text(&hover).contains("number"), "stale version replaced the latest full-document change: {hover}" ); server.close(); } #[test] fn queued_changes_publish_diagnostics_once_before_the_query() { let Some(backend) = backend() else { return; }; let root = TempDir::new().unwrap(); files( root.path(), &[ ("tsconfig.json", "{}"), ("main.ts", "const value: string = 'x';\nvalue;"), ], ); let mut server = Server::start(root.path(), &backend, &[]); server.initialize(); let uri = server.uri("main.ts"); let mut batch = Vec::new(); let mut append = |mut message: Value| { message["jsonrpc"] = json!("2.0"); write_frame(&mut batch, &message).unwrap(); }; append( json!({"method":"textDocument/didOpen","params":{"textDocument":{"uri":uri,"version":1,"languageId":"typescript","text":"const value: string = 'x';\nvalue;"}}}), ); for version in 2..=9 { append( json!({"method":"textDocument/didChange","params":{"textDocument":{"uri":uri,"version":version},"contentChanges":[{"text":format!("const value: number = {version};\nvalue;")}]}}), ); } let query_id = 99; append( json!({"id":query_id,"method":"textDocument/hover","params":{"textDocument":{"uri":uri},"position":{"line":1,"character":2}}}), ); server.write_raw(&batch); let query = server.request_wait(query_id); assert!( common::hover_text(&query["result"]).contains("number"), "{query}" ); let publications = server .notifications .iter() .filter(|message| { message["method"] == "textDocument/publishDiagnostics" && message["params"]["uri"] == uri }) .count(); assert_eq!( publications, 1, "queued notifications triggered multiple refreshes" ); server.close(); } #[cfg(target_os = "linux")] #[test] fn failed_backend_is_restarted_before_the_following_request() { let Some(backend) = backend() else { return; }; let root = TempDir::new().unwrap(); let source = "const answer: number = 42;\nanswer;"; files(root.path(), &[("tsconfig.json", "{}"), ("main.ts", source)]); let mut server = Server::start(root.path(), &backend, &[]); server.initialize(); let children = fs::read_to_string(format!( "/proc/{}/task/{}/children", server.pid(), server.pid() )) .unwrap(); let pid = children .split_whitespace() .next() .expect("backend child process"); assert!( Command::new("kill") .args(["-KILL", pid]) .status() .unwrap() .success() ); let params = json!({"textDocument":{"uri":server.uri("main.ts")},"position":{"line":1,"character":2}}); // The reader may notice the crash before the next query arrives. match server.call("textDocument/hover", params.clone()) { Ok(hover) => assert!(common::hover_text(&hover).contains("number"), "{hover}"), Err(error) => assert_eq!(error.code, -32603, "{error:?}"), } let hover = server.call("textDocument/hover", params).unwrap(); assert!(common::hover_text(&hover).contains("number"), "{hover}"); server.close(); }