// The reporting context: policy resolution, queueing, and flushing.
///|
/// Where and how a context renders.
///
/// Held only by a RENDERING context; a collector has none, since it merely
/// accumulates entries for a rendering context to re-report later.
struct Render {
theme : Theme
output : Sink
format : OutputFormat
exit_on_error : Bool
}
///|
/// A reporting context.
pub struct Context {
/// How many errors to queue before flushing.
max : Int
queue : Array[Diagnostic]
source : String?
/// Labels added to every diagnostic reported here.
related : Array[Label]
policy : @warning.Policy
render : Render?
/// Error-recovery mode: the input had syntax errors and a best-effort AST
/// was recovered past them, so name resolution is unreliable -- a construct
/// dropped at a sync boundary leaves its bindings absent. "Not bound"
/// diagnostics are then suppressed as likely cascades while genuine errors
/// in the intact regions still surface.
mut recovery : Bool
/// Set once an error has been flushed with `exit_on_error`, so the caller
/// can turn it into an exit code. MoonBit has no `exit` inside a library, so
/// the decision is handed back rather than taken here.
mut failed : Bool
}
///|
pub fn Context::source(self : Context) -> String? {
self.source
}
///|
pub fn Context::in_recovery(self : Context) -> Bool {
self.recovery
}
///|
pub fn Context::set_recovery(self : Context, v : Bool) -> Unit {
self.recovery = v
}
///|
/// Whether an error was reported. The CLI turns this into an exit code.
pub fn Context::failed(self : Context) -> Bool {
self.failed
}
///|
/// The entries accumulated in a collector, neither cleared nor printed.
pub fn Context::collected(self : Context) -> Array[Diagnostic] {
self.queue
}
///|
/// A context that buffers reported diagnostics without printing or exiting.
///
/// It renders nothing, so it needs none of a rendering context's parameters.
/// `source` is still worth threading: a lint that inspects the original text
/// runs against this context.
pub fn collector(
parent? : Context? = None,
source? : String? = None,
) -> Context {
let c = {
max: @int.MAX_VALUE,
queue: [],
source,
related: [],
policy: @warning.default_policy,
render: None,
recovery: false,
failed: false,
}
// A collector checking part of a larger run inherits the parent's recovery
// mode, so cascade suppression carries into it.
match parent {
Some(p) => c.recovery = p.recovery
None => ()
}
c
}
///|
/// Flush the queued errors.
fn Context::flush(self : Context) -> Unit {
match self.render {
// A collector renders nothing; read its entries with `collected`.
None => ()
Some(r) =>
if self.queue.length() > 0 {
for d in self.queue {
output_error(r.output, r.theme, r.format, self.source, d)
}
self.queue.clear()
if r.exit_on_error {
(r.output.flush)()
self.failed = true
}
}
}
}
///|
/// Report a diagnostic.
///
/// `warning` names the warning so the policy can decide its level. It is
/// meaningful for Warning and Suggestion severities and ignored for errors.
pub fn Context::report(
self : Context,
loc : @basic.Location,
severity : Severity,
message : @message.Message,
warning? : @warning.Warning? = None,
universal? : Bool = false,
hint? : @message.Message? = None,
edit? : Edit? = None,
related? : Array[Label] = [],
) -> Unit {
let all_related = self.related.copy()
all_related.append(related)
fn entry(sev : Severity) -> Diagnostic {
{
loc,
severity: sev,
warning,
message,
hint,
edit,
related: all_related,
universal,
}
}
match self.render {
// A collecting context buffers everything RAW, policy unapplied: the
// policy belongs to whichever rendering context finally re-reports it.
None => self.queue.push(entry(severity))
Some(r) => {
// Resolve a named warning's level now: hide it, leave it, or promote it
// to an error. A displayed suggestion stays a Suggestion.
let resolved : Severity? = match (severity, warning) {
(Warning, Some(w)) | (Suggestion, Some(w)) =>
match self.policy.resolve(w) {
Hidden => None
Displayed => Some(severity)
ErrorLevel => Some(Error)
}
_ => Some(severity)
}
match resolved {
None => ()
Some(Error) => {
self.queue.push(entry(Error))
if self.queue.length() == self.max {
self.flush()
}
}
Some(sev) =>
// Warnings and suggestions print immediately; only errors queue.
output_error(r.output, r.theme, r.format, self.source, entry(sev))
}
}
}
}
///|
/// Run `f` in a rendering context, flushing its diagnostics afterwards.
///
/// `color` and `palette` are both required: a rendering context must decide up
/// front whether to emit colour, and which source palette to colour embedded
/// AST fragments with, so a caller cannot silently fall back to a default.
pub fn[T] run(
sink : Sink,
color : @colors.Flag,
palette : @colors.Theme,
source : String?,
f : (Context) -> T,
format? : OutputFormat = Human,
related? : Array[Label] = [],
exit_on_error? : Bool = true,
policy? : @warning.Policy = @warning.default_policy,
is_tty? : Bool = false,
max? : Int = 1,
) -> (T, Context) {
let ctx = {
max,
queue: [],
source,
related,
policy,
render: Some({
theme: get_theme(color~, palette~, is_tty~),
output: sink,
format,
exit_on_error,
}),
recovery: false,
failed: false,
}
let res = f(ctx)
ctx.flush()
(res, ctx)
}