vue_sfc/ast/block/
mod.rs

1use std::{borrow::Cow, fmt::Display};
2
3pub use self::attribute::{
4    Attribute, AttributeName, AttributeValue, InvalidAttributeName, InvalidAttributeValue,
5};
6pub use self::name::{BlockName, InvalidBlockName};
7
8mod attribute;
9mod name;
10
11/// A block as defined in the [SFC specifications][1].
12///
13/// [1]: https://v3.vuejs.org/api/sfc-spec.html#language-blocks
14#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct Block<'a> {
16    pub name: BlockName<'a>,
17    pub attributes: Vec<Attribute<'a>>,
18    pub content: Cow<'a, str>,
19}
20
21impl Display for Block<'_> {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        let Self {
24            name,
25            attributes,
26            content,
27        } = self;
28
29        let content = content.trim_end();
30        write!(f, "<{name}")?;
31
32        for (name, value) in attributes {
33            match value {
34                Some(value) if value.as_str().contains('\u{0022}') => {
35                    write!(f, " {name}='{value}'")?;
36                }
37                Some(value) => {
38                    write!(f, r#" {name}="{value}""#)?;
39                }
40                None => {
41                    write!(f, " {name}")?;
42                }
43            }
44        }
45
46        write!(f, ">")?;
47
48        if !content.is_empty() {
49            writeln!(f)?;
50            writeln!(f, "{content}")?;
51        }
52
53        write!(f, "</{name}>")
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use std::borrow::Cow;
60
61    use super::{AttributeName, AttributeValue, Block, BlockName};
62
63    #[test]
64    fn test_display() {
65        assert_eq!(
66            Block {
67                name: BlockName::try_from("template").unwrap(),
68                attributes: Vec::new(),
69                content: Cow::Borrowed("")
70            }
71            .to_string(),
72            "<template></template>"
73        );
74
75        assert_eq!(
76            Block {
77                name: BlockName::try_from("script").unwrap(),
78                attributes: vec![(
79                    AttributeName::try_from("lang").unwrap(),
80                    Some(AttributeValue::try_from("ts").unwrap())
81                )],
82                content: Cow::Borrowed("")
83            }
84            .to_string(),
85            r#"<script lang="ts"></script>"#
86        );
87
88        assert_eq!(
89            Block {
90                name: BlockName::try_from("script").unwrap(),
91                attributes: vec![
92                    (
93                        AttributeName::try_from("lang").unwrap(),
94                        Some(AttributeValue::try_from("ts").unwrap())
95                    ),
96                    (AttributeName::try_from("setup").unwrap(), None)
97                ],
98                content: Cow::Borrowed("")
99            }
100            .to_string(),
101            r#"<script lang="ts" setup></script>"#
102        );
103
104        assert_eq!(
105            Block {
106                name: BlockName::try_from("style").unwrap(),
107                attributes: vec![(AttributeName::try_from("scoped").unwrap(), None)],
108                content: Cow::Borrowed("")
109            }
110            .to_string(),
111            r#"<style scoped></style>"#
112        );
113
114        assert_eq!(
115            Block {
116                name: BlockName::try_from("template").unwrap(),
117                attributes: Vec::new(),
118                content: Cow::Borrowed("<!-- content -->")
119            }
120            .to_string(),
121            concat!("<template>\n", "<!-- content -->\n", "</template>")
122        );
123
124        assert_eq!(
125            Block {
126                name: BlockName::try_from("template").unwrap(),
127                attributes: Vec::new(),
128                content: Cow::Borrowed("<!-- multiline -->\n<!-- content -->")
129            }
130            .to_string(),
131            concat!(
132                "<template>\n",
133                "<!-- multiline -->\n",
134                "<!-- content -->\n",
135                "</template>"
136            )
137        );
138    }
139}