Skip to main content

svelte_syntax/
error.rs

1use std::sync::Arc;
2
3use camino::{Utf8Path, Utf8PathBuf};
4use miette::Diagnostic;
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8use crate::primitives::{SourceId, Span};
9use crate::source::SourceText;
10
11/// A byte range in source text, used by [`CompileError`] to indicate where
12/// the error occurred.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct SourcePosition {
15    /// UTF-16 character offset of the start of the error.
16    pub start: usize,
17    /// UTF-16 character offset of the end of the error.
18    pub end: usize,
19}
20
21/// A line/column location in source text.
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct LineColumn {
24    /// One-based line number.
25    pub line: usize,
26    /// Zero-based UTF-16 column.
27    pub column: usize,
28    /// UTF-16 character offset from the start of the source.
29    pub character: usize,
30}
31
32/// An error produced during parsing.
33///
34/// Contains a machine-readable `code`, a human-readable `message`, and
35/// optional source position information.
36#[derive(Debug, Clone, Error, Serialize, Deserialize)]
37#[error("{message}")]
38pub struct CompileError {
39    /// Machine-readable error code (e.g. `"expected_token"`).
40    pub code: Arc<str>,
41    /// Human-readable error message.
42    pub message: Arc<str>,
43    /// Character range where the error occurred.
44    pub position: Option<Box<SourcePosition>>,
45    /// Line/column location of the start of the error.
46    pub start: Option<Box<LineColumn>>,
47    /// Line/column location of the end of the error.
48    pub end: Option<Box<LineColumn>>,
49    /// File path, if known.
50    pub filename: Option<Arc<Utf8PathBuf>>,
51}
52
53impl CompileError {
54    /// Create an error for an unimplemented feature.
55    pub fn unimplemented(feature: &'static str) -> Self {
56        Self {
57            code: Arc::from("unimplemented"),
58            message: Arc::from(format!("{feature} is not implemented yet in rust-port")),
59            position: None,
60            start: None,
61            end: None,
62            filename: None,
63        }
64    }
65
66    /// Create an internal error with the given message.
67    pub fn internal(message: impl Into<Arc<str>>) -> Self {
68        Self {
69            code: Arc::from("internal"),
70            message: message.into(),
71            position: None,
72            start: None,
73            end: None,
74            filename: None,
75        }
76    }
77
78    /// Attach a source span to this error.
79    pub fn with_span(mut self, span: Span) -> Self {
80        self.position = Some(Box::new(SourcePosition {
81            start: span.start.as_usize(),
82            end: span.end.as_usize(),
83        }));
84        self
85    }
86
87    /// Attach a filename to this error (only if one is not already set).
88    pub fn with_filename(mut self, filename: Option<&Utf8Path>) -> Self {
89        if self.filename.is_none() {
90            self.filename = filename.map(|path| Arc::new(path.to_path_buf()));
91        }
92        self
93    }
94
95    /// Attach filename information from a [`SourceText`].
96    pub fn with_source_text(self, source: SourceText<'_>) -> Self {
97        self.with_filename(source.filename)
98    }
99}
100
101/// Diagnostic codes for Svelte-specific parse and validation errors.
102///
103/// Each variant carries its own error message and a `miette` diagnostic code.
104/// Use [`DiagnosticKind::to_compile_error`] to convert a variant into
105/// a [`CompileError`] with source position information.
106#[derive(Debug, Clone, PartialEq, Eq, Error, Diagnostic)]
107pub enum DiagnosticKind {
108    #[error("Expected attribute value")]
109    #[diagnostic(code(svelte::expected_attribute_value))]
110    ExpectedAttributeValue,
111
112    #[error("Attributes need to be unique")]
113    #[diagnostic(code(svelte::attribute_duplicate))]
114    AttributeDuplicate,
115
116    #[error("Duplicate slot name '{slot}' in <{component}>")]
117    #[diagnostic(code(svelte::slot_attribute_duplicate))]
118    SlotAttributeDuplicate { slot: Arc<str>, component: Arc<str> },
119
120    #[error(
121        "Element with a slot='...' attribute must be a child of a component or a descendant of a custom element"
122    )]
123    #[diagnostic(code(svelte::slot_attribute_invalid_placement))]
124    SlotAttributeInvalidPlacement,
125
126    #[error("Found default slot content alongside an explicit slot=\"default\"")]
127    #[diagnostic(code(svelte::slot_default_duplicate))]
128    SlotDefaultDuplicate,
129
130    #[error("The $ name is reserved, and cannot be used for variables and imports")]
131    #[diagnostic(code(svelte::dollar_binding_invalid))]
132    DollarBindingInvalid,
133
134    #[error(
135        "`{ident}` is an illegal variable name. To reference a global variable called `{ident}`, use `globalThis.{ident}`"
136    )]
137    #[diagnostic(code(svelte::global_reference_invalid))]
138    GlobalReferenceInvalid { ident: Arc<str> },
139
140    #[error(
141        "`$state(...)` can only be used as a variable declaration initializer, a class field declaration, or the first assignment to a class field at the top level of the constructor."
142    )]
143    #[diagnostic(code(svelte::state_invalid_placement))]
144    StateInvalidPlacement,
145
146    #[error("`{directive}:` name cannot be empty")]
147    #[diagnostic(code(svelte::directive_missing_name))]
148    DirectiveMissingName { directive: Arc<str> },
149
150    #[error("Attribute shorthand cannot be empty")]
151    #[diagnostic(code(svelte::attribute_empty_shorthand))]
152    AttributeEmptyShorthand,
153
154    #[error("`bind:value` can only be used with `<input>`, `<textarea>`, `<select>`")]
155    #[diagnostic(code(svelte::bind_invalid_target))]
156    BindInvalidTarget,
157
158    #[error("An `{{#each ...}}` block without an `as` clause cannot have a key")]
159    #[diagnostic(code(svelte::each_key_without_as))]
160    EachKeyWithoutAs,
161
162    #[error("`$effect.active` is now `$effect.tracking`")]
163    #[diagnostic(code(svelte::rune_renamed))]
164    RuneRenamedEffectActive,
165
166    #[error(
167        "Cannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties"
168    )]
169    #[diagnostic(code(svelte::state_invalid_export))]
170    StateInvalidExport,
171
172    #[error(
173        "Cannot export derived state from a module. To expose the current derived value, export a function returning its value"
174    )]
175    #[diagnostic(code(svelte::derived_invalid_export))]
176    DerivedInvalidExport,
177
178    #[error("`{name}` is not defined")]
179    #[diagnostic(code(svelte::export_undefined))]
180    ExportUndefined { name: Arc<str> },
181
182    #[error("`{name}` is not a valid rune")]
183    #[diagnostic(code(svelte::rune_invalid_name))]
184    RuneInvalidName { name: Arc<str> },
185
186    #[error(
187        "The arguments keyword cannot be used within the template or at the top level of a component"
188    )]
189    #[diagnostic(code(svelte::invalid_arguments_usage))]
190    InvalidArgumentsUsage,
191
192    #[error("Assigning to rvalue")]
193    #[diagnostic(code(svelte::js_parse_error))]
194    JsParseErrorAssigningToRvalue,
195
196    #[error("Cannot assign to constant")]
197    #[diagnostic(code(svelte::constant_assignment))]
198    ConstantAssignment,
199
200    #[error("A component can have a single top-level `<style>` element")]
201    #[diagnostic(code(svelte::style_duplicate))]
202    StyleDuplicate,
203
204    #[error("A component cannot have a default export")]
205    #[diagnostic(code(svelte::module_illegal_default_export))]
206    ModuleIllegalDefaultExport,
207
208    #[error("<svelte:options> cannot have children")]
209    #[diagnostic(code(svelte::svelte_meta_invalid_content))]
210    SvelteMetaInvalidContent,
211
212    #[error("<svelte:window> cannot have children")]
213    #[diagnostic(code(svelte::svelte_meta_invalid_content))]
214    SvelteWindowInvalidContent,
215
216    #[error("Cannot reassign or bind to snippet parameter")]
217    #[diagnostic(code(svelte::snippet_parameter_assignment))]
218    SnippetParameterAssignment,
219
220    #[error("`{name}` has already been declared on this class")]
221    #[diagnostic(code(svelte::state_field_duplicate))]
222    StateFieldDuplicate { name: Arc<str> },
223
224    #[error("Cannot assign to a state field before its declaration")]
225    #[diagnostic(code(svelte::state_field_invalid_assignment))]
226    StateFieldInvalidAssignment,
227
228    #[error("`{name}` has already been declared")]
229    #[diagnostic(code(svelte::duplicate_class_field))]
230    DuplicateClassField { name: Arc<str> },
231
232    #[error("Expected token }}")]
233    #[diagnostic(code(svelte::expected_token))]
234    ExpectedTokenRightBrace,
235
236    #[error("Expected token )")]
237    #[diagnostic(code(svelte::expected_token))]
238    ExpectedTokenRightParen,
239
240    #[error("Calling a snippet function using apply, bind or call is not allowed")]
241    #[diagnostic(code(svelte::render_tag_invalid_call_expression))]
242    RenderTagInvalidCallExpression,
243
244    #[error("`{{@render ...}}` tags can only contain call expressions")]
245    #[diagnostic(code(svelte::render_tag_invalid_expression))]
246    RenderTagInvalidExpression,
247
248    #[error("cannot use spread arguments in `{{@render ...}}` tags")]
249    #[diagnostic(code(svelte::render_tag_invalid_spread_argument))]
250    RenderTagInvalidSpreadArgument,
251
252    #[error("{{@debug ...}} arguments must be identifiers, not arbitrary expressions")]
253    #[diagnostic(code(svelte::debug_tag_invalid_arguments))]
254    DebugTagInvalidArguments,
255
256    #[error("beforeUpdate cannot be used in runes mode")]
257    #[diagnostic(code(svelte::runes_mode_invalid_import))]
258    RunesModeInvalidImportBeforeUpdate,
259
260    #[error("Cannot use rune without parentheses")]
261    #[diagnostic(code(svelte::rune_missing_parentheses))]
262    RuneMissingParentheses,
263
264    #[error("Cannot use `$props()` more than once")]
265    #[diagnostic(code(svelte::props_duplicate))]
266    PropsDuplicate,
267
268    #[error("Cannot use `export let` in runes mode — use `$props()` instead")]
269    #[diagnostic(code(svelte::legacy_export_invalid))]
270    LegacyExportInvalid,
271
272    #[error(
273        "Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={{array[i]}}` instead of `bind:value={{entry}}`)"
274    )]
275    #[diagnostic(code(svelte::each_item_invalid_assignment))]
276    EachItemInvalidAssignment,
277
278    #[error("The `{{@const foo = ...}}` declaration is not available in this snippet")]
279    #[diagnostic(code(svelte::const_tag_invalid_reference))]
280    ConstTagInvalidReference,
281
282    #[error("Cannot reference store value outside a `.svelte` file")]
283    #[diagnostic(code(svelte::store_invalid_subscription_module))]
284    StoreInvalidSubscriptionModule,
285
286    #[error(
287        "Declaring or accessing a prop starting with `$$` is illegal (they are reserved for Svelte internals)"
288    )]
289    #[diagnostic(code(svelte::props_illegal_name))]
290    PropsIllegalName,
291
292    #[error("`$bindable` must be called with zero or one arguments")]
293    #[diagnostic(code(svelte::rune_invalid_arguments_length))]
294    RuneInvalidArgumentsLengthBindable,
295
296    #[error("Expected a valid CSS identifier")]
297    #[diagnostic(code(svelte::css_expected_identifier))]
298    CssExpectedIdentifier,
299
300    #[error("Invalid selector")]
301    #[diagnostic(code(svelte::css_selector_invalid))]
302    CssSelectorInvalid,
303
304    #[error("A `:global` selector cannot follow a `>` combinator")]
305    #[diagnostic(code(svelte::css_global_block_invalid_combinator))]
306    CssGlobalBlockInvalidCombinator,
307
308    #[error("A top-level `:global {{...}}` block can only contain rules, not declarations")]
309    #[diagnostic(code(svelte::css_global_block_invalid_declaration))]
310    CssGlobalBlockInvalidDeclaration,
311
312    #[error("A `:global` selector cannot be inside a pseudoclass")]
313    #[diagnostic(code(svelte::css_global_block_invalid_placement))]
314    CssGlobalBlockInvalidPlacement,
315
316    #[error(
317        "A `:global` selector cannot be part of a selector list with entries that don't contain `:global`"
318    )]
319    #[diagnostic(code(svelte::css_global_block_invalid_list))]
320    CssGlobalBlockInvalidList,
321
322    #[error("A `:global` selector cannot modify an existing selector")]
323    #[diagnostic(code(svelte::css_global_block_invalid_modifier))]
324    CssGlobalBlockInvalidModifier,
325
326    #[error("A `:global` selector can only be modified if it is a descendant of other selectors")]
327    #[diagnostic(code(svelte::css_global_block_invalid_modifier_start))]
328    CssGlobalBlockInvalidModifierStart,
329
330    #[error(
331        "`:global(...)` can be at the start or end of a selector sequence, but not in the middle"
332    )]
333    #[diagnostic(code(svelte::css_global_invalid_placement))]
334    CssGlobalInvalidPlacement,
335
336    #[error("`:global(...)` must contain exactly one selector")]
337    #[diagnostic(code(svelte::css_global_invalid_selector))]
338    CssGlobalInvalidSelector,
339
340    #[error(
341        "`:global(...)` must not contain type or universal selectors when used in a compound selector"
342    )]
343    #[diagnostic(code(svelte::css_global_invalid_selector_list))]
344    CssGlobalInvalidSelectorList,
345
346    #[error(
347        "Nesting selectors can only be used inside a rule or as the first selector inside a lone `:global(...)`"
348    )]
349    #[diagnostic(code(svelte::css_nesting_selector_invalid_placement))]
350    CssNestingSelectorInvalidPlacement,
351
352    #[error("`:global(...)` must not be followed by a type selector")]
353    #[diagnostic(code(svelte::css_type_selector_invalid_placement))]
354    CssTypeSelectorInvalidPlacement,
355
356    #[error("`$bindable()` can only be used inside a `$props()` declaration")]
357    #[diagnostic(code(svelte::bindable_invalid_location))]
358    BindableInvalidLocation,
359
360    #[error("`$derived` must be called with exactly one argument")]
361    #[diagnostic(code(svelte::rune_invalid_arguments_length))]
362    RuneInvalidArgumentsLengthDerived,
363
364    #[error("`$effect` must be called with exactly one argument")]
365    #[diagnostic(code(svelte::rune_invalid_arguments_length))]
366    RuneInvalidArgumentsLengthEffect,
367
368    #[error("`$state` must be called with zero or one arguments")]
369    #[diagnostic(code(svelte::rune_invalid_arguments_length))]
370    RuneInvalidArgumentsLengthState,
371
372    #[error("`$state.raw` must be called with zero or one arguments")]
373    #[diagnostic(code(svelte::rune_invalid_arguments_length))]
374    RuneInvalidArgumentsLengthStateRaw,
375
376    #[error("`$state.snapshot` must be called with exactly one argument")]
377    #[diagnostic(code(svelte::rune_invalid_arguments_length))]
378    RuneInvalidArgumentsLengthStateSnapshot,
379
380    #[error("`$props` cannot be called with arguments")]
381    #[diagnostic(code(svelte::rune_invalid_arguments))]
382    RuneInvalidArgumentsProps,
383
384    #[error(
385        "`$props()` can only be used at the top level of components as a variable declaration initializer"
386    )]
387    #[diagnostic(code(svelte::props_invalid_placement))]
388    PropsInvalidPlacement,
389
390    #[error(
391        "`$derived(...)` can only be used as a variable declaration initializer, a class field declaration, or the first assignment to a class field at the top level of the constructor."
392    )]
393    #[diagnostic(code(svelte::state_invalid_placement))]
394    StateInvalidPlacementDerived,
395
396    #[error("`$effect()` can only be used as an expression statement")]
397    #[diagnostic(code(svelte::effect_invalid_placement))]
398    EffectInvalidPlacement,
399
400    #[error("`$host()` can only be used inside custom element component instances")]
401    #[diagnostic(code(svelte::host_invalid_placement))]
402    HostInvalidPlacement,
403
404    #[error("`<script>` was left open")]
405    #[diagnostic(code(svelte::element_unclosed))]
406    ElementUnclosedScript,
407
408    #[error("`<div>` was left open")]
409    #[diagnostic(code(svelte::element_unclosed))]
410    ElementUnclosedDiv,
411
412    #[error(
413        "`<svelte:self>` components can only exist inside `{{#if}}` blocks, `{{#each}}` blocks, `{{#snippet}}` blocks or slots passed to components"
414    )]
415    #[diagnostic(code(svelte::svelte_self_invalid_placement))]
416    SvelteSelfInvalidPlacement,
417
418    #[error(
419        "Cannot use `<slot>` syntax and `{{@render ...}}` tags in the same component. Migrate towards `{{@render ...}}` tags completely"
420    )]
421    #[diagnostic(code(svelte::slot_snippet_conflict))]
422    SlotSnippetConflict,
423
424    #[error(
425        "Cannot use explicit children snippet at the same time as implicit children content. Remove either the non-whitespace content or the children snippet block"
426    )]
427    #[diagnostic(code(svelte::snippet_conflict))]
428    SnippetConflict,
429
430    #[error(
431        "An exported snippet can only reference things declared in a `<script module>`, or other exportable snippets"
432    )]
433    #[diagnostic(code(svelte::snippet_invalid_export))]
434    SnippetInvalidExport,
435
436    #[error("Snippets do not support rest parameters; use an array instead")]
437    #[diagnostic(code(svelte::snippet_invalid_rest_parameter))]
438    SnippetInvalidRestParameter,
439
440    #[error("Cannot reference store value inside `<script module>`")]
441    #[diagnostic(code(svelte::store_invalid_subscription))]
442    StoreInvalidSubscription,
443
444    #[error("Cannot subscribe to stores that are not declared at the top level of the component")]
445    #[diagnostic(code(svelte::store_invalid_scoped_subscription))]
446    StoreInvalidScopedSubscription,
447
448    #[error("The $ prefix is reserved, and cannot be used for variables and imports")]
449    #[diagnostic(code(svelte::dollar_prefix_invalid))]
450    DollarPrefixInvalid,
451
452    #[error(
453        "Expected a valid element or component name. Components must have a valid variable name or dot notation expression"
454    )]
455    #[diagnostic(code(svelte::tag_invalid_name))]
456    TagInvalidName,
457
458    #[error("Expected whitespace")]
459    #[diagnostic(code(svelte::expected_whitespace))]
460    ExpectedWhitespace,
461
462    #[error("{{@const ...}} must consist of a single variable declaration")]
463    #[diagnostic(code(svelte::const_tag_invalid_expression))]
464    ConstTagInvalidExpression,
465
466    #[error("Cyclical dependency detected: a → b → a")]
467    #[diagnostic(code(svelte::const_tag_cycle))]
468    ConstTagCycle,
469
470    #[error(
471        "Comma-separated expressions are not allowed as attribute/directive values in runes mode, unless wrapped in parentheses"
472    )]
473    #[diagnostic(code(svelte::attribute_invalid_sequence_expression))]
474    AttributeInvalidSequenceExpression,
475
476    #[error(
477        "{{:...}} block is invalid at this position (did you forget to close the preceding element or block?)"
478    )]
479    #[diagnostic(code(svelte::block_invalid_continuation_placement))]
480    BlockInvalidContinuationPlacement,
481
482    #[error("Expected token -->")]
483    #[diagnostic(code(svelte::expected_token))]
484    ExpectedTokenCommentClose,
485
486    #[error("Expected token </style")]
487    #[diagnostic(code(svelte::expected_token))]
488    ExpectedTokenStyleClose,
489
490    #[error("Expected token {{:else}}")]
491    #[diagnostic(code(svelte::expected_token))]
492    ExpectedTokenElse,
493
494    #[error("Expected token {{:then ...}} or {{:catch ...}}")]
495    #[diagnostic(code(svelte::expected_token))]
496    ExpectedTokenAwaitBranch,
497
498    #[error(
499        "Valid `<svelte:...>` tag names are svelte:head, svelte:options, svelte:window, svelte:document, svelte:body, svelte:element, svelte:component, svelte:self, svelte:fragment or svelte:boundary"
500    )]
501    #[diagnostic(code(svelte::svelte_meta_invalid_tag))]
502    SvelteMetaInvalidTag,
503
504    #[error(
505        "Attribute values containing `{{...}}` must be enclosed in quote marks, unless the value only contains the expression"
506    )]
507    #[diagnostic(code(svelte::attribute_unquoted_sequence))]
508    AttributeUnquotedSequence,
509
510    #[error("Block was left open")]
511    #[diagnostic(code(svelte::block_unclosed))]
512    BlockUnclosed,
513
514    #[error("`</div>` attempted to close an element that was not open")]
515    #[diagnostic(code(svelte::element_invalid_closing_tag))]
516    ElementInvalidClosingTag,
517
518    #[error("`</p>` attempted to close an element that was not open")]
519    #[diagnostic(code(svelte::element_invalid_closing_tag))]
520    ElementInvalidClosingTagP,
521
522    #[error(
523        "`</p>` attempted to close element that was already automatically closed by `<pre>` (cannot nest `<pre>` inside `<p>`)"
524    )]
525    #[diagnostic(code(svelte::element_invalid_closing_tag_autoclosed))]
526    ElementInvalidClosingTagAutoclosed,
527
528    #[error("Void elements cannot have children or closing tags")]
529    #[diagnostic(code(svelte::void_element_invalid_content))]
530    VoidElementInvalidContent,
531
532    #[error("A component can only have one `<svelte:window>` element")]
533    #[diagnostic(code(svelte::svelte_meta_duplicate))]
534    SvelteMetaDuplicate,
535
536    #[error("`<svelte:window>` tags cannot be inside elements or blocks")]
537    #[diagnostic(code(svelte::svelte_meta_invalid_placement))]
538    SvelteMetaInvalidPlacement,
539
540    #[error("Unexpected end of input")]
541    #[diagnostic(code(svelte::unexpected_eof))]
542    UnexpectedEof,
543
544    #[error(
545        "Imports of `svelte/internal/*` are forbidden. It contains private runtime code which is subject to change without notice. If you're importing from `svelte/internal/*` to work around a limitation of Svelte, please open an issue at https://github.com/sveltejs/svelte and explain your use case"
546    )]
547    #[diagnostic(code(svelte::import_svelte_internal_forbidden))]
548    ImportSvelteInternalForbidden,
549}
550
551impl DiagnosticKind {
552    /// Return the machine-readable diagnostic code string for this variant.
553    pub fn code(&self) -> &'static str {
554        match self {
555            Self::ExpectedAttributeValue => "expected_attribute_value",
556            Self::AttributeDuplicate => "attribute_duplicate",
557            Self::SlotAttributeDuplicate { .. } => "slot_attribute_duplicate",
558            Self::SlotAttributeInvalidPlacement => "slot_attribute_invalid_placement",
559            Self::SlotDefaultDuplicate => "slot_default_duplicate",
560            Self::DollarBindingInvalid => "dollar_binding_invalid",
561            Self::GlobalReferenceInvalid { .. } => "global_reference_invalid",
562            Self::StateInvalidPlacement => "state_invalid_placement",
563            Self::DirectiveMissingName { .. } => "directive_missing_name",
564            Self::AttributeEmptyShorthand => "attribute_empty_shorthand",
565            Self::BindInvalidTarget => "bind_invalid_target",
566            Self::EachKeyWithoutAs => "each_key_without_as",
567            Self::RuneRenamedEffectActive => "rune_renamed",
568            Self::StateInvalidExport => "state_invalid_export",
569            Self::DerivedInvalidExport => "derived_invalid_export",
570            Self::ExportUndefined { .. } => "export_undefined",
571            Self::RuneInvalidName { .. } => "rune_invalid_name",
572            Self::InvalidArgumentsUsage => "invalid_arguments_usage",
573            Self::JsParseErrorAssigningToRvalue => "js_parse_error",
574            Self::ConstantAssignment => "constant_assignment",
575            Self::StyleDuplicate => "style_duplicate",
576            Self::ModuleIllegalDefaultExport => "module_illegal_default_export",
577            Self::SvelteMetaInvalidContent | Self::SvelteWindowInvalidContent => {
578                "svelte_meta_invalid_content"
579            }
580            Self::SnippetParameterAssignment => "snippet_parameter_assignment",
581            Self::StateFieldDuplicate { .. } => "state_field_duplicate",
582            Self::StateFieldInvalidAssignment => "state_field_invalid_assignment",
583            Self::DuplicateClassField { .. } => "duplicate_class_field",
584            Self::ExpectedTokenRightBrace | Self::ExpectedTokenRightParen => "expected_token",
585            Self::RenderTagInvalidCallExpression => "render_tag_invalid_call_expression",
586            Self::RenderTagInvalidExpression => "render_tag_invalid_expression",
587            Self::RenderTagInvalidSpreadArgument => "render_tag_invalid_spread_argument",
588            Self::DebugTagInvalidArguments => "debug_tag_invalid_arguments",
589            Self::RunesModeInvalidImportBeforeUpdate => "runes_mode_invalid_import",
590            Self::RuneMissingParentheses => "rune_missing_parentheses",
591            Self::PropsDuplicate => "props_duplicate",
592            Self::LegacyExportInvalid => "legacy_export_invalid",
593            Self::EachItemInvalidAssignment => "each_item_invalid_assignment",
594            Self::ConstTagInvalidReference => "const_tag_invalid_reference",
595            Self::StoreInvalidSubscriptionModule => "store_invalid_subscription_module",
596            Self::PropsIllegalName => "props_illegal_name",
597            Self::RuneInvalidArgumentsLengthBindable => "rune_invalid_arguments_length",
598            Self::CssExpectedIdentifier => "css_expected_identifier",
599            Self::CssSelectorInvalid => "css_selector_invalid",
600            Self::CssGlobalBlockInvalidCombinator => "css_global_block_invalid_combinator",
601            Self::CssGlobalBlockInvalidDeclaration => "css_global_block_invalid_declaration",
602            Self::CssGlobalBlockInvalidPlacement => "css_global_block_invalid_placement",
603            Self::CssGlobalBlockInvalidList => "css_global_block_invalid_list",
604            Self::CssGlobalBlockInvalidModifier => "css_global_block_invalid_modifier",
605            Self::CssGlobalBlockInvalidModifierStart => "css_global_block_invalid_modifier_start",
606            Self::CssGlobalInvalidPlacement => "css_global_invalid_placement",
607            Self::CssGlobalInvalidSelector => "css_global_invalid_selector",
608            Self::CssGlobalInvalidSelectorList => "css_global_invalid_selector_list",
609            Self::CssNestingSelectorInvalidPlacement => "css_nesting_selector_invalid_placement",
610            Self::CssTypeSelectorInvalidPlacement => "css_type_selector_invalid_placement",
611            Self::BindableInvalidLocation => "bindable_invalid_location",
612            Self::RuneInvalidArgumentsLengthDerived
613            | Self::RuneInvalidArgumentsLengthEffect
614            | Self::RuneInvalidArgumentsLengthState
615            | Self::RuneInvalidArgumentsLengthStateRaw
616            | Self::RuneInvalidArgumentsLengthStateSnapshot => "rune_invalid_arguments_length",
617            Self::RuneInvalidArgumentsProps => "rune_invalid_arguments",
618            Self::PropsInvalidPlacement => "props_invalid_placement",
619            Self::StateInvalidPlacementDerived => "state_invalid_placement",
620            Self::EffectInvalidPlacement => "effect_invalid_placement",
621            Self::HostInvalidPlacement => "host_invalid_placement",
622            Self::ElementUnclosedScript | Self::ElementUnclosedDiv => "element_unclosed",
623            Self::SvelteSelfInvalidPlacement => "svelte_self_invalid_placement",
624            Self::SlotSnippetConflict => "slot_snippet_conflict",
625            Self::SnippetConflict => "snippet_conflict",
626            Self::SnippetInvalidExport => "snippet_invalid_export",
627            Self::SnippetInvalidRestParameter => "snippet_invalid_rest_parameter",
628            Self::StoreInvalidSubscription => "store_invalid_subscription",
629            Self::StoreInvalidScopedSubscription => "store_invalid_scoped_subscription",
630            Self::DollarPrefixInvalid => "dollar_prefix_invalid",
631            Self::TagInvalidName => "tag_invalid_name",
632            Self::ExpectedWhitespace => "expected_whitespace",
633            Self::ConstTagInvalidExpression => "const_tag_invalid_expression",
634            Self::ConstTagCycle => "const_tag_cycle",
635            Self::AttributeInvalidSequenceExpression => "attribute_invalid_sequence_expression",
636            Self::BlockInvalidContinuationPlacement => "block_invalid_continuation_placement",
637            Self::ExpectedTokenCommentClose
638            | Self::ExpectedTokenStyleClose
639            | Self::ExpectedTokenElse
640            | Self::ExpectedTokenAwaitBranch => "expected_token",
641            Self::SvelteMetaInvalidTag => "svelte_meta_invalid_tag",
642            Self::AttributeUnquotedSequence => "attribute_unquoted_sequence",
643            Self::BlockUnclosed => "block_unclosed",
644            Self::ElementInvalidClosingTag | Self::ElementInvalidClosingTagP => {
645                "element_invalid_closing_tag"
646            }
647            Self::ElementInvalidClosingTagAutoclosed => "element_invalid_closing_tag_autoclosed",
648            Self::VoidElementInvalidContent => "void_element_invalid_content",
649            Self::SvelteMetaDuplicate => "svelte_meta_duplicate",
650            Self::SvelteMetaInvalidPlacement => "svelte_meta_invalid_placement",
651            Self::UnexpectedEof => "unexpected_eof",
652            Self::ImportSvelteInternalForbidden => "import_svelte_internal_forbidden",
653        }
654    }
655
656    /// Convert this diagnostic into a [`CompileError`] with source positions
657    /// computed from byte offsets.
658    pub fn to_compile_error(self, source: &str, start: usize, end: usize) -> CompileError {
659        self.to_compile_error_in(SourceText::new(SourceId::new(0), source, None), start, end)
660    }
661
662    /// Convert this diagnostic into a [`CompileError`] using a [`SourceText`]
663    /// for position and filename information.
664    pub fn to_compile_error_in(
665        self,
666        source: SourceText<'_>,
667        start: usize,
668        end: usize,
669    ) -> CompileError {
670        let start_location = source.location_at_offset(start);
671        let end_location = source.location_at_offset(end);
672
673        CompileError {
674            code: Arc::from(self.code()),
675            message: Arc::from(self.to_string()),
676            position: Some(Box::new(SourcePosition {
677                start: start_location.character,
678                end: end_location.character,
679            })),
680            start: Some(Box::new(start_location)),
681            end: Some(Box::new(end_location)),
682            filename: source.filename.map(|path| Arc::new(path.to_path_buf())),
683        }
684    }
685}
686
687#[cfg(test)]
688mod tests {
689    use camino::Utf8Path;
690
691    use super::DiagnosticKind;
692    use crate::{SourceId, SourceText};
693
694    #[test]
695    fn compile_error_uses_source_text_locations_and_filename() {
696        let source = SourceText::new(
697            SourceId::new(7),
698            "a\nšŸ˜€b",
699            Some(Utf8Path::new("input.svelte")),
700        );
701        let error = DiagnosticKind::ExpectedWhitespace.to_compile_error_in(
702            source,
703            "a\nšŸ˜€".len(),
704            "a\nšŸ˜€b".len(),
705        );
706
707        assert_eq!(error.start.as_ref().expect("start").line, 2);
708        assert_eq!(error.start.as_ref().expect("start").column, 2);
709        assert_eq!(error.position.as_ref().expect("position").start, 4);
710        assert_eq!(
711            error.filename.as_deref().map(|path| path.as_str()),
712            Some("input.svelte"),
713        );
714    }
715}