1use rowan::TextRange;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub enum DiagnosticKind {
17 UnclosedTree,
19 UnclosedSequence,
20 UnclosedAlternation,
21
22 ExpectedExpression,
24 ExpectedTypeName,
25 ExpectedCaptureName,
26 ExpectedFieldName,
27 ExpectedSubtype,
28
29 EmptyTree,
31 BareIdentifier,
32 InvalidSeparator,
33 InvalidFieldEquals,
34 InvalidSupertypeSyntax,
35 InvalidTypeAnnotationSyntax,
36 ErrorTakesNoArguments,
37 RefCannotHaveChildren,
38 ErrorMissingOutsideParens,
39 UnsupportedPredicate,
40 UnexpectedToken,
41 CaptureWithoutTarget,
42 LowercaseBranchLabel,
43
44 CaptureNameHasDots,
46 CaptureNameHasHyphens,
47 CaptureNameUppercase,
48 DefNameLowercase,
49 DefNameHasSeparators,
50 BranchLabelHasSeparators,
51 FieldNameHasDots,
52 FieldNameHasHyphens,
53 FieldNameUppercase,
54 TypeNameInvalidChars,
55
56 DuplicateDefinition,
58 UndefinedReference,
59 MixedAltBranches,
60 RecursionNoEscape,
61 DirectRecursion,
62 FieldSequenceValue,
63
64 IncompatibleTypes,
66 MultiCaptureQuantifierNoName,
67 UnusedBranchLabels,
68
69 UnknownNodeType,
71 UnknownField,
72 FieldNotOnNodeType,
73 InvalidFieldChildType,
74 InvalidChildType,
75
76 UnnamedDefNotLast,
78}
79
80impl DiagnosticKind {
81 pub fn default_severity(&self) -> Severity {
83 match self {
84 Self::UnusedBranchLabels => Severity::Warning,
85 _ => Severity::Error,
86 }
87 }
88
89 pub fn suppresses(&self, other: &DiagnosticKind) -> bool {
94 self < other
95 }
96
97 pub fn is_structural_error(&self) -> bool {
100 matches!(
101 self,
102 Self::UnclosedTree | Self::UnclosedSequence | Self::UnclosedAlternation
103 )
104 }
105
106 pub fn is_root_cause_error(&self) -> bool {
109 matches!(
110 self,
111 Self::ExpectedExpression
112 | Self::ExpectedTypeName
113 | Self::ExpectedCaptureName
114 | Self::ExpectedFieldName
115 | Self::ExpectedSubtype
116 )
117 }
118
119 pub fn is_consequence_error(&self) -> bool {
122 matches!(self, Self::UnnamedDefNotLast)
123 }
124
125 pub fn fallback_message(&self) -> &'static str {
127 match self {
128 Self::UnclosedTree => "missing closing `)`",
130 Self::UnclosedSequence => "missing closing `}`",
131 Self::UnclosedAlternation => "missing closing `]`",
132
133 Self::ExpectedExpression => "expected an expression",
135 Self::ExpectedTypeName => "expected type name after `::`",
136 Self::ExpectedCaptureName => "expected name after `@`",
137 Self::ExpectedFieldName => "expected field name",
138 Self::ExpectedSubtype => "expected subtype after `/`",
139
140 Self::EmptyTree => "empty parentheses are not allowed",
142 Self::BareIdentifier => "bare identifier is not a valid expression",
143 Self::InvalidSeparator => "separators are not needed",
144 Self::InvalidFieldEquals => "use `:` for field constraints, not `=`",
145 Self::InvalidSupertypeSyntax => "supertype syntax not allowed on references",
146 Self::InvalidTypeAnnotationSyntax => "use `::` for type annotations, not `:`",
147 Self::ErrorTakesNoArguments => "`(ERROR)` cannot have child nodes",
148 Self::RefCannotHaveChildren => "references cannot have children",
149 Self::ErrorMissingOutsideParens => {
150 "`ERROR` and `MISSING` must be wrapped in parentheses"
151 }
152 Self::UnsupportedPredicate => "predicates like `#match?` are not supported",
153 Self::UnexpectedToken => "unexpected token",
154 Self::CaptureWithoutTarget => "`@` must follow an expression to capture",
155 Self::LowercaseBranchLabel => "branch labels must be capitalized",
156
157 Self::CaptureNameHasDots => "capture names cannot contain `.`",
159 Self::CaptureNameHasHyphens => "capture names cannot contain `-`",
160 Self::CaptureNameUppercase => "capture names must be lowercase",
161 Self::DefNameLowercase => "definition names must start uppercase",
162 Self::DefNameHasSeparators => "definition names must be PascalCase",
163 Self::BranchLabelHasSeparators => "branch labels must be PascalCase",
164 Self::FieldNameHasDots => "field names cannot contain `.`",
165 Self::FieldNameHasHyphens => "field names cannot contain `-`",
166 Self::FieldNameUppercase => "field names must be lowercase",
167 Self::TypeNameInvalidChars => "type names cannot contain `.` or `-`",
168
169 Self::DuplicateDefinition => "name already defined",
171 Self::UndefinedReference => "undefined reference",
172 Self::MixedAltBranches => "cannot mix labeled and unlabeled branches",
173 Self::RecursionNoEscape => "infinite recursion: cycle has no escape path",
174 Self::DirectRecursion => "infinite recursion: cycle consumes no input",
175 Self::FieldSequenceValue => "field must match exactly one node",
176
177 Self::IncompatibleTypes => "incompatible types in alternation branches",
179 Self::MultiCaptureQuantifierNoName => {
180 "quantified expression with multiple captures requires `@name`"
181 }
182 Self::UnusedBranchLabels => "branch labels have no effect without capture",
183
184 Self::UnknownNodeType => "unknown node type",
186 Self::UnknownField => "unknown field",
187 Self::FieldNotOnNodeType => "field not valid on this node type",
188 Self::InvalidFieldChildType => "node type not valid for this field",
189 Self::InvalidChildType => "node type not valid as child",
190
191 Self::UnnamedDefNotLast => "only the last definition can be unnamed",
193 }
194 }
195
196 pub fn custom_message(&self) -> String {
198 match self {
199 Self::RefCannotHaveChildren => {
201 "`{}` is a reference and cannot have children".to_string()
202 }
203 Self::FieldSequenceValue => {
204 "field `{}` must match exactly one node, not a sequence".to_string()
205 }
206
207 Self::DuplicateDefinition => "`{}` is already defined".to_string(),
209 Self::UndefinedReference => "`{}` is not defined".to_string(),
210 Self::IncompatibleTypes => "incompatible types: {}".to_string(),
211
212 Self::UnknownNodeType => "`{}` is not a valid node type".to_string(),
214 Self::UnknownField => "`{}` is not a valid field".to_string(),
215 Self::FieldNotOnNodeType => "field `{}` is not valid on this node type".to_string(),
216 Self::InvalidFieldChildType => "node type `{}` is not valid for this field".to_string(),
217 Self::InvalidChildType => "`{}` cannot be a child of this node".to_string(),
218
219 Self::MixedAltBranches => "cannot mix labeled and unlabeled branches: {}".to_string(),
221
222 Self::UnclosedTree | Self::UnclosedSequence | Self::UnclosedAlternation => {
224 format!("{}; {{}}", self.fallback_message())
225 }
226
227 Self::InvalidTypeAnnotationSyntax => {
229 "type annotations use `::`, not `:` — {}".to_string()
230 }
231
232 Self::UnnamedDefNotLast => "only the last definition can be unnamed — {}".to_string(),
234
235 _ => format!("{}; {{}}", self.fallback_message()),
237 }
238 }
239
240 pub fn message(&self, msg: Option<&str>) -> String {
245 match msg {
246 None => self.fallback_message().to_string(),
247 Some(detail) => self.custom_message().replace("{}", detail),
248 }
249 }
250}
251
252#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
253pub enum Severity {
254 #[default]
255 Error,
256 Warning,
257}
258
259impl std::fmt::Display for Severity {
260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261 match self {
262 Severity::Error => write!(f, "error"),
263 Severity::Warning => write!(f, "warning"),
264 }
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Eq)]
269pub struct Fix {
270 pub(crate) replacement: String,
271 pub(crate) description: String,
272}
273
274impl Fix {
275 pub fn new(replacement: impl Into<String>, description: impl Into<String>) -> Self {
276 Self {
277 replacement: replacement.into(),
278 description: description.into(),
279 }
280 }
281}
282
283#[derive(Debug, Clone, PartialEq, Eq)]
284pub struct RelatedInfo {
285 pub(crate) range: TextRange,
286 pub(crate) message: String,
287}
288
289impl RelatedInfo {
290 pub fn new(range: TextRange, message: impl Into<String>) -> Self {
291 Self {
292 range,
293 message: message.into(),
294 }
295 }
296}
297
298#[derive(Debug, Clone, PartialEq, Eq)]
299pub(crate) struct DiagnosticMessage {
300 pub(crate) kind: DiagnosticKind,
301 pub(crate) range: TextRange,
303 pub(crate) suppression_range: TextRange,
308 pub(crate) message: String,
309 pub(crate) fix: Option<Fix>,
310 pub(crate) related: Vec<RelatedInfo>,
311 pub(crate) hints: Vec<String>,
312}
313
314impl DiagnosticMessage {
315 pub(crate) fn new(kind: DiagnosticKind, range: TextRange, message: impl Into<String>) -> Self {
316 Self {
317 kind,
318 range,
319 suppression_range: range,
320 message: message.into(),
321 fix: None,
322 related: Vec::new(),
323 hints: Vec::new(),
324 }
325 }
326
327 pub(crate) fn with_default_message(kind: DiagnosticKind, range: TextRange) -> Self {
328 Self::new(kind, range, kind.fallback_message())
329 }
330
331 pub(crate) fn severity(&self) -> Severity {
332 self.kind.default_severity()
333 }
334
335 pub(crate) fn is_error(&self) -> bool {
336 self.severity() == Severity::Error
337 }
338
339 pub(crate) fn is_warning(&self) -> bool {
340 self.severity() == Severity::Warning
341 }
342}
343
344impl std::fmt::Display for DiagnosticMessage {
345 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346 write!(
347 f,
348 "{} at {}..{}: {}",
349 self.severity(),
350 u32::from(self.range.start()),
351 u32::from(self.range.end()),
352 self.message
353 )?;
354 if let Some(fix) = &self.fix {
355 write!(f, " (fix: {})", fix.description)?;
356 }
357 for related in &self.related {
358 write!(
359 f,
360 " (related: {} at {}..{})",
361 related.message,
362 u32::from(related.range.start()),
363 u32::from(related.range.end())
364 )?;
365 }
366 for hint in &self.hints {
367 write!(f, " (hint: {})", hint)?;
368 }
369 Ok(())
370 }
371}