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 UncapturedOutputWithCaptures,
81 AmbiguousUncapturedOutputs,
82 DuplicateCaptureInScope,
83 IncompatibleCaptureTypes,
84 IncompatibleStructShapes,
85
86 PredicateOnNonLeaf,
88 EmptyRegex,
89 RegexBackreference,
90 RegexLookaround,
91 RegexNamedCapture,
92 RegexSyntaxError,
93
94 UnknownNodeType,
96 UnknownField,
97 FieldNotOnNodeType,
98 InvalidFieldChildType,
99 InvalidChildType,
100
101 UnnamedDef,
103}
104
105impl DiagnosticKind {
106 pub fn default_severity(&self) -> Severity {
108 match self {
109 Self::UnusedBranchLabels
110 | Self::TreeSitterSequenceSyntax
111 | Self::NegationSyntaxDeprecated => Severity::Warning,
112 _ => Severity::Error,
113 }
114 }
115
116 pub fn suppresses(&self, other: &DiagnosticKind) -> bool {
121 self < other
122 }
123
124 pub fn is_structural_error(&self) -> bool {
127 matches!(
128 self,
129 Self::UnclosedTree | Self::UnclosedSequence | Self::UnclosedAlternation | Self::UnclosedRegex
130 )
131 }
132
133 pub fn is_root_cause_error(&self) -> bool {
136 matches!(
137 self,
138 Self::ExpectedExpression
139 | Self::ExpectedTypeName
140 | Self::ExpectedCaptureName
141 | Self::ExpectedFieldName
142 | Self::ExpectedSubtype
143 | Self::ExpectedPredicateValue
144 )
145 }
146
147 pub fn is_consequence_error(&self) -> bool {
150 matches!(self, Self::UnnamedDef)
151 }
152
153 pub fn default_hint(&self) -> Option<&'static str> {
156 match self {
157 Self::ExpectedSubtype => Some("e.g., `expression/binary_expression`"),
158 Self::ExpectedTypeName => Some("e.g., `::MyType` or `::string`"),
159 Self::ExpectedFieldName => Some("e.g., `-value`"),
160 Self::EmptyTree => Some("use `(_)` to match any named node, or `_` for any node"),
161 Self::EmptyAnonymousNode => Some("use a valid anonymous node or remove it"),
162 Self::EmptySequence => Some("sequences must contain at least one expression"),
163 Self::EmptyAlternation => Some("alternations must contain at least one branch"),
164 Self::TreeSitterSequenceSyntax => Some("use `{...}` for sequences"),
165 Self::NegationSyntaxDeprecated => Some("use `-field` instead of `!field`"),
166 Self::MixedAltBranches => {
167 Some("use all labels for a tagged union, or none for a merged struct")
168 }
169 Self::RecursionNoEscape => {
170 Some("add a non-recursive branch to terminate: `[Base: ... Rec: (Self)]`")
171 }
172 Self::DirectRecursion => {
173 Some("recursive references must consume input before recursing")
174 }
175 Self::AnchorWithoutContext => Some("wrap in a named node: `(parent . (child))`"),
176 Self::AnchorInAlternation => Some("use `[{(a) . (b)} (c)]` to anchor within a branch"),
177 Self::UncapturedOutputWithCaptures => Some("add `@name` to capture the output"),
178 Self::AmbiguousUncapturedOutputs => {
179 Some("capture each expression explicitly: `(X) @x (Y) @y`")
180 }
181 _ => None,
182 }
183 }
184
185 pub fn fallback_message(&self) -> &'static str {
187 match self {
188 Self::UnclosedTree => "missing closing `)`",
190 Self::UnclosedSequence => "missing closing `}`",
191 Self::UnclosedAlternation => "missing closing `]`",
192 Self::UnclosedRegex => "missing closing `/` for regex",
193
194 Self::ExpectedExpression => "expected an expression",
196 Self::ExpectedTypeName => "expected type name",
197 Self::ExpectedCaptureName => "expected capture name",
198 Self::ExpectedFieldName => "expected field name",
199 Self::ExpectedSubtype => "expected subtype name",
200 Self::ExpectedPredicateValue => "expected string or regex after predicate operator",
201
202 Self::EmptyTree => "empty `()` is not allowed",
204 Self::EmptyAnonymousNode => "empty anonymous node",
205 Self::EmptySequence => "empty `{}` is not allowed",
206 Self::EmptyAlternation => "empty `[]` is not allowed",
207 Self::BareIdentifier => "bare identifier is not valid",
208 Self::InvalidSeparator => "unexpected separator",
209 Self::AnchorInAlternation => "anchors cannot appear directly in alternations",
210 Self::InvalidFieldEquals => "use `:` instead of `=`",
211 Self::InvalidSupertypeSyntax => "references cannot have supertypes",
212 Self::InvalidTypeAnnotationSyntax => "use `::` for type annotations",
213 Self::ErrorTakesNoArguments => "`(ERROR)` cannot have children",
214 Self::RefCannotHaveChildren => "references cannot have children",
215 Self::ErrorMissingOutsideParens => "special node requires parentheses",
216 Self::UnsupportedPredicate => "predicates are not supported",
217 Self::UnexpectedToken => "unexpected token",
218 Self::CaptureWithoutTarget => "capture has no target",
219 Self::LowercaseBranchLabel => "branch label must start with uppercase",
220
221 Self::CaptureNameHasDots => "capture names cannot contain `.`",
223 Self::CaptureNameHasHyphens => "capture names cannot contain `-`",
224 Self::CaptureNameUppercase => "capture names must be lowercase",
225 Self::DefNameLowercase => "definition names must start uppercase",
226 Self::DefNameHasSeparators => "definition names must be PascalCase",
227 Self::BranchLabelHasSeparators => "branch labels must be PascalCase",
228 Self::FieldNameHasDots => "field names cannot contain `.`",
229 Self::FieldNameHasHyphens => "field names cannot contain `-`",
230 Self::FieldNameUppercase => "field names must be lowercase",
231 Self::TypeNameInvalidChars => "type names cannot contain `.` or `-`",
232 Self::TreeSitterSequenceSyntax => "tree-sitter sequence syntax",
233 Self::NegationSyntaxDeprecated => "deprecated negation syntax",
234
235 Self::DuplicateDefinition => "duplicate definition",
237 Self::UndefinedReference => "undefined reference",
238 Self::MixedAltBranches => "cannot mix labeled and unlabeled branches",
239 Self::RecursionNoEscape => "infinite recursion: no escape path",
240 Self::DirectRecursion => "infinite recursion: cycle consumes no input",
241 Self::FieldSequenceValue => "field cannot match a sequence",
242 Self::AnchorWithoutContext => "boundary anchor requires parent node context",
243
244 Self::IncompatibleTypes => "incompatible types",
246 Self::MultiCaptureQuantifierNoName => {
247 "quantified expression with multiple captures requires a struct capture"
248 }
249 Self::UnusedBranchLabels => "branch labels have no effect without capture",
250 Self::StrictDimensionalityViolation => {
251 "quantifier with captures requires a struct capture"
252 }
253 Self::UncapturedOutputWithCaptures => {
254 "output-producing expression requires capture when siblings have captures"
255 }
256 Self::AmbiguousUncapturedOutputs => {
257 "multiple expressions produce output without capture"
258 }
259 Self::DuplicateCaptureInScope => "duplicate capture in scope",
260 Self::IncompatibleCaptureTypes => "incompatible capture types",
261 Self::IncompatibleStructShapes => "incompatible struct shapes",
262
263 Self::PredicateOnNonLeaf => "predicates match text content, but this node can contain children",
265 Self::EmptyRegex => "empty regex pattern",
266 Self::RegexBackreference => "backreferences are not supported in regex",
267 Self::RegexLookaround => "lookahead/lookbehind is not supported in regex",
268 Self::RegexNamedCapture => "named captures are not supported in regex",
269 Self::RegexSyntaxError => "invalid regex syntax",
270
271 Self::UnknownNodeType => "unknown node type",
273 Self::UnknownField => "unknown field",
274 Self::FieldNotOnNodeType => "field not valid on this node type",
275 Self::InvalidFieldChildType => "node type not valid for this field",
276 Self::InvalidChildType => "node type not valid as child",
277
278 Self::UnnamedDef => "definition must be named",
280 }
281 }
282
283 pub fn custom_message(&self) -> String {
285 match self {
286 Self::RefCannotHaveChildren => {
288 "`{}` is a reference and cannot have children".to_string()
289 }
290 Self::FieldSequenceValue => "field `{}` cannot match a sequence".to_string(),
291
292 Self::DuplicateDefinition => "`{}` is already defined".to_string(),
294 Self::UndefinedReference => "`{}` is not defined".to_string(),
295 Self::IncompatibleTypes => "incompatible types: {}".to_string(),
296
297 Self::StrictDimensionalityViolation => "{}".to_string(),
299 Self::DuplicateCaptureInScope => {
300 "capture `@{}` already defined in this scope".to_string()
301 }
302 Self::IncompatibleCaptureTypes => {
303 "capture `@{}` has incompatible types across branches".to_string()
304 }
305 Self::IncompatibleStructShapes => {
306 "capture `@{}` has incompatible struct fields across branches".to_string()
307 }
308
309 Self::UnknownNodeType => "`{}` is not a valid node type".to_string(),
311 Self::UnknownField => "`{}` is not a valid field".to_string(),
312 Self::FieldNotOnNodeType => "field `{}` is not valid on this node type".to_string(),
313 Self::InvalidFieldChildType => "node type `{}` is not valid for this field".to_string(),
314 Self::InvalidChildType => "`{}` cannot be a child of this node".to_string(),
315
316 Self::MixedAltBranches => "cannot mix labeled and unlabeled branches: {}".to_string(),
318
319 Self::UnclosedTree | Self::UnclosedSequence | Self::UnclosedAlternation => {
321 format!("{}; {{}}", self.fallback_message())
322 }
323
324 Self::InvalidTypeAnnotationSyntax => "use `::` for type annotations: {}".to_string(),
326
327 Self::UnnamedDef => self.fallback_message().to_string(),
329
330 _ => format!("{}: {{}}", self.fallback_message()),
332 }
333 }
334
335 pub fn message(&self, msg: Option<&str>) -> String {
340 match msg {
341 None => self.fallback_message().to_string(),
342 Some(detail) => self.custom_message().replace("{}", detail),
343 }
344 }
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
348pub enum Severity {
349 #[default]
350 Error,
351 Warning,
352}
353
354impl std::fmt::Display for Severity {
355 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356 match self {
357 Severity::Error => write!(f, "error"),
358 Severity::Warning => write!(f, "warning"),
359 }
360 }
361}
362
363#[derive(Debug, Clone, PartialEq, Eq)]
364pub struct Fix {
365 pub(crate) replacement: String,
366 pub(crate) description: String,
367}
368
369impl Fix {
370 pub fn new(replacement: impl Into<String>, description: impl Into<String>) -> Self {
371 Self {
372 replacement: replacement.into(),
373 description: description.into(),
374 }
375 }
376}
377
378#[derive(Debug, Clone, PartialEq, Eq)]
379pub struct RelatedInfo {
380 pub(crate) span: Span,
381 pub(crate) message: String,
382}
383
384impl RelatedInfo {
385 pub fn new(source: SourceId, range: TextRange, message: impl Into<String>) -> Self {
386 Self {
387 span: Span::new(source, range),
388 message: message.into(),
389 }
390 }
391}
392
393#[derive(Debug, Clone, PartialEq, Eq)]
394pub(crate) struct DiagnosticMessage {
395 pub(crate) kind: DiagnosticKind,
396 pub(crate) source: SourceId,
398 pub(crate) range: TextRange,
400 pub(crate) suppression_range: TextRange,
405 pub(crate) message: String,
406 pub(crate) fix: Option<Fix>,
407 pub(crate) related: Vec<RelatedInfo>,
408 pub(crate) hints: Vec<String>,
409}
410
411impl DiagnosticMessage {
412 pub(crate) fn new(
413 source: SourceId,
414 kind: DiagnosticKind,
415 range: TextRange,
416 message: impl Into<String>,
417 ) -> Self {
418 Self {
419 kind,
420 source,
421 range,
422 suppression_range: range,
423 message: message.into(),
424 fix: None,
425 related: Vec::new(),
426 hints: Vec::new(),
427 }
428 }
429
430 pub(crate) fn with_default_message(
431 source: SourceId,
432 kind: DiagnosticKind,
433 range: TextRange,
434 ) -> Self {
435 Self::new(source, kind, range, kind.fallback_message())
436 }
437
438 pub(crate) fn severity(&self) -> Severity {
439 self.kind.default_severity()
440 }
441
442 pub(crate) fn is_error(&self) -> bool {
443 self.severity() == Severity::Error
444 }
445
446 pub(crate) fn is_warning(&self) -> bool {
447 self.severity() == Severity::Warning
448 }
449}
450
451impl std::fmt::Display for DiagnosticMessage {
452 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
453 write!(
454 f,
455 "{} at {}..{}: {}",
456 self.severity(),
457 u32::from(self.range.start()),
458 u32::from(self.range.end()),
459 self.message
460 )?;
461 if let Some(fix) = &self.fix {
462 write!(f, " (fix: {})", fix.description)?;
463 }
464 for related in &self.related {
465 write!(
466 f,
467 " (related: {} at {}..{})",
468 related.message,
469 u32::from(related.span.range.start()),
470 u32::from(related.span.range.end())
471 )?;
472 }
473 for hint in &self.hints {
474 write!(f, " (hint: {})", hint)?;
475 }
476 Ok(())
477 }
478}