Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render inner diagnostics #170

Merged
merged 5 commits into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions src/handlers/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fmt::{self, Write};
use owo_colors::{OwoColorize, Style};
use unicode_width::UnicodeWidthChar;

use crate::diagnostic_chain::DiagnosticChain;
use crate::diagnostic_chain::{DiagnosticChain, ErrorKind};
use crate::handlers::theme::*;
use crate::protocol::{Diagnostic, Severity};
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};
Expand Down Expand Up @@ -151,7 +151,6 @@ impl GraphicalReportHandler {
diagnostic: &(dyn Diagnostic),
) -> fmt::Result {
self.render_header(f, diagnostic)?;
writeln!(f)?;
self.render_causes(f, diagnostic)?;
let src = diagnostic.source_code();
self.render_snippets(f, diagnostic, src)?;
Expand Down Expand Up @@ -190,13 +189,15 @@ impl GraphicalReportHandler {
);
write!(header, "{}", link)?;
writeln!(f, "{}", header)?;
writeln!(f)?;
} else if let Some(code) = diagnostic.code() {
write!(header, "{}", code.style(severity_style),)?;
if self.links == LinkStyle::Text && diagnostic.url().is_some() {
let url = diagnostic.url().unwrap(); // safe
write!(header, " ({})", url.style(self.theme.styles.link))?;
}
writeln!(f, "{}", header)?;
writeln!(f)?;
}
Ok(())
}
Expand Down Expand Up @@ -253,7 +254,21 @@ impl GraphicalReportHandler {
let opts = textwrap::Options::new(width)
.initial_indent(&initial_indent)
.subsequent_indent(&rest_indent);
writeln!(f, "{}", textwrap::fill(&error.to_string(), opts))?;
match error {
ErrorKind::Diagnostic(diag) => {
let mut inner = String::new();

// Don't print footer for inner errors
let mut inner_renderer = self.clone();
inner_renderer.footer = None;
inner_renderer.render_report(&mut inner, diag)?;

writeln!(f, "{}", textwrap::fill(&inner, opts))?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is, ultimately, the desired user experience for this? Can you give some examples, and how this scales for more complex errors? I'm concerned that this will just create a lot of unintended noise, rather than help users focus on the error at hand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is that if a developer chooses to go into giving a complete error experience they do not have to go and implement their own printer and instead can re-use the existing infrastructure.

As a concrete example I have the following error tree possible:

  • User gives a configuration file with a variable amount of components and how to initialize them
    • Each component then gets loaded and can fail for various reasons, so that users can easily fix their errors all errors are collected and exposed as related.
      • Each of these errors are a Diagnostic so that users get complete information with source_codes etc

An example output of this:

image

Now, this screenshot is without this patch, but once added I will be able to attach more info to the inner error and thus have a clear hierarchy of what happened and how the user could fix it.

}
ErrorKind::StdError(err) => {
writeln!(f, "{}", textwrap::fill(&err.to_string(), opts))?;
}
}
}
}

Expand Down Expand Up @@ -287,7 +302,6 @@ impl GraphicalReportHandler {
Some(Severity::Advice) => write!(f, "Advice: ")?,
};
self.render_header(f, rel)?;
writeln!(f)?;
self.render_causes(f, rel)?;
let src = rel.source_code().or(parent_src);
self.render_snippets(f, rel, src)?;
Expand Down
6 changes: 2 additions & 4 deletions tests/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ fn single_line_highlight_span_full_line() {
let out = fmt_report(err.into());
println!("Error: {}", out);

let expected = r#"
× oops!
let expected = r#" × oops!
╭─[issue:1:1]
1 │ source
2 │ text
Expand Down Expand Up @@ -1201,8 +1200,7 @@ fn zero_length_eol_span() {
let out = fmt_report(err.into());
println!("Error: {}", out);

let expected = r#"
× oops!
let expected = r#" × oops!
╭─[issue:1:1]
1 │ this is the first line
2 │ this is the second line
Expand Down
42 changes: 42 additions & 0 deletions tests/test_diagnostic_source_macro.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
use miette::Diagnostic;

#[derive(Debug, miette::Diagnostic, thiserror::Error)]
#[error("A complex error happened")]
struct SourceError {
#[source_code]
code: String,
#[help]
help: String,
#[label("here")]
label: (usize, usize),
}

#[derive(Debug, miette::Diagnostic, thiserror::Error)]
#[error("AnErr")]
struct AnErr;
Expand Down Expand Up @@ -59,3 +70,34 @@ fn test_diagnostic_source() {
let error = TestArcedError(std::sync::Arc::new(AnErr));
assert!(error.diagnostic_source().is_some());
}

#[test]
fn test_diagnostic_source_pass_extra_info() {
let diag = TestBoxedError(Box::new(SourceError {
code: String::from("Hello\nWorld!"),
help: format!("Have you tried turning it on and off again?"),
label: (1, 4),
}));
let mut out = String::new();
miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
.with_width(80)
.with_footer("this is a footer".into())
.render_report(&mut out, &diag)
.unwrap();
println!("Error: {}", out);
let expected = r#" × TestError
╰─▶ × A complex error happened
╭─[1:1]
1 │ Hello
· ──┬─
· ╰── here
2 │ World!
╰────
help: Have you tried turning it on and off again?


this is a footer
"#
.to_string();
assert_eq!(expected, out);
}