element.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. use crate::Writer;
  2. use dioxus_rsx::*;
  3. use proc_macro2::Span;
  4. use std::{
  5. fmt::Result,
  6. fmt::{self, Write},
  7. };
  8. use syn::{spanned::Spanned, token::Brace, Expr};
  9. #[derive(Debug)]
  10. enum ShortOptimization {
  11. /// Special because we want to print the closing bracket immediately
  12. ///
  13. /// IE
  14. /// `div {}` instead of `div { }`
  15. Empty,
  16. /// Special optimization to put everything on the same line and add some buffer spaces
  17. ///
  18. /// IE
  19. ///
  20. /// `div { "asdasd" }` instead of a multiline variant
  21. Oneliner,
  22. /// Optimization where children flow but props remain fixed on top
  23. PropsOnTop,
  24. /// The noisiest optimization where everything flows
  25. NoOpt,
  26. }
  27. /*
  28. // whitespace
  29. div {
  30. // some whitespace
  31. class: "asdasd"
  32. // whjiot
  33. asdasd // whitespace
  34. }
  35. */
  36. impl Writer {
  37. pub fn write_element(&mut self, el: &Element) -> Result {
  38. let Element {
  39. name,
  40. key,
  41. attributes,
  42. children,
  43. _is_static,
  44. brace,
  45. } = el;
  46. /*
  47. 1. Write the tag
  48. 2. Write the key
  49. 3. Write the attributes
  50. 4. Write the children
  51. */
  52. write!(self.out, "{name} {{")?;
  53. // decide if we have any special optimizations
  54. // Default with none, opt the cases in one-by-one
  55. let mut opt_level = ShortOptimization::NoOpt;
  56. // check if we have a lot of attributes
  57. let attr_len = self.is_short_attrs(attributes);
  58. let is_short_attr_list = attr_len < 80;
  59. let is_small_children = self.is_short_children(children).is_some();
  60. // if we have few attributes and a lot of children, place the attrs on top
  61. if is_short_attr_list && !is_small_children {
  62. opt_level = ShortOptimization::PropsOnTop;
  63. }
  64. // even if the attr is long, it should be put on one line
  65. if !is_short_attr_list && attributes.len() <= 1 {
  66. if children.is_empty() {
  67. opt_level = ShortOptimization::Oneliner;
  68. } else {
  69. opt_level = ShortOptimization::PropsOnTop;
  70. }
  71. }
  72. // if we have few children and few attributes, make it a one-liner
  73. if is_short_attr_list && is_small_children {
  74. opt_level = ShortOptimization::Oneliner;
  75. }
  76. // If there's nothing at all, empty optimization
  77. if attributes.is_empty() && children.is_empty() && key.is_none() {
  78. opt_level = ShortOptimization::Empty;
  79. // Write comments if they exist
  80. self.write_todo_body(brace)?;
  81. }
  82. // multiline handlers bump everything down
  83. if attr_len > 1000 {
  84. opt_level = ShortOptimization::NoOpt;
  85. }
  86. match opt_level {
  87. ShortOptimization::Empty => {}
  88. ShortOptimization::Oneliner => {
  89. write!(self.out, " ")?;
  90. self.write_attributes(attributes, key, true)?;
  91. if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
  92. write!(self.out, ", ")?;
  93. }
  94. for (id, child) in children.iter().enumerate() {
  95. self.write_ident(child)?;
  96. if id != children.len() - 1 && children.len() > 1 {
  97. write!(self.out, ", ")?;
  98. }
  99. }
  100. write!(self.out, " ")?;
  101. }
  102. ShortOptimization::PropsOnTop => {
  103. if !attributes.is_empty() || key.is_some() {
  104. write!(self.out, " ")?;
  105. }
  106. self.write_attributes(attributes, key, true)?;
  107. if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
  108. write!(self.out, ",")?;
  109. }
  110. if !children.is_empty() {
  111. self.write_body_indented(children)?;
  112. }
  113. self.out.tabbed_line()?;
  114. }
  115. ShortOptimization::NoOpt => {
  116. self.write_attributes(attributes, key, false)?;
  117. if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
  118. write!(self.out, ",")?;
  119. }
  120. if !children.is_empty() {
  121. self.write_body_indented(children)?;
  122. }
  123. self.out.tabbed_line()?;
  124. }
  125. }
  126. write!(self.out, "}}")?;
  127. Ok(())
  128. }
  129. fn write_attributes(
  130. &mut self,
  131. attributes: &[ElementAttrNamed],
  132. key: &Option<IfmtInput>,
  133. sameline: bool,
  134. ) -> Result {
  135. let mut attr_iter = attributes.iter().peekable();
  136. if let Some(key) = key {
  137. if !sameline {
  138. self.out.indented_tabbed_line()?;
  139. }
  140. write!(
  141. self.out,
  142. "key: \"{}\"",
  143. key.source.as_ref().unwrap().value()
  144. )?;
  145. if !attributes.is_empty() {
  146. write!(self.out, ",")?;
  147. if sameline {
  148. write!(self.out, " ")?;
  149. }
  150. }
  151. }
  152. while let Some(attr) = attr_iter.next() {
  153. self.out.indent += 1;
  154. if !sameline {
  155. self.write_comments(attr.attr.start())?;
  156. }
  157. self.out.indent -= 1;
  158. if !sameline {
  159. self.out.indented_tabbed_line()?;
  160. }
  161. self.write_attribute(attr)?;
  162. if attr_iter.peek().is_some() {
  163. write!(self.out, ",")?;
  164. if sameline {
  165. write!(self.out, " ")?;
  166. }
  167. }
  168. }
  169. Ok(())
  170. }
  171. fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
  172. match &attr.attr {
  173. ElementAttr::AttrText { name, value } => {
  174. write!(
  175. self.out,
  176. "{name}: \"{value}\"",
  177. value = value.source.as_ref().unwrap().value()
  178. )?;
  179. }
  180. ElementAttr::AttrExpression { name, value } => {
  181. let out = prettyplease::unparse_expr(value);
  182. write!(self.out, "{}: {}", name, out)?;
  183. }
  184. ElementAttr::CustomAttrText { name, value } => {
  185. write!(
  186. self.out,
  187. "\"{name}\": \"{value}\"",
  188. name = name.value(),
  189. value = value.source.as_ref().unwrap().value()
  190. )?;
  191. }
  192. ElementAttr::CustomAttrExpression { name, value } => {
  193. let out = prettyplease::unparse_expr(value);
  194. write!(self.out, "\"{}\": {}", name.value(), out)?;
  195. }
  196. ElementAttr::EventTokens { name, tokens } => {
  197. let out = self.retrieve_formatted_expr(tokens).to_string();
  198. let mut lines = out.split('\n').peekable();
  199. let first = lines.next().unwrap();
  200. // a one-liner for whatever reason
  201. // Does not need a new line
  202. if lines.peek().is_none() {
  203. write!(self.out, "{}: {}", name, first)?;
  204. } else {
  205. writeln!(self.out, "{}: {}", name, first)?;
  206. while let Some(line) = lines.next() {
  207. self.out.indented_tab()?;
  208. write!(self.out, "{}", line)?;
  209. if lines.peek().is_none() {
  210. write!(self.out, "")?;
  211. } else {
  212. writeln!(self.out)?;
  213. }
  214. }
  215. }
  216. }
  217. }
  218. Ok(())
  219. }
  220. // make sure the comments are actually relevant to this element.
  221. // test by making sure this element is the primary element on this line
  222. pub fn current_span_is_primary(&self, location: Span) -> bool {
  223. let start = location.start();
  224. let line_start = start.line - 1;
  225. let this_line = self.src[line_start].as_str();
  226. let beginning = if this_line.len() > start.column {
  227. this_line[..start.column].trim()
  228. } else {
  229. ""
  230. };
  231. beginning.is_empty()
  232. }
  233. // check if the children are short enough to be on the same line
  234. // We don't have the notion of current line depth - each line tries to be < 80 total
  235. // returns the total line length if it's short
  236. // returns none if the length exceeds the limit
  237. // I think this eventually becomes quadratic :(
  238. pub fn is_short_children(&mut self, children: &[BodyNode]) -> Option<usize> {
  239. if children.is_empty() {
  240. // todo: allow elements with comments but no children
  241. // like div { /* comment */ }
  242. // or
  243. // div {
  244. // // some helpful
  245. // }
  246. return Some(0);
  247. }
  248. for child in children {
  249. if self.current_span_is_primary(child.span()) {
  250. 'line: for line in self.src[..child.span().start().line - 1].iter().rev() {
  251. match (line.trim().starts_with("//"), line.is_empty()) {
  252. (true, _) => return None,
  253. (_, true) => continue 'line,
  254. _ => break 'line,
  255. }
  256. }
  257. }
  258. }
  259. match children {
  260. [BodyNode::Text(ref text)] => Some(text.source.as_ref().unwrap().value().len()),
  261. [BodyNode::Component(ref comp)] => {
  262. let attr_len = self.field_len(&comp.fields, &comp.manual_props);
  263. if attr_len > 80 {
  264. None
  265. } else if comp.children.is_empty() {
  266. Some(attr_len)
  267. } else {
  268. None
  269. }
  270. }
  271. [BodyNode::RawExpr(ref expr)] => {
  272. // TODO: let rawexprs to be inlined
  273. get_expr_length(expr)
  274. }
  275. [BodyNode::Element(ref el)] => {
  276. let attr_len = self.is_short_attrs(&el.attributes);
  277. if el.children.is_empty() && attr_len < 80 {
  278. return Some(el.name.to_string().len());
  279. }
  280. if el.children.len() == 1 {
  281. if let BodyNode::Text(ref text) = el.children[0] {
  282. let value = text.source.as_ref().unwrap().value();
  283. if value.len() + el.name.to_string().len() + attr_len < 80 {
  284. return Some(value.len() + el.name.to_string().len() + attr_len);
  285. }
  286. }
  287. }
  288. None
  289. }
  290. // todo, allow non-elements to be on the same line
  291. items => {
  292. let mut total_count = 0;
  293. for item in items {
  294. match item {
  295. BodyNode::Component(_) | BodyNode::Element(_) => return None,
  296. BodyNode::Text(text) => {
  297. total_count += text.source.as_ref().unwrap().value().len()
  298. }
  299. BodyNode::RawExpr(expr) => match get_expr_length(expr) {
  300. Some(len) => total_count += len,
  301. None => return None,
  302. },
  303. BodyNode::ForLoop(_) => todo!(),
  304. BodyNode::IfChain(_) => todo!(),
  305. }
  306. }
  307. Some(total_count)
  308. }
  309. }
  310. }
  311. /// empty everything except for some comments
  312. fn write_todo_body(&mut self, brace: &Brace) -> fmt::Result {
  313. let span = brace.span.span();
  314. let start = span.start();
  315. let end = span.end();
  316. if start.line == end.line {
  317. return Ok(());
  318. }
  319. writeln!(self.out)?;
  320. for idx in start.line..end.line {
  321. let line = &self.src[idx];
  322. if line.trim().starts_with("//") {
  323. for _ in 0..self.out.indent + 1 {
  324. write!(self.out, " ")?
  325. }
  326. writeln!(self.out, "{}", line.trim()).unwrap();
  327. }
  328. }
  329. for _ in 0..self.out.indent {
  330. write!(self.out, " ")?
  331. }
  332. Ok(())
  333. }
  334. }
  335. fn get_expr_length(expr: &Expr) -> Option<usize> {
  336. let span = expr.span();
  337. let (start, end) = (span.start(), span.end());
  338. if start.line == end.line {
  339. Some(end.column - start.column)
  340. } else {
  341. None
  342. }
  343. }