Sanitize language server diagnostics coming from Rust

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

Cargo.lock                      | 24 ++++++++++++------------
crates/language/src/language.rs | 17 +++++++++++++++++
crates/project/src/project.rs   |  3 ++-
crates/zed/Cargo.toml           |  1 +
crates/zed/src/language.rs      | 27 +++++++++++++++++++++++++++
5 files changed, 59 insertions(+), 13 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -107,9 +107,9 @@ dependencies = [
 
 [[package]]
 name = "aho-corasick"
-version = "0.7.15"
+version = "0.7.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
 dependencies = [
  "memchr",
 ]
@@ -2814,9 +2814,9 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "2.3.4"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
 
 [[package]]
 name = "memmap2"
@@ -2922,9 +2922,9 @@ dependencies = [
 
 [[package]]
 name = "nom"
-version = "6.2.1"
+version = "6.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6"
+checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
 dependencies = [
  "bitvec",
  "funty",
@@ -3765,21 +3765,20 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.4.3"
+version = "1.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
 dependencies = [
  "aho-corasick",
  "memchr",
  "regex-syntax",
- "thread_local",
 ]
 
 [[package]]
 name = "regex-syntax"
-version = "0.6.22"
+version = "0.6.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
 
 [[package]]
 name = "remove_dir_all"
@@ -4451,7 +4450,7 @@ checksum = "6d86e3c77ff882a828346ba401a7ef4b8e440df804491c6064fe8295765de71c"
 dependencies = [
  "lazy_static",
  "maplit",
- "nom 6.2.1",
+ "nom 6.1.2",
  "regex",
  "unicode_categories",
 ]
@@ -5748,6 +5747,7 @@ dependencies = [
  "project",
  "project_panel",
  "rand 0.8.3",
+ "regex",
  "rpc",
  "rsa",
  "rust-embed",

crates/language/src/language.rs 🔗

@@ -39,6 +39,10 @@ pub trait ToPointUtf16 {
     fn to_point_utf16(self) -> PointUtf16;
 }
 
+pub trait DiagnosticProcessor: 'static + Send + Sync {
+    fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams);
+}
+
 #[derive(Default, Deserialize)]
 pub struct LanguageConfig {
     pub name: String,
@@ -69,6 +73,7 @@ pub struct BracketPair {
 pub struct Language {
     pub(crate) config: LanguageConfig,
     pub(crate) grammar: Option<Arc<Grammar>>,
+    pub(crate) diagnostic_processor: Option<Box<dyn DiagnosticProcessor>>,
 }
 
 pub struct Grammar {
@@ -135,6 +140,7 @@ impl Language {
                     highlight_map: Default::default(),
                 })
             }),
+            diagnostic_processor: None,
         }
     }
 
@@ -178,6 +184,11 @@ impl Language {
         Ok(self)
     }
 
+    pub fn with_diagnostics_processor(mut self, processor: impl DiagnosticProcessor) -> Self {
+        self.diagnostic_processor = Some(Box::new(processor));
+        self
+    }
+
     pub fn name(&self) -> &str {
         self.config.name.as_str()
     }
@@ -225,6 +236,12 @@ impl Language {
             .and_then(|config| config.disk_based_diagnostics_progress_token.as_ref())
     }
 
+    pub fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
+        if let Some(processor) = self.diagnostic_processor.as_ref() {
+            processor.process_diagnostics(diagnostics);
+        }
+    }
+
     pub fn brackets(&self) -> &[BracketPair] {
         &self.config.brackets
     }

crates/project/src/project.rs 🔗

@@ -822,7 +822,8 @@ impl Project {
                             send.await.log_err();
                         }
                     }
-                    LspEvent::DiagnosticsUpdate(params) => {
+                    LspEvent::DiagnosticsUpdate(mut params) => {
+                        language.process_diagnostics(&mut params);
                         this.update(&mut cx, |this, cx| {
                             this.update_diagnostics(params, &disk_based_sources, cx)
                                 .log_err();

crates/zed/Cargo.toml 🔗

@@ -73,6 +73,7 @@ num_cpus = "1.13.0"
 parking_lot = "0.11.1"
 postage = { version = "0.4.1", features = ["futures-traits"] }
 rand = "0.8.3"
+regex = "1.5"
 rsa = "0.4"
 rust-embed = { version = "6.2", features = ["include-exclude"] }
 serde = { version = "1", features = ["derive"] }

crates/zed/src/language.rs 🔗

@@ -1,4 +1,6 @@
 pub use language::*;
+use lazy_static::lazy_static;
+use regex::Regex;
 use rust_embed::RustEmbed;
 use std::borrow::Cow;
 use std::{str, sync::Arc};
@@ -7,6 +9,30 @@ use std::{str, sync::Arc};
 #[folder = "languages"]
 struct LanguageDir;
 
+struct RustDiagnosticProcessor;
+
+impl DiagnosticProcessor for RustDiagnosticProcessor {
+    fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
+        lazy_static! {
+            static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`").unwrap();
+        }
+
+        for diagnostic in &mut params.diagnostics {
+            for message in diagnostic
+                .related_information
+                .iter_mut()
+                .flatten()
+                .map(|info| &mut info.message)
+                .chain([&mut diagnostic.message])
+            {
+                if let Cow::Owned(sanitized) = REGEX.replace_all(message, "`$1`") {
+                    *message = sanitized;
+                }
+            }
+        }
+    }
+}
+
 pub fn build_language_registry() -> LanguageRegistry {
     let mut languages = LanguageRegistry::default();
     languages.add(Arc::new(rust()));
@@ -26,6 +52,7 @@ fn rust() -> Language {
         .unwrap()
         .with_outline_query(load_query("rust/outline.scm").as_ref())
         .unwrap()
+        .with_diagnostics_processor(RustDiagnosticProcessor)
 }
 
 fn markdown() -> Language {