1use rowan::TextRange;
2
3use super::{SourceId, Span};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub enum DiagnosticKind {
19 UnclosedTree,
21 UnclosedSequence,
22 UnclosedAlternation,
23 UnclosedRegex,
24
25 ExpectedExpression,
27 ExpectedTypeName,
28 ExpectedCaptureName,
29 ExpectedFieldName,
30 ExpectedSubtype,
31 ExpectedPredicateValue,
32
33 EmptyTree,
35 EmptyAnonymousNode,
36 EmptySequence,
37 EmptyAlternation,
38 BareIdentifier,
39 InvalidSeparator,
40 AnchorInAlternation,
41 InvalidFieldEquals,
42 InvalidSupertypeSyntax,
43 InvalidTypeAnnotationSyntax,
44 ErrorTakesNoArguments,
45 RefCannotHaveChildren,
46 ErrorMissingOutsideParens,
47 UnsupportedPredicate,
48 UnexpectedToken,
49 CaptureWithoutTarget,
50 LowercaseBranchLabel,
51
52 CaptureNameHasDots,
54 CaptureNameHasHyphens,
55 CaptureNameUppercase,
56 DefNameLowercase,
57 DefNameHasSeparators,
58 BranchLabelHasSeparators,
59 FieldNameHasDots,
60 FieldNameHasHyphens,
61 FieldNameUppercase,
62 TypeNameInvalidChars,
63 TreeSitterSequenceSyntax,
64 NegationSyntaxDeprecated,
65
66 DuplicateDefinition,
68 UndefinedReference,
69 MixedAltBranches,
70 RecursionNoEscape,
71 DirectRecursion,
72 FieldSequenceValue,
73 AnchorWithoutContext,
74
75 IncompatibleTypes,
77 MultiCaptureQuantifierNoName,
78 UnusedBranchLabels,
79 StrictDimensionalityViolation,
80 MultiElementScalarCapture,
81 UncapturedOutputWithCaptures,
82 AmbiguousUncapturedOutputs,
83 DuplicateCaptureInScope,
84 IncompatibleCaptureTypes,
85 IncompatibleStructShapes,
86
87 PredicateOnNonLeaf,
89 EmptyRegex,
90 RegexBackreference,
91 RegexLookaround,
92 RegexNamedCapture,
93 RegexSyntaxError,
94
95 UnknownNodeType,
97 UnknownField,
98 FieldNotOnNodeType,
99 InvalidFieldChildType,
100 InvalidChildType,
101
102 UnnamedDef,
104}
105
106impl DiagnosticKind {
107 pub fn default_severity(&self) -> Severity {
109 match self {
110 Self::UnusedBranchLabels
111 | Self::TreeSitterSequenceSyntax
112 | Self::NegationSyntaxDeprecated => Severity::Warning,
113 _ => Severity::Error,
114 }
115 }
116
117 pub fn suppresses(&self, other: &DiagnosticKind) -> bool {
122 self < other
123 }
124
125 pub fn is_structural_error(&self) -> bool {
128 matches!(
129 self,
130 Self::UnclosedTree
131 | Self::UnclosedSequence
132 | Self::UnclosedAlternation
133 | Self::UnclosedRegex
134 )
135 }
136
137 pub fn is_root_cause_error(&self) -> bool {
140 matches!(
141 self,
142 Self::ExpectedExpression
143 | Self::ExpectedTypeName
144 | Self::ExpectedCaptureName
145 | Self::ExpectedFieldName
146 | Self::ExpectedSubtype
147 | Self::ExpectedPredicateValue
148 )
149 }
150
151 pub fn is_consequence_error(&self) -> bool {
154 matches!(self, Self::UnnamedDef)
155 }
156
157 pub fn default_hint(&self) -> Option<&'static str> {
160 match self {
161 Self::ExpectedSubtype => Some("e.g., `expression/binary_expression`"),
162 Self::ExpectedTypeName => Some("e.g., `::MyType` or `::string`"),
163 Self::ExpectedFieldName => Some("e.g., `-value`"),
164 Self::EmptyTree => Some("use `(_)` to match any named node, or `_` for any node"),
165 Self::EmptyAnonymousNode => Some("use a valid anonymous node or remove it"),
166 Self::EmptySequence => Some("sequences must contain at least one expression"),
167 Self::EmptyAlternation => Some("alternations must contain at least one branch"),
168 Self::TreeSitterSequenceSyntax => Some("use `{...}` for sequences"),
169 Self::NegationSyntaxDeprecated => Some("use `-field` instead of `!field`"),
170 Self::MixedAltBranches => {
171 Some("use all labels for a tagged union, or none for a merged struct")
172 }
173 Self::RecursionNoEscape => {
174 Some("add a non-recursive branch to terminate: `[Base: ... Rec: (Self)]`")
175 }
176 Self::DirectRecursion => {
177 Some("recursive references must consume input before recursing")
178 }
179 Self::AnchorWithoutContext => Some("wrap in a named node: `(parent . (child))`"),
180 Self::AnchorInAlternation => Some("use `[{(a) . (b)} (c)]` to anchor within a branch"),
181 Self::UncapturedOutputWithCaptures => Some("add `@name` to capture the output"),
182 Self::AmbiguousUncapturedOutputs => {
183 Some("capture each expression explicitly: `(X) @x (Y) @y`")
184 }
185 Self::MultiElementScalarCapture => {
186 Some("add internal captures: `{(a) @a (b) @b}* @items`")
187 }
188 _ => None,
189 }
190 }
191
192 pub fn fallback_message(&self) -> &'static str {
194 match self {
195 Self::UnclosedTree => "missing closing `)`",
197 Self::UnclosedSequence => "missing closing `}`",
198 Self::UnclosedAlternation => "missing closing `]`",
199 Self::UnclosedRegex => "missing closing `/` for regex",
200
201 Self::ExpectedExpression => "expected an expression",
203 Self::ExpectedTypeName => "expected type name",
204 Self::ExpectedCaptureName => "expected capture name",
205 Self::ExpectedFieldName => "expected field name",
206 Self::ExpectedSubtype => "expected subtype name",
207 Self::ExpectedPredicateValue => "expected string or regex after predicate operator",
208
209 Self::EmptyTree => "empty `()` is not allowed",
211 Self::EmptyAnonymousNode => "empty anonymous node",
212 Self::EmptySequence => "empty `{}` is not allowed",
213 Self::EmptyAlternation => "empty `[]` is not allowed",
214 Self::BareIdentifier => "bare identifier is not valid",
215 Self::InvalidSeparator => "unexpected separator",
216 Self::AnchorInAlternation => "anchors cannot appear directly in alternations",
217 Self::InvalidFieldEquals => "use `:` instead of `=`",
218 Self::InvalidSupertypeSyntax => "references cannot have supertypes",
219 Self::InvalidTypeAnnotationSyntax => "use `::` for type annotations",
220 Self::ErrorTakesNoArguments => "`(ERROR)` cannot have children",
221 Self::RefCannotHaveChildren => "references cannot have children",
222 Self::ErrorMissingOutsideParens => "special node requires parentheses",
223 Self::UnsupportedPredicate => "predicates are not supported",
224 Self::UnexpectedToken => "unexpected token",
225 Self::CaptureWithoutTarget => "capture has no target",
226 Self::LowercaseBranchLabel => "branch label must start with uppercase",
227
228 Self::CaptureNameHasDots => "capture names cannot contain `.`",
230 Self::CaptureNameHasHyphens => "capture names cannot contain `-`",
231 Self::CaptureNameUppercase => "capture names must be lowercase",
232 Self::DefNameLowercase => "definition names must start uppercase",
233 Self::DefNameHasSeparators => "definition names must be PascalCase",
234 Self::BranchLabelHasSeparators => "branch labels must be PascalCase",
235 Self::FieldNameHasDots => "field names cannot contain `.`",
236 Self::FieldNameHasHyphens => "field names cannot contain `-`",
237 Self::FieldNameUppercase => "field names must be lowercase",
238 Self::TypeNameInvalidChars => "type names cannot contain `.` or `-`",
239 Self::TreeSitterSequenceSyntax => "tree-sitter sequence syntax",
240 Self::NegationSyntaxDeprecated => "deprecated negation syntax",
241
242 Self::DuplicateDefinition => "duplicate definition",
244 Self::UndefinedReference => "undefined reference",
245 Self::MixedAltBranches => "cannot mix labeled and unlabeled branches",
246 Self::RecursionNoEscape => "infinite recursion: no escape path",
247 Self::DirectRecursion => "infinite recursion: cycle consumes no input",
248 Self::FieldSequenceValue => "field cannot match a sequence",
249 Self::AnchorWithoutContext => "boundary anchor requires parent node context",
250
251 Self::IncompatibleTypes => "incompatible types",
253 Self::MultiCaptureQuantifierNoName => {
254 "quantified expression with multiple captures requires a struct capture"
255 }
256 Self::UnusedBranchLabels => "branch labels have no effect without capture",
257 Self::StrictDimensionalityViolation => {
258 "quantifier with captures requires a struct capture"
259 }
260 Self::MultiElementScalarCapture => {
261 "cannot capture multi-element pattern as scalar array"
262 }
263 Self::UncapturedOutputWithCaptures => {
264 "output-producing expression requires capture when siblings have captures"
265 }
266 Self::AmbiguousUncapturedOutputs => {
267 "multiple expressions produce output without capture"
268 }
269 Self::DuplicateCaptureInScope => "duplicate capture in scope",
270 Self::IncompatibleCaptureTypes => "incompatible capture types",
271 Self::IncompatibleStructShapes => "incompatible struct shapes",
272
273 Self::PredicateOnNonLeaf => {
275 "predicates match text content, but this node can contain children"
276 }
277 Self::EmptyRegex => "empty regex pattern",
278 Self::RegexBackreference => "backreferences are not supported in regex",
279 Self::RegexLookaround => "lookahead/lookbehind is not supported in regex",
280 Self::RegexNamedCapture => "named captures are not supported in regex",
281 Self::RegexSyntaxError => "invalid regex syntax",
282
283 Self::UnknownNodeType => "unknown node type",
285 Self::UnknownField => "unknown field",
286 Self::FieldNotOnNodeType => "field not valid on this node type",
287 Self::InvalidFieldChildType => "node type not valid for this field",
288 Self::InvalidChildType => "node type not valid as child",
289
290 Self::UnnamedDef => "definition must be named",
292 }
293 }
294
295 pub fn custom_message(&self) -> String {
297 match self {
298 Self::RefCannotHaveChildren => {
300 "`{}` is a reference and cannot have children".to_string()
301 }
302 Self::FieldSequenceValue => "field `{}` cannot match a sequence".to_string(),
303
304 Self::DuplicateDefinition => "`{}` is already defined".to_string(),
306 Self::UndefinedReference => "`{}` is not defined".to_string(),
307 Self::IncompatibleTypes => "incompatible types: {}".to_string(),
308
309 Self::StrictDimensionalityViolation => "{}".to_string(),
311 Self::MultiElementScalarCapture => "{}".to_string(),
312 Self::DuplicateCaptureInScope => {
313 "capture `@{}` already defined in this scope".to_string()
314 }
315 Self::IncompatibleCaptureTypes => {
316 "capture `@{}` has incompatible types across branches".to_string()
317 }
318 Self::IncompatibleStructShapes => {
319 "capture `@{}` has incompatible struct fields across branches".to_string()
320 }
321
322 Self::UnknownNodeType => "`{}` is not a valid node type".to_string(),
324 Self::UnknownField => "`{}` is not a valid field".to_string(),
325 Self::FieldNotOnNodeType => "field `{}` is not valid on this node type".to_string(),
326 Self::InvalidFieldChildType => "node type `{}` is not valid for this field".to_string(),
327 Self::InvalidChildType => "`{}` cannot be a child of this node".to_string(),
328
329 Self::MixedAltBranches => "cannot mix labeled and unlabeled branches: {}".to_string(),
331
332 Self::UnclosedTree | Self::UnclosedSequence | Self::UnclosedAlternation => {
334 format!("{}; {{}}", self.fallback_message())
335 }
336
337 Self::InvalidTypeAnnotationSyntax => "use `::` for type annotations: {}".to_string(),
339
340 Self::UnnamedDef => self.fallback_message().to_string(),
342
343 _ => format!("{}: {{}}", self.fallback_message()),
345 }
346 }
347
348 pub fn message(&self, msg: Option<&str>) -> String {
353 match msg {
354 None => self.fallback_message().to_string(),
355 Some(detail) => self.custom_message().replace("{}", detail),
356 }
357 }
358}
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
361pub enum Severity {
362 #[default]
363 Error,
364 Warning,
365}
366
367impl std::fmt::Display for Severity {
368 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369 match self {
370 Severity::Error => write!(f, "error"),
371 Severity::Warning => write!(f, "warning"),
372 }
373 }
374}
375
376#[derive(Debug, Clone, PartialEq, Eq)]
377pub struct Fix {
378 pub(crate) replacement: String,
379 pub(crate) description: String,
380}
381
382impl Fix {
383 pub fn new(replacement: impl Into<String>, description: impl Into<String>) -> Self {
384 Self {
385 replacement: replacement.into(),
386 description: description.into(),
387 }
388 }
389}
390
391#[derive(Debug, Clone, PartialEq, Eq)]
392pub struct RelatedInfo {
393 pub(crate) span: Span,
394 pub(crate) message: String,
395}
396
397impl RelatedInfo {
398 pub fn new(source: SourceId, range: TextRange, message: impl Into<String>) -> Self {
399 Self {
400 span: Span::new(source, range),
401 message: message.into(),
402 }
403 }
404}
405
406#[derive(Debug, Clone, PartialEq, Eq)]
407pub(crate) struct DiagnosticMessage {
408 pub(crate) kind: DiagnosticKind,
409 pub(crate) source: SourceId,
411 pub(crate) range: TextRange,
413 pub(crate) suppression_range: TextRange,
418 pub(crate) message: String,
419 pub(crate) fix: Option<Fix>,
420 pub(crate) related: Vec<RelatedInfo>,
421 pub(crate) hints: Vec<String>,
422}
423
424impl DiagnosticMessage {
425 pub(crate) fn new(
426 source: SourceId,
427 kind: DiagnosticKind,
428 range: TextRange,
429 message: impl Into<String>,
430 ) -> Self {
431 Self {
432 kind,
433 source,
434 range,
435 suppression_range: range,
436 message: message.into(),
437 fix: None,
438 related: Vec::new(),
439 hints: Vec::new(),
440 }
441 }
442
443 pub(crate) fn with_default_message(
444 source: SourceId,
445 kind: DiagnosticKind,
446 range: TextRange,
447 ) -> Self {
448 Self::new(source, kind, range, kind.fallback_message())
449 }
450
451 pub(crate) fn severity(&self) -> Severity {
452 self.kind.default_severity()
453 }
454
455 pub(crate) fn is_error(&self) -> bool {
456 self.severity() == Severity::Error
457 }
458
459 pub(crate) fn is_warning(&self) -> bool {
460 self.severity() == Severity::Warning
461 }
462}
463
464impl std::fmt::Display for DiagnosticMessage {
465 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
466 write!(
467 f,
468 "{} at {}..{}: {}",
469 self.severity(),
470 u32::from(self.range.start()),
471 u32::from(self.range.end()),
472 self.message
473 )?;
474 if let Some(fix) = &self.fix {
475 write!(f, " (fix: {})", fix.description)?;
476 }
477 for related in &self.related {
478 write!(
479 f,
480 " (related: {} at {}..{})",
481 related.message,
482 u32::from(related.span.range.start()),
483 u32::from(related.span.range.end())
484 )?;
485 }
486 for hint in &self.hints {
487 write!(f, " (hint: {})", hint)?;
488 }
489 Ok(())
490 }
491}