#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::fmt;
use use_diagnostic_message::DiagnosticMessage;
use use_diagnostic_span::DiagnosticSpan;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum DiagnosticLabelKind {
Primary,
Secondary,
Help,
Note,
}
impl DiagnosticLabelKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Primary => "primary",
Self::Secondary => "secondary",
Self::Help => "help",
Self::Note => "note",
}
}
}
impl fmt::Display for DiagnosticLabelKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct DiagnosticLabel {
kind: DiagnosticLabelKind,
message: DiagnosticMessage,
span: Option<DiagnosticSpan>,
}
impl DiagnosticLabel {
#[must_use]
pub const fn new(
kind: DiagnosticLabelKind,
message: DiagnosticMessage,
span: Option<DiagnosticSpan>,
) -> Self {
Self {
kind,
message,
span,
}
}
#[must_use]
pub const fn primary(message: DiagnosticMessage, span: DiagnosticSpan) -> Self {
Self::new(DiagnosticLabelKind::Primary, message, Some(span))
}
#[must_use]
pub const fn secondary(message: DiagnosticMessage, span: DiagnosticSpan) -> Self {
Self::new(DiagnosticLabelKind::Secondary, message, Some(span))
}
#[must_use]
pub const fn help(message: DiagnosticMessage) -> Self {
Self::new(DiagnosticLabelKind::Help, message, None)
}
#[must_use]
pub const fn note(message: DiagnosticMessage) -> Self {
Self::new(DiagnosticLabelKind::Note, message, None)
}
#[must_use]
pub const fn kind(&self) -> DiagnosticLabelKind {
self.kind
}
#[must_use]
pub const fn message(&self) -> &DiagnosticMessage {
&self.message
}
#[must_use]
pub const fn span(&self) -> Option<&DiagnosticSpan> {
self.span.as_ref()
}
}
impl fmt::Display for DiagnosticLabel {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.message.fmt(formatter)
}
}
#[cfg(test)]
mod tests {
use super::{DiagnosticLabel, DiagnosticLabelKind};
use use_diagnostic_message::DiagnosticMessage;
use use_diagnostic_span::{DiagnosticPosition, DiagnosticSpan};
fn test_span() -> DiagnosticSpan {
let start = DiagnosticPosition::new(2, 5).expect("position should be valid");
let end = DiagnosticPosition::new(2, 9).expect("position should be valid");
DiagnosticSpan::without_source(start, end).expect("span should be valid")
}
#[test]
fn creates_primary_label_with_span() {
let label = DiagnosticLabel::primary(
DiagnosticMessage::new("invalid value").expect("message should be valid"),
test_span(),
);
assert_eq!(label.kind(), DiagnosticLabelKind::Primary);
assert!(label.span().is_some());
}
#[test]
fn creates_secondary_label_with_span() {
let label = DiagnosticLabel::secondary(
DiagnosticMessage::new("defined here").expect("message should be valid"),
test_span(),
);
assert_eq!(label.kind(), DiagnosticLabelKind::Secondary);
assert!(label.span().is_some());
}
#[test]
fn creates_help_label_without_span() {
let label = DiagnosticLabel::help(
DiagnosticMessage::new("provide the missing field").expect("message should be valid"),
);
assert_eq!(label.kind(), DiagnosticLabelKind::Help);
assert!(label.span().is_none());
}
#[test]
fn display_and_debug_do_not_assume_renderer_output() {
let label = DiagnosticLabel::note(
DiagnosticMessage::new("plain context").expect("message should be valid"),
);
assert_eq!(label.to_string(), "plain context");
assert!(format!("{label:?}").contains("Note"));
}
}