dioxus/packages/autofmt/src/component.rs

278 lines
8.4 KiB
Rust
Raw Normal View History

2023-06-02 12:03:56 -05:00
use crate::{ifmt_to_string, writer::Location, Writer};
use dioxus_rsx::*;
use quote::ToTokens;
2022-06-28 15:15:09 -04:00
use std::fmt::{Result, Write};
use syn::{spanned::Spanned, AngleBracketedGenericArguments};
2022-06-28 17:16:26 -04:00
#[derive(Debug)]
2022-06-28 17:16:26 -04:00
enum ShortOptimization {
// Special because we want to print the closing bracket immediately
Empty,
// Special optimization to put everything on the same line
Oneliner,
// Optimization where children flow but props remain fixed on top
PropsOnTop,
// The noisiest optimization where everything flows
NoOpt,
}
impl Writer<'_> {
2022-06-27 16:24:36 -04:00
pub fn write_component(
&mut self,
Component {
name,
2022-06-28 17:16:26 -04:00
fields,
2022-06-27 16:24:36 -04:00
children,
manual_props,
prop_gen_args,
..
2022-06-27 16:24:36 -04:00
}: &Component,
2022-06-28 17:16:26 -04:00
) -> Result {
self.write_component_name(name, prop_gen_args)?;
// decide if we have any special optimizations
// Default with none, opt the cases in one-by-one
let mut opt_level = ShortOptimization::NoOpt;
// check if we have a lot of attributes
let attr_len = self.field_len(fields, manual_props);
let is_short_attr_list = attr_len < 80;
2022-06-28 17:16:26 -04:00
let is_small_children = self.is_short_children(children).is_some();
// if we have few attributes and a lot of children, place the attrs on top
if is_short_attr_list && !is_small_children {
opt_level = ShortOptimization::PropsOnTop;
}
// even if the attr is long, it should be put on one line
if !is_short_attr_list && (fields.len() <= 1 && manual_props.is_none()) {
if children.is_empty() {
opt_level = ShortOptimization::Oneliner;
} else {
opt_level = ShortOptimization::PropsOnTop;
}
}
// if we have few children and few attributes, make it a one-liner
if is_short_attr_list && is_small_children {
opt_level = ShortOptimization::Oneliner;
}
// If there's nothing at all, empty optimization
2022-07-05 18:03:04 -04:00
if fields.is_empty() && children.is_empty() && manual_props.is_none() {
2022-06-28 17:16:26 -04:00
opt_level = ShortOptimization::Empty;
}
// multiline handlers bump everything down
2023-12-09 16:51:41 +01:00
if attr_len > 1000 || self.out.indent.split_line_attributes() {
opt_level = ShortOptimization::NoOpt;
}
// Useful for debugging
// dbg!(
// name.to_token_stream().to_string(),
// &opt_level,
// attr_len,
// is_short_attr_list,
// is_small_children
// );
2022-06-28 17:16:26 -04:00
match opt_level {
ShortOptimization::Empty => {}
ShortOptimization::Oneliner => {
write!(self.out, " ")?;
2022-06-28 17:16:26 -04:00
self.write_component_fields(fields, manual_props, true)?;
if !children.is_empty() && !fields.is_empty() {
write!(self.out, ", ")?;
2022-06-28 17:16:26 -04:00
}
for child in children {
self.write_ident(child)?;
}
write!(self.out, " ")?;
2022-06-28 17:16:26 -04:00
}
ShortOptimization::PropsOnTop => {
write!(self.out, " ")?;
2022-06-28 17:16:26 -04:00
self.write_component_fields(fields, manual_props, true)?;
if !children.is_empty() && !fields.is_empty() {
write!(self.out, ",")?;
2022-06-28 17:16:26 -04:00
}
self.write_body_indented(children)?;
self.out.tabbed_line()?;
2022-06-28 17:16:26 -04:00
}
ShortOptimization::NoOpt => {
self.write_component_fields(fields, manual_props, false)?;
if !children.is_empty() && !fields.is_empty() {
write!(self.out, ",")?;
}
2022-06-28 17:16:26 -04:00
self.write_body_indented(children)?;
self.out.tabbed_line()?;
2022-06-28 17:16:26 -04:00
}
}
write!(self.out, "}}")?;
2022-06-28 17:16:26 -04:00
Ok(())
}
fn write_component_name(
&mut self,
name: &syn::Path,
generics: &Option<AngleBracketedGenericArguments>,
2022-06-27 16:24:36 -04:00
) -> Result {
let mut name = name.to_token_stream().to_string();
name.retain(|c| !c.is_whitespace());
2022-06-28 17:16:26 -04:00
write!(self.out, "{name}")?;
2022-06-27 16:24:36 -04:00
2022-06-28 17:16:26 -04:00
if let Some(generics) = generics {
2022-06-27 16:24:36 -04:00
let mut written = generics.to_token_stream().to_string();
written.retain(|c| !c.is_whitespace());
2022-06-28 17:16:26 -04:00
2023-01-27 20:35:46 -06:00
write!(self.out, "{written}")?;
2022-06-27 16:24:36 -04:00
}
write!(self.out, " {{")?;
2022-06-27 16:24:36 -04:00
2022-06-28 17:16:26 -04:00
Ok(())
}
fn write_component_fields(
&mut self,
fields: &[ComponentField],
manual_props: &Option<syn::Expr>,
sameline: bool,
) -> Result {
let mut field_iter = fields.iter().peekable();
while let Some(field) = field_iter.next() {
if !sameline {
self.out.indented_tabbed_line().unwrap();
2022-06-28 17:16:26 -04:00
}
2022-06-27 16:24:36 -04:00
let name = &field.name;
match &field.content {
ContentField::ManExpr(exp) => {
let out = prettyplease::unparse_expr(exp);
let mut lines = out.split('\n').peekable();
let first = lines.next().unwrap();
write!(self.out, "{name}: {first}")?;
for line in lines {
self.out.new_line()?;
self.out.indented_tab()?;
write!(self.out, "{line}")?;
}
2022-06-27 16:24:36 -04:00
}
ContentField::Formatted(s) => {
write!(
self.out,
2023-06-02 12:03:56 -05:00
"{}: {}",
name,
2023-06-02 12:03:56 -05:00
s.source.as_ref().unwrap().to_token_stream()
)?;
2022-06-27 16:24:36 -04:00
}
ContentField::OnHandlerRaw(exp) => {
let out = prettyplease::unparse_expr(exp);
let mut lines = out.split('\n').peekable();
let first = lines.next().unwrap();
2023-01-27 20:35:46 -06:00
write!(self.out, "{name}: {first}")?;
2022-06-27 16:24:36 -04:00
for line in lines {
self.out.new_line()?;
self.out.indented_tab()?;
2023-01-27 20:35:46 -06:00
write!(self.out, "{line}")?;
2022-06-27 16:24:36 -04:00
}
2022-06-28 17:16:26 -04:00
}
}
if field_iter.peek().is_some() || manual_props.is_some() {
write!(self.out, ",")?;
2022-06-28 17:16:26 -04:00
if sameline {
write!(self.out, " ")?;
}
}
}
2022-06-27 16:24:36 -04:00
if let Some(exp) = manual_props {
2022-06-28 17:16:26 -04:00
if !sameline {
self.out.indented_tabbed_line().unwrap();
2022-06-27 16:24:36 -04:00
}
2022-06-28 17:16:26 -04:00
self.write_manual_props(exp)?;
}
2022-06-27 16:24:36 -04:00
2022-06-28 17:16:26 -04:00
Ok(())
}
pub fn field_len(
&mut self,
2022-06-28 17:29:10 -04:00
fields: &[ComponentField],
manual_props: &Option<syn::Expr>,
) -> usize {
2022-06-28 17:16:26 -04:00
let attr_len = fields
.iter()
.map(|field| match &field.content {
2023-06-02 12:03:56 -05:00
ContentField::Formatted(s) => ifmt_to_string(s).len() ,
ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => {
let formatted = prettyplease::unparse_expr(exp);
let len = if formatted.contains('\n') {
10000
} else {
formatted.len()
};
self.cached_formats.insert(Location::new(exp.span().start()) , formatted);
len
},
2022-06-28 17:16:26 -04:00
} + 10)
.sum::<usize>();
2022-06-28 17:16:26 -04:00
match manual_props {
Some(p) => {
let content = prettyplease::unparse_expr(p);
if content.len() + attr_len > 80 {
return 100000;
2022-06-28 17:16:26 -04:00
}
let mut lines = content.lines();
lines.next().unwrap();
2022-06-28 17:29:10 -04:00
if lines.next().is_none() {
attr_len + content.len()
2022-06-28 17:29:10 -04:00
} else {
100000
2022-06-28 17:29:10 -04:00
}
2022-06-28 17:16:26 -04:00
}
None => attr_len,
2022-06-27 16:24:36 -04:00
}
2022-06-28 17:16:26 -04:00
}
fn write_manual_props(&mut self, exp: &syn::Expr) -> Result {
/*
We want to normalize the expr to the appropriate indent level.
*/
let formatted = prettyplease::unparse_expr(exp);
let mut lines = formatted.lines();
let first_line = lines.next().unwrap();
write!(self.out, "..{first_line}")?;
2022-06-28 17:16:26 -04:00
for line in lines {
self.out.indented_tabbed_line()?;
write!(self.out, "{line}")?;
2022-06-27 16:24:36 -04:00
}
2022-06-28 17:16:26 -04:00
2022-06-27 16:24:36 -04:00
Ok(())
}
}