element.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. use crate::innerlude::*;
  2. use proc_macro2::{Span, TokenStream as TokenStream2};
  3. use proc_macro2_diagnostics::SpanDiagnosticExt;
  4. use quote::{quote, ToTokens, TokenStreamExt};
  5. use std::fmt::{Display, Formatter};
  6. use syn::{
  7. parse::{Parse, ParseStream},
  8. punctuated::Punctuated,
  9. spanned::Spanned,
  10. token::Brace,
  11. Ident, LitStr, Result, Token,
  12. };
  13. /// Parse the VNode::Element type
  14. #[derive(PartialEq, Eq, Clone, Debug)]
  15. pub struct Element {
  16. /// div { } -> div
  17. pub name: ElementName,
  18. /// The actual attributes that were parsed
  19. pub raw_attributes: Vec<Attribute>,
  20. /// The attributes after merging - basically the formatted version of the combined attributes
  21. /// where possible.
  22. ///
  23. /// These are the actual attributes that get rendered out
  24. pub merged_attributes: Vec<Attribute>,
  25. /// The `...` spread attributes.
  26. pub spreads: Vec<Spread>,
  27. // /// Elements can have multiple, unlike components which can only have one
  28. // pub spreads: Vec<Spread>,
  29. /// The children of the element
  30. pub children: Vec<BodyNode>,
  31. /// the brace of the `div { }`
  32. pub brace: Option<Brace>,
  33. /// A list of diagnostics that were generated during parsing. This element might be a valid rsx_block
  34. /// but not technically a valid element - these diagnostics tell us what's wrong and then are used
  35. /// when rendering
  36. pub diagnostics: Diagnostics,
  37. }
  38. impl Parse for Element {
  39. fn parse(stream: ParseStream) -> Result<Self> {
  40. let name = stream.parse::<ElementName>()?;
  41. // We very liberally parse elements - they might not even have a brace!
  42. // This is designed such that we can throw a compile error but still give autocomplete
  43. // ... partial completions mean we do some weird parsing to get the right completions
  44. let mut brace = None;
  45. let mut block = RsxBlock::default();
  46. match stream.peek(Brace) {
  47. // If the element is followed by a brace, it is complete. Parse the body
  48. true => {
  49. block = stream.parse::<RsxBlock>()?;
  50. brace = Some(block.brace);
  51. }
  52. // Otherwise, it is incomplete. Add a diagnostic
  53. false => block.diagnostics.push(
  54. name.span()
  55. .error("Elements must be followed by braces")
  56. .help("Did you forget a brace?"),
  57. ),
  58. }
  59. // Make sure these attributes have an el_name set for completions and Template generation
  60. for attr in block.attributes.iter_mut() {
  61. attr.el_name = Some(name.clone());
  62. }
  63. // Assemble the new element from the contents of the block
  64. let mut element = Element {
  65. brace,
  66. name: name.clone(),
  67. raw_attributes: block.attributes,
  68. children: block.children,
  69. diagnostics: block.diagnostics,
  70. spreads: block.spreads.clone(),
  71. merged_attributes: Vec::new(),
  72. };
  73. // And then merge the various attributes together
  74. // The original raw_attributes are kept for lossless parsing used by hotreload/autofmt
  75. element.merge_attributes();
  76. // And then merge the spreads *after* the attributes are merged. This ensures walking the
  77. // merged attributes in path order stops before we hit the spreads, but spreads are still
  78. // counted as dynamic attributes
  79. for spread in block.spreads.iter() {
  80. element.merged_attributes.push(Attribute {
  81. name: AttributeName::Spread(spread.dots),
  82. colon: None,
  83. value: AttributeValue::AttrExpr(PartialExpr::from_expr(&spread.expr)),
  84. comma: spread.comma,
  85. dyn_idx: spread.dyn_idx.clone(),
  86. el_name: Some(name.clone()),
  87. });
  88. }
  89. Ok(element)
  90. }
  91. }
  92. impl ToTokens for Element {
  93. fn to_tokens(&self, tokens: &mut TokenStream2) {
  94. let el = self;
  95. let el_name = &el.name;
  96. let ns = |name| match el_name {
  97. ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
  98. ElementName::Custom(_) => quote! { None },
  99. };
  100. let static_attrs = el
  101. .merged_attributes
  102. .iter()
  103. .map(|attr| {
  104. // Rendering static attributes requires a bit more work than just a dynamic attrs
  105. // Early return for dynamic attributes
  106. let Some((name, value)) = attr.as_static_str_literal() else {
  107. let id = attr.dyn_idx.get();
  108. return quote! { dioxus_core::TemplateAttribute::Dynamic { id: #id } };
  109. };
  110. let ns = match name {
  111. AttributeName::BuiltIn(name) => ns(quote!(#name.1)),
  112. AttributeName::Custom(_) => quote!(None),
  113. AttributeName::Spread(_) => {
  114. unreachable!("spread attributes should not be static")
  115. }
  116. };
  117. let name = match (el_name, name) {
  118. (ElementName::Ident(_), AttributeName::BuiltIn(_)) => {
  119. quote! { dioxus_elements::#el_name::#name.0 }
  120. }
  121. //hmmmm I think we could just totokens this, but the to_string might be inserting quotes
  122. _ => {
  123. let as_string = name.to_string();
  124. quote! { #as_string }
  125. }
  126. };
  127. let value = value.to_static().unwrap();
  128. quote! {
  129. dioxus_core::TemplateAttribute::Static {
  130. name: #name,
  131. namespace: #ns,
  132. value: #value,
  133. }
  134. }
  135. })
  136. .collect::<Vec<_>>();
  137. // Render either the child
  138. let children = el.children.iter().map(|c| match c {
  139. BodyNode::Element(el) => quote! { #el },
  140. BodyNode::Text(text) if text.is_static() => {
  141. let text = text.input.to_static().unwrap();
  142. quote! { dioxus_core::TemplateNode::Text { text: #text } }
  143. }
  144. BodyNode::Text(text) => {
  145. let id = text.dyn_idx.get();
  146. quote! { dioxus_core::TemplateNode::Dynamic { id: #id } }
  147. }
  148. BodyNode::ForLoop(floop) => {
  149. let id = floop.dyn_idx.get();
  150. quote! { dioxus_core::TemplateNode::Dynamic { id: #id } }
  151. }
  152. BodyNode::RawExpr(exp) => {
  153. let id = exp.dyn_idx.get();
  154. quote! { dioxus_core::TemplateNode::Dynamic { id: #id } }
  155. }
  156. BodyNode::Component(exp) => {
  157. let id = exp.dyn_idx.get();
  158. quote! { dioxus_core::TemplateNode::Dynamic { id: #id } }
  159. }
  160. BodyNode::IfChain(exp) => {
  161. let id = exp.dyn_idx.get();
  162. quote! { dioxus_core::TemplateNode::Dynamic { id: #id } }
  163. }
  164. });
  165. let ns = ns(quote!(NAME_SPACE));
  166. let el_name = el_name.tag_name();
  167. let diagnostics = &el.diagnostics;
  168. let completion_hints = &el.completion_hints();
  169. // todo: generate less code if there's no diagnostics by not including the curlies
  170. tokens.append_all(quote! {
  171. {
  172. #completion_hints
  173. #diagnostics
  174. dioxus_core::TemplateNode::Element {
  175. tag: #el_name,
  176. namespace: #ns,
  177. attrs: &[ #(#static_attrs),* ],
  178. children: &[ #(#children),* ],
  179. }
  180. }
  181. })
  182. }
  183. }
  184. impl Element {
  185. pub(crate) fn add_merging_non_string_diagnostic(diagnostics: &mut Diagnostics, span: Span) {
  186. diagnostics.push(span.error("Cannot merge non-fmt literals").help(
  187. "Only formatted strings can be merged together. If you want to merge literals, you can use a format string.",
  188. ));
  189. }
  190. /// Collapses ifmt attributes into a single dynamic attribute using a space or `;` as a delimiter
  191. ///
  192. /// ```ignore,
  193. /// div {
  194. /// class: "abc-def",
  195. /// class: if some_expr { "abc" },
  196. /// }
  197. /// ```
  198. fn merge_attributes(&mut self) {
  199. let mut attrs: Vec<&Attribute> = vec![];
  200. for attr in &self.raw_attributes {
  201. if attrs.iter().any(|old_attr| old_attr.name == attr.name) {
  202. continue;
  203. }
  204. attrs.push(attr);
  205. }
  206. for attr in attrs {
  207. if attr.name.is_likely_key() {
  208. continue;
  209. }
  210. // Collect all the attributes with the same name
  211. let matching_attrs = self
  212. .raw_attributes
  213. .iter()
  214. .filter(|a| a.name == attr.name)
  215. .collect::<Vec<_>>();
  216. // if there's only one attribute with this name, then we don't need to merge anything
  217. if matching_attrs.len() == 1 {
  218. self.merged_attributes.push(attr.clone());
  219. continue;
  220. }
  221. // If there are multiple attributes with the same name, then we need to merge them
  222. // This will be done by creating an ifmt attribute that combines all the segments
  223. // We might want to throw a diagnostic of trying to merge things together that might not
  224. // make a whole lot of sense - like merging two exprs together
  225. let mut out = IfmtInput::new(attr.span());
  226. for (idx, matching_attr) in matching_attrs.iter().enumerate() {
  227. // If this is the first attribute, then we don't need to add a delimiter
  228. if idx != 0 {
  229. // FIXME: I don't want to special case anything - but our delimiter is special cased to a space
  230. // We really don't want to special case anything in the macro, but the hope here is that
  231. // multiline strings can be merged with a space
  232. out.push_raw_str(" ".to_string());
  233. }
  234. // Merge raw literals into the output
  235. if let AttributeValue::AttrLiteral(HotLiteral::Fmted(lit)) = &matching_attr.value {
  236. out.push_ifmt(lit.formatted_input.clone());
  237. continue;
  238. }
  239. // Merge `if cond { "abc" } else if ...` into the output
  240. if let AttributeValue::IfExpr(value) = &matching_attr.value {
  241. out.push_expr(value.quote_as_string(&mut self.diagnostics));
  242. continue;
  243. }
  244. Self::add_merging_non_string_diagnostic(
  245. &mut self.diagnostics,
  246. matching_attr.span(),
  247. );
  248. }
  249. let out_lit = HotLiteral::Fmted(out.into());
  250. self.merged_attributes.push(Attribute {
  251. name: attr.name.clone(),
  252. value: AttributeValue::AttrLiteral(out_lit),
  253. colon: attr.colon,
  254. dyn_idx: attr.dyn_idx.clone(),
  255. comma: matching_attrs.last().unwrap().comma,
  256. el_name: attr.el_name.clone(),
  257. });
  258. }
  259. }
  260. pub(crate) fn key(&self) -> Option<&AttributeValue> {
  261. self.raw_attributes
  262. .iter()
  263. .find(|attr| attr.name.is_likely_key())
  264. .map(|attr| &attr.value)
  265. }
  266. fn completion_hints(&self) -> TokenStream2 {
  267. // If there is already a brace, we don't need any completion hints
  268. if self.brace.is_some() {
  269. return quote! {};
  270. }
  271. let ElementName::Ident(name) = &self.name else {
  272. return quote! {};
  273. };
  274. quote! {
  275. {
  276. #[allow(dead_code)]
  277. #[doc(hidden)]
  278. mod __completions {
  279. fn ignore() {
  280. super::dioxus_elements::elements::completions::CompleteWithBraces::#name
  281. }
  282. }
  283. }
  284. }
  285. }
  286. }
  287. #[derive(PartialEq, Eq, Clone, Debug, Hash)]
  288. pub enum ElementName {
  289. Ident(Ident),
  290. Custom(LitStr),
  291. }
  292. impl ToTokens for ElementName {
  293. fn to_tokens(&self, tokens: &mut TokenStream2) {
  294. match self {
  295. ElementName::Ident(i) => tokens.append_all(quote! { #i }),
  296. ElementName::Custom(s) => s.to_tokens(tokens),
  297. }
  298. }
  299. }
  300. impl Parse for ElementName {
  301. fn parse(stream: ParseStream) -> Result<Self> {
  302. let raw =
  303. Punctuated::<Ident, Token![-]>::parse_separated_nonempty_with(stream, parse_raw_ident)?;
  304. if raw.len() == 1 {
  305. Ok(ElementName::Ident(raw.into_iter().next().unwrap()))
  306. } else {
  307. let span = raw.span();
  308. let tag = raw
  309. .into_iter()
  310. .map(|ident| ident.to_string())
  311. .collect::<Vec<_>>()
  312. .join("-");
  313. let tag = LitStr::new(&tag, span);
  314. Ok(ElementName::Custom(tag))
  315. }
  316. }
  317. }
  318. impl ElementName {
  319. pub(crate) fn tag_name(&self) -> TokenStream2 {
  320. match self {
  321. ElementName::Ident(i) => quote! { dioxus_elements::elements::#i::TAG_NAME },
  322. ElementName::Custom(s) => quote! { #s },
  323. }
  324. }
  325. pub fn span(&self) -> Span {
  326. match self {
  327. ElementName::Ident(i) => i.span(),
  328. ElementName::Custom(s) => s.span(),
  329. }
  330. }
  331. }
  332. impl PartialEq<&str> for ElementName {
  333. fn eq(&self, other: &&str) -> bool {
  334. match self {
  335. ElementName::Ident(i) => i == *other,
  336. ElementName::Custom(s) => s.value() == *other,
  337. }
  338. }
  339. }
  340. impl Display for ElementName {
  341. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
  342. match self {
  343. ElementName::Ident(i) => write!(f, "{}", i),
  344. ElementName::Custom(s) => write!(f, "{}", s.value()),
  345. }
  346. }
  347. }
  348. #[cfg(test)]
  349. mod tests {
  350. use super::*;
  351. use prettier_please::PrettyUnparse;
  352. #[test]
  353. fn parses_name() {
  354. let _parsed: ElementName = syn::parse2(quote::quote! { div }).unwrap();
  355. let _parsed: ElementName = syn::parse2(quote::quote! { some-cool-element }).unwrap();
  356. let _parsed: Element = syn::parse2(quote::quote! { div {} }).unwrap();
  357. let _parsed: Element = syn::parse2(quote::quote! { some-cool-element {} }).unwrap();
  358. let parsed: Element = syn::parse2(quote::quote! {
  359. some-cool-div {
  360. id: "hi",
  361. id: "hi {abc}",
  362. id: "hi {def}",
  363. class: 123,
  364. something: bool,
  365. data_attr: "data",
  366. data_attr: "data2",
  367. data_attr: "data3",
  368. exp: { some_expr },
  369. something: {cool},
  370. something: bool,
  371. something: 123,
  372. onclick: move |_| {
  373. println!("hello world");
  374. },
  375. "some-attr": "hello world",
  376. onclick: move |_| {},
  377. class: "hello world",
  378. id: "my-id",
  379. data_attr: "data",
  380. data_attr: "data2",
  381. data_attr: "data3",
  382. "somte_attr3": "hello world",
  383. something: {cool},
  384. something: bool,
  385. something: 123,
  386. onclick: move |_| {
  387. println!("hello world");
  388. },
  389. ..attrs1,
  390. ..attrs2,
  391. ..attrs3
  392. }
  393. })
  394. .unwrap();
  395. dbg!(parsed);
  396. }
  397. #[test]
  398. fn parses_variety() {
  399. let input = quote::quote! {
  400. div {
  401. class: "hello world",
  402. id: "my-id",
  403. data_attr: "data",
  404. data_attr: "data2",
  405. data_attr: "data3",
  406. "somte_attr3": "hello world",
  407. something: {cool},
  408. something: bool,
  409. something: 123,
  410. onclick: move |_| {
  411. println!("hello world");
  412. },
  413. ..attrs,
  414. ..attrs2,
  415. ..attrs3
  416. }
  417. };
  418. let parsed: Element = syn::parse2(input).unwrap();
  419. dbg!(parsed);
  420. }
  421. #[test]
  422. fn to_tokens_properly() {
  423. let input = quote::quote! {
  424. div {
  425. class: "hello world",
  426. class2: "hello {world}",
  427. class3: "goodbye {world}",
  428. class4: "goodbye world",
  429. "something": "cool {blah}",
  430. "something2": "cooler",
  431. div {
  432. div {
  433. h1 { class: "h1 col" }
  434. h2 { class: "h2 col" }
  435. h3 { class: "h3 col" }
  436. div {}
  437. }
  438. }
  439. }
  440. };
  441. let parsed: Element = syn::parse2(input).unwrap();
  442. println!("{}", parsed.to_token_stream().pretty_unparse());
  443. }
  444. #[test]
  445. fn to_tokens_with_diagnostic() {
  446. let input = quote::quote! {
  447. div {
  448. class: "hello world",
  449. id: "my-id",
  450. ..attrs,
  451. div {
  452. ..attrs,
  453. class: "hello world",
  454. id: "my-id",
  455. }
  456. }
  457. };
  458. let parsed: Element = syn::parse2(input).unwrap();
  459. println!("{}", parsed.to_token_stream().pretty_unparse());
  460. }
  461. #[test]
  462. fn merges_attributes() {
  463. let input = quote::quote! {
  464. div {
  465. class: "hello world",
  466. class: if count > 3 { "abc {def}" },
  467. class: if count < 50 { "small" } else { "big" }
  468. }
  469. };
  470. let parsed: Element = syn::parse2(input).unwrap();
  471. assert_eq!(parsed.diagnostics.len(), 0);
  472. assert_eq!(parsed.merged_attributes.len(), 1);
  473. assert_eq!(
  474. parsed.merged_attributes[0].name.to_string(),
  475. "class".to_string()
  476. );
  477. let attr = &parsed.merged_attributes[0].value;
  478. println!("{}", attr.to_token_stream().pretty_unparse());
  479. let _attr = match attr {
  480. AttributeValue::AttrLiteral(lit) => lit,
  481. _ => panic!("expected literal"),
  482. };
  483. }
  484. /// There are a number of cases where merging attributes doesn't make sense
  485. /// - merging two expressions together
  486. /// - merging two literals together
  487. /// - merging a literal and an expression together
  488. ///
  489. /// etc
  490. ///
  491. /// We really only want to merge formatted things together
  492. ///
  493. /// IE
  494. /// class: "hello world ",
  495. /// class: if some_expr { "abc" }
  496. ///
  497. /// Some open questions - should the delimiter be explicit?
  498. #[test]
  499. fn merging_weird_fails() {
  500. let input = quote::quote! {
  501. div {
  502. class: "hello world",
  503. class: if some_expr { 123 },
  504. style: "color: red;",
  505. style: "color: blue;",
  506. width: "1px",
  507. width: 1,
  508. width: false,
  509. contenteditable: true,
  510. }
  511. };
  512. let parsed: Element = syn::parse2(input).unwrap();
  513. assert_eq!(parsed.merged_attributes.len(), 4);
  514. assert_eq!(parsed.diagnostics.len(), 3);
  515. // style should not generate a diagnostic
  516. assert!(!parsed
  517. .diagnostics
  518. .diagnostics
  519. .into_iter()
  520. .any(|f| f.emit_as_item_tokens().to_string().contains("style")));
  521. }
  522. #[test]
  523. fn diagnostics() {
  524. let input = quote::quote! {
  525. p {
  526. class: "foo bar"
  527. "Hello world"
  528. }
  529. };
  530. let _parsed: Element = syn::parse2(input).unwrap();
  531. }
  532. #[test]
  533. fn parses_raw_elements() {
  534. let input = quote::quote! {
  535. use {
  536. "hello"
  537. }
  538. };
  539. let _parsed: Element = syn::parse2(input).unwrap();
  540. }
  541. }