rstml_component_macro/template/
parsing.rs1use 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 fn is_valid_identifier(value: &str) -> bool {
149 !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 self.visit_html_attributes(&name, open_tag.attributes);
300
301 if self.empty_elements.contains(&*name.to_string()) {
302 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 self.instructions.push(TemplateWriteInstruction::OpenTagEnd);
315
316 self.visit_nodes(children);
318
319 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 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}