mirror of https://github.com/zkat/miette.git
feat(handlers): Add JSON handler (#90)
This commit is contained in:
parent
fe77d8c754
commit
53b246829a
|
|
@ -0,0 +1,129 @@
|
|||
use std::fmt;
|
||||
|
||||
use crate::{protocol::Diagnostic, ReportHandler, Severity};
|
||||
|
||||
/**
|
||||
[ReportHandler] that renders json output.
|
||||
It's a machine-readable output.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JSONReportHandler;
|
||||
|
||||
impl JSONReportHandler {
|
||||
/// Create a new [JSONReportHandler]. There are no customization
|
||||
/// options.
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for JSONReportHandler {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn escape(input: &str) -> String {
|
||||
input
|
||||
.chars()
|
||||
.map(|c| match c {
|
||||
'"' => "\\\\\"".to_string(),
|
||||
'\'' => "\\\\'".to_string(),
|
||||
'\r' => "\\\\r".to_string(),
|
||||
'\n' => "\\\\n".to_string(),
|
||||
'\t' => "\\\\t".to_string(),
|
||||
'\u{08}' => "\\\\b".to_string(),
|
||||
'\u{0c}' => "\\\\f".to_string(),
|
||||
c => format!("{}", c),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl JSONReportHandler {
|
||||
/// Render a [Diagnostic]. This function is mostly internal and meant to
|
||||
/// be called by the toplevel [ReportHandler] handler, but is
|
||||
/// made public to make it easier (possible) to test in isolation from
|
||||
/// global state.
|
||||
pub fn render_report(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
) -> fmt::Result {
|
||||
write!(f, r#"{{"message": "{}","#, escape(&diagnostic.to_string()))?;
|
||||
if let Some(code) = diagnostic.code() {
|
||||
write!(f, r#""code": "{}","#, escape(&code.to_string()))?;
|
||||
}
|
||||
let severity = match diagnostic.severity() {
|
||||
Some(Severity::Error) | None => "error",
|
||||
Some(Severity::Warning) => "warning",
|
||||
Some(Severity::Advice) => "advice",
|
||||
};
|
||||
write!(f, r#""severity": "{:}","#, severity)?;
|
||||
if let Some(url) = diagnostic.url() {
|
||||
write!(f, r#""url": "{}","#, &url.to_string())?;
|
||||
}
|
||||
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)?;
|
||||
}
|
||||
if let Some(labels) = diagnostic.labels() {
|
||||
write!(f, r#""labels": ["#)?;
|
||||
let mut add_comma = false;
|
||||
for label in labels {
|
||||
if add_comma {
|
||||
write!(f, ",")?;
|
||||
} else {
|
||||
add_comma = true;
|
||||
}
|
||||
write!(f, "{{")?;
|
||||
if let Some(label_name) = label.label() {
|
||||
write!(f, r#""label": "{}","#, escape(label_name))?;
|
||||
}
|
||||
write!(f, r#""span": {{"#)?;
|
||||
write!(f, r#""offset": {},"#, label.offset())?;
|
||||
write!(f, r#""length": {}"#, label.len())?;
|
||||
|
||||
write!(f, "}}}}")?;
|
||||
}
|
||||
write!(f, "],")?;
|
||||
} else {
|
||||
write!(f, r#""labels": [],"#)?;
|
||||
}
|
||||
if let Some(relateds) = diagnostic.related() {
|
||||
write!(f, r#""related": ["#)?;
|
||||
for related in relateds {
|
||||
self.render_report(f, related)?;
|
||||
}
|
||||
write!(f, "]")?;
|
||||
} else {
|
||||
write!(f, r#""related": []"#)?;
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
|
||||
fn render_snippets(
|
||||
&self,
|
||||
f: &mut impl fmt::Write,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
) -> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
write!(f, r#""filename": "","#)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReportHandler for JSONReportHandler {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.render_report(f, diagnostic)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ pub use debug::*;
|
|||
#[cfg(feature = "fancy")]
|
||||
pub use graphical::*;
|
||||
#[allow(unreachable_pub)]
|
||||
pub use json::*;
|
||||
#[allow(unreachable_pub)]
|
||||
pub use narratable::*;
|
||||
#[allow(unreachable_pub)]
|
||||
#[cfg(feature = "fancy")]
|
||||
|
|
@ -16,6 +18,7 @@ pub use theme::*;
|
|||
mod debug;
|
||||
#[cfg(feature = "fancy")]
|
||||
mod graphical;
|
||||
mod json;
|
||||
mod narratable;
|
||||
#[cfg(feature = "fancy")]
|
||||
mod theme;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,789 @@
|
|||
mod json_report_handler {
|
||||
|
||||
use miette::{Diagnostic, MietteError, NamedSource, Report, SourceSpan};
|
||||
|
||||
use miette::JSONReportHandler;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
fn fmt_report(diag: Report) -> String {
|
||||
let mut out = String::new();
|
||||
JSONReportHandler::new()
|
||||
.render_report(&mut out, diag.as_ref())
|
||||
.unwrap();
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_with_wide_char() -> 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,
|
||||
}
|
||||
|
||||
let src = "source\n 👼🏼text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 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": 6
|
||||
}
|
||||
}
|
||||
],
|
||||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_highlight() -> 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,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 4).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": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_highlight_offset_zero() -> 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,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (0, 0).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": 0,
|
||||
"length": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_highlight_with_empty_span() -> 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,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 0).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": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_highlight_no_label() -> 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]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 4).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": [
|
||||
{
|
||||
"span": {
|
||||
"offset": 9,
|
||||
"length": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_highlight_at_line_start() -> 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,
|
||||
}
|
||||
|
||||
let src = "source\ntext\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (7, 4).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": 7,
|
||||
"length": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_same_line_highlights() -> 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 = "x"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "y"]
|
||||
highlight2: SourceSpan,
|
||||
#[label = "z"]
|
||||
highlight3: SourceSpan,
|
||||
}
|
||||
|
||||
let src = "source\n text text text text text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight1: (9, 4).into(),
|
||||
highlight2: (14, 4).into(),
|
||||
highlight3: (24, 4).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": "x",
|
||||
"span": {
|
||||
"offset": 9,
|
||||
"length": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "y",
|
||||
"span": {
|
||||
"offset": 14,
|
||||
"length": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "z",
|
||||
"span": {
|
||||
"offset": 24,
|
||||
"length": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_highlight_adjacent() -> 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 = "these two lines"]
|
||||
highlight: SourceSpan,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 11).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": "these two lines",
|
||||
"span": {
|
||||
"offset": 9,
|
||||
"length": 11
|
||||
}
|
||||
}
|
||||
],
|
||||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_highlight_flyby() -> 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 = "block 1"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "block 2"]
|
||||
highlight2: SourceSpan,
|
||||
}
|
||||
|
||||
let src = r#"line1
|
||||
line2
|
||||
line3
|
||||
line4
|
||||
line5
|
||||
"#
|
||||
.to_string();
|
||||
let len = src.len();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight1: (0, len).into(),
|
||||
highlight2: (10, 9).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": "block 1",
|
||||
"span": {
|
||||
"offset": 0,
|
||||
"length": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "block 2",
|
||||
"span": {
|
||||
"offset": 10,
|
||||
"length": 9
|
||||
}
|
||||
}
|
||||
],
|
||||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_highlight_no_label() -> Result<(), MietteError> {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("wtf?!\nit broke :(")]
|
||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||
struct MyBad {
|
||||
#[source]
|
||||
source: Inner,
|
||||
#[source_code]
|
||||
src: NamedSource,
|
||||
#[label = "block 1"]
|
||||
highlight1: SourceSpan,
|
||||
#[label]
|
||||
highlight2: SourceSpan,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("something went wrong\n\nHere's a more detailed explanation of everything that actually went wrong because it's actually important.\n")]
|
||||
struct Inner(#[source] InnerInner);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("very much went wrong")]
|
||||
struct InnerInner;
|
||||
|
||||
let src = r#"line1
|
||||
line2
|
||||
line3
|
||||
line4
|
||||
line5
|
||||
"#
|
||||
.to_string();
|
||||
let len = src.len();
|
||||
let err = MyBad {
|
||||
source: Inner(InnerInner),
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight1: (0, len).into(),
|
||||
highlight2: (10, 9).into(),
|
||||
};
|
||||
let out = fmt_report(err.into());
|
||||
println!("Error: {}", out);
|
||||
let expected: String = r#"
|
||||
{
|
||||
"message": "wtf?!\\nit broke :(",
|
||||
"code": "oops::my::bad",
|
||||
"severity": "error",
|
||||
"help": "try doing it better next time?",
|
||||
"filename": "bad_file.rs",
|
||||
"labels": [
|
||||
{
|
||||
"label": "block 1",
|
||||
"span": {
|
||||
"offset": 0,
|
||||
"length": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"span": {
|
||||
"offset": 10,
|
||||
"length": 9
|
||||
}
|
||||
}
|
||||
],
|
||||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_multiline_highlights_adjacent() -> 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"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "also this bit"]
|
||||
highlight2: SourceSpan,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here\nmore here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight1: (0, 10).into(),
|
||||
highlight2: (20, 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": 0,
|
||||
"length": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "also this bit",
|
||||
"span": {
|
||||
"offset": 20,
|
||||
"length": 6
|
||||
}
|
||||
}
|
||||
],
|
||||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_multiline_highlights_overlapping_lines() -> 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"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "also this bit"]
|
||||
highlight2: SourceSpan,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight1: (0, 8).into(),
|
||||
highlight2: (9, 10).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": 0,
|
||||
"length": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "also this bit",
|
||||
"span": {
|
||||
"offset": 9,
|
||||
"length": 10
|
||||
}
|
||||
}
|
||||
],
|
||||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_multiline_highlights_overlapping_offsets() -> 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"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "also this bit"]
|
||||
highlight2: SourceSpan,
|
||||
}
|
||||
|
||||
let src = "source\n text\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight1: (0, 8).into(),
|
||||
highlight2: (10, 10).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": 0,
|
||||
"length": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "also this bit",
|
||||
"span": {
|
||||
"offset": 10,
|
||||
"length": 10
|
||||
}
|
||||
}
|
||||
],
|
||||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url() -> Result<(), MietteError> {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("oops!")]
|
||||
#[diagnostic(help("try doing it better next time?"), url("https://example.com"))]
|
||||
struct MyBad;
|
||||
|
||||
let err = MyBad;
|
||||
let out = fmt_report(err.into());
|
||||
println!("Error: {}", out);
|
||||
let expected: String = r#"
|
||||
{
|
||||
"message": "oops!",
|
||||
"severity": "error",
|
||||
"url": "https://example.com",
|
||||
"help": "try doing it better next time?",
|
||||
"labels": [],
|
||||
"related": []
|
||||
}"#
|
||||
.lines()
|
||||
.into_iter()
|
||||
.map(|s| s.trim_matches(|c| c == ' ' || c == '\n'))
|
||||
.collect();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn related() -> 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<MyBad>,
|
||||
}
|
||||
|
||||
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![MyBad {
|
||||
src: NamedSource::new("bad_file2.rs", src),
|
||||
highlight: (0, 6).into(),
|
||||
related: vec![],
|
||||
}],
|
||||
};
|
||||
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_file2.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(())
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue