use rowan::TextRange;
use super::{SourceId, Span};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DiagnosticKind {
UnclosedTree,
UnclosedSequence,
UnclosedAlternation,
UnclosedRegex,
ExpectedExpression,
ExpectedTypeName,
ExpectedCaptureName,
ExpectedFieldName,
ExpectedSubtype,
ExpectedPredicateValue,
EmptyTree,
EmptyAnonymousNode,
EmptySequence,
EmptyAlternation,
BareIdentifier,
InvalidSeparator,
AnchorInAlternation,
InvalidFieldEquals,
InvalidSupertypeSyntax,
InvalidTypeAnnotationSyntax,
ErrorTakesNoArguments,
RefCannotHaveChildren,
ErrorMissingOutsideParens,
UnsupportedPredicate,
UnexpectedToken,
CaptureWithoutTarget,
LowercaseBranchLabel,
CaptureNameHasDots,
CaptureNameHasHyphens,
CaptureNameUppercase,
DefNameLowercase,
DefNameHasSeparators,
BranchLabelHasSeparators,
FieldNameHasDots,
FieldNameHasHyphens,
FieldNameUppercase,
TypeNameInvalidChars,
TreeSitterSequenceSyntax,
NegationSyntaxDeprecated,
DuplicateDefinition,
UndefinedReference,
MixedAltBranches,
RecursionNoEscape,
DirectRecursion,
FieldSequenceValue,
AnchorWithoutContext,
IncompatibleTypes,
MultiCaptureQuantifierNoName,
UnusedBranchLabels,
StrictDimensionalityViolation,
MultiElementScalarCapture,
UncapturedOutputWithCaptures,
AmbiguousUncapturedOutputs,
DuplicateCaptureInScope,
IncompatibleCaptureTypes,
IncompatibleStructShapes,
PredicateOnNonLeaf,
EmptyRegex,
RegexBackreference,
RegexLookaround,
RegexNamedCapture,
RegexSyntaxError,
UnknownNodeType,
UnknownField,
FieldNotOnNodeType,
InvalidFieldChildType,
InvalidChildType,
UnnamedDef,
}
impl DiagnosticKind {
pub fn default_severity(&self) -> Severity {
match self {
Self::UnusedBranchLabels
| Self::TreeSitterSequenceSyntax
| Self::NegationSyntaxDeprecated => Severity::Warning,
_ => Severity::Error,
}
}
pub fn suppresses(&self, other: &DiagnosticKind) -> bool {
self < other
}
pub fn is_structural_error(&self) -> bool {
matches!(
self,
Self::UnclosedTree
| Self::UnclosedSequence
| Self::UnclosedAlternation
| Self::UnclosedRegex
)
}
pub fn is_root_cause_error(&self) -> bool {
matches!(
self,
Self::ExpectedExpression
| Self::ExpectedTypeName
| Self::ExpectedCaptureName
| Self::ExpectedFieldName
| Self::ExpectedSubtype
| Self::ExpectedPredicateValue
)
}
pub fn is_consequence_error(&self) -> bool {
matches!(self, Self::UnnamedDef)
}
pub fn default_hint(&self) -> Option<&'static str> {
match self {
Self::ExpectedSubtype => Some("e.g., `expression/binary_expression`"),
Self::ExpectedTypeName => Some("e.g., `::MyType` or `::string`"),
Self::ExpectedFieldName => Some("e.g., `-value`"),
Self::EmptyTree => Some("use `(_)` to match any named node, or `_` for any node"),
Self::EmptyAnonymousNode => Some("use a valid anonymous node or remove it"),
Self::EmptySequence => Some("sequences must contain at least one expression"),
Self::EmptyAlternation => Some("alternations must contain at least one branch"),
Self::TreeSitterSequenceSyntax => Some("use `{...}` for sequences"),
Self::NegationSyntaxDeprecated => Some("use `-field` instead of `!field`"),
Self::MixedAltBranches => {
Some("use all labels for a tagged union, or none for a merged struct")
}
Self::RecursionNoEscape => {
Some("add a non-recursive branch to terminate: `[Base: ... Rec: (Self)]`")
}
Self::DirectRecursion => {
Some("recursive references must consume input before recursing")
}
Self::AnchorWithoutContext => Some("wrap in a named node: `(parent . (child))`"),
Self::AnchorInAlternation => Some("use `[{(a) . (b)} (c)]` to anchor within a branch"),
Self::UncapturedOutputWithCaptures => Some("add `@name` to capture the output"),
Self::AmbiguousUncapturedOutputs => {
Some("capture each expression explicitly: `(X) @x (Y) @y`")
}
Self::MultiElementScalarCapture => {
Some("add internal captures: `{(a) @a (b) @b}* @items`")
}
_ => None,
}
}
pub fn fallback_message(&self) -> &'static str {
match self {
Self::UnclosedTree => "missing closing `)`",
Self::UnclosedSequence => "missing closing `}`",
Self::UnclosedAlternation => "missing closing `]`",
Self::UnclosedRegex => "missing closing `/` for regex",
Self::ExpectedExpression => "expected an expression",
Self::ExpectedTypeName => "expected type name",
Self::ExpectedCaptureName => "expected capture name",
Self::ExpectedFieldName => "expected field name",
Self::ExpectedSubtype => "expected subtype name",
Self::ExpectedPredicateValue => "expected string or regex after predicate operator",
Self::EmptyTree => "empty `()` is not allowed",
Self::EmptyAnonymousNode => "empty anonymous node",
Self::EmptySequence => "empty `{}` is not allowed",
Self::EmptyAlternation => "empty `[]` is not allowed",
Self::BareIdentifier => "bare identifier is not valid",
Self::InvalidSeparator => "unexpected separator",
Self::AnchorInAlternation => "anchors cannot appear directly in alternations",
Self::InvalidFieldEquals => "use `:` instead of `=`",
Self::InvalidSupertypeSyntax => "references cannot have supertypes",
Self::InvalidTypeAnnotationSyntax => "use `::` for type annotations",
Self::ErrorTakesNoArguments => "`(ERROR)` cannot have children",
Self::RefCannotHaveChildren => "references cannot have children",
Self::ErrorMissingOutsideParens => "special node requires parentheses",
Self::UnsupportedPredicate => "predicates are not supported",
Self::UnexpectedToken => "unexpected token",
Self::CaptureWithoutTarget => "capture has no target",
Self::LowercaseBranchLabel => "branch label must start with uppercase",
Self::CaptureNameHasDots => "capture names cannot contain `.`",
Self::CaptureNameHasHyphens => "capture names cannot contain `-`",
Self::CaptureNameUppercase => "capture names must be lowercase",
Self::DefNameLowercase => "definition names must start uppercase",
Self::DefNameHasSeparators => "definition names must be PascalCase",
Self::BranchLabelHasSeparators => "branch labels must be PascalCase",
Self::FieldNameHasDots => "field names cannot contain `.`",
Self::FieldNameHasHyphens => "field names cannot contain `-`",
Self::FieldNameUppercase => "field names must be lowercase",
Self::TypeNameInvalidChars => "type names cannot contain `.` or `-`",
Self::TreeSitterSequenceSyntax => "tree-sitter sequence syntax",
Self::NegationSyntaxDeprecated => "deprecated negation syntax",
Self::DuplicateDefinition => "duplicate definition",
Self::UndefinedReference => "undefined reference",
Self::MixedAltBranches => "cannot mix labeled and unlabeled branches",
Self::RecursionNoEscape => "infinite recursion: no escape path",
Self::DirectRecursion => "infinite recursion: cycle consumes no input",
Self::FieldSequenceValue => "field cannot match a sequence",
Self::AnchorWithoutContext => "boundary anchor requires parent node context",
Self::IncompatibleTypes => "incompatible types",
Self::MultiCaptureQuantifierNoName => {
"quantified expression with multiple captures requires a struct capture"
}
Self::UnusedBranchLabels => "branch labels have no effect without capture",
Self::StrictDimensionalityViolation => {
"quantifier with captures requires a struct capture"
}
Self::MultiElementScalarCapture => {
"cannot capture multi-element pattern as scalar array"
}
Self::UncapturedOutputWithCaptures => {
"output-producing expression requires capture when siblings have captures"
}
Self::AmbiguousUncapturedOutputs => {
"multiple expressions produce output without capture"
}
Self::DuplicateCaptureInScope => "duplicate capture in scope",
Self::IncompatibleCaptureTypes => "incompatible capture types",
Self::IncompatibleStructShapes => "incompatible struct shapes",
Self::PredicateOnNonLeaf => {
"predicates match text content, but this node can contain children"
}
Self::EmptyRegex => "empty regex pattern",
Self::RegexBackreference => "backreferences are not supported in regex",
Self::RegexLookaround => "lookahead/lookbehind is not supported in regex",
Self::RegexNamedCapture => "named captures are not supported in regex",
Self::RegexSyntaxError => "invalid regex syntax",
Self::UnknownNodeType => "unknown node type",
Self::UnknownField => "unknown field",
Self::FieldNotOnNodeType => "field not valid on this node type",
Self::InvalidFieldChildType => "node type not valid for this field",
Self::InvalidChildType => "node type not valid as child",
Self::UnnamedDef => "definition must be named",
}
}
pub fn custom_message(&self) -> String {
match self {
Self::RefCannotHaveChildren => {
"`{}` is a reference and cannot have children".to_string()
}
Self::FieldSequenceValue => "field `{}` cannot match a sequence".to_string(),
Self::DuplicateDefinition => "`{}` is already defined".to_string(),
Self::UndefinedReference => "`{}` is not defined".to_string(),
Self::IncompatibleTypes => "incompatible types: {}".to_string(),
Self::StrictDimensionalityViolation => "{}".to_string(),
Self::MultiElementScalarCapture => "{}".to_string(),
Self::DuplicateCaptureInScope => {
"capture `@{}` already defined in this scope".to_string()
}
Self::IncompatibleCaptureTypes => {
"capture `@{}` has incompatible types across branches".to_string()
}
Self::IncompatibleStructShapes => {
"capture `@{}` has incompatible struct fields across branches".to_string()
}
Self::UnknownNodeType => "`{}` is not a valid node type".to_string(),
Self::UnknownField => "`{}` is not a valid field".to_string(),
Self::FieldNotOnNodeType => "field `{}` is not valid on this node type".to_string(),
Self::InvalidFieldChildType => "node type `{}` is not valid for this field".to_string(),
Self::InvalidChildType => "`{}` cannot be a child of this node".to_string(),
Self::MixedAltBranches => "cannot mix labeled and unlabeled branches: {}".to_string(),
Self::UnclosedTree | Self::UnclosedSequence | Self::UnclosedAlternation => {
format!("{}; {{}}", self.fallback_message())
}
Self::InvalidTypeAnnotationSyntax => "use `::` for type annotations: {}".to_string(),
Self::UnnamedDef => self.fallback_message().to_string(),
_ => format!("{}: {{}}", self.fallback_message()),
}
}
pub fn message(&self, msg: Option<&str>) -> String {
match msg {
None => self.fallback_message().to_string(),
Some(detail) => self.custom_message().replace("{}", detail),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Severity {
#[default]
Error,
Warning,
}
impl std::fmt::Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Severity::Error => write!(f, "error"),
Severity::Warning => write!(f, "warning"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Fix {
pub(crate) replacement: String,
pub(crate) description: String,
}
impl Fix {
pub fn new(replacement: impl Into<String>, description: impl Into<String>) -> Self {
Self {
replacement: replacement.into(),
description: description.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RelatedInfo {
pub(crate) span: Span,
pub(crate) message: String,
}
impl RelatedInfo {
pub fn new(source: SourceId, range: TextRange, message: impl Into<String>) -> Self {
Self {
span: Span::new(source, range),
message: message.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DiagnosticMessage {
pub(crate) kind: DiagnosticKind,
pub(crate) source: SourceId,
pub(crate) range: TextRange,
pub(crate) suppression_range: TextRange,
pub(crate) message: String,
pub(crate) fix: Option<Fix>,
pub(crate) related: Vec<RelatedInfo>,
pub(crate) hints: Vec<String>,
}
impl DiagnosticMessage {
pub(crate) fn new(
source: SourceId,
kind: DiagnosticKind,
range: TextRange,
message: impl Into<String>,
) -> Self {
Self {
kind,
source,
range,
suppression_range: range,
message: message.into(),
fix: None,
related: Vec::new(),
hints: Vec::new(),
}
}
pub(crate) fn with_default_message(
source: SourceId,
kind: DiagnosticKind,
range: TextRange,
) -> Self {
Self::new(source, kind, range, kind.fallback_message())
}
pub(crate) fn severity(&self) -> Severity {
self.kind.default_severity()
}
pub(crate) fn is_error(&self) -> bool {
self.severity() == Severity::Error
}
pub(crate) fn is_warning(&self) -> bool {
self.severity() == Severity::Warning
}
}
impl std::fmt::Display for DiagnosticMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} at {}..{}: {}",
self.severity(),
u32::from(self.range.start()),
u32::from(self.range.end()),
self.message
)?;
if let Some(fix) = &self.fix {
write!(f, " (fix: {})", fix.description)?;
}
for related in &self.related {
write!(
f,
" (related: {} at {}..{})",
related.message,
u32::from(related.span.range.start()),
u32::from(related.span.range.end())
)?;
}
for hint in &self.hints {
write!(f, " (hint: {})", hint)?;
}
Ok(())
}
}