1use proc_macro2::Ident;
11use proc_macro2_diagnostics::{Diagnostic, Level};
12use syn::{ext::IdentExt, Token};
13
14use crate::{
15 node::{parse, NodeAttribute, NodeName},
16 parser::recoverable::RecoverableContext,
17};
18
19pub(crate) mod tokens {
20 use syn::{custom_punctuation, Token};
22
23 use crate::node::parse;
24 custom_punctuation!(Dash, -);
26
27 #[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
47 pub struct DocStart {
48 pub token_lt: Token![<],
49 pub token_not: Token![!],
50 }
51
52 #[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
55 pub struct ComStart {
56 pub token_lt: Token![<],
57 pub token_not: Token![!],
58 #[parse(parse::parse_array_of2_tokens)]
59 #[to_tokens(parse::to_tokens_array)]
60 pub token_minus: [Token![-]; 2],
61 }
62
63 #[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
66 pub struct ComEnd {
67 #[parse(parse::parse_array_of2_tokens)]
68 #[to_tokens(parse::to_tokens_array)]
69 pub token_minus: [Token![-]; 2],
70 pub token_gt: Token![>],
71 }
72
73 #[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
76 pub struct OpenTagEnd {
77 pub token_solidus: Option<Token![/]>,
78 pub token_gt: Token![>],
79 }
80
81 #[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
85 pub struct CloseTagStart {
86 pub token_lt: Token![<],
87 pub token_solidus: Token![/],
88 }
89}
90
91pub use tokens::*;
92
93#[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
96pub struct FragmentOpen {
97 pub token_lt: Token![<],
98 pub token_gt: Token![>],
99}
100
101#[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
104pub struct FragmentClose {
105 pub start_tag: tokens::CloseTagStart,
106 pub token_gt: Token![>],
107}
108
109impl FragmentClose {
110 pub fn parse_with_start_tag(
111 parser: &mut RecoverableContext,
112 input: syn::parse::ParseStream,
113 start_tag: Option<tokens::CloseTagStart>,
114 ) -> Option<Self> {
115 let start_tag = start_tag?;
116 if input.peek(Ident::peek_any) {
117 let ident_from_invalid_closing = Ident::parse_any(input).expect("parse after peek");
118 parser.push_diagnostic(Diagnostic::spanned(
119 ident_from_invalid_closing.span(),
120 Level::Error,
121 "expected fragment closing, found element closing tag",
122 ));
123 };
124 Some(Self {
125 start_tag,
126 token_gt: parser.parse_simple(input)?,
127 })
128 }
129}
130
131#[derive(Clone, Debug, syn_derive::ToTokens)]
134pub struct OpenTag {
135 pub token_lt: Token![<],
136 pub name: NodeName,
137 pub generics: syn::Generics,
138 #[to_tokens(parse::to_tokens_array)]
139 pub attributes: Vec<NodeAttribute>,
140 pub end_tag: tokens::OpenTagEnd,
141}
142
143impl OpenTag {
144 pub fn is_self_closed(&self) -> bool {
145 self.end_tag.token_solidus.is_some()
146 }
147}
148
149#[derive(Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
151pub struct CloseTag {
152 pub start_tag: tokens::CloseTagStart,
153 pub name: NodeName,
154 pub generics: syn::Generics,
155 pub token_gt: Token![>],
156}
157
158impl CloseTag {
159 pub fn parse_with_start_tag(
160 parser: &mut RecoverableContext,
161 input: syn::parse::ParseStream,
162 start_tag: Option<tokens::CloseTagStart>,
163 ) -> Option<Self> {
164 Some(Self {
165 start_tag: start_tag?,
166 name: parser.parse_simple(input)?,
167 generics: parser.parse_simple(input)?,
168 token_gt: parser.parse_simple(input)?,
169 })
170 }
171}
172
173#[cfg(test)]
174mod test {
175 use syn::custom_punctuation;
176
177 use super::*;
178
179 macro_rules! parse_quote {
180 ($mod_name:ident, $name: ident=> $($tts:tt)*) => {
181 mod $mod_name {
182 use super::*;
183 #[test]
185 fn parse_quote() {
186
187 let tts = quote::quote!{
188 $($tts)*
189 };
190 syn::parse2::<$name>(tts).unwrap();
191
192 }
193 }
194 }
195 }
196
197 parse_quote! {docstart, DocStart => <!}
198
199 parse_quote! {comstart, ComStart => <!--}
200
201 parse_quote! {comend, ComEnd => -->}
202
203 parse_quote! {open_tag_end1, OpenTagEnd => >}
204
205 parse_quote! {open_tag_end2, OpenTagEnd => />}
206
207 parse_quote! {close_tag_start, CloseTagStart => </}
208
209 #[test]
212 fn parse_quote_doc_comp_custom_punct() {
213 custom_punctuation!(CloseTagStart2, </);
214 let tts = quote::quote! {
215 </
216 };
217 syn::parse2::<CloseTagStart2>(tts).unwrap_err();
218 }
219}