Add a unit test for disk-based diagnostics

Antonio Scandurra created

Change summary

crates/language/src/tests.rs | 104 ++++++++++++++++++++++++++++++-------
1 file changed, 84 insertions(+), 20 deletions(-)

Detailed changes

crates/language/src/tests.rs 🔗

@@ -1,6 +1,7 @@
 use super::*;
+use crate::language::LanguageServerConfig;
 use gpui::{ModelHandle, MutableAppContext};
-use std::rc::Rc;
+use std::{iter::FromIterator, rc::Rc};
 use unindent::Unindent as _;
 
 #[gpui::test]
@@ -426,10 +427,14 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
         .receive_notification::<lsp::notification::DidOpenTextDocument>()
         .await;
 
-    buffer.update(&mut cx, |buffer, cx| {
-        // Edit the buffer, moving the content down
-        buffer.edit([0..0], "\n\n", cx);
+    // Edit the buffer, moving the content down
+    buffer.update(&mut cx, |buffer, cx| buffer.edit([0..0], "\n\n", cx));
+    let change_notification_1 = fake
+        .receive_notification::<lsp::notification::DidChangeTextDocument>()
+        .await;
+    assert!(change_notification_1.text_document.version > open_notification.text_document.version);
 
+    buffer.update(&mut cx, |buffer, cx| {
         // Receive diagnostics for an earlier version of the buffer.
         buffer
             .update_diagnostics(
@@ -551,25 +556,80 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
                 ("\n".to_string(), None),
             ]
         );
+    });
 
-        fn chunks_with_diagnostics<T: ToOffset>(
-            buffer: &Buffer,
-            range: Range<T>,
-        ) -> Vec<(String, Option<DiagnosticSeverity>)> {
-            let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
-            for chunk in buffer.snapshot().highlighted_text_for_range(range) {
-                if chunks
-                    .last()
-                    .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic)
-                {
-                    chunks.last_mut().unwrap().0.push_str(chunk.text);
-                } else {
-                    chunks.push((chunk.text.to_string(), chunk.diagnostic));
-                }
+    // Keep editing the buffer and ensure disk-based diagnostics get translated according to the
+    // changes since the last save.
+    buffer.update(&mut cx, |buffer, cx| {
+        buffer.edit(Some(Point::new(2, 0)..Point::new(2, 0)), "    ", cx);
+        buffer.edit(Some(Point::new(2, 8)..Point::new(2, 10)), "(x: usize)", cx);
+    });
+    let change_notification_2 = fake
+        .receive_notification::<lsp::notification::DidChangeTextDocument>()
+        .await;
+    assert!(
+        change_notification_2.text_document.version > change_notification_1.text_document.version
+    );
+
+    buffer.update(&mut cx, |buffer, cx| {
+        buffer
+            .update_diagnostics(
+                Some(change_notification_2.text_document.version),
+                vec![
+                    lsp::Diagnostic {
+                        range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
+                        severity: Some(lsp::DiagnosticSeverity::ERROR),
+                        message: "undefined variable 'BB'".to_string(),
+                        source: Some("rustc".to_string()),
+                        ..Default::default()
+                    },
+                    lsp::Diagnostic {
+                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
+                        severity: Some(lsp::DiagnosticSeverity::ERROR),
+                        message: "undefined variable 'A'".to_string(),
+                        source: Some("rustc".to_string()),
+                        ..Default::default()
+                    },
+                ],
+                cx,
+            )
+            .unwrap();
+        assert_eq!(
+            buffer
+                .diagnostics_in_range(0..buffer.len())
+                .collect::<Vec<_>>(),
+            &[
+                Diagnostic {
+                    range: Point::new(2, 21)..Point::new(2, 22),
+                    severity: DiagnosticSeverity::ERROR,
+                    message: "undefined variable 'A'".to_string()
+                },
+                Diagnostic {
+                    range: Point::new(3, 9)..Point::new(3, 11),
+                    severity: DiagnosticSeverity::ERROR,
+                    message: "undefined variable 'BB'".to_string()
+                },
+            ]
+        );
+    });
+
+    fn chunks_with_diagnostics<T: ToOffset>(
+        buffer: &Buffer,
+        range: Range<T>,
+    ) -> Vec<(String, Option<DiagnosticSeverity>)> {
+        let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
+        for chunk in buffer.snapshot().highlighted_text_for_range(range) {
+            if chunks
+                .last()
+                .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic)
+            {
+                chunks.last_mut().unwrap().0.push_str(chunk.text);
+            } else {
+                chunks.push((chunk.text.to_string(), chunk.diagnostic));
             }
-            chunks
         }
-    });
+        chunks
+    }
 }
 
 #[test]
@@ -605,6 +665,10 @@ fn rust_lang() -> Option<Arc<Language>> {
             LanguageConfig {
                 name: "Rust".to_string(),
                 path_suffixes: vec!["rs".to_string()],
+                language_server: Some(LanguageServerConfig {
+                    binary: "rust-analyzer".to_string(),
+                    disk_based_diagnostic_sources: HashSet::from_iter(vec!["rustc".to_string()]),
+                }),
                 ..Default::default()
             },
             tree_sitter_rust::language(),