use core::{error::Error, fmt, ops::Range};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Severity {
Error,
Warning,
Info,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DiagnosticCode {
InvalidKey,
UnterminatedQuote,
TrailingText,
UnknownEscape,
DuplicateKey,
BareKey,
NonPosixKey,
TightHash,
Backtick,
ByteOrderMark,
LoneCarriageReturn,
}
impl DiagnosticCode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::InvalidKey => "dotenv.invalid-key",
Self::UnterminatedQuote => "dotenv.unterminated-quote",
Self::TrailingText => "dotenv.trailing-text",
Self::UnknownEscape => "dotenv.unknown-escape",
Self::DuplicateKey => "dotenv.duplicate-key",
Self::BareKey => "dotenv.bare-key",
Self::NonPosixKey => "dotenv.non-posix-key",
Self::TightHash => "dotenv.tight-hash",
Self::Backtick => "dotenv.backtick",
Self::ByteOrderMark => "dotenv.bom",
Self::LoneCarriageReturn => "dotenv.lone-cr",
}
}
#[must_use]
pub const fn severity(self) -> Severity {
match self {
Self::InvalidKey | Self::UnterminatedQuote | Self::TrailingText => Severity::Error,
Self::ByteOrderMark => Severity::Info,
_ => Severity::Warning,
}
}
#[must_use]
pub const fn message(self) -> &'static str {
match self {
Self::InvalidKey => "expected a non-empty Portable key",
Self::UnterminatedQuote => "quoted value has no closing delimiter",
Self::TrailingText => "unexpected text after the closing quote",
Self::UnknownEscape => "unknown escape retained literally",
Self::DuplicateKey => "duplicate key; lookup uses the last declaration",
Self::BareKey => "bare declaration retained without assigning a value",
Self::NonPosixKey => "key is not a POSIX shell variable name",
Self::TightHash => "hash without preceding whitespace is literal",
Self::Backtick => "backticks are literal in Portable syntax",
Self::ByteOrderMark => "leading UTF-8 byte-order mark retained",
Self::LoneCarriageReturn => "lone carriage return retained as a line ending",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub(crate) code: DiagnosticCode,
pub(crate) span: Range<usize>,
pub(crate) secondary: Option<Range<usize>>,
pub(crate) line: usize,
}
impl Diagnostic {
pub(crate) fn new(code: DiagnosticCode, span: Range<usize>, line: usize) -> Self {
Self {
code,
span,
secondary: None,
line,
}
}
#[must_use]
pub const fn code(&self) -> DiagnosticCode {
self.code
}
#[must_use]
pub const fn severity(&self) -> Severity {
self.code.severity()
}
#[must_use]
pub fn span(&self) -> Range<usize> {
self.span.clone()
}
#[must_use]
pub fn secondary_span(&self) -> Option<Range<usize>> {
self.secondary.clone()
}
#[must_use]
pub const fn line(&self) -> usize {
self.line
}
#[must_use]
pub const fn message(&self) -> &'static str {
self.code.message()
}
}
impl fmt::Display for Diagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"line {}: {} ({})",
self.line,
self.message(),
self.code.as_str()
)
}
}
impl Error for Diagnostic {}