1use proc_macro2::{extra::DelimSpan, Delimiter, TokenStream};
5use proc_macro2_diagnostics::{Diagnostic, Level};
6use quote::ToTokens;
7use syn::{
8 braced,
9 parse::{discouraged::Speculative, Parse, ParseStream, Parser},
10 spanned::Spanned,
11 token::Brace,
12 Block, Ident, LitStr, Token,
13};
14
15use super::{
16 atoms::{
17 tokens::{self, DocStart},
18 CloseTag, FragmentClose, FragmentOpen, OpenTag,
19 },
20 raw_text::RawText,
21 CustomNode, Node, NodeBlock, NodeDoctype, NodeFragment,
22};
23use crate::{
24 atoms::CloseTagStart,
25 config::TransformBlockFn,
26 node::{NodeAttribute, NodeElement},
27 parser::recoverable::{ParseRecoverable, RecoverableContext},
28};
29
30impl ParseRecoverable for NodeBlock {
31 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
32 let fork = input.fork();
33
34 let block = match parse_valid_block_expr(parser, &fork) {
35 Ok(value) => {
36 input.advance_to(&fork);
37 NodeBlock::ValidBlock(value)
38 }
39 Err(e) if parser.config().recover_block => {
40 parser.push_diagnostic(e);
41 NodeBlock::Invalid(parser.parse_simple(input)?)
42 }
43 Err(e) => {
44 parser.push_diagnostic(e);
45 return None;
46 }
47 };
48 Some(block)
49 }
50}
51
52impl<C: CustomNode> ParseRecoverable for NodeFragment<C> {
53 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
54 let tag_open: FragmentOpen = parser.parse_simple(input)?;
55
56 let is_raw = |name| parser.config().raw_text_elements.contains(name);
57
58 let (children, tag_close) = if is_raw("") {
59 let (child, closed_tag) =
60 parser.parse_with_ending(input, |_, t| RawText::from(t), FragmentClose::parse);
61
62 (vec![Node::<C>::RawText(child)], closed_tag)
63 } else {
64 let (child, close_tag_start) =
65 parser.parse_tokens_until_call::<Node<C>, _, _>(input, CloseTagStart::parse);
66 (
67 child,
68 FragmentClose::parse_with_start_tag(parser, input, close_tag_start),
69 )
70 };
71 let open_tag_end = tag_open.token_gt.span();
72 let close_tag_start = tag_close.as_ref().map(|v| v.start_tag.token_lt.span());
73
74 let children = RawText::vec_set_context(open_tag_end, close_tag_start, children);
75
76 Some(NodeFragment {
77 tag_open,
78 children,
79 tag_close,
80 })
81 }
82}
83
84impl ParseRecoverable for NodeDoctype {
85 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
86 let token_start = parser.parse_simple::<DocStart>(input)?;
87 let doctype_keyword = parser.parse_simple::<Ident>(input)?;
88 if doctype_keyword.to_string().to_lowercase() != "doctype" {
89 parser.push_diagnostic(Diagnostic::spanned(
90 doctype_keyword.span(),
91 Level::Error,
92 "expected DOCTYPE keyword",
93 ));
94 return None;
95 }
96 let (value, token_end) =
97 parser.parse_with_ending(input, |_, t| RawText::from(t), <Token![>]>::parse);
98
99 let token_end = token_end?;
100 Some(Self {
101 token_start,
102 token_doctype: doctype_keyword,
103 value,
104 token_end,
105 })
106 }
107}
108
109impl OpenTag {
110 pub fn parse_start_tag(
116 parser: &mut RecoverableContext,
117 input: ParseStream,
118 ) -> Option<Token![<]> {
119 let token_lt = parser.parse_simple::<Token![<]>(input)?;
120 if input.peek(Token![/]) {
123 let span = if let Ok(solidus) = input.parse::<Token![/]>() {
124 solidus.span()
125 } else {
126 token_lt.span()
127 };
128 parser.push_diagnostic(Diagnostic::spanned(
129 span,
130 Level::Error,
131 "close tag was parsed while waiting for open tag",
132 ));
133 }
134 Some(token_lt)
135 }
136}
137
138impl ParseRecoverable for OpenTag {
139 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
140 let token_lt = Self::parse_start_tag(parser, input)?;
141 let name = parser.parse_simple(input)?;
142 let generics = parser.parse_simple(input)?;
143
144 let (attributes, end_tag) = parser
145 .parse_tokens_with_conflicted_ending::<NodeAttribute, _, _>(
146 input,
147 tokens::OpenTagEnd::parse,
148 );
149
150 if end_tag.is_none() {
151 parser.push_diagnostic(Diagnostic::new(Level::Error, "expected end of tag '>'"));
152 }
153 end_tag.map(|end_tag| OpenTag {
154 token_lt,
155 name,
156 generics,
157 attributes,
158 end_tag,
159 })
160 }
161}
162
163impl<C: CustomNode> NodeElement<C> {
164 pub fn parse_children(
172 parser: &mut RecoverableContext,
173 input: ParseStream,
174 raw: bool,
175 open_tag: &OpenTag,
176 ) -> Option<(Vec<Node<C>>, Option<CloseTag>)> {
177 let (children, close_tag) = if raw {
178 let (child, closed_tag) =
179 parser.parse_with_ending(input, |_, t| RawText::from(t), CloseTag::parse);
180 let children = if !child.is_empty() {
182 vec![Node::RawText(child)]
183 } else {
184 vec![]
185 };
186 (children, closed_tag)
187 } else {
188 let (children, close_tag) =
192 parser.parse_tokens_until_call::<Node<C>, _, _>(input, CloseTagStart::parse);
193
194 let close_tag = CloseTag::parse_with_start_tag(parser, input, close_tag);
195
196 (children, close_tag)
197 };
198
199 let open_tag_end = open_tag.end_tag.token_gt.span();
200 let close_tag_start = close_tag.as_ref().map(|c| c.start_tag.token_lt.span());
201 let children = RawText::vec_set_context(open_tag_end, close_tag_start, children);
202
203 let Some(close_tag) = close_tag else {
204 let mut diagnostic = Diagnostic::spanned(
205 open_tag.span(),
206 Level::Error,
207 "open tag has no corresponding close tag",
208 );
209 if !children.is_empty() {
210 let mut note_span = TokenStream::new();
211 children.iter().for_each(|v| v.to_tokens(&mut note_span));
212 diagnostic = diagnostic.span_note(
213 note_span.span(),
214 "treating all inputs after open tag as it content",
215 );
216 }
217
218 parser.push_diagnostic(diagnostic);
219 return Some((children, None));
220 };
221
222 if close_tag.name != open_tag.name {
223 match parser.config().element_close_wildcard.as_deref() {
224 Some(is_wildcard) if is_wildcard(open_tag, &close_tag) => {}
225 _ => {
226 let diagnostic = Diagnostic::spanned(
227 close_tag.span(),
228 Level::Error,
229 "wrong close tag found",
230 )
231 .spanned_child(
232 open_tag.span(),
233 Level::Help,
234 "open tag that should be closed; it's started here",
235 );
236
237 parser.push_diagnostic(diagnostic)
238 }
239 }
240 }
241 if close_tag.generics != open_tag.generics {
242 let diagnostic = Diagnostic::spanned(
243 close_tag.span(),
244 Level::Error,
245 "close tag generics missmatch",
246 )
247 .spanned_child(
248 open_tag.span(),
249 Level::Help,
250 "open tag generics should match close tag generics",
251 );
252 parser.push_diagnostic(diagnostic)
253 }
254 Some((children, Some(close_tag)))
255 }
256}
257
258impl<C: CustomNode> ParseRecoverable for NodeElement<C> {
259 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
260 let open_tag: OpenTag = parser.parse_recoverable(input)?;
261 let is_known_self_closed =
262 |name| parser.config().always_self_closed_elements.contains(name);
263 let is_raw = |name| parser.config().raw_text_elements.contains(name);
264
265 let tag_name_str = &*open_tag.name.to_string();
266 if open_tag.is_self_closed() || is_known_self_closed(tag_name_str) {
267 return Some(NodeElement {
268 open_tag,
269 children: vec![],
270 close_tag: None,
271 });
272 }
273 let (children, close_tag) =
274 Self::parse_children(parser, input, is_raw(tag_name_str), &open_tag)?;
275 let element = NodeElement {
276 open_tag,
277 children,
278 close_tag,
279 };
280 Some(element)
281 }
282}
283
284impl<C: CustomNode> ParseRecoverable for Node<C> {
285 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
286 let node = if C::peek_element(&input.fork()) {
287 Node::Custom(C::parse_recoverable(parser, input)?)
288 } else if input.peek(Token![<]) {
289 if input.peek2(Token![!]) {
290 if input.peek3(Ident) {
291 Node::Doctype(parser.parse_recoverable(input)?)
292 } else {
293 Node::Comment(parser.parse_simple(input)?)
294 }
295 } else if input.peek2(Token![>]) {
296 Node::Fragment(parser.parse_recoverable(input)?)
297 } else {
298 Node::Element(parser.parse_recoverable(input)?)
299 }
300 } else if input.peek(Brace) {
301 Node::Block(parser.parse_recoverable(input)?)
302 } else if input.peek(LitStr) {
303 Node::Text(parser.parse_simple(input)?)
304 } else if !input.is_empty() {
305 Node::RawText(parser.parse_recoverable(input)?)
307 } else {
308 return None;
309 };
310 Some(node)
311 }
312}
313
314pub(super) fn parse_array_of2_tokens<T: Parse>(input: ParseStream) -> syn::Result<[T; 2]> {
317 Ok([input.parse()?, input.parse()?])
318}
319
320pub(super) fn to_tokens_array<I>(input: &mut TokenStream, iter: I)
321where
322 I: IntoIterator,
323 I::Item: ToTokens,
324{
325 use quote::TokenStreamExt;
326 input.append_all(iter)
327}
328
329fn block_transform(input: ParseStream, transform_fn: &TransformBlockFn) -> syn::Result<Block> {
333 input.step(|cursor| {
334 let (block_group, block_span, next) = cursor
335 .group(Delimiter::Brace)
336 .ok_or_else(|| cursor.error("unexpected: no Group found"))?;
337 let parser = move |block_content: ParseStream| {
338 let forked_block_content = block_content.fork();
339
340 match transform_fn(&forked_block_content) {
341 Ok(transformed_tokens) => match transformed_tokens {
342 Some(tokens) => {
343 let parser = move |input: ParseStream| {
344 Ok(block_expr_with_extern_span(input, block_span))
345 };
346 let transformed_content = parser.parse2(tokens)?;
347 block_content.advance_to(&forked_block_content);
348 transformed_content
349 }
350 None => block_expr_with_extern_span(block_content, block_span),
351 },
352 Err(error) => Err(error),
353 }
354 };
355
356 Ok((parser.parse2(block_group.token_stream())?, next))
357 })
358}
359
360#[allow(clippy::needless_pass_by_ref_mut)]
361pub(crate) fn parse_valid_block_expr(
362 parser: &mut RecoverableContext,
363 input: syn::parse::ParseStream,
364) -> syn::Result<Block> {
365 let transform_block = parser.config().transform_block.clone();
366 let value = if let Some(transform_fn) = transform_block {
367 block_transform(input, &*transform_fn)?
368 } else {
369 block_expr(input)?
370 };
371 Ok(value)
372}
373fn block_expr_with_extern_span(input: ParseStream, span: DelimSpan) -> syn::Result<Block> {
375 Ok(Block {
376 brace_token: Brace { span },
377 stmts: Block::parse_within(input)?,
378 })
379}
380
381pub(crate) fn block_expr(input: syn::parse::ParseStream) -> syn::Result<Block> {
383 let content;
384 let brace_token = braced!(content in input);
385 Ok(Block {
386 brace_token,
387 stmts: Block::parse_within(&content)?,
388 })
389}