use crate::source::SourceSpan;
use serde::{Deserialize, Serialize};
use std::fmt;
pub use foch_annotation::Severity;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum DiagnosticCode {
Parse,
Annotation,
UnknownAnnotation,
OrphanAnnotation,
NestedAnnotation,
DuplicateCase,
DuplicateFixture,
UnknownFixture,
FixtureCycle,
UnsupportedTarget,
TooManyCases,
InvalidMod,
UnknownTag,
UnknownEvent,
TriggerAsEffect,
EffectAsTrigger,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Diagnostic {
pub code: DiagnosticCode,
pub severity: Severity,
pub span: Option<SourceSpan>,
pub message: String,
pub bypassable: bool,
}
impl Diagnostic {
pub(crate) fn error(
code: DiagnosticCode,
span: Option<SourceSpan>,
message: impl Into<String>,
) -> Self {
Self {
code,
severity: Severity::Error,
span,
message: message.into(),
bypassable: false,
}
}
pub(crate) fn model_error(
code: DiagnosticCode,
span: Option<SourceSpan>,
message: impl Into<String>,
) -> Self {
Self {
bypassable: true,
..Self::error(code, span, message)
}
}
}
impl From<foch_annotation::Diagnostic> for Diagnostic {
fn from(diagnostic: foch_annotation::Diagnostic) -> Self {
use foch_annotation::Code;
let code = match diagnostic.code {
Code::Parse => DiagnosticCode::Parse,
Code::UnknownAnnotation => DiagnosticCode::UnknownAnnotation,
Code::Orphan => DiagnosticCode::OrphanAnnotation,
Code::Nested => DiagnosticCode::NestedAnnotation,
Code::UnsupportedTarget => DiagnosticCode::UnsupportedTarget,
_ => DiagnosticCode::Annotation,
};
Self {
code,
severity: diagnostic.severity,
span: Some(diagnostic.span),
message: diagnostic.message,
bypassable: false,
}
}
}
impl fmt::Display for Diagnostic {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(span) = &self.span {
write!(formatter, "{span}: ")?;
}
let severity = match self.severity {
Severity::Error => "error",
Severity::Warning => "warning",
};
write!(formatter, "{severity}: {}", self.message)
}
}
pub fn blocks_run(diagnostics: &[Diagnostic], run_anyway: bool) -> bool {
diagnostics.iter().any(|diagnostic| {
diagnostic.severity == Severity::Error && !(run_anyway && diagnostic.bypassable)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_model_findings_can_be_overridden() {
let model = Diagnostic::model_error(DiagnosticCode::UnknownTag, None, "x");
let malformed = Diagnostic::error(DiagnosticCode::Annotation, None, "x");
let warning = Diagnostic {
severity: Severity::Warning,
..Diagnostic::error(DiagnosticCode::UnknownEvent, None, "x")
};
assert!(!blocks_run(std::slice::from_ref(&warning), false));
assert!(blocks_run(std::slice::from_ref(&model), false));
assert!(!blocks_run(std::slice::from_ref(&model), true));
assert!(blocks_run(&[model, malformed], true));
}
}