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 InconsistentIndentation(Span, usize, usize),
19 MissingAsterisk(Span),
20 MissingWhitespaceAfterAsterisk(Span),
21 MissingWhitespaceAfterOpeningAsterisk(Span),
22 MissingWhitespaceBeforeClosingAsterisk(Span),
23 ExpectedLine(Span),
24}
25
26impl HasSpan for ParseError {
27 fn span(&self) -> Span {
28 match self {
29 ParseError::InvalidTrivia(span)
30 | ParseError::UnclosedInlineTag(span)
31 | ParseError::UnclosedInlineCode(span)
32 | ParseError::UnclosedCodeBlock(span)
33 | ParseError::InvalidTagName(span)
34 | ParseError::InvalidAnnotationName(span)
35 | ParseError::UnclosedAnnotationArguments(span)
36 | ParseError::MalformedCodeBlock(span)
37 | ParseError::InvalidComment(span)
38 | ParseError::InconsistentIndentation(span, _, _)
39 | ParseError::MissingAsterisk(span)
40 | ParseError::MissingWhitespaceAfterAsterisk(span)
41 | ParseError::MissingWhitespaceAfterOpeningAsterisk(span)
42 | ParseError::MissingWhitespaceBeforeClosingAsterisk(span)
43 | ParseError::ExpectedLine(span) => *span,
44 }
45 }
46}
47
48impl std::error::Error for ParseError {}
49
50impl std::fmt::Display for ParseError {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 match self {
53 ParseError::InvalidTrivia(_) | ParseError::InvalidComment(_) => {
54 write!(f, "Invalid docblock format")
55 }
56 ParseError::UnclosedInlineTag(_) => write!(f, "Unclosed inline tag"),
57 ParseError::UnclosedInlineCode(_) => write!(f, "Unclosed inline code"),
58 ParseError::UnclosedCodeBlock(_) => write!(f, "Unclosed code block"),
59 ParseError::InvalidTagName(_) => write!(f, "Invalid tag name"),
60 ParseError::InvalidAnnotationName(_) => write!(f, "Invalid annotation name"),
61 ParseError::UnclosedAnnotationArguments(_) => write!(f, "Unclosed annotation arguments"),
62 ParseError::MalformedCodeBlock(_) => write!(f, "Malformed code block"),
63 ParseError::InconsistentIndentation(_, expected, actual) => {
64 write!(f, "Inconsistent indentation: expected {expected}, found {actual}")
65 }
66 ParseError::MissingAsterisk(_) => write!(f, "Missing leading asterisk on docblock line"),
67 ParseError::MissingWhitespaceAfterAsterisk(_) => {
68 write!(f, "Missing space after leading asterisk")
69 }
70 ParseError::MissingWhitespaceAfterOpeningAsterisk(_)
71 | ParseError::MissingWhitespaceBeforeClosingAsterisk(_) => {
72 write!(f, "Improperly formatted single-line docblock")
73 }
74 ParseError::ExpectedLine(_) => write!(f, "Unexpected end of docblock"),
75 }
76 }
77}
78
79impl ParseError {
80 pub fn note(&self) -> String {
81 match self {
82 ParseError::InvalidTrivia(_) | ParseError::InvalidComment(_) => {
83 "Docblocks must start with `/**` and end with `*/`.".to_string()
84 }
85 ParseError::UnclosedInlineTag(_) => {
86 "Inline tags like `{@see}` must be closed with a matching `}`.".to_string()
87 }
88 ParseError::UnclosedInlineCode(_) => {
89 "Inline code snippets must be enclosed in matching backticks (`).".to_string()
90 }
91 ParseError::UnclosedCodeBlock(_) => {
92 "Multi-line code blocks must be terminated with a closing ```.".to_string()
93 }
94 ParseError::InvalidTagName(_) => {
95 "Docblock tags like `@param` must contain only letters, numbers, and hyphens.".to_string()
96 }
97 ParseError::InvalidAnnotationName(_) => {
98 "Annotations must start with an uppercase letter, `_`, or `\\`.".to_string()
99 }
100 ParseError::UnclosedAnnotationArguments(_) => {
101 "Arguments for an annotation must be enclosed in parentheses `()`.".to_string()
102 }
103 ParseError::MalformedCodeBlock(_) => {
104 "A code block must start with ``` optionally followed by a language identifier.".to_string()
105 }
106 ParseError::InconsistentIndentation(_, expected, actual) => {
107 format!(
108 "This line has an indentation of {actual}, but {expected} was expected based on the first line."
109 )
110 }
111 ParseError::MissingAsterisk(_) => {
112 "Each line in a multi-line docblock should start with an aligned asterisk `*`.".to_string()
113 }
114 ParseError::MissingWhitespaceAfterAsterisk(_) => {
115 "A space is required after the leading `*` to separate it from the content.".to_string()
116 }
117 ParseError::MissingWhitespaceAfterOpeningAsterisk(_)
118 | ParseError::MissingWhitespaceBeforeClosingAsterisk(_) => {
119 "Single-line docblocks should have spaces padding the content, like `/** content */`.".to_string()
120 }
121 ParseError::ExpectedLine(_) => {
122 "A tag or description was expected here, but the docblock ended prematurely.".to_string()
123 }
124 }
125 }
126
127 pub fn help(&self) -> String {
128 match self {
129 ParseError::UnclosedInlineTag(_) => "Add a closing `}` to complete the inline tag.".to_string(),
130 ParseError::UnclosedInlineCode(_) => {
131 "Add a closing backtick ` ` ` to terminate the inline code.".to_string()
132 }
133 ParseError::UnclosedCodeBlock(_) => "Add a closing ``` to terminate the code block.".to_string(),
134 ParseError::InvalidTagName(_) => {
135 "Correct the tag name to use only valid characters (e.g., `@my-custom-tag`).".to_string()
136 }
137 ParseError::InvalidAnnotationName(_) => {
138 "Correct the annotation name to follow PSR-5 standards.".to_string()
139 }
140 ParseError::UnclosedAnnotationArguments(_) => {
141 "Add a closing `)` to complete the annotation's argument list.".to_string()
142 }
143 ParseError::InconsistentIndentation(_, _, _) => {
144 "Adjust the indentation to be consistent across all lines in the docblock.".to_string()
145 }
146 ParseError::MissingAsterisk(_) => "Add a leading `*` to the beginning of this line.".to_string(),
147 ParseError::MissingWhitespaceAfterAsterisk(_) => {
148 "Insert a space after the `*` at the beginning of the line.".to_string()
149 }
150 _ => "Review the docblock syntax to ensure it is correctly formatted.".to_string(),
151 }
152 }
153}