1use crate::source::SourceSpan;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6
7pub use foch_annotation::Severity;
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11#[non_exhaustive]
12pub enum DiagnosticCode {
13 Parse,
15 Annotation,
17 UnknownAnnotation,
19 OrphanAnnotation,
21 NestedAnnotation,
23 DuplicateCase,
24 DuplicateFixture,
25 UnknownFixture,
26 FixtureCycle,
27 UnsupportedTarget,
29 TooManyCases,
30 InvalidMod,
31 UnknownTag,
32 UnknownEvent,
33 TriggerAsEffect,
35 EffectAsTrigger,
37}
38
39#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
43pub struct Diagnostic {
44 pub code: DiagnosticCode,
45 pub severity: Severity,
46 pub span: Option<SourceSpan>,
47 pub message: String,
48 pub bypassable: bool,
49}
50
51impl Diagnostic {
52 pub(crate) fn error(
53 code: DiagnosticCode,
54 span: Option<SourceSpan>,
55 message: impl Into<String>,
56 ) -> Self {
57 Self {
58 code,
59 severity: Severity::Error,
60 span,
61 message: message.into(),
62 bypassable: false,
63 }
64 }
65
66 pub(crate) fn model_error(
67 code: DiagnosticCode,
68 span: Option<SourceSpan>,
69 message: impl Into<String>,
70 ) -> Self {
71 Self {
72 bypassable: true,
73 ..Self::error(code, span, message)
74 }
75 }
76}
77
78impl From<foch_annotation::Diagnostic> for Diagnostic {
79 fn from(diagnostic: foch_annotation::Diagnostic) -> Self {
80 use foch_annotation::Code;
81 let code = match diagnostic.code {
82 Code::Parse => DiagnosticCode::Parse,
83 Code::UnknownAnnotation => DiagnosticCode::UnknownAnnotation,
84 Code::Orphan => DiagnosticCode::OrphanAnnotation,
85 Code::Nested => DiagnosticCode::NestedAnnotation,
86 Code::UnsupportedTarget => DiagnosticCode::UnsupportedTarget,
87 _ => DiagnosticCode::Annotation,
88 };
89 Self {
90 code,
91 severity: diagnostic.severity,
92 span: Some(diagnostic.span),
93 message: diagnostic.message,
94 bypassable: false,
95 }
96 }
97}
98
99impl fmt::Display for Diagnostic {
100 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
101 if let Some(span) = &self.span {
102 write!(formatter, "{span}: ")?;
103 }
104 let severity = match self.severity {
105 Severity::Error => "error",
106 Severity::Warning => "warning",
107 };
108 write!(formatter, "{severity}: {}", self.message)
109 }
110}
111
112pub fn blocks_run(diagnostics: &[Diagnostic], run_anyway: bool) -> bool {
115 diagnostics.iter().any(|diagnostic| {
116 diagnostic.severity == Severity::Error && !(run_anyway && diagnostic.bypassable)
117 })
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn only_model_findings_can_be_overridden() {
126 let model = Diagnostic::model_error(DiagnosticCode::UnknownTag, None, "x");
127 let malformed = Diagnostic::error(DiagnosticCode::Annotation, None, "x");
128 let warning = Diagnostic {
129 severity: Severity::Warning,
130 ..Diagnostic::error(DiagnosticCode::UnknownEvent, None, "x")
131 };
132 assert!(!blocks_run(std::slice::from_ref(&warning), false));
133 assert!(blocks_run(std::slice::from_ref(&model), false));
134 assert!(!blocks_run(std::slice::from_ref(&model), true));
135 assert!(blocks_run(&[model, malformed], true));
136 }
137}