diff --git a/Cargo.lock b/Cargo.lock index 15a74b4..1c46356 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,28 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "0.3.2" @@ -227,6 +249,27 @@ dependencies = [ "libc", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "glob" version = "0.3.1" @@ -260,6 +303,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.9.3" @@ -312,6 +361,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -344,6 +402,7 @@ dependencies = [ "proc-macro2", "quote", "rstml", + "rustywind_core", "serde", "syn 2.0.66", "thiserror", @@ -366,9 +425,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.146" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linked-hash-map" @@ -490,6 +549,35 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "regex" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + [[package]] name = "rstml" version = "0.11.2" @@ -518,6 +606,20 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustywind_core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7d826c0111cebd3db09ef1a89204bbd45cb389904f8a3cb2befd945d337919" +dependencies = [ + "ahash", + "aho-corasick", + "eyre", + "itertools", + "once_cell", + "regex", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -682,6 +784,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "windows-sys" version = "0.45.0" @@ -837,3 +945,23 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] diff --git a/formatter/Cargo.toml b/formatter/Cargo.toml index 68be404..9af87f2 100644 --- a/formatter/Cargo.toml +++ b/formatter/Cargo.toml @@ -13,12 +13,13 @@ leptosfmt-prettyplease.workspace = true rstml = "0.11.2" syn = { workspace = true } proc-macro2 = { workspace = true } -thiserror = "1.0.40" +thiserror = "1.0.61" crop = "0.3.0" -serde = { version = "1.0.163", features = ["derive"] } -quote = "1.0.26" +serde = { version = "1.0.203", features = ["derive"] } +quote = "1.0.36" +rustywind_core = "0.1.2" [dev-dependencies] -indoc = "2.0.1" -insta = "1.28.0" -quote = "1.0.26" +indoc = "2.0.5" +insta = "1.39.0" +quote = "1.0.36" diff --git a/formatter/src/formatter/attribute.rs b/formatter/src/formatter/attribute.rs index 2771dfd..6fbff0f 100644 --- a/formatter/src/formatter/attribute.rs +++ b/formatter/src/formatter/attribute.rs @@ -3,6 +3,8 @@ use syn::{spanned::Spanned, Expr}; use crate::{formatter::Formatter, AttributeValueBraceStyle as Braces}; +use super::ExpressionFormatter; + impl Formatter<'_> { pub fn attribute(&mut self, attribute: &NodeAttribute) { self.flush_comments(attribute.span().start().line - 1); @@ -16,24 +18,32 @@ impl Formatter<'_> { self.node_name(&attribute.key); if let Some(value) = attribute.value() { + let formatter = self + .settings + .attr_values + .get(&attribute.key.to_string()) + .copied(); + self.printer.word("="); - self.attribute_value(value); + self.attribute_value(value, formatter); } } - fn attribute_value(&mut self, value: &Expr) { + fn attribute_value(&mut self, value: &Expr, formatter: Option) { match (self.settings.attr_value_brace_style, value) { - (Braces::Always, syn::Expr::Block(_)) => self.node_value_expr(value, false, false), + (Braces::Always, syn::Expr::Block(_)) => { + self.node_value_expr(value, false, false, formatter) + } (Braces::AlwaysUnlessLit, syn::Expr::Block(_) | syn::Expr::Lit(_)) => { - self.node_value_expr(value, false, true) + self.node_value_expr(value, false, true, formatter) } (Braces::Always | Braces::AlwaysUnlessLit, _) => { self.printer.word("{"); - self.node_value_expr(value, false, false); + self.node_value_expr(value, false, false, formatter); self.printer.word("}"); } - (Braces::WhenRequired, _) => self.node_value_expr(value, true, true), - (Braces::Preserve, _) => self.node_value_expr(value, false, false), + (Braces::WhenRequired, _) => self.node_value_expr(value, true, true, formatter), + (Braces::Preserve, _) => self.node_value_expr(value, false, false, formatter), } } } diff --git a/formatter/src/formatter/expr.rs b/formatter/src/formatter/expr.rs index 652617d..87f98ef 100644 --- a/formatter/src/formatter/expr.rs +++ b/formatter/src/formatter/expr.rs @@ -4,6 +4,8 @@ use syn::{spanned::Spanned, Block, Expr, ExprBlock, ExprLit, LitStr}; use crate::{formatter::Formatter, get_text_beween_spans, view_macro::ViewMacroFormatter}; +use super::ExpressionFormatter; + fn trim_start_with_max(str: &str, max_chars: usize) -> &str { let mut chars = 0; str.trim_start_matches(|c: char| { @@ -65,20 +67,23 @@ impl Formatter<'_> { if unwrap_single_expr_blocks || (unwrap_single_lit_blocks && matches!(single_expr, syn::Expr::Lit(_))) { - self.expr(single_expr); + self.expr(single_expr, None); } else { self.printer.word("{"); - self.expr(single_expr); + self.expr(single_expr, None); self.printer.word("}"); } return; } - self.expr(&Expr::Block(ExprBlock { - attrs: vec![], - label: None, - block: block.clone(), - })) + self.expr( + &Expr::Block(ExprBlock { + attrs: vec![], + label: None, + block: block.clone(), + }), + None, + ) } pub fn node_value_expr( @@ -86,6 +91,7 @@ impl Formatter<'_> { value: &syn::Expr, unwrap_single_expr_blocks: bool, unwrap_single_lit_blocks: bool, + formatter: Option, ) { // if single line expression, format as '{expr}' instead of '{ expr }' (prettyplease inserts a space) if let syn::Expr::Block(expr_block) = value { @@ -98,10 +104,10 @@ impl Formatter<'_> { } } - self.expr(value) + self.expr(value, formatter) } - fn expr(&mut self, expr: &syn::Expr) { + fn expr(&mut self, expr: &syn::Expr, formatter: Option) { let span = expr.span(); self.flush_comments(span.start().line - 1); if let syn::Expr::Lit(ExprLit { @@ -109,7 +115,11 @@ impl Formatter<'_> { .. }) = expr { - self.literal_str(lit_str); + if let Some(formatter) = formatter { + formatter.format(self, lit_str.value()) + } else { + self.literal_str(lit_str); + } return; } diff --git a/formatter/src/formatter/mod.rs b/formatter/src/formatter/mod.rs index 4d33705..3c780c1 100644 --- a/formatter/src/formatter/mod.rs +++ b/formatter/src/formatter/mod.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::fmt::Debug; use crop::Rope; @@ -10,6 +11,7 @@ mod expr; mod fragment; mod mac; mod node; +mod tailwind; pub use mac::format_macro; pub use mac::{ParentIndent, ViewMacro}; @@ -40,26 +42,42 @@ pub enum NewlineStyle { Windows, } +#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] +pub enum ExpressionFormatter { + Tailwind, +} + +impl ExpressionFormatter { + pub fn format(&self, formatter: &mut Formatter, value: String) { + match self { + Self::Tailwind => formatter.tailwind_expr(value), + } + } +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(default)] pub struct FormatterSettings { - // Maximum width of each line + /// Maximum width of each line pub max_width: usize, - // Number of spaces per tab + /// Number of spaces per tab pub tab_spaces: usize, - // Determines indentation style (tabs or spaces) + /// Determines indentation style (tabs or spaces) pub indentation_style: IndentationStyle, - // Determines line ending (unix or windows) + /// Determines line ending (unix or windows) pub newline_style: NewlineStyle, - // Determines placement of braces around single expression attribute values + /// Determines placement of braces around single expression attribute values pub attr_value_brace_style: AttributeValueBraceStyle, - // Determines macros to be formatted. Default: leptos::view, view + /// Determines macros to be formatted. Default: leptos::view, view pub macro_names: Vec, + + /// Determines whether to format attribute values with a specific formatter (e.g. tailwind) + pub attr_values: HashMap, } impl Default for FormatterSettings { @@ -71,6 +89,7 @@ impl Default for FormatterSettings { indentation_style: IndentationStyle::Auto, newline_style: NewlineStyle::Auto, macro_names: vec!["leptos::view".to_string(), "view".to_string()], + attr_values: HashMap::new(), } } } diff --git a/formatter/src/formatter/tailwind.rs b/formatter/src/formatter/tailwind.rs new file mode 100644 index 0000000..da99fc1 --- /dev/null +++ b/formatter/src/formatter/tailwind.rs @@ -0,0 +1,16 @@ +use rustywind_core::sorter::{self, FinderRegex}; + +use crate::Formatter; + +impl Formatter<'_> { + pub fn tailwind_expr(&mut self, attr_value: String) { + static OPTIONS: sorter::Options = sorter::Options { + regex: FinderRegex::DefaultRegex, + sorter: sorter::Sorter::DefaultSorter, + allow_duplicates: true, + }; + + let sorted = sorter::sort_classes(&attr_value, &OPTIONS); + self.printer.word(sorted); + } +} diff --git a/formatter/src/source_file.rs b/formatter/src/source_file.rs index 1df9325..a6c2ae6 100644 --- a/formatter/src/source_file.rs +++ b/formatter/src/source_file.rs @@ -77,9 +77,11 @@ fn format_source( #[cfg(test)] mod tests { + use std::collections::HashMap; + use indoc::indoc; - use crate::IndentationStyle; + use crate::{ExpressionFormatter, IndentationStyle}; use super::*; @@ -753,4 +755,34 @@ mod tests { assert_eq!(result, expected); } + + #[test] + fn tailwind() { + let source = indoc! {r#" + view! { + + + + }"#}; + + let result = format_file_source( + source, + &FormatterSettings { + attr_values: [("class".to_string(), ExpressionFormatter::Tailwind)] + .into_iter() + .collect(), + ..Default::default() + }, + ) + .unwrap(); + insta::assert_snapshot!(result, @r###" + view! { + + + + } + "###); + } }