From 410d847b0b783c0b7c582f3fe270ead7e0c43776 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Wed, 23 Feb 2022 01:43:53 +0200 Subject: [PATCH] fix(handlers): source code propagation for JSON handler A part of fix of #99, follow up of #117 --- src/handlers/json.rs | 31 ++++++++++----- tests/test_json.rs | 95 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 11 deletions(-) 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)); } } } 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, + } + + #[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(()) + } }