rstml_component_macro/template/
parsing.rs

1use crate::template::{Component, ComponentProp};
2
3use super::{ide::IdeHelper, AttributeValue, Children, Template, TemplateWriteInstruction};
4use proc_macro2::{Ident, Span};
5use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt};
6use rstml::{
7	node::{
8		AttributeValueExpr, FnBinding, Infallible, KVAttributeValue, KeyedAttribute,
9		KeyedAttributeValue, Node, NodeAttribute, NodeBlock, NodeComment, NodeDoctype, NodeElement,
10		NodeFragment, NodeName, NodeText, RawText,
11	},
12	ParsingResult,
13};
14use std::collections::HashSet;
15use syn::{spanned::Spanned, Expr, ExprLit, Lit, LitBool, Path};
16
17enum TagType {
18	Component(Path),
19	Element,
20	Block,
21}
22
23pub struct TemplateParser {
24	empty_elements: &'static HashSet<&'static str>,
25	instructions: Vec<TemplateWriteInstruction>,
26	diagnostics: Vec<Diagnostic>,
27	ide_helper: IdeHelper,
28}
29
30impl TemplateParser {
31	pub(super) fn new(empty_elements: &'static HashSet<&'static str>) -> Self {
32		Self {
33			empty_elements,
34			instructions: Vec::new(),
35			diagnostics: Vec::new(),
36			ide_helper: IdeHelper::new(),
37		}
38	}
39
40	pub fn parse_syn_stream(self, stream: syn::parse::ParseStream) -> Template {
41		let config = rstml::ParserConfig::new()
42			.recover_block(true)
43			.always_self_closed_elements(self.empty_elements.clone())
44			.raw_text_elements(["script", "style"].into_iter().collect());
45
46		let parser = rstml::Parser::new(config);
47		let parsing_result = parser.parse_syn_stream(stream);
48
49		self.parse(parsing_result)
50	}
51
52	pub fn parse(mut self, parsing_result: ParsingResult<Vec<Node>>) -> Template {
53		let (nodes, diagnostics) = parsing_result.split();
54		self.diagnostics = diagnostics;
55
56		self.parse_nodes(nodes)
57	}
58
59	pub fn parse_nodes(mut self, nodes: Option<Vec<Node>>) -> Template {
60		if let Some(nodes) = nodes {
61			if !nodes.is_empty() {
62				self.visit_nodes(nodes);
63			}
64		}
65
66		Template {
67			instructions: self.instructions,
68			diagnostics: self.diagnostics,
69			ide_helper: self.ide_helper,
70		}
71	}
72
73	fn visit_nodes(&mut self, nodes: impl IntoIterator<Item = Node>) {
74		for node in nodes {
75			self.visit_node(node);
76		}
77	}
78
79	fn visit_node(&mut self, node: Node) {
80		match node {
81			Node::Doctype(doctype) => self.visit_doctype(doctype),
82			Node::Element(element) => self.visit_element(element),
83			Node::Text(text) => self.visit_text(text),
84			Node::RawText(raw_text) => self.visit_raw_text(raw_text),
85			Node::Fragment(fragment) => self.visit_fragment(fragment),
86			Node::Comment(comment) => self.visit_comment(comment),
87			Node::Block(block) => self.visit_block(block),
88			Node::Custom(_) => unreachable!(),
89		}
90	}
91
92	fn visit_doctype(&mut self, doctype: NodeDoctype) {
93		self
94			.instructions
95			.push(TemplateWriteInstruction::Doctype(doctype.value));
96	}
97
98	fn visit_element(&mut self, element: NodeElement<Infallible>) {
99		fn tag_type(name: &NodeName) -> TagType {
100			match name {
101				NodeName::Block(_) => TagType::Block,
102				NodeName::Punctuated(_) => TagType::Element,
103				NodeName::Path(path) => {
104					if path.qself.is_some()
105						|| path.path.leading_colon.is_some()
106						|| path.path.segments.len() > 1
107						|| path.path.segments[0]
108							.ident
109							.to_string()
110							.starts_with(char::is_lowercase)
111					{
112						TagType::Element
113					} else {
114						TagType::Component(path.path.clone())
115					}
116				}
117			}
118		}
119
120		match tag_type(element.name()) {
121			TagType::Component(path) => self.visit_component(element, path),
122			TagType::Element => self.visit_html_element(element),
123			TagType::Block => self.visit_block_element(element),
124		}
125	}
126
127	fn visit_component_children(&mut self, children: Vec<Node>) -> Option<Children> {
128		if children.len() == 1 && matches!(children[0], Node::Block(_)) {
129			let block = match children.into_iter().next().unwrap() {
130				Node::Block(block) => block,
131				_ => unreachable!(),
132			};
133
134			Some(Children::Expr(block))
135		} else {
136			let template = TemplateParser::new(self.empty_elements).parse_nodes(Some(children));
137
138			if template.is_empty() {
139				None
140			} else {
141				Some(Children::Template(template))
142			}
143		}
144	}
145
146	fn visit_component(&mut self, element: NodeElement<Infallible>, path: Path) {
147		// TODO: improve
148		fn is_valid_identifier(value: &str) -> bool {
149			// let chars = value.as_bytes().iter().copied();
150			!value.is_empty()
151				&& value.is_ascii()
152				&& value.chars().next().unwrap().is_ascii_alphabetic()
153				&& value[1..]
154					.chars()
155					.all(|c| c.is_ascii_alphanumeric() || c == '_')
156		}
157
158		let children = if !element.children.is_empty() {
159			self.visit_component_children(element.children)
160		} else {
161			None
162		};
163
164		let NodeElement { open_tag, .. } = element;
165
166		let props = open_tag
167			.attributes
168			.into_iter()
169			.filter_map(|attr| {
170				let NodeAttribute::Attribute(KeyedAttribute {
171					key,
172					possible_value,
173				}) = attr
174				else {
175					self
176						.diagnostics
177						.push(attr.span().error("Only keyed attributes are supported"));
178					return None;
179				};
180
181				let name = match key {
182					NodeName::Path(path) => {
183						if path.qself.is_some()
184							|| path.path.leading_colon.is_some()
185							|| path.path.segments.len() != 1
186						{
187							self.diagnostics.push(
188								path
189									.span()
190									.error("Only simple identifiers are supported as component prop-names"),
191							);
192							return None;
193						}
194
195						path.path.segments[0].ident.clone()
196					}
197					NodeName::Punctuated(name) => {
198						let mut s = NodeName::Punctuated(name.clone()).to_string();
199						s.retain(|c| !c.is_ascii_whitespace());
200						let s = s.replace('-', "_");
201
202						if is_valid_identifier(&s) {
203							Ident::new(&s, name.span())
204						} else {
205							self
206								.diagnostics
207								.push(name.span().error(format!("Invalid prop-name `{}`", s)));
208							return None;
209						}
210					}
211					NodeName::Block(block) => {
212						self
213							.diagnostics
214							.push(block.span().error("Dynamic attributes are not supported"));
215						return None;
216					}
217				};
218
219				let value = match possible_value {
220					KeyedAttributeValue::Binding(binding) => {
221						self.diagnostics.push(
222							binding
223								.span()
224								.error("Component prop-functions are not supported."),
225						);
226						return None;
227					}
228					KeyedAttributeValue::Value(value) => match value.value {
229						KVAttributeValue::InvalidBraced(blk) => {
230							self.diagnostics.push(
231								blk
232									.span()
233									.error("Invalid braced expression in attribute value"),
234							);
235							return None;
236						}
237						KVAttributeValue::Expr(expr) => expr,
238					},
239					KeyedAttributeValue::None => Expr::Lit(ExprLit {
240						attrs: vec![],
241						lit: Lit::Bool(LitBool {
242							value: true,
243							span: Span::call_site(),
244						}),
245					}),
246				};
247
248				if &name == "children" {
249					self.diagnostics.push(
250						name
251							.span()
252							.error("The `children` prop is reserved for components."),
253					);
254
255					return None;
256				}
257
258				Some(ComponentProp { name, value })
259			})
260			.collect::<Vec<_>>();
261
262		self
263			.instructions
264			.push(TemplateWriteInstruction::Component(Component {
265				path,
266				props,
267				children,
268			}));
269	}
270
271	fn visit_block_element(&mut self, element: NodeElement<Infallible>) {
272		self.diagnostics.push(
273			element
274				.name()
275				.span()
276				.error("Dynamic elements are not supported. Use a component or an HTML element instead."),
277		);
278	}
279
280	fn visit_html_element(&mut self, element: NodeElement<Infallible>) {
281		let element_span = element.span();
282		let NodeElement {
283			open_tag,
284			close_tag,
285			children,
286		} = element;
287
288		self.ide_helper.mark_open_tag(open_tag.name.clone());
289		if let Some(close_tag) = close_tag {
290			self.ide_helper.mark_close_tag(close_tag.name);
291		}
292
293		let name = open_tag.name;
294		self
295			.instructions
296			.push(TemplateWriteInstruction::OpenTagStart(name.clone()));
297
298		// attributes
299		self.visit_html_attributes(&name, open_tag.attributes);
300
301		if self.empty_elements.contains(&*name.to_string()) {
302			// special empty tags that can't have children (for instance <br>)
303			self
304				.instructions
305				.push(TemplateWriteInstruction::SelfCloseTag);
306
307			if !children.is_empty() {
308				self
309					.diagnostics
310					.push(element_span.error("Empty elements cannot have children."));
311			}
312		} else {
313			// normal tags
314			self.instructions.push(TemplateWriteInstruction::OpenTagEnd);
315
316			// children
317			self.visit_nodes(children);
318
319			// end tag
320			self
321				.instructions
322				.push(TemplateWriteInstruction::EndTag(name.clone()));
323		}
324	}
325
326	fn visit_html_attributes(
327		&mut self,
328		element_name: &NodeName,
329		attributes: impl IntoIterator<Item = NodeAttribute>,
330	) {
331		for attribute in attributes {
332			// TODO: Special handling of class? and duplicates?
333			self.visit_html_attribute(element_name, attribute);
334		}
335	}
336
337	fn visit_html_attribute(&mut self, element_name: &NodeName, attribute: NodeAttribute) {
338		match attribute {
339			NodeAttribute::Block(block) => self.visit_html_block_attribute(element_name, block),
340			NodeAttribute::Attribute(attribute) => {
341				self.visit_html_static_attribute(element_name, attribute)
342			}
343		}
344	}
345
346	fn visit_html_block_attribute(&mut self, _element_name: &NodeName, block: NodeBlock) {
347		self
348			.instructions
349			.push(TemplateWriteInstruction::DynamicAttributes(block));
350	}
351
352	fn visit_html_static_attribute(&mut self, element_name: &NodeName, attribute: KeyedAttribute) {
353		let KeyedAttribute {
354			key,
355			possible_value,
356		} = attribute;
357		self.ide_helper.mark_attr_name(key.clone());
358
359		self
360			.instructions
361			.push(TemplateWriteInstruction::AttributeName(key.clone()));
362
363		match possible_value {
364			KeyedAttributeValue::Binding(binding) => {
365				self.visit_attribute_binding(element_name, &key, binding)
366			}
367			KeyedAttributeValue::Value(value) => self.visit_attribute_value(element_name, &key, value),
368			KeyedAttributeValue::None => (),
369		}
370	}
371
372	fn visit_attribute_binding(
373		&mut self,
374		_element_name: &NodeName,
375		_attribute_name: &NodeName,
376		binding: FnBinding,
377	) {
378		self.diagnostics.push(
379			binding
380				.span()
381				.error("Attribute bindings are not supported."),
382		);
383	}
384
385	fn visit_attribute_value(
386		&mut self,
387		_element_name: &NodeName,
388		_attribute_name: &NodeName,
389		value: AttributeValueExpr,
390	) {
391		if let Some(value) = value.value_literal_string() {
392			self
393				.instructions
394				.push(TemplateWriteInstruction::AttributeValue(
395					AttributeValue::Constant(value),
396				));
397		} else {
398			let expr = match &value.value {
399				KVAttributeValue::InvalidBraced(blk) => {
400					self.diagnostics.push(
401						blk
402							.span()
403							.error("Invalid braced expression in attribute value"),
404					);
405					return;
406				}
407				KVAttributeValue::Expr(expr) => expr,
408			};
409			self
410				.instructions
411				.push(TemplateWriteInstruction::AttributeValue(
412					AttributeValue::Expression(Box::new(expr.clone())),
413				));
414		}
415	}
416
417	fn visit_text(&mut self, text: NodeText) {
418		self.instructions.push(TemplateWriteInstruction::Text(text));
419	}
420
421	fn visit_raw_text(&mut self, raw_text: RawText) {
422		self
423			.instructions
424			.push(TemplateWriteInstruction::RawText(raw_text));
425	}
426
427	fn visit_fragment(&mut self, fragment: NodeFragment<Infallible>) {
428		self.visit_nodes(fragment.children);
429	}
430
431	fn visit_comment(&mut self, comment: NodeComment) {
432		self
433			.instructions
434			.push(TemplateWriteInstruction::Comment(comment));
435	}
436
437	fn visit_block(&mut self, block: NodeBlock) {
438		self
439			.instructions
440			.push(TemplateWriteInstruction::DynamicContent(block));
441	}
442}