// The diagnostic value, its theme, and where rendered output goes.
///|
pub(all) enum Severity {
Error
Warning
Suggestion
} derive(Eq, Debug)
///|
pub fn Severity::to_str(self : Severity) -> String {
match self {
Error => "error"
Warning => "warning"
Suggestion => "suggestion"
}
}
///|
/// The capitalised word heading a human-format diagnostic.
pub fn Severity::header(self : Severity) -> String {
match self {
Error => "Error"
Warning => "Warning"
Suggestion => "Suggestion"
}
}
///|
/// A secondary span with its own note, rendered under the snippet.
pub(all) struct Label {
loc : @basic.Location
message : @message.Message
}
///|
/// A machine-applicable rewrite: replace the source spanned by `loc` with
/// `new_text`. The diagnostic's own message doubles as the quick-fix title.
pub(all) struct Edit {
loc : @basic.Location
new_text : String
}
///|
/// One diagnostic.
pub(all) struct Diagnostic {
loc : @basic.Location
severity : Severity
/// The named warning this came from, so the policy can be applied when it is
/// finally reported.
warning : @warning.Warning?
message : @message.Message
hint : @message.Message?
edit : Edit?
related : Array[Label]
/// Reported during path-sensitive exploration only if it holds in EVERY
/// reachable configuration. The unused-local warning is universal: a local
/// used in one branch is not unused merely because another configuration
/// prunes that branch.
universal : Bool
}
///|
/// How diagnostics are rendered.
pub(all) enum OutputFormat {
/// Source snippets. The default.
Human
/// One JSON object per diagnostic per line.
Json
/// One `file:line:col: severity: message` line, gcc/rustc style, for editors
/// with a line-based error parser.
Short
} derive(Eq, Debug)
///|
/// The colours of the diagnostic frame.
pub(all) struct Theme {
error_header : String
warning_header : String
hint_header : String
error_label : String
warning_label : String
secondary_label : String
line_numbers : String
/// The palette for atoms embedded in a message body. Coloured or not in
/// lockstep with the frame above -- both come from the one colour flag.
body : @colors.Theme
}
///|
pub fn get_theme(
color? : @colors.Flag = Auto,
palette? : @colors.Theme = @colors.wax_theme,
is_tty? : Bool = false,
) -> Theme {
let use_color = @colors.should_use_color(color, is_tty~)
let body = if use_color { palette } else { @colors.no_color }
if use_color {
{
error_header: @colors.bold + @colors.high_red,
warning_header: @colors.bold + @colors.high_yellow,
hint_header: @colors.bold + @colors.cyan,
error_label: @colors.red,
warning_label: @colors.yellow,
secondary_label: @colors.bold + @colors.blue,
line_numbers: @colors.cyan,
body,
}
} else {
{
error_header: "",
warning_header: "",
hint_header: "",
error_label: "",
warning_label: "",
secondary_label: "",
line_numbers: "",
body,
}
}
}
///|
/// A destination for rendered diagnostics.
///
/// An indirection rather than a hard-wired stderr, because two consumers need
/// to capture: the differential harness, and the renderer's own tests.
pub(all) struct Sink {
write : (String) -> Unit
flush : () -> Unit
}
///|
/// A sink collecting into a growable buffer. Its flush does nothing.
pub fn buffer_sink(buf : StringBuilder) -> Sink {
{ write: s => buf.write_string(s), flush: () => () }
}
///|
/// Emit one physical line and flush.
///
/// Flushing per LINE, not per diagnostic: it keeps stdout/stderr interleaving
/// deterministic when a cram test or a parent process captures both.
fn line(sink : Sink, s : String) -> Unit {
(sink.write)(s + "\n")
(sink.flush)()
}
///|
/// Wrap `s` in `color`'s escape, or return it unchanged when uncoloured.
///
/// Pure string composition: a header's colour contributes no display width,
/// since every alignment here is computed from the plain text.
fn with_style(color : String, s : String) -> String {
if color == "" {
s
} else {
color + s + @colors.reset
}
}