element.rs 13 KB

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