diff --git a/crates/oxc_language_server/src/backend.rs b/crates/oxc_language_server/src/backend.rs index 83ab80f564e1d..d9aa42cc7a9b8 100644 --- a/crates/oxc_language_server/src/backend.rs +++ b/crates/oxc_language_server/src/backend.rs @@ -506,7 +506,8 @@ impl LanguageServer for Backend { // saving the file means we can read again from the file system self.file_system.write().await.remove(uri); - if let Some(diagnostics) = worker.run_diagnostic_on_save(uri, None).await { + if let Some(diagnostics) = worker.run_diagnostic_on_save(uri, params.text.as_deref()).await + { self.client.publish_diagnostics(uri.clone(), diagnostics, None).await; } } diff --git a/crates/oxc_language_server/src/tests.rs b/crates/oxc_language_server/src/tests.rs index 3cf519a89d265..e432a6a3b7ea9 100644 --- a/crates/oxc_language_server/src/tests.rs +++ b/crates/oxc_language_server/src/tests.rs @@ -148,6 +148,33 @@ impl Tool for FakeTool { vec![] } + + fn run_diagnostic(&self, uri: &Uri, content: Option<&str>) -> Option> { + if uri.as_str().ends_with("diagnostics.config") { + return Some(vec![Diagnostic { + message: format!( + "Fake diagnostic for content: {}", + content.unwrap_or("") + ), + ..Default::default() + }]); + } + None + } + + fn run_diagnostic_on_change( + &self, + uri: &Uri, + content: Option<&str>, + ) -> Option> { + // For this fake tool, we use the same logic as run_diagnostic + self.run_diagnostic(uri, content) + } + + fn run_diagnostic_on_save(&self, uri: &Uri, content: Option<&str>) -> Option> { + // For this fake tool, we use the same logic as run_diagnostic + self.run_diagnostic(uri, content) + } } // A test server that can send requests and receive responses. @@ -454,8 +481,8 @@ mod test_suite { use tower_lsp_server::{ jsonrpc::{Id, Response}, lsp_types::{ - ApplyWorkspaceEditResponse, InitializeResult, ServerInfo, WorkspaceEdit, - WorkspaceFolder, + ApplyWorkspaceEditResponse, InitializeResult, PublishDiagnosticsParams, ServerInfo, + WorkspaceEdit, WorkspaceFolder, }, }; @@ -989,4 +1016,95 @@ mod test_suite { server.shutdown(4).await; } + + #[tokio::test] + async fn test_diagnostic_on_open() { + let mut server = TestServer::new_initialized( + |client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]), + initialize_request(false, false, false), + ) + .await; + + let file = format!("{WORKSPACE}/diagnostics.config"); + let content = "some text"; + server.send_request(did_open(&file, content)).await; + + let diagnostic_response = server.recv_notification().await; + assert_eq!(diagnostic_response.method(), "textDocument/publishDiagnostics"); + let params: PublishDiagnosticsParams = + serde_json::from_value(diagnostic_response.params().unwrap().clone()).unwrap(); + assert_eq!(params.uri, file.parse().unwrap()); + assert_eq!(params.diagnostics.len(), 1); + assert_eq!( + params.diagnostics[0].message, + format!("Fake diagnostic for content: {content}") + ); + + server.shutdown(4).await; + } + + #[tokio::test] + async fn test_diagnostic_on_change() { + let mut server = TestServer::new_initialized( + |client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]), + initialize_request(false, false, false), + ) + .await; + + let file = format!("{WORKSPACE}/diagnostics.config"); + let content = "new text"; + server.send_request(did_open(&file, "old text")).await; + let diagnostic_response = server.recv_notification().await; + assert_eq!(diagnostic_response.method(), "textDocument/publishDiagnostics"); + + server.send_request(did_change(&file, content)).await; + + let diagnostic_response = server.recv_notification().await; + assert_eq!(diagnostic_response.method(), "textDocument/publishDiagnostics"); + let params: PublishDiagnosticsParams = + serde_json::from_value(diagnostic_response.params().unwrap().clone()).unwrap(); + assert_eq!(params.uri, file.parse().unwrap()); + assert_eq!(params.diagnostics.len(), 1); + assert_eq!( + params.diagnostics[0].message, + format!("Fake diagnostic for content: {content}") + ); + + server.shutdown(4).await; + } + + #[tokio::test] + async fn test_diagnostic_on_save() { + let mut server = TestServer::new_initialized( + |client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]), + initialize_request(false, false, false), + ) + .await; + + let file = format!("{WORKSPACE}/diagnostics.config"); + let content = "new text"; + server.send_request(did_open(&file, "old text")).await; + let diagnostic_response = server.recv_notification().await; + assert_eq!(diagnostic_response.method(), "textDocument/publishDiagnostics"); + + server.send_request(did_change(&file, content)).await; + + let diagnostic_response = server.recv_notification().await; + assert_eq!(diagnostic_response.method(), "textDocument/publishDiagnostics"); + + server.send_request(did_save(&file, content)).await; + + let diagnostic_response = server.recv_notification().await; + assert_eq!(diagnostic_response.method(), "textDocument/publishDiagnostics"); + let params: PublishDiagnosticsParams = + serde_json::from_value(diagnostic_response.params().unwrap().clone()).unwrap(); + assert_eq!(params.uri, file.parse().unwrap()); + assert_eq!(params.diagnostics.len(), 1); + assert_eq!( + params.diagnostics[0].message, + format!("Fake diagnostic for content: {content}") + ); + + server.shutdown(4).await; + } } diff --git a/crates/oxc_language_server/src/worker.rs b/crates/oxc_language_server/src/worker.rs index a77ef7e4e8544..6c9346cc30264 100644 --- a/crates/oxc_language_server/src/worker.rs +++ b/crates/oxc_language_server/src/worker.rs @@ -563,4 +563,123 @@ mod tests { panic!("Expected CodeAction"); } } + + #[tokio::test] + async fn test_run_diagnostic() { + let worker = WorkspaceWorker::new(Uri::from_str("file:///root/").unwrap()); + let tools: Vec> = vec![Box::new(FakeToolBuilder)]; + worker.start_worker(serde_json::Value::Null, &tools).await; + + let diagnostics_no_content = worker + .run_diagnostic(&Uri::from_str("file:///root/diagnostics.config").unwrap(), None) + .await; + + assert!(diagnostics_no_content.is_some()); + assert_eq!(diagnostics_no_content.as_ref().unwrap().len(), 1); + assert_eq!( + diagnostics_no_content.unwrap()[0].message, + "Fake diagnostic for content: " + ); + + let diagnostics_with_content = worker + .run_diagnostic( + &Uri::from_str("file:///root/diagnostics.config").unwrap(), + Some("helloworld"), + ) + .await; + + assert!(diagnostics_with_content.is_some()); + assert_eq!(diagnostics_with_content.as_ref().unwrap().len(), 1); + assert_eq!( + diagnostics_with_content.unwrap()[0].message, + "Fake diagnostic for content: helloworld" + ); + + let no_diagnostics = + worker.run_diagnostic(&Uri::from_str("file:///root/unknown.file").unwrap(), None).await; + + assert!(no_diagnostics.is_none()); + } + + #[tokio::test] + async fn test_run_diagnostic_on_change() { + let worker = WorkspaceWorker::new(Uri::from_str("file:///root/").unwrap()); + let tools: Vec> = vec![Box::new(FakeToolBuilder)]; + worker.start_worker(serde_json::Value::Null, &tools).await; + + let diagnostics_no_content = worker + .run_diagnostic_on_change( + &Uri::from_str("file:///root/diagnostics.config").unwrap(), + None, + ) + .await; + + assert!(diagnostics_no_content.is_some()); + assert_eq!(diagnostics_no_content.as_ref().unwrap().len(), 1); + assert_eq!( + diagnostics_no_content.unwrap()[0].message, + "Fake diagnostic for content: " + ); + + let diagnostics_with_content = worker + .run_diagnostic_on_change( + &Uri::from_str("file:///root/diagnostics.config").unwrap(), + Some("helloworld"), + ) + .await; + + assert!(diagnostics_with_content.is_some()); + assert_eq!(diagnostics_with_content.as_ref().unwrap().len(), 1); + assert_eq!( + diagnostics_with_content.unwrap()[0].message, + "Fake diagnostic for content: helloworld" + ); + + let no_diagnostics = worker + .run_diagnostic_on_change(&Uri::from_str("file:///root/unknown.file").unwrap(), None) + .await; + + assert!(no_diagnostics.is_none()); + } + + #[tokio::test] + async fn test_run_diagnostic_on_save() { + let worker = WorkspaceWorker::new(Uri::from_str("file:///root/").unwrap()); + let tools: Vec> = vec![Box::new(FakeToolBuilder)]; + worker.start_worker(serde_json::Value::Null, &tools).await; + + let diagnostics_no_content = worker + .run_diagnostic_on_save( + &Uri::from_str("file:///root/diagnostics.config").unwrap(), + None, + ) + .await; + + assert!(diagnostics_no_content.is_some()); + assert_eq!(diagnostics_no_content.as_ref().unwrap().len(), 1); + assert_eq!( + diagnostics_no_content.unwrap()[0].message, + "Fake diagnostic for content: " + ); + + let diagnostics_with_content = worker + .run_diagnostic_on_save( + &Uri::from_str("file:///root/diagnostics.config").unwrap(), + Some("helloworld"), + ) + .await; + + assert!(diagnostics_with_content.is_some()); + assert_eq!(diagnostics_with_content.as_ref().unwrap().len(), 1); + assert_eq!( + diagnostics_with_content.unwrap()[0].message, + "Fake diagnostic for content: helloworld" + ); + + let no_diagnostics = worker + .run_diagnostic_on_save(&Uri::from_str("file:///root/unknown.file").unwrap(), None) + .await; + + assert!(no_diagnostics.is_none()); + } }