1use serde::Deserialize;
2use serde::Serialize;
3
4use mago_span::HasSpan;
5use mago_span::Span;
6
7#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
8pub enum ParseError {
9 InvalidTrivia(Span),
10 UnclosedInlineTag(Span),
11 UnclosedInlineCode(Span),
12 UnclosedCodeBlock(Span),
13 InvalidTagName(Span),
14 InvalidAnnotationName(Span),
15 UnclosedAnnotationArguments(Span),
16 MalformedCodeBlock(Span),
17 InvalidComment(Span),
18 ExpectedLine(Span),
19}
20
21impl HasSpan for ParseError {
22 fn span(&self) -> Span {
23 match self {
24 ParseError::InvalidTrivia(span)
25 | ParseError::UnclosedInlineTag(span)
26 | ParseError::UnclosedInlineCode(span)
27 | ParseError::UnclosedCodeBlock(span)
28 | ParseError::InvalidTagName(span)
29 | ParseError::InvalidAnnotationName(span)
30 | ParseError::UnclosedAnnotationArguments(span)
31 | ParseError::MalformedCodeBlock(span)
32 | ParseError::InvalidComment(span)
33 | ParseError::ExpectedLine(span) => *span,
34 }
35 }
36}
37
38impl std::error::Error for ParseError {}
39
40impl std::fmt::Display for ParseError {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 match self {
43 ParseError::InvalidTrivia(_) | ParseError::InvalidComment(_) => {
44 write!(f, "Invalid docblock format")
45 }
46 ParseError::UnclosedInlineTag(_) => write!(f, "Unclosed inline tag"),
47 ParseError::UnclosedInlineCode(_) => write!(f, "Unclosed inline code"),
48 ParseError::UnclosedCodeBlock(_) => write!(f, "Unclosed code block"),
49 ParseError::InvalidTagName(_) => write!(f, "Invalid tag name"),
50 ParseError::InvalidAnnotationName(_) => write!(f, "Invalid annotation name"),
51 ParseError::UnclosedAnnotationArguments(_) => write!(f, "Unclosed annotation arguments"),
52 ParseError::MalformedCodeBlock(_) => write!(f, "Malformed code block"),
53 ParseError::ExpectedLine(_) => write!(f, "Unexpected end of docblock"),
54 }
55 }
56}
57
58impl ParseError {
59 pub fn note(&self) -> String {
60 match self {
61 ParseError::InvalidTrivia(_) | ParseError::InvalidComment(_) => {
62 "Docblocks must start with `/**` and end with `*/`.".to_string()
63 }
64 ParseError::UnclosedInlineTag(_) => {
65 "Inline tags like `{@see}` must be closed with a matching `}`.".to_string()
66 }
67 ParseError::UnclosedInlineCode(_) => {
68 "Inline code snippets must be enclosed in matching backticks (`).".to_string()
69 }
70 ParseError::UnclosedCodeBlock(_) => {
71 "Multi-line code blocks must be terminated with a closing ```.".to_string()
72 }
73 ParseError::InvalidTagName(_) => {
74 "Docblock tags like `@param` must contain only letters, numbers, hyphens, and colons.".to_string()
75 }
76 ParseError::InvalidAnnotationName(_) => {
77 "Annotations must start with an uppercase letter, `_`, or `\\`.".to_string()
78 }
79 ParseError::UnclosedAnnotationArguments(_) => {
80 "Arguments for an annotation must be enclosed in parentheses `()`.".to_string()
81 }
82 ParseError::MalformedCodeBlock(_) => {
83 "A code block must start with ``` optionally followed by a language identifier.".to_string()
84 }
85 ParseError::ExpectedLine(_) => {
86 "A tag or description was expected here, but the docblock ended prematurely.".to_string()
87 }
88 }
89 }
90
91 pub fn help(&self) -> String {
92 match self {
93 ParseError::UnclosedInlineTag(_) => "Add a closing `}` to complete the inline tag.".to_string(),
94 ParseError::UnclosedInlineCode(_) => {
95 "Add a closing backtick ` ` ` to terminate the inline code.".to_string()
96 }
97 ParseError::UnclosedCodeBlock(_) => "Add a closing ``` to terminate the code block.".to_string(),
98 ParseError::InvalidTagName(_) => {
99 "Correct the tag name to use only valid characters (e.g., `@my-custom-tag`).".to_string()
100 }
101 ParseError::InvalidAnnotationName(_) => {
102 "Correct the annotation name to follow PSR-5 standards.".to_string()
103 }
104 ParseError::UnclosedAnnotationArguments(_) => {
105 "Add a closing `)` to complete the annotation's argument list.".to_string()
106 }
107 _ => "Review the docblock syntax to ensure it is correctly formatted.".to_string(),
108 }
109 }
110}