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}