vue_sfc/ast/block/
name.rs

1use std::{
2    borrow::{Borrow, Cow},
3    fmt::Display,
4    ops::Deref,
5};
6
7pub use self::error::InvalidBlockName;
8use self::error::InvalidBlockNameKind;
9
10mod error {
11    use std::error::Error;
12    use std::fmt::Display;
13
14    /// Returned when a function was unable to convert a string to a [`BlockName`][super::BlockName].
15    #[derive(Debug)]
16    pub struct InvalidBlockName(pub(super) InvalidBlockNameKind);
17
18    impl Display for InvalidBlockName {
19        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20            match self {
21                Self(InvalidBlockNameKind::StartsWithNonAsciiAlpha) => {
22                    write!(f, "block name must start with ASCII alpha")
23                }
24                Self(InvalidBlockNameKind::IllegalChar(ch)) => {
25                    write!(f, "block name cannot contain `{ch}`")
26                }
27            }
28        }
29    }
30
31    impl Error for InvalidBlockName {}
32
33    #[derive(Debug)]
34    pub(super) enum InvalidBlockNameKind {
35        IllegalChar(char),
36        StartsWithNonAsciiAlpha,
37    }
38}
39
40/// The name of a block, i.e: `script` in `<script lang="ts">`.
41#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
42#[must_use]
43pub struct BlockName<'a>(Cow<'a, str>);
44
45impl<'a> BlockName<'a> {
46    /// Attempts to convert a string to a [`BlockName`].
47    ///
48    /// # Errors
49    /// Will return an error if the string:
50    /// - doesn't start with an ASCII alpha,
51    /// - contains any of the following characters:
52    ///   - `U+0009 CHARACTER TABULATION`,
53    ///   - `U+000A LINE FEED`,
54    ///   - `U+000C FORM FEED`,
55    ///   - `U+0020 SPACE`,
56    ///   - `U+002F SOLIDUS (/)`,
57    ///   - `U+003E GREATER-THAN SIGN (>)`.
58    pub fn from_cow(mut src: Cow<'a, str>) -> Result<Self, InvalidBlockName> {
59        if !src.starts_with(|ch: char| ch.is_ascii_alphabetic()) {
60            return Err(InvalidBlockName(
61                InvalidBlockNameKind::StartsWithNonAsciiAlpha,
62            ));
63        }
64
65        if let Some(ch) = src.chars().find(|ch| {
66            matches!(
67                ch,
68                '\u{0009}' | '\u{000A}' | '\u{000C}' | '\u{0020}' | '\u{002F}' | '\u{003E}'
69            )
70        }) {
71            return Err(InvalidBlockName(InvalidBlockNameKind::IllegalChar(ch)));
72        }
73
74        if src.contains(|ch: char| ch.is_ascii_uppercase()) {
75            src.to_mut().make_ascii_lowercase();
76        }
77
78        Ok(Self(src))
79    }
80
81    /// Convert a string into a [`BlockName`] **without** validating
82    /// (unless `debug_assertions` is enabled).
83    ///
84    /// # Panics
85    /// If `debug_assertions` is enabled, validate the input and panic on failure.
86    ///
87    /// # Safety
88    /// See string prerequisites of [`BlockName::from_cow`].
89    pub unsafe fn from_cow_unchecked(src: Cow<'a, str>) -> Self {
90        if cfg!(debug_assertions) {
91            match Self::from_cow(src) {
92                Ok(val) => val,
93                Err(err) => {
94                    panic!("BlockName::from_cow_unchecked(): {err}")
95                }
96            }
97        } else {
98            Self(src)
99        }
100    }
101
102    #[must_use]
103    pub fn as_str(&self) -> &str {
104        &self.0
105    }
106}
107
108impl Display for BlockName<'_> {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        self.as_str().fmt(f)
111    }
112}
113
114impl Deref for BlockName<'_> {
115    type Target = str;
116
117    fn deref(&self) -> &Self::Target {
118        self.as_str()
119    }
120}
121
122impl Borrow<str> for BlockName<'_> {
123    fn borrow(&self) -> &str {
124        self.as_str()
125    }
126}
127
128impl<'a> TryFrom<&'a str> for BlockName<'a> {
129    type Error = InvalidBlockName;
130    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
131        Self::from_cow(Cow::Borrowed(value))
132    }
133}
134
135impl<'a> TryFrom<String> for BlockName<'a> {
136    type Error = InvalidBlockName;
137    fn try_from(value: String) -> Result<Self, Self::Error> {
138        Self::from_cow(Cow::Owned(value))
139    }
140}
141
142impl<'a> TryFrom<Cow<'a, str>> for BlockName<'a> {
143    type Error = InvalidBlockName;
144    fn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> {
145        Self::from_cow(value)
146    }
147}