Repository: zkat/miette
Pull request #122 (2022-02-22 23:49:09)

fix(handlers): source code propagation for JSON handler
A part of fix of #99, follow up of #117

Forgot this one. Also, I think "debug" handler can stay as is, right?


Patch:
diff --git a/src/handlers/json.rs b/src/handlers/json.rs
index eb47abc..af52e41 100644
--- a/src/handlers/json.rs
+++ b/src/handlers/json.rs
@@ -1,6 +1,6 @@
 use std::fmt::{self, Write};
 
-use crate::{protocol::Diagnostic, ReportHandler, Severity};
+use crate::{protocol::Diagnostic, ReportHandler, Severity, SourceCode};
 
 /**
 [ReportHandler] that renders json output.
@@ -62,6 +62,15 @@ impl JSONReportHandler {
         &self,
         f: &mut impl fmt::Write,
         diagnostic: &(dyn Diagnostic),
+    ) -> fmt::Result {
+        self._render_report(f, diagnostic, None)
+    }
+
+    fn _render_report(
+        &self,
+        f: &mut impl fmt::Write,
+        diagnostic: &(dyn Diagnostic),
+        parent_src: Option<&dyn SourceCode>,
     ) -> fmt::Result {
         write!(f, r#"{{"message": "{}","#, escape(&diagnostic.to_string()))?;
         if let Some(code) = diagnostic.code() {
@@ -79,8 +88,9 @@ impl JSONReportHandler {
         if let Some(help) = diagnostic.help() {
             write!(f, r#""help": "{}","#, escape(&help.to_string()))?;
         }
-        if diagnostic.source_code().is_some() {
-            self.render_snippets(f, diagnostic)?;
+        let src = diagnostic.source_code().or(parent_src);
+        if let Some(src) = src {
+            self.render_snippets(f, diagnostic, src)?;
         }
         if let Some(labels) = diagnostic.labels() {
             write!(f, r#""labels": ["#)?;
@@ -114,7 +124,7 @@ impl JSONReportHandler {
                 } else {
                     add_comma = true;
                 }
-                self.render_report(f, related)?;
+                self._render_report(f, related, src)?;
             }
             write!(f, "]")?;
         } else {
@@ -127,14 +137,13 @@ impl JSONReportHandler {
         &self,
         f: &mut impl fmt::Write,
         diagnostic: &(dyn Diagnostic),
+        source: &dyn SourceCode,
     ) -> fmt::Result {
-        if let Some(source) = diagnostic.source_code() {
-            if let Some(mut labels) = diagnostic.labels() {
-                if let Some(label) = labels.next() {
-                    if let Ok(span_content) = source.read_span(label.inner(), 0, 0) {
-                        let filename = span_content.name().unwrap_or_default();
-                        return write!(f, r#""filename": "{}","#, escape(filename));
-                    }
+        if let Some(mut labels) = diagnostic.labels() {
+            if let Some(label) = labels.next() {
+                if let Ok(span_content) = source.read_span(label.inner(), 0, 0) {
+                    let filename = span_content.name().unwrap_or_default();
+                    return write!(f, r#""filename": "{}","#, escape(filename));
                 }
             }
         }


Test patch:
diff --git a/tests/test_json.rs b/tests/test_json.rs
index f7ad3a6..206cb02 100644
--- a/tests/test_json.rs
+++ b/tests/test_json.rs
@@ -809,4 +809,99 @@ mod json_report_handler {
         assert_eq!(expected, out);
         Ok(())
     }
+
+    #[test]
+    fn related_source_code_propagation() -> Result<(), MietteError> {
+        #[derive(Debug, Diagnostic, Error)]
+        #[error("oops!")]
+        #[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
+        struct MyBad {
+            #[source_code]
+            src: NamedSource,
+            #[label("this bit here")]
+            highlight: SourceSpan,
+            #[related]
+            related: Vec<InnerError>,
+        }
+
+        #[derive(Debug, Diagnostic, Error)]
+        #[error("oops!")]
+        #[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
+        struct InnerError {
+            #[label("this bit here")]
+            highlight: SourceSpan,
+        }
+
+        let src = "source\n  text\n    here".to_string();
+        let err = MyBad {
+            src: NamedSource::new("bad_file.rs", src.clone()),
+            highlight: (9, 4).into(),
+            related: vec![
+                InnerError {
+                    highlight: (0, 6).into(),
+                },
+                InnerError {
+                    highlight: (0, 6).into(),
+                },
+            ],
+        };
+        let out = fmt_report(err.into());
+        println!("Error: {}", out);
+        let expected: String = r#"
+        {
+            "message": "oops!",
+            "code": "oops::my::bad",
+            "severity": "error",
+            "help": "try doing it better next time?",
+            "filename": "bad_file.rs",
+            "labels": [
+                {
+                    "label": "this bit here",
+                    "span": {
+                        "offset": 9,
+                        "length": 4
+                    }
+                }
+            ],
+            "related": [{
+                "message": "oops!",
+                "code": "oops::my::bad",
+                "severity": "error",
+                "help": "try doing it better next time?",
+                "filename": "bad_file.rs",
+                "labels": [
+                    {
+                        "label": "this bit here",
+                        "span": {
+                            "offset": 0,
+                            "length": 6
+                        }
+                    }
+                ],
+                "related": []
+            },{
+                "message": "oops!",
+                "code": "oops::my::bad",
+                "severity": "error",
+                "help": "try doing it better next time?",
+                "filename": "bad_file.rs",
+                "labels": [
+                    {
+                        "label": "this bit here",
+                        "span": {
+                            "offset": 0,
+                            "length": 6
+                        }
+                    }
+                ],
+                "related": []
+            }]
+        }"#
+        .lines()
+        .into_iter()
+        .map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
+        .collect();
+        assert_eq!(expected, out);
+        Ok(())
+    }
 }