///|
/// Diagnostics for the bridge.
///
/// The same arrangement as `@html.error`, `@css.error` and `lib/error`: one
/// function reaches `error-report`, and everything else names a problem without
/// linking a renderer.
///
/// Spans come from `@basic.Span`, the shrubbery side's position type, because
/// what a bridge diagnostic points at is a place in a shrubbery file. The
/// conversion to `@report.Span` reads `.idx`, which is already a UTF-16 code
/// unit offset, so this is a change of representation and not of unit.
///|
pub(all) struct Diagnostic {
kind : @kind.ErrorKind
span : @basic.Span
severity : @report.Severity
related : Array[(@basic.Span, String)]
}
///|
pub fn Diagnostic::new(
kind : @kind.ErrorKind,
span : @basic.Span,
severity? : @report.Severity = Error,
related? : Array[(@basic.Span, String)] = [],
) -> Diagnostic {
{ kind, span, severity, related, }
}
///|
/// A diagnostic whose severity follows from its kind.
pub fn Diagnostic::of_kind(
kind : @kind.ErrorKind,
span : @basic.Span,
) -> Diagnostic {
let severity : @report.Severity = if kind.is_warning() {
Warning
} else {
Error
}
{ kind, span, severity, related: [], }
}
///|
pub fn Diagnostic::is_error(self : Diagnostic) -> Bool {
match self.severity {
Error => true
_ => false
}
}
///|
/// A Shrubbery HTML source was rejected.
pub(all) suberror ShrubHtmlError {
ShrubHtmlError(Diagnostic)
}
///|
pub fn[T] Diagnostic::raise_(self : Diagnostic) -> T raise ShrubHtmlError {
raise ShrubHtmlError(self)
}
///|
pub fn ShrubHtmlError::diagnostic(self : ShrubHtmlError) -> Diagnostic {
let ShrubHtmlError(d) = self
d
}
///|
/// Turn a diagnostic into a renderable report.
///
/// **This is the only function in the module that mentions `error-report`.**
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(s : @basic.Span) -> @report.Span {
@report.Span::of_range(s.start.idx, s.end.idx)
}
///|
fn Diagnostic::explain(self : Diagnostic) -> String {
match self.kind {
SigilTag(t) => "`" + t + "` is HTML's spelling, not this syntax's"
ReservedCall(n) => "`" + n + "()` is taken by this syntax"
BareTermInBlock(t) =>
"`" + t + "` is a bare word, with no call and no block"
AttrNotDeclaration => "this is not an attribute"
BadAttrValue => "an attribute's value has to be a literal or a name"
ChildrenOnVoid(t) => "`" + t + "` cannot have children"
BadEscapeCall(n) => "`" + n + "` was given arguments of the wrong shape"
BlockOnNonElement => "only an element can have a block"
AltsUnsupported => "`|` has no meaning in Shrubbery HTML"
BracesUnsupported => "`{ }` has no meaning in Shrubbery HTML"
TrailingRunAfterBlock =>
"everything after the `:` on this line went inside the block"
NameNotSpellable(n) => "`" + n + "` has no bare spelling"
Unsupported(what) => what + " has no meaning in Shrubbery HTML"
}
}
///|
fn Diagnostic::point(self : Diagnostic) -> String? {
match self.kind {
TrailingRunAfterBlock => Some("this became a child, not a sibling")
ReservedCall(_) => Some("read as this syntax's own construct")
BareTermInBlock(_) => Some("nothing here says what it is")
_ => None
}
}
///|
/// The rule, in one sentence, and where possible the exact thing to type.
///
/// This is the part that teaches, and for a syntax nobody has seen before it is
/// most of the value of the diagnostic. So it names the replacement rather than
/// describing it.
fn Diagnostic::help(self : Diagnostic) -> String? {
match self.kind {
SigilTag(t) => Some(sigil_help(t))
ReservedCall(n) =>
Some("for the element of that name write `element(\"" + n + "\")`")
BareTermInBlock(t) =>
Some("an element is a call: write `" + t + "()`, or quote it for text")
AttrNotDeclaration =>
Some("an attribute is `name: \"value\"`, or a bare name for a flag")
BadAttrValue =>
Some("write a string: `class: \"card\"`; a bare name means the same text")
TrailingRunAfterBlock =>
Some(
"a block runs to the end of its line, so an element with children has to come last",
)
NameNotSpellable(n) => Some("write it literally: `element(\"" + n + "\")`")
AltsUnsupported =>
Some("nothing in markup branches this way; `|` is reserved")
BracesUnsupported => Some("a block is opened with `:` and indentation")
ChildrenOnVoid(t) => Some("`" + t + "` closes itself; write `" + t + "()`")
_ => None
}
}
///|
fn sigil_help(t : String) -> String {
match t {
"<" => "an element is a call: write `div():` for ``"
"" => "an element's children are its block; there is no end tag to write"
"&" => "write the character itself, inside the string"
_ => "write the thing's name instead of a symbol for it"
}
}