///|
/// A rejected source, with the span it was rejected at.
///
/// The data a consumer reads. `related` carries the second place worth looking
/// — the opener that was never closed, the `|` an alternative was expected to
/// line up with — which the reference does not record and which is most of what
/// makes an indentation complaint answerable.
pub(all) struct Diagnostic {
kind : ErrorKind
span : @basic.Span
severity : @report.Severity
related : Array[(@basic.Span, String)]
}
///|
pub fn Diagnostic::new(
kind : ErrorKind,
span : @basic.Span,
severity? : @report.Severity = Error,
related? : Array[(@basic.Span, String)] = [],
) -> Diagnostic {
{ kind, span, severity, related, }
}
///|
/// A shrubbery source was rejected.
///
/// One error type for the whole library. `Diagnostic` carries the structure, so
/// nothing is lost by there being a single constructor.
pub(all) suberror ShrubberyError {
ShrubberyError(Diagnostic)
}
///|
/// Raise this diagnostic.
pub fn[T] Diagnostic::raise_(self : Diagnostic) -> T raise ShrubberyError {
raise ShrubberyError(self)
}
///|
/// The diagnostic inside an error.
pub fn ShrubberyError::diagnostic(self : ShrubberyError) -> Diagnostic {
let ShrubberyError(d) = self
d
}
///|
/// Turn a diagnostic into a renderable report.
///
/// **This is the only function in the library that mentions `error-report`.**
/// Keeping it to one is what makes that library's eventual spin-out a rename
/// rather than an untangling, and `tools/boundary-check.sh` gates the other
/// half of the arrangement. If a second call site appears, the question to ask
/// is whether the thing it needs belongs in `Diagnostic` instead.
pub fn Diagnostic::to_report(
self : Diagnostic,
source : @report.SourceId,
) -> @report.Report {
let r = @report.Report::new(self.severity, self.explain())
.with_code(self.kind.code())
.with_label(
@report.Label::primary(source, span_of(self.span), message?=self.point()),
)
for rel in self.related {
let (span, note) = rel
let _ = r.with_label(
@report.Label::secondary(source, span_of(span), message=note),
)
}
match self.help() {
Some(h) => r.with_help(h)
None => r
}
}
///|
fn span_of(span : @basic.Span) -> @report.Span {
@report.Span::of_range(span.start.idx, span.end.idx)
}
///|
/// The headline. Defaults to the reference's wording, and says something
/// clearer where the reference's phrasing assumes you already know the rule.
fn Diagnostic::explain(self : Diagnostic) -> String {
match self.kind {
WrongIndentation(missing_colon_hint~) =>
if missing_colon_hint {
"this line is indented, but the line before it does not open a block"
} else {
"this group is not indented like the ones around it"
}
IncomparableIndentation =>
"these two lines are indented with different mixtures of tabs and spaces"
EmptyBlock(after~, ..) => "nothing follows this `\{after}`"
DidNotFindMatching(_) => "this opener is never closed"
ExpectedCloser(expected~, found~) =>
"expected `\{expected}` to close this, found `\{found}`"
UnexpectedCloser(found~) => "there is nothing here for `\{found}` to close"
NoGroupForTermComment => "`#//` has nothing after it to comment out"
_ => self.kind.reference_text()
}
}
///|
/// What to write beside the caret. `None` where the headline already said it.
fn Diagnostic::point(self : Diagnostic) -> String? {
match self.kind {
WrongIndentation(_) => Some("this column does not line up")
IncomparableIndentation => Some("this indentation is not comparable")
EmptyBlock(after~, ..) => Some("this `\{after}` opens a block")
DidNotFindMatching(_) => Some("opened here")
NoGroupForTermComment => Some("nothing to comment out")
MisplacedComma(_) => Some("this comma")
MissingCommaBeforeGroup => Some("a comma is needed before this")
AltBeforeGroupColumn => Some("this `|` starts too far left")
_ => None
}
}
///|
/// The rule, in one sentence, where knowing it is the whole fix.
fn Diagnostic::help(self : Diagnostic) -> String? {
match self.kind {
WrongIndentation(missing_colon_hint~) =>
if missing_colon_hint {
Some(
"end the previous line with `:` to open a block, or unindent this line",
)
} else {
Some("every group in a sequence starts at the same column")
}
IncomparableIndentation =>
Some(
"one indentation is more than another only when it extends it; mixing tabs and spaces differently makes them incomparable",
)
EmptyBlock(..) =>
Some("a block needs at least one group, or explicit `«»` to be empty")
MisplacedSemicolon =>
Some(
"inside `()`, `[]` or `{}`, groups are separated by `,` rather than `;`",
)
MissingCommaBeforeGroup =>
Some(
"inside `()`, `[]` or `{}`, groups on separate lines still need a `,`",
)
AltBeforeGroupColumn =>
Some("a `|` may not start left of the column its group started at")
UnnecessaryColonBeforeBar =>
Some("drop the `:`; a `|` on the next line opens the block by itself")
NoGroupForTermComment =>
Some("`#//` comments out the group or `|` after it")
_ => None
}
}