Skip to main content

vize_relief/
errors.rs

1//! Compiler error types and codes.
2
3use crate::SourceLocation;
4use thiserror::Error;
5use vize_carton::{CompactString, ToCompactString};
6
7/// Compiler error
8#[derive(Debug, Clone, Error)]
9#[error("{message}")]
10pub struct CompilerError {
11    pub code: ErrorCode,
12    pub message: CompactString,
13    pub loc: Option<SourceLocation>,
14}
15
16impl CompilerError {
17    pub fn new(code: ErrorCode, loc: Option<SourceLocation>) -> Self {
18        Self {
19            message: code.message().to_compact_string(),
20            code,
21            loc,
22        }
23    }
24
25    pub fn with_message(
26        code: ErrorCode,
27        message: impl Into<CompactString>,
28        loc: Option<SourceLocation>,
29    ) -> Self {
30        Self {
31            code,
32            message: message.into(),
33            loc,
34        }
35    }
36}
37
38/// Error codes for compiler errors
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40#[repr(u16)]
41pub enum ErrorCode {
42    // Parse errors
43    AbruptClosingOfEmptyComment = 0,
44    CdataInHtmlContent = 1,
45    DuplicateAttribute = 2,
46    EndTagWithAttributes = 3,
47    EndTagWithTrailingSolidus = 4,
48    EofBeforeTagName = 5,
49    EofInCdata = 6,
50    EofInComment = 7,
51    EofInScriptHtmlCommentLikeText = 8,
52    EofInTag = 9,
53    IncorrectlyClosedComment = 10,
54    IncorrectlyOpenedComment = 11,
55    InvalidFirstCharacterOfTagName = 12,
56    MissingAttributeValue = 13,
57    MissingEndTagName = 14,
58    MissingWhitespaceBetweenAttributes = 15,
59    NestedComment = 16,
60    UnexpectedCharacterInAttributeName = 17,
61    UnexpectedCharacterInUnquotedAttributeValue = 18,
62    UnexpectedEqualsSignBeforeAttributeName = 19,
63    UnexpectedNullCharacter = 20,
64    UnexpectedQuestionMarkInsteadOfTagName = 21,
65    UnexpectedSolidusInTag = 22,
66
67    // Vue-specific parse errors
68    InvalidEndTag = 23,
69    MissingEndTag = 24,
70    MissingInterpolationEnd = 25,
71    MissingDynamicDirectiveArgumentEnd = 26,
72    MissingDirectiveName = 27,
73    MissingDirectiveModifier = 28,
74
75    // Transform errors
76    VIfNoExpression = 29,
77    VIfSameKey = 30,
78    VElseNoAdjacentIf = 31,
79    VForNoExpression = 32,
80    VForMalformedExpression = 33,
81    VForTemplateKeyPlacement = 34,
82    VBindNoExpression = 35,
83    VBindSameNameShorthand = 36,
84    VOnNoExpression = 37,
85    VSlotUnexpectedDirectiveOnSlotOutlet = 38,
86    VSlotMixedSlotUsage = 39,
87    VSlotDuplicateSlotNames = 40,
88    VSlotExtraneousDefaultSlotChildren = 41,
89    VSlotMisplaced = 42,
90    VModelNoExpression = 43,
91    VModelMalformedExpression = 44,
92    VModelOnScope = 45,
93    VModelOnProps = 46,
94    VModelArgOnElement = 47,
95    VShowNoExpression = 48,
96
97    // Generic errors
98    PrefixIdNotSupported = 49,
99    ModuleModeNotSupported = 50,
100    CacheHandlerNotSupported = 51,
101    ScopeIdNotSupported = 52,
102
103    // Extended errors
104    UnhandledCodePath = 100,
105    ExtendPoint = 1000,
106}
107
108impl ErrorCode {
109    pub fn message(&self) -> &'static str {
110        match self {
111            Self::AbruptClosingOfEmptyComment => "Illegal comment.",
112            Self::CdataInHtmlContent => "CDATA section is allowed only in XML context.",
113            Self::DuplicateAttribute => "Duplicate attribute.",
114            Self::EndTagWithAttributes => "End tag cannot have attributes.",
115            Self::EndTagWithTrailingSolidus => "Trailing solidus not allowed in end tags.",
116            Self::EofBeforeTagName => "Unexpected EOF in tag.",
117            Self::EofInCdata => "EOF in CDATA section.",
118            Self::EofInComment => "EOF in comment.",
119            Self::EofInScriptHtmlCommentLikeText => "EOF in script.",
120            Self::EofInTag => "EOF in tag.",
121            Self::IncorrectlyClosedComment => "Incorrectly closed comment.",
122            Self::IncorrectlyOpenedComment => "Incorrectly opened comment.",
123            Self::InvalidFirstCharacterOfTagName => "Invalid first character of tag name.",
124            Self::MissingAttributeValue => "Attribute value expected.",
125            Self::MissingEndTagName => "End tag name expected.",
126            Self::MissingWhitespaceBetweenAttributes => "Whitespace expected between attributes.",
127            Self::NestedComment => "Nested comments are not allowed.",
128            Self::UnexpectedCharacterInAttributeName => "Unexpected character in attribute name.",
129            Self::UnexpectedCharacterInUnquotedAttributeValue => {
130                "Unexpected character in unquoted attribute value."
131            }
132            Self::UnexpectedEqualsSignBeforeAttributeName => {
133                "Unexpected equals sign before attribute name."
134            }
135            Self::UnexpectedNullCharacter => "Unexpected null character.",
136            Self::UnexpectedQuestionMarkInsteadOfTagName => "Invalid tag name.",
137            Self::UnexpectedSolidusInTag => "Unexpected solidus in tag.",
138
139            Self::InvalidEndTag => "Invalid end tag.",
140            Self::MissingEndTag => "Element is missing end tag.",
141            Self::MissingInterpolationEnd => "Interpolation end sign was not found.",
142            Self::MissingDynamicDirectiveArgumentEnd => {
143                "End bracket for dynamic directive argument was not found."
144            }
145            Self::MissingDirectiveName => "Directive name is missing.",
146            Self::MissingDirectiveModifier => "Directive modifier is expected.",
147
148            Self::VIfNoExpression => "v-if/v-else-if is missing expression.",
149            Self::VIfSameKey => "v-if/v-else-if branches must use unique keys.",
150            Self::VElseNoAdjacentIf => "v-else/v-else-if has no adjacent v-if.",
151            Self::VForNoExpression => "v-for is missing expression.",
152            Self::VForMalformedExpression => "v-for has invalid expression.",
153            Self::VForTemplateKeyPlacement => {
154                "<template v-for> key should be placed on the <template> tag."
155            }
156            Self::VBindNoExpression => "v-bind is missing expression.",
157            Self::VBindSameNameShorthand => "v-bind shorthand requires prop name.",
158            Self::VOnNoExpression => "v-on is missing expression.",
159            Self::VSlotUnexpectedDirectiveOnSlotOutlet => {
160                "Unexpected custom directive on <slot> outlet."
161            }
162            Self::VSlotMixedSlotUsage => "Mixed v-slot usage with named slots detected.",
163            Self::VSlotDuplicateSlotNames => "Duplicate slot names detected.",
164            Self::VSlotExtraneousDefaultSlotChildren => {
165                "Extraneous children found when component already has an explicit default slot."
166            }
167            Self::VSlotMisplaced => "v-slot can only be used on components or <template> tags.",
168            Self::VModelNoExpression => "v-model is missing expression.",
169            Self::VModelMalformedExpression => {
170                "v-model value must be a valid JavaScript member expression."
171            }
172            Self::VModelOnScope => "v-model cannot be used on v-for or v-slot scope variables.",
173            Self::VModelOnProps => "v-model cannot be used on props.",
174            Self::VModelArgOnElement => "v-model argument is not supported on plain elements.",
175            Self::VShowNoExpression => "v-show is missing expression.",
176
177            Self::PrefixIdNotSupported => "prefixIdentifiers option is not supported in this mode.",
178            Self::ModuleModeNotSupported => "ES module mode is not supported in this mode.",
179            Self::CacheHandlerNotSupported => "cacheHandlers option is not supported in this mode.",
180            Self::ScopeIdNotSupported => "scopeId option is not supported in this mode.",
181
182            Self::UnhandledCodePath => "Unhandled code path.",
183            Self::ExtendPoint => "Extension point.",
184        }
185    }
186
187    pub fn is_parse_error(&self) -> bool {
188        (*self as u16) < (Self::VIfNoExpression as u16)
189    }
190
191    pub fn is_transform_error(&self) -> bool {
192        let code = *self as u16;
193        code >= (Self::VIfNoExpression as u16) && code < (Self::PrefixIdNotSupported as u16)
194    }
195}
196
197/// Result type for compiler operations
198pub type CompilerResult<T> = Result<T, CompilerError>;
199
200#[cfg(test)]
201mod tests {
202    use super::{CompilerError, ErrorCode};
203
204    #[test]
205    fn compiler_error_new() {
206        let err = CompilerError::new(ErrorCode::EofInTag, None);
207        assert_eq!(err.code, ErrorCode::EofInTag);
208        assert_eq!(err.message, "EOF in tag.");
209        assert!(err.loc.is_none());
210    }
211
212    #[test]
213    fn compiler_error_with_message() {
214        let err =
215            CompilerError::with_message(ErrorCode::UnhandledCodePath, "custom error message", None);
216        assert_eq!(err.code, ErrorCode::UnhandledCodePath);
217        assert_eq!(err.message, "custom error message");
218    }
219
220    #[test]
221    fn error_code_messages_not_empty() {
222        let codes = [
223            ErrorCode::AbruptClosingOfEmptyComment,
224            ErrorCode::CdataInHtmlContent,
225            ErrorCode::DuplicateAttribute,
226            ErrorCode::EndTagWithAttributes,
227            ErrorCode::EofInTag,
228            ErrorCode::InvalidEndTag,
229            ErrorCode::MissingEndTag,
230            ErrorCode::MissingInterpolationEnd,
231            ErrorCode::MissingDirectiveName,
232            ErrorCode::MissingDirectiveModifier,
233            ErrorCode::VIfNoExpression,
234            ErrorCode::VForNoExpression,
235            ErrorCode::VBindNoExpression,
236            ErrorCode::VOnNoExpression,
237            ErrorCode::VModelNoExpression,
238            ErrorCode::VShowNoExpression,
239            ErrorCode::PrefixIdNotSupported,
240            ErrorCode::UnhandledCodePath,
241            ErrorCode::ExtendPoint,
242        ];
243        for code in &codes {
244            assert!(!code.message().is_empty(), "{:?} has empty message", code);
245        }
246    }
247
248    #[test]
249    fn is_parse_error_true() {
250        let parse_errors = [
251            ErrorCode::AbruptClosingOfEmptyComment,
252            ErrorCode::CdataInHtmlContent,
253            ErrorCode::DuplicateAttribute,
254            ErrorCode::EofInTag,
255            ErrorCode::InvalidEndTag,
256            ErrorCode::MissingEndTag,
257            ErrorCode::MissingInterpolationEnd,
258            ErrorCode::MissingDirectiveName,
259            ErrorCode::MissingDirectiveModifier,
260        ];
261        for code in &parse_errors {
262            assert!(code.is_parse_error(), "{:?} should be parse error", code);
263        }
264    }
265
266    #[test]
267    fn is_parse_error_false_for_transform() {
268        assert!(!ErrorCode::VIfNoExpression.is_parse_error());
269        assert!(!ErrorCode::VShowNoExpression.is_parse_error());
270        assert!(!ErrorCode::PrefixIdNotSupported.is_parse_error());
271    }
272
273    #[test]
274    fn is_transform_error_true() {
275        let transform_errors = [
276            ErrorCode::VIfNoExpression,
277            ErrorCode::VIfSameKey,
278            ErrorCode::VElseNoAdjacentIf,
279            ErrorCode::VForNoExpression,
280            ErrorCode::VBindNoExpression,
281            ErrorCode::VOnNoExpression,
282            ErrorCode::VModelNoExpression,
283            ErrorCode::VShowNoExpression,
284        ];
285        for code in &transform_errors {
286            assert!(
287                code.is_transform_error(),
288                "{:?} should be transform error",
289                code
290            );
291        }
292    }
293
294    #[test]
295    fn is_transform_error_false() {
296        // Parse errors should not be transform errors
297        assert!(!ErrorCode::EofInTag.is_transform_error());
298        assert!(!ErrorCode::MissingDirectiveModifier.is_transform_error());
299        // Generic errors should not be transform errors
300        assert!(!ErrorCode::PrefixIdNotSupported.is_transform_error());
301    }
302
303    #[test]
304    fn boundary_error_codes() {
305        // MissingDirectiveModifier (28) is the last parse error
306        assert!(ErrorCode::MissingDirectiveModifier.is_parse_error());
307        assert!(!ErrorCode::MissingDirectiveModifier.is_transform_error());
308
309        // VIfNoExpression (29) is the first transform error
310        assert!(!ErrorCode::VIfNoExpression.is_parse_error());
311        assert!(ErrorCode::VIfNoExpression.is_transform_error());
312
313        // VShowNoExpression (48) is the last transform error
314        assert!(ErrorCode::VShowNoExpression.is_transform_error());
315        assert!(!ErrorCode::VShowNoExpression.is_parse_error());
316
317        // PrefixIdNotSupported (49) is neither
318        assert!(!ErrorCode::PrefixIdNotSupported.is_parse_error());
319        assert!(!ErrorCode::PrefixIdNotSupported.is_transform_error());
320    }
321
322    #[test]
323    fn mutual_exclusion() {
324        let all_codes = [
325            ErrorCode::AbruptClosingOfEmptyComment,
326            ErrorCode::CdataInHtmlContent,
327            ErrorCode::DuplicateAttribute,
328            ErrorCode::EndTagWithAttributes,
329            ErrorCode::EndTagWithTrailingSolidus,
330            ErrorCode::EofBeforeTagName,
331            ErrorCode::EofInCdata,
332            ErrorCode::EofInComment,
333            ErrorCode::EofInScriptHtmlCommentLikeText,
334            ErrorCode::EofInTag,
335            ErrorCode::IncorrectlyClosedComment,
336            ErrorCode::IncorrectlyOpenedComment,
337            ErrorCode::InvalidFirstCharacterOfTagName,
338            ErrorCode::MissingAttributeValue,
339            ErrorCode::MissingEndTagName,
340            ErrorCode::MissingWhitespaceBetweenAttributes,
341            ErrorCode::NestedComment,
342            ErrorCode::UnexpectedCharacterInAttributeName,
343            ErrorCode::UnexpectedCharacterInUnquotedAttributeValue,
344            ErrorCode::UnexpectedEqualsSignBeforeAttributeName,
345            ErrorCode::UnexpectedNullCharacter,
346            ErrorCode::UnexpectedQuestionMarkInsteadOfTagName,
347            ErrorCode::UnexpectedSolidusInTag,
348            ErrorCode::InvalidEndTag,
349            ErrorCode::MissingEndTag,
350            ErrorCode::MissingInterpolationEnd,
351            ErrorCode::MissingDynamicDirectiveArgumentEnd,
352            ErrorCode::MissingDirectiveName,
353            ErrorCode::MissingDirectiveModifier,
354            ErrorCode::VIfNoExpression,
355            ErrorCode::VIfSameKey,
356            ErrorCode::VElseNoAdjacentIf,
357            ErrorCode::VForNoExpression,
358            ErrorCode::VForMalformedExpression,
359            ErrorCode::VForTemplateKeyPlacement,
360            ErrorCode::VBindNoExpression,
361            ErrorCode::VBindSameNameShorthand,
362            ErrorCode::VOnNoExpression,
363            ErrorCode::VSlotUnexpectedDirectiveOnSlotOutlet,
364            ErrorCode::VSlotMixedSlotUsage,
365            ErrorCode::VSlotDuplicateSlotNames,
366            ErrorCode::VSlotExtraneousDefaultSlotChildren,
367            ErrorCode::VSlotMisplaced,
368            ErrorCode::VModelNoExpression,
369            ErrorCode::VModelMalformedExpression,
370            ErrorCode::VModelOnScope,
371            ErrorCode::VModelOnProps,
372            ErrorCode::VModelArgOnElement,
373            ErrorCode::VShowNoExpression,
374            ErrorCode::PrefixIdNotSupported,
375            ErrorCode::ModuleModeNotSupported,
376            ErrorCode::CacheHandlerNotSupported,
377            ErrorCode::ScopeIdNotSupported,
378            ErrorCode::UnhandledCodePath,
379            ErrorCode::ExtendPoint,
380        ];
381        for code in &all_codes {
382            assert!(
383                !(code.is_parse_error() && code.is_transform_error()),
384                "{:?} should not be both parse and transform error",
385                code
386            );
387        }
388    }
389}