mago_docblock/
error.rs

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    InvalidTypeTag(Span, String),
20    InvalidImportTypeTag(Span, String),
21    InvalidTemplateTag(Span, String),
22    InvalidParameterTag(Span, String),
23    InvalidReturnTag(Span, String),
24    InvalidPropertyTag(Span, String),
25    InvalidMethodTag(Span, String),
26    InvalidThrowsTag(Span, String),
27    InvalidAssertionTag(Span, String),
28    InvalidVarTag(Span, String),
29    InvalidWhereTag(Span, String),
30    InvalidParameterOutTag(Span, String),
31}
32
33impl HasSpan for ParseError {
34    fn span(&self) -> Span {
35        match self {
36            ParseError::InvalidTrivia(span)
37            | ParseError::UnclosedInlineTag(span)
38            | ParseError::UnclosedInlineCode(span)
39            | ParseError::UnclosedCodeBlock(span)
40            | ParseError::InvalidTagName(span)
41            | ParseError::InvalidAnnotationName(span)
42            | ParseError::UnclosedAnnotationArguments(span)
43            | ParseError::MalformedCodeBlock(span)
44            | ParseError::InvalidComment(span)
45            | ParseError::ExpectedLine(span)
46            | ParseError::InvalidTypeTag(span, _)
47            | ParseError::InvalidImportTypeTag(span, _)
48            | ParseError::InvalidTemplateTag(span, _)
49            | ParseError::InvalidParameterTag(span, _)
50            | ParseError::InvalidReturnTag(span, _)
51            | ParseError::InvalidPropertyTag(span, _)
52            | ParseError::InvalidMethodTag(span, _)
53            | ParseError::InvalidThrowsTag(span, _)
54            | ParseError::InvalidAssertionTag(span, _)
55            | ParseError::InvalidVarTag(span, _)
56            | ParseError::InvalidWhereTag(span, _)
57            | ParseError::InvalidParameterOutTag(span, _) => *span,
58        }
59    }
60}
61
62impl std::error::Error for ParseError {}
63
64impl std::fmt::Display for ParseError {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        match self {
67            ParseError::InvalidTrivia(_) | ParseError::InvalidComment(_) => {
68                write!(f, "Invalid docblock format")
69            }
70            ParseError::UnclosedInlineTag(_) => write!(f, "Unclosed inline tag"),
71            ParseError::UnclosedInlineCode(_) => write!(f, "Unclosed inline code"),
72            ParseError::UnclosedCodeBlock(_) => write!(f, "Unclosed code block"),
73            ParseError::InvalidTagName(_) => write!(f, "Invalid tag name"),
74            ParseError::InvalidAnnotationName(_) => write!(f, "Invalid annotation name"),
75            ParseError::UnclosedAnnotationArguments(_) => write!(f, "Unclosed annotation arguments"),
76            ParseError::MalformedCodeBlock(_) => write!(f, "Malformed code block"),
77            ParseError::ExpectedLine(_) => write!(f, "Unexpected end of docblock"),
78            ParseError::InvalidTypeTag(_, msg) => write!(f, "{msg}"),
79            ParseError::InvalidImportTypeTag(_, msg) => write!(f, "{msg}"),
80            ParseError::InvalidTemplateTag(_, msg) => write!(f, "{msg}"),
81            ParseError::InvalidParameterTag(_, msg) => write!(f, "{msg}"),
82            ParseError::InvalidReturnTag(_, msg) => write!(f, "{msg}"),
83            ParseError::InvalidPropertyTag(_, msg) => write!(f, "{msg}"),
84            ParseError::InvalidMethodTag(_, msg) => write!(f, "{msg}"),
85            ParseError::InvalidThrowsTag(_, msg) => write!(f, "{msg}"),
86            ParseError::InvalidAssertionTag(_, msg) => write!(f, "{msg}"),
87            ParseError::InvalidVarTag(_, msg) => write!(f, "{msg}"),
88            ParseError::InvalidWhereTag(_, msg) => write!(f, "{msg}"),
89            ParseError::InvalidParameterOutTag(_, msg) => write!(f, "{msg}"),
90        }
91    }
92}
93
94impl ParseError {
95    #[must_use]
96    pub fn note(&self) -> String {
97        match self {
98            ParseError::InvalidTrivia(_) | ParseError::InvalidComment(_) => {
99                "Docblocks must start with `/**` and end with `*/`.".to_string()
100            }
101            ParseError::UnclosedInlineTag(_) => {
102                "Inline tags like `{@see}` must be closed with a matching `}`.".to_string()
103            }
104            ParseError::UnclosedInlineCode(_) => {
105                "Inline code snippets must be enclosed in matching backticks (`).".to_string()
106            }
107            ParseError::UnclosedCodeBlock(_) => {
108                "Multi-line code blocks must be terminated with a closing ```.".to_string()
109            }
110            ParseError::InvalidTagName(_) => {
111                "Docblock tags like `@param` must contain only letters, numbers, hyphens, and colons.".to_string()
112            }
113            ParseError::InvalidAnnotationName(_) => {
114                "Annotations must start with an uppercase letter, `_`, or `\\`.".to_string()
115            }
116            ParseError::UnclosedAnnotationArguments(_) => {
117                "Arguments for an annotation must be enclosed in parentheses `()`.".to_string()
118            }
119            ParseError::MalformedCodeBlock(_) => {
120                "A code block must start with ``` optionally followed by a language identifier.".to_string()
121            }
122            ParseError::ExpectedLine(_) => {
123                "A tag or description was expected here, but the docblock ended prematurely.".to_string()
124            }
125            ParseError::InvalidTypeTag(_, _) => "Type alias must have name followed by type definition".to_string(),
126            ParseError::InvalidImportTypeTag(_, _) => {
127                "Import must have type name, `from` keyword, and class name".to_string()
128            }
129            ParseError::InvalidTemplateTag(_, _) => "Template must have parameter name".to_string(),
130            ParseError::InvalidParameterTag(_, _) => "Parameter must have type followed by variable name".to_string(),
131            ParseError::InvalidReturnTag(_, _) => "Return must have valid type".to_string(),
132            ParseError::InvalidPropertyTag(_, _) => "Property must have type and/or variable name".to_string(),
133            ParseError::InvalidMethodTag(_, _) => "Method must have return type, name, and parameter list".to_string(),
134            ParseError::InvalidThrowsTag(_, _) => "Throws must have exception type".to_string(),
135            ParseError::InvalidAssertionTag(_, _) => "Assertion must have type followed by variable name".to_string(),
136            ParseError::InvalidVarTag(_, _) => "Variable must have type".to_string(),
137            ParseError::InvalidWhereTag(_, _) => {
138                "Template constraint must have parameter name, `is` or `:`, and type".to_string()
139            }
140            ParseError::InvalidParameterOutTag(_, _) => {
141                "Output parameter must have type followed by variable name".to_string()
142            }
143        }
144    }
145
146    #[must_use]
147    pub fn help(&self) -> String {
148        match self {
149            ParseError::UnclosedInlineTag(_) => "Add a closing `}` to complete the inline tag.".to_string(),
150            ParseError::UnclosedInlineCode(_) => {
151                "Add a closing backtick ` ` ` to terminate the inline code.".to_string()
152            }
153            ParseError::UnclosedCodeBlock(_) => "Add a closing ``` to terminate the code block.".to_string(),
154            ParseError::InvalidTagName(_) => {
155                "Correct the tag name to use only valid characters (e.g., `@my-custom-tag`).".to_string()
156            }
157            ParseError::InvalidAnnotationName(_) => {
158                "Correct the annotation name to follow PSR-5 standards.".to_string()
159            }
160            ParseError::UnclosedAnnotationArguments(_) => {
161                "Add a closing `)` to complete the annotation's argument list.".to_string()
162            }
163            ParseError::InvalidTypeTag(_, _) => {
164                "Add type definition after alias name (can span multiple lines)".to_string()
165            }
166            ParseError::InvalidImportTypeTag(_, _) => {
167                "Ensure type name is followed by `from` and a valid class name".to_string()
168            }
169            ParseError::InvalidTemplateTag(_, _) => "Provide a valid template parameter name".to_string(),
170            ParseError::InvalidParameterTag(_, _) => {
171                "Ensure type is followed by a valid parameter name (e.g., `$param`)".to_string()
172            }
173            ParseError::InvalidReturnTag(_, _) => "Provide a valid return type".to_string(),
174            ParseError::InvalidPropertyTag(_, _) => {
175                "Ensure property has valid type and/or variable name (e.g., `$prop`)".to_string()
176            }
177            ParseError::InvalidMethodTag(_, _) => "Provide return type, method name, and parameter list".to_string(),
178            ParseError::InvalidThrowsTag(_, _) => "Provide a valid exception class name".to_string(),
179            ParseError::InvalidAssertionTag(_, _) => {
180                "Ensure type is followed by a valid variable name (e.g., `$var`)".to_string()
181            }
182            ParseError::InvalidVarTag(_, _) => "Provide a valid type for the variable".to_string(),
183            ParseError::InvalidWhereTag(_, _) => {
184                "Ensure template name is followed by `is` or `:` and a type".to_string()
185            }
186            ParseError::InvalidParameterOutTag(_, _) => {
187                "Ensure type is followed by a valid parameter name (e.g., `$param`)".to_string()
188            }
189            _ => "Review the docblock syntax to ensure it is correctly formatted.".to_string(),
190        }
191    }
192}