Parse `children` from `cargo check` output to provide hints

Antonio Scandurra and Nathan Sobo created

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

Change summary

crates/zed/src/language.rs | 82 +++++++++++++++++++++++++++++++++++----
1 file changed, 73 insertions(+), 9 deletions(-)

Detailed changes

crates/zed/src/language.rs 🔗

@@ -31,10 +31,11 @@ mod rust {
 
     #[derive(Debug, Deserialize)]
     struct CompilerMessage {
-        code: ErrorCode,
+        code: Option<ErrorCode>,
         spans: Vec<Span>,
         message: String,
         level: ErrorLevel,
+        children: Vec<CompilerMessage>,
     }
 
     #[derive(Debug, Deserialize)]
@@ -43,6 +44,8 @@ mod rust {
         Warning,
         #[serde(rename = "error")]
         Error,
+        #[serde(rename = "help")]
+        Help,
         #[serde(rename = "note")]
         Note,
     }
@@ -52,24 +55,30 @@ mod rust {
         code: String,
     }
 
-    #[derive(Debug, Deserialize)]
+    #[derive(Clone, Debug, Deserialize)]
     struct Span {
         is_primary: bool,
         file_name: PathBuf,
         byte_start: usize,
         byte_end: usize,
+        expansion: Option<Box<Expansion>>,
+    }
+
+    #[derive(Clone, Debug, Deserialize)]
+    struct Expansion {
+        span: Span,
     }
 
     #[async_trait]
     impl language::DiagnosticProvider for DiagnosticProvider {
         async fn diagnose(
             &self,
-            path: Arc<Path>,
+            root_path: Arc<Path>,
         ) -> Result<HashMap<Arc<Path>, Vec<DiagnosticEntry<usize>>>> {
             let output = Command::new("cargo")
                 .arg("check")
                 .args(["--message-format", "json"])
-                .current_dir(&path)
+                .current_dir(&root_path)
                 .output()
                 .await?;
 
@@ -80,13 +89,21 @@ mod rust {
                 Deserializer::from_slice(&output.stdout).into_iter::<&serde_json::value::RawValue>()
             {
                 if let Ok(check) = serde_json::from_str::<Check>(value?.get()) {
-                    let severity = match check.message.level {
+                    let check_severity = match check.message.level {
                         ErrorLevel::Warning => DiagnosticSeverity::WARNING,
                         ErrorLevel::Error => DiagnosticSeverity::ERROR,
+                        ErrorLevel::Help => DiagnosticSeverity::HINT,
                         ErrorLevel::Note => DiagnosticSeverity::INFORMATION,
                     };
-                    for span in check.message.spans {
-                        let span_path: Arc<Path> = span.file_name.into();
+
+                    let mut primary_span = None;
+                    for mut span in check.message.spans {
+                        if let Some(mut expansion) = span.expansion {
+                            expansion.span.is_primary = span.is_primary;
+                            span = expansion.span;
+                        }
+
+                        let span_path: Arc<Path> = span.file_name.as_path().into();
                         new_reported_paths.insert(span_path.clone());
                         diagnostics_by_path
                             .entry(span_path)
@@ -94,8 +111,8 @@ mod rust {
                             .push(DiagnosticEntry {
                                 range: span.byte_start..span.byte_end,
                                 diagnostic: Diagnostic {
-                                    code: Some(check.message.code.code.clone()),
-                                    severity,
+                                    code: check.message.code.as_ref().map(|c| c.code.clone()),
+                                    severity: check_severity,
                                     message: check.message.message.clone(),
                                     group_id,
                                     is_valid: true,
@@ -103,7 +120,54 @@ mod rust {
                                     is_disk_based: true,
                                 },
                             });
+
+                        if span.is_primary {
+                            primary_span = Some(span);
+                        }
+                    }
+
+                    for mut child in check.message.children {
+                        if child.spans.is_empty() {
+                            if let Some(primary_span) = primary_span.clone() {
+                                child.spans.push(primary_span);
+                            }
+                        } else {
+                            // TODO
+                            continue;
+                        }
+
+                        let child_severity = match child.level {
+                            ErrorLevel::Warning => DiagnosticSeverity::WARNING,
+                            ErrorLevel::Error => DiagnosticSeverity::ERROR,
+                            ErrorLevel::Help => DiagnosticSeverity::HINT,
+                            ErrorLevel::Note => DiagnosticSeverity::INFORMATION,
+                        };
+
+                        for mut span in child.spans {
+                            if let Some(expansion) = span.expansion {
+                                span = expansion.span;
+                            }
+
+                            let span_path: Arc<Path> = span.file_name.as_path().into();
+                            new_reported_paths.insert(span_path.clone());
+                            diagnostics_by_path
+                                .entry(span_path)
+                                .or_insert(Vec::new())
+                                .push(DiagnosticEntry {
+                                    range: span.byte_start..span.byte_end,
+                                    diagnostic: Diagnostic {
+                                        code: child.code.as_ref().map(|c| c.code.clone()),
+                                        severity: child_severity,
+                                        message: child.message.clone(),
+                                        group_id,
+                                        is_valid: true,
+                                        is_primary: false,
+                                        is_disk_based: true,
+                                    },
+                                });
+                        }
                     }
+
                     group_id += 1;
                 }
             }