use std::fmt;
use crate::parse::is_surrogate;
use crate::{Dialect, Rule, Severity};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Problem {
pub(crate) offset: usize,
pub(crate) severity: Severity,
pub(crate) detail: Detail,
}
impl Problem {
pub fn offset(&self) -> usize {
self.offset
}
pub fn severity(&self) -> Severity {
self.severity
}
pub fn rule(&self) -> Rule {
match self.detail {
Detail::Syntax(_) => Rule::Syntax,
Detail::DuplicateKey { .. } => Rule::DuplicateKey,
Detail::InvalidNumber { .. } => Rule::InvalidNumber,
Detail::InvalidCodePoint { .. } => Rule::InvalidCodePoint,
Detail::ByteOrderMark => Rule::ByteOrderMark,
Detail::MissingFinalNewline => Rule::MissingFinalNewline,
}
}
pub fn related_offset(&self) -> Option<usize> {
match &self.detail {
Detail::DuplicateKey { first, .. } => Some(*first),
Detail::Syntax(s) => s.started,
_ => None,
}
}
}
impl fmt::Display for Problem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.detail.fmt(f)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Detail {
Syntax(SyntaxError),
DuplicateKey {
first: usize,
path: String,
},
InvalidNumber {
overflows: bool,
},
InvalidCodePoint {
code_point: u32,
},
ByteOrderMark,
MissingFinalNewline,
}
impl fmt::Display for Detail {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Detail::Syntax(s) => s.fmt(f),
Detail::DuplicateKey { path, .. } => write!(f, "duplicate key {path}"),
Detail::InvalidNumber { overflows: true } => f.write_str("number overflows a double"),
Detail::InvalidNumber { overflows: false } => {
f.write_str("integer beyond ±(2^53 − 1) cannot be represented exactly")
}
Detail::InvalidCodePoint { code_point } if is_surrogate(*code_point) => {
write!(f, "lone surrogate U+{code_point:04X}")
}
Detail::InvalidCodePoint { code_point } => {
write!(f, "noncharacter U+{code_point:04X}")
}
Detail::ByteOrderMark => f.write_str("byte order mark at the start of the file"),
Detail::MissingFinalNewline => f.write_str("no newline at the end of the file"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct SyntaxError {
pub(crate) kind: SyntaxKind,
pub(crate) started: Option<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum SyntaxKind {
UnexpectedEnd {
expected: &'static str,
},
Unexpected {
found: char,
expected: &'static str,
},
NotAllowed {
what: &'static str,
dialect: Dialect,
},
TrailingComma {
close: char,
dialect: Dialect,
},
UnterminatedString,
LineBreakInString,
ControlCharacter {
found: char,
},
InvalidEscape {
found: char,
},
InvalidUnicodeEscape,
InvalidHexEscape,
UnterminatedComment,
BlankLine,
TooDeep {
limit: usize,
},
}
impl fmt::Display for SyntaxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
SyntaxKind::UnexpectedEnd { expected } => {
write!(f, "expected {expected}, found the end of the file")
}
SyntaxKind::Unexpected { found, expected } => {
write!(f, "expected {expected}, found {}", Shown(*found))
}
SyntaxKind::NotAllowed { what, dialect } => {
write!(f, "{what} not allowed in {dialect}")
}
SyntaxKind::TrailingComma { close, dialect } => {
write!(
f,
"trailing comma before `{close}` is not allowed in {dialect}"
)
}
SyntaxKind::UnterminatedString => f.write_str("unterminated string"),
SyntaxKind::LineBreakInString => f.write_str("line break inside a string"),
SyntaxKind::ControlCharacter { found } => {
write!(
f,
"control character {} inside a string must be escaped",
Shown(*found)
)
}
SyntaxKind::InvalidEscape { found } => {
write!(f, "invalid escape `\\{}`", found.escape_debug())
}
SyntaxKind::InvalidUnicodeEscape => f.write_str("`\\u` needs four hex digits"),
SyntaxKind::InvalidHexEscape => f.write_str("`\\x` needs two hex digits"),
SyntaxKind::UnterminatedComment => f.write_str("unterminated block comment"),
SyntaxKind::BlankLine => {
f.write_str("blank line; JSON Lines needs a value on every line")
}
SyntaxKind::TooDeep { limit } => write!(f, "nested deeper than {limit} levels"),
}
}
}
struct Shown(char);
impl fmt::Display for Shown {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let c = self.0;
if c.is_control() || c.is_whitespace() || c == '\u{FEFF}' {
write!(f, "U+{:04X}", u32::from(c))
} else {
write!(f, "`{c}`")
}
}
}