From 284860f470181158e453c43d5fe65f5f273dd814 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 28 Apr 2022 14:16:21 -0400 Subject: [PATCH] feat: more tests and spans --- packages/autofmt/Cargo.toml | 2 +- packages/autofmt/src/lib.rs | 114 ++++++++++++++++++++++++--------- packages/autofmt/tests/sink.rs | 109 ++++++++++++++++++++++++++++++- 3 files changed, 194 insertions(+), 31 deletions(-) diff --git a/packages/autofmt/Cargo.toml b/packages/autofmt/Cargo.toml index 2c527ed98..8fb7a128f 100644 --- a/packages/autofmt/Cargo.toml +++ b/packages/autofmt/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -proc-macro2 = { version = "1.0.6" } +proc-macro2 = { version = "1.0.6", features = ["span-locations"] } quote = "1.0" syn = { version = "1.0.11", features = ["full", "extra-traits"] } dioxus-rsx = { path = "../rsx" } diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 8d4a0cedf..4f467e139 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -41,20 +41,6 @@ pub fn get_format_blocks(contents: &str) -> Vec { continue; } - // match cur_match { - // Some(ref this_match) => { - // // abort nested matches - these get handled automatically - // if start < this_match.end { - // continue; - // } else { - // cur_match = Some(item); - // } - // } - // None => { - // cur_match = Some(item); - // } - // } - let remaining = &contents[end - 1..]; if let Some(bracket_end) = find_bracket_end(remaining) { @@ -67,6 +53,14 @@ pub fn get_format_blocks(contents: &str) -> Vec { if let Some(new) = fmt_block(sub_string) { if !new.is_empty() { + println!("{}", &contents[end + 1..bracket_end + end - 1]); + println!("{}", new); + + let stripped = &contents[end + 1..bracket_end + end - 1]; + if stripped == new { + println!("no changes necessary"); + } + // if we have code to push, we want the code to end up on the right lines with the right indentation let mut output = String::new(); @@ -94,18 +88,25 @@ pub fn get_format_blocks(contents: &str) -> Vec { } pub fn fmt_block(block: &str) -> Option { + let mut raw_lines = block.split('\n').collect::>(); + let parsed: CallBody = syn::parse_str(block).ok()?; let mut buf = String::new(); for node in parsed.roots.iter() { - write_ident(&mut buf, node, 0).ok()?; + write_ident(&mut buf, &raw_lines, node, 0).ok()?; } Some(buf) } -pub fn write_ident(buf: &mut String, node: &BodyNode, indent: usize) -> fmt::Result { +pub fn write_ident( + buf: &mut String, + lines: &[&str], + node: &BodyNode, + indent: usize, +) -> fmt::Result { match node { BodyNode::Element(el) => { let Element { @@ -168,7 +169,7 @@ pub fn write_ident(buf: &mut String, node: &BodyNode, indent: usize) -> fmt::Res ElementAttr::CustomAttrText { name, value } => { write!( buf, - "{name}: \"{value}\"", + "\"{name}\": \"{value}\"", name = name.value(), value = value.value() )?; @@ -176,22 +177,32 @@ pub fn write_ident(buf: &mut String, node: &BodyNode, indent: usize) -> fmt::Res ElementAttr::CustomAttrExpression { name, value } => { let out = prettyplease::unparse_expr(value); - write!(buf, "{}: {}", name.value(), out)?; + write!(buf, "\"{}\": {}", name.value(), out)?; } ElementAttr::EventTokens { name, tokens } => { let out = prettyplease::unparse_expr(tokens); + dbg!(&out); + let mut lines = out.split('\n').peekable(); let first = lines.next().unwrap(); - write!(buf, "{}: {}", name, first)?; - while let Some(line) = lines.next() { - write_tabs(buf, indent + 1)?; - write!(buf, "{}", line)?; - // write!(buf, "{}", line)?; - if lines.peek().is_none() { - write!(buf, "")?; + // a one-liner for whatever reason + // Does not need a new line + if lines.peek().is_none() { + write!(buf, "{}: {}", name, first)?; + } else { + writeln!(buf, "{}: {}", name, first)?; + + while let Some(line) = lines.next() { + write_tabs(buf, indent + 1)?; + write!(buf, "{}", line)?; + if lines.peek().is_none() { + write!(buf, "")?; + } else { + writeln!(buf)?; + } } } } @@ -221,7 +232,7 @@ pub fn write_ident(buf: &mut String, node: &BodyNode, indent: usize) -> fmt::Res } for child in children { - write_ident(buf, child, indent + 1)?; + write_ident(buf, lines, child, indent + 1)?; } if is_long_attr_list || !children.is_empty() { @@ -260,7 +271,7 @@ pub fn write_ident(buf: &mut String, node: &BodyNode, indent: usize) -> fmt::Res writeln!(buf, "{}: {},", name, out)?; } ContentField::Formatted(s) => { - writeln!(buf, "{}: {},", name, s.value())?; + writeln!(buf, "{}: \"{}\",", name, s.value())?; } ContentField::OnHandlerRaw(exp) => { let out = prettyplease::unparse_expr(exp); @@ -292,7 +303,7 @@ pub fn write_ident(buf: &mut String, node: &BodyNode, indent: usize) -> fmt::Res } for child in children { - write_ident(buf, child, indent + 1)?; + write_ident(buf, lines, child, indent + 1)?; } if !body.is_empty() || !children.is_empty() { @@ -310,7 +321,52 @@ pub fn write_ident(buf: &mut String, node: &BodyNode, indent: usize) -> fmt::Res write_tabs(buf, indent)?; writeln!(buf, "\"{}\"", t.value())?; } - BodyNode::RawExpr(_) => { + BodyNode::RawExpr(exp) => { + use syn::spanned::Spanned; + + let placement = exp.span(); + + let start = placement.start(); + let end = placement.end(); + + let num_spaces_desired = (indent * 4) as isize; + let first = lines[start.line - 1]; + let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize; + let offset = num_spaces_real - num_spaces_desired; + + for line_id in start.line - 1..end.line { + let line = lines[line_id]; + + // trim the leading whitespace + + if offset < 0 { + for _ in 0..-offset { + write!(buf, " ")?; + } + + writeln!(buf, "{}", line)?; + } else { + let offset = offset as usize; + + let right = &line[offset..]; + writeln!(buf, "{}", right)?; + } + } + + // let toks = exp.to_token_stream(); + + // let out = prettyplease::unparse_expr(exp); + // let mut lines = out.split('\n').peekable(); + // for line in lines { + // write_tabs(buf, indent)?; + // writeln!(buf, "{}", line)?; + // // writeln!(buf)?; + // } + // write_tabs(buf, indent)?; + // let first = lines.next().unwrap(); + // write!(buf, "{}", name, first)?; + // writeln!(buf)?; + // // write!(buf, "{}", " ".repeat(ident)) } diff --git a/packages/autofmt/tests/sink.rs b/packages/autofmt/tests/sink.rs index ced3af07b..736abc7dc 100644 --- a/packages/autofmt/tests/sink.rs +++ b/packages/autofmt/tests/sink.rs @@ -203,6 +203,98 @@ fn formats_component_tiny() { print!("{formatted}"); } +#[test] +fn formats_exprs() { + let block = r#" + ul { + (0..10).map(|f| rsx!{ + li { + "hi" + } + }) + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_exprs_neg_indent() { + let block = r#" + ul { + (0..10).map(|f| rsx!{ + li { + "hi" + } + }) + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_exprs_handlers() { + let block = r#" + button { + class: "flex items-center pl-3 py-3 pr-2 text-gray-500 hover:bg-indigo-50 rounded", + onclick: move |evt| { + show_user_menu.set(!show_user_menu.get()); evt.cancel_bubble(); }, + + onclick: move |evt| + + show_user_menu.set(!show_user_menu.get()), + span { class: "inline-block mr-4", + icons::icon_14 {} + } + span { "Settings" } + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_complex() { + let block = r#" + li { + Link { + class: "flex items-center pl-3 py-3 pr-4 {active_class} rounded", + to: "{to}", + span { class: "inline-block mr-3", + icons::icon_0 {} + } + span { "{name}" } + children.is_some().then(|| rsx!{ + span { + class: "inline-block ml-auto hover:bg-gray-500", + onclick: move |evt| { + // open.set(!open.get()); + evt.cancel_bubble(); + }, + icons::icon_8 {} + } + }) + } + div { + class: "px-4", + is_current.then(|| rsx!{ children }) + // open.then(|| rsx!{ children }) + } + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + #[test] fn formats_document() { let block = r#" @@ -321,7 +413,6 @@ fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape) } } } - #[inline_props] fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape) -> Element { const ICON_SIZE: u32 = 36; @@ -362,3 +453,19 @@ fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape) &format!("{{ {} }}", &block.formatted), ); } + +#[test] +fn empty_blocks() { + let mut src = r###" +pub fn Alert(cx: Scope) -> Element { + cx.render(rsx! { + div { } + }) +} +"### + .to_string(); + + let formatted = get_format_blocks(&src); + + dbg!(&formatted); +}