From 1c697c4a8e07f20c08c8dcc01fa26b82c5752c64 Mon Sep 17 00:00:00 2001 From: SofusA Date: Sat, 2 Mar 2024 17:21:36 +0100 Subject: [PATCH] feat: Add option to override macro name (#109) * Add option to override html macro * Format multiple macros * Actually support fully qualified macro path * macro_names as a reference in ViewMacroVisitor --- README.md | 3 + cli/src/main.rs | 14 ++- formatter/src/collect.rs | 21 +++- formatter/src/formatter/element.rs | 2 +- formatter/src/formatter/mac.rs | 10 +- formatter/src/formatter/mod.rs | 13 +- formatter/src/lib.rs | 2 +- formatter/src/source_file.rs | 190 ++++++++++++++++++++++++++--- formatter/src/test_helpers.rs | 4 +- formatter/src/view_macro.rs | 55 ++++++--- 10 files changed, 256 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 6fcf342..29f3576 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ Options: -c, --config-file Configuration file -s, --stdin Format stdin and write to stdout -r, --rustfmt Format with rustfmt after formatting with leptosfmt (requires stdin) + --override-macro-names + ... Override formatted macro names -q, --quiet --check Check if the file is correctly formatted. Exit with code 1 if not -h, --help Print help @@ -64,6 +66,7 @@ tab_spaces = 4 # Number of spaces per tab indentation_style = "Auto" # "Tabs", "Spaces" or "Auto" newline_style = "Auto" # "Unix", "Windows" or "Auto" attr_value_brace_style = "WhenRequired" # "Always", "AlwaysUnlessLit", "WhenRequired" or "Preserve" +macro_names = [ "leptos::view, view" ] # Macro names which will be formatted ``` To see what each setting does, the see [configuration docs](./docs/configuration.md) diff --git a/cli/src/main.rs b/cli/src/main.rs index a66a4a1..1bc1987 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -45,6 +45,10 @@ struct Args { #[arg(short, long, default_value = "false", requires = "stdin")] rustfmt: bool, + /// Override formatted macro names + #[arg(long, num_args=1.., value_delimiter= ' ')] + override_macro_names: Option>, + #[arg( short, long, @@ -137,7 +141,7 @@ fn main() { let format_results = file_paths .into_par_iter() - .map(|path| (path.clone(), format_file(&path, settings, !args.check))) + .map(|path| (path.clone(), format_file(&path, &settings, !args.check))) .collect::>(); let mut check_failed = false; @@ -200,7 +204,7 @@ fn format_stdin(settings: FormatterSettings) -> anyhow::Result { let mut stdin = String::new(); let _ = std::io::stdin().read_to_string(&mut stdin); - let formatted = panic::catch_unwind(|| format_file_source(&stdin, settings)) + let formatted = panic::catch_unwind(|| format_file_source(&stdin, &settings)) .map_err(|e| anyhow::anyhow!(e.downcast::().unwrap()))??; Ok(FormatOutput { @@ -211,7 +215,7 @@ fn format_stdin(settings: FormatterSettings) -> anyhow::Result { fn format_file( file: &PathBuf, - settings: FormatterSettings, + settings: &FormatterSettings, write_result: bool, ) -> anyhow::Result { let file_source = std::fs::read_to_string(file)?; @@ -254,6 +258,10 @@ fn create_settings(args: &Args) -> anyhow::Result { if let Some(tab_spaces) = args.tab_spaces { settings.tab_spaces = tab_spaces; } + + if let Some(macro_names) = args.override_macro_names.to_owned() { + settings.macro_names = macro_names; + } Ok(settings) } diff --git a/formatter/src/collect.rs b/formatter/src/collect.rs index 15ffa6f..e3f302b 100644 --- a/formatter/src/collect.rs +++ b/formatter/src/collect.rs @@ -5,16 +5,22 @@ use syn::{ File, Macro, }; -use crate::{ParentIndent, ViewMacro}; +use crate::{view_macro::get_macro_full_path, ParentIndent, ViewMacro}; -struct ViewMacroVisitor<'ast> { - macros: Vec>, +struct ViewMacroVisitor<'a> { + macros: Vec>, source: Rope, + macro_names: &'a Vec, } impl<'ast> Visit<'ast> for ViewMacroVisitor<'ast> { fn visit_macro(&mut self, node: &'ast Macro) { - if node.path.is_ident("view") { + let should_format = self + .macro_names + .iter() + .any(|macro_name| &get_macro_full_path(node) == macro_name); + + if should_format { let span_line = node.span().start().line; let line = self.source.line(span_line - 1); @@ -35,10 +41,15 @@ impl<'ast> Visit<'ast> for ViewMacroVisitor<'ast> { } } -pub fn collect_macros_in_file(file: &File, source: Rope) -> (Rope, Vec>) { +pub fn collect_macros_in_file<'a>( + file: &'a File, + source: Rope, + macro_names: &'a Vec, +) -> (Rope, Vec>) { let mut visitor = ViewMacroVisitor { source, macros: Vec::new(), + macro_names, }; visitor.visit_file(file); diff --git a/formatter/src/formatter/element.rs b/formatter/src/formatter/element.rs index 802d210..ea7e2b7 100644 --- a/formatter/src/formatter/element.rs +++ b/formatter/src/formatter/element.rs @@ -62,7 +62,7 @@ impl Formatter<'_> { } } - pub fn children(&mut self, children: &Vec, attribute_count: usize) { + pub fn children(&mut self, children: &[Node], attribute_count: usize) { if children.is_empty() { return; } diff --git a/formatter/src/formatter/mac.rs b/formatter/src/formatter/mac.rs index 5afd1fd..ad66207 100644 --- a/formatter/src/formatter/mac.rs +++ b/formatter/src/formatter/mac.rs @@ -4,6 +4,8 @@ use proc_macro2::{token_stream, Span, TokenStream, TokenTree}; use rstml::node::Node; use syn::{spanned::Spanned, Macro}; +use crate::view_macro::get_macro_full_path; + use super::{Formatter, FormatterSettings}; pub struct ViewMacro<'a> { @@ -85,7 +87,9 @@ impl Formatter<'_> { .cbox((parent_indent.tabs * self.settings.tab_spaces + parent_indent.spaces) as isize); self.flush_comments(cx.span().start().line - 1); - self.printer.word("view! {"); + + let macro_word = format!("{}! {{", get_macro_full_path(view_mac.mac)); + self.printer.word(macro_word); if let Some(cx) = cx { self.printer.word(" "); @@ -170,9 +174,9 @@ pub fn format_macro( mac.mac.tokens.clone(), ); - Formatter::with_source(*settings, &mut printer, source, whitespace) + Formatter::with_source(settings, &mut printer, source, whitespace) } - None => Formatter::new(*settings, &mut printer), + None => Formatter::new(settings, &mut printer), }; formatter.view_macro(mac); diff --git a/formatter/src/formatter/mod.rs b/formatter/src/formatter/mod.rs index e3a058d..4d33705 100644 --- a/formatter/src/formatter/mod.rs +++ b/formatter/src/formatter/mod.rs @@ -40,7 +40,7 @@ pub enum NewlineStyle { Windows, } -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(default)] pub struct FormatterSettings { // Maximum width of each line @@ -57,6 +57,9 @@ pub struct FormatterSettings { // Determines placement of braces around single expression attribute values pub attr_value_brace_style: AttributeValueBraceStyle, + + // Determines macros to be formatted. Default: leptos::view, view + pub macro_names: Vec, } impl Default for FormatterSettings { @@ -67,6 +70,7 @@ impl Default for FormatterSettings { attr_value_brace_style: AttributeValueBraceStyle::WhenRequired, indentation_style: IndentationStyle::Auto, newline_style: NewlineStyle::Auto, + macro_names: vec!["leptos::view".to_string(), "view".to_string()], } } } @@ -110,14 +114,14 @@ impl FormatterSettings { pub struct Formatter<'a> { pub printer: &'a mut leptosfmt_pretty_printer::Printer, - pub settings: FormatterSettings, + pub settings: &'a FormatterSettings, pub(crate) source: Option<&'a Rope>, pub(crate) whitespace_and_comments: HashMap>, pub(crate) line_offset: Option, } impl<'a> Formatter<'a> { - pub fn new(settings: FormatterSettings, printer: &'a mut Printer) -> Self { + pub fn new(settings: &'a FormatterSettings, printer: &'a mut Printer) -> Self { Self { printer, settings, @@ -127,7 +131,7 @@ impl<'a> Formatter<'a> { } } pub fn with_source( - settings: FormatterSettings, + settings: &'a FormatterSettings, printer: &'a mut Printer, source: &'a Rope, comments: HashMap>, @@ -184,4 +188,3 @@ impl<'a> Formatter<'a> { self.line_offset = Some(line_index); } } - diff --git a/formatter/src/lib.rs b/formatter/src/lib.rs index de0b235..0e8b6bf 100644 --- a/formatter/src/lib.rs +++ b/formatter/src/lib.rs @@ -20,7 +20,7 @@ pub use formatter::*; pub fn format_file(path: &Path, settings: FormatterSettings) -> Result { let file = std::fs::read_to_string(path)?; - format_file_source(&file, settings) + format_file_source(&file, &settings) } fn get_text_beween_spans(rope: &Rope, start: LineColumn, end: LineColumn) -> RopeSlice<'_> { diff --git a/formatter/src/source_file.rs b/formatter/src/source_file.rs index 438f77e..182dcb5 100644 --- a/formatter/src/source_file.rs +++ b/formatter/src/source_file.rs @@ -30,18 +30,18 @@ struct TextEdit { pub fn format_file_source( source: &str, - settings: FormatterSettings, + settings: &FormatterSettings, ) -> Result { let ast = syn::parse_file(source)?; - let rope = Rope::try_from(source).unwrap(); - let (mut rope, macros) = collect_macros_in_file(&ast, rope); + let rope = Rope::from(source); + let (mut rope, macros) = collect_macros_in_file(&ast, rope, &settings.macro_names); format_source(&mut rope, macros, settings) } fn format_source( source: &mut Rope, macros: Vec>, - settings: FormatterSettings, + settings: &FormatterSettings, ) -> Result { let mut edits = Vec::new(); @@ -51,7 +51,7 @@ fn format_source( let end = mac.delimiter.span().close().end(); let start_byte = line_column_to_byte(source, start); let end_byte = line_column_to_byte(source, end); - let new_text = format_macro(&view_mac, &settings, Some(source)); + let new_text = format_macro(&view_mac, settings, Some(source)); edits.push(TextEdit { range: start_byte..end_byte, @@ -99,7 +99,7 @@ mod tests { let result = format_file_source( source, - FormatterSettings { + &FormatterSettings { tab_spaces: 2, ..Default::default() }, @@ -122,7 +122,7 @@ mod tests { } "#}; - let result = format_file_source(source, Default::default()).unwrap(); + let result = format_file_source(source, &Default::default()).unwrap(); insta::assert_snapshot!(result, @r#" fn main() { view! { cx, @@ -135,6 +135,160 @@ mod tests { "#); } + #[test] + fn fully_qualified_macro_path() { + let source = indoc! {r#" + fn main() { + leptos::view! { cx ,
"hello"
}; + } + "#}; + + let result = format_file_source(source, &Default::default()).unwrap(); + insta::assert_snapshot!(result, @r#" + fn main() { + leptos::view! { cx, +
+ "hello" +
+ }; + } + + "#); + } + + #[test] + fn ignore_other_macros() { + let source = indoc! {r#" + fn main() { + leptos::view! { cx ,
"hello"
}; + } + "#}; + + let result = format_file_source(source, &Default::default()).unwrap(); + insta::assert_snapshot!(result, @r#" + fn main() { + leptos::view! { cx, +
+ "hello" +
+ }; + } + + "#); + } + + #[test] + fn fully_qualified_macro_path_overridden() { + let source = indoc! {r#" + fn main() { + foo::bar::some_view! { cx ,
"hello"
}; + } + "#}; + + let result = format_file_source( + source, + &FormatterSettings { + macro_names: vec!["foo::bar::some_view".to_string()], + ..Default::default() + }, + ) + .unwrap(); + insta::assert_snapshot!(result, @r#" + fn main() { + foo::bar::some_view! { cx, +
+ "hello" +
+ }; + } + + "#); + } + + #[test] + fn fully_qualified_macro_path_with_indent() { + let source = indoc! {r#" + fn main() { + foo::bar::some_view! { cx ,
{ + let a = 12; + + + foo::bar::some_view! { cx, + + {a} + } + }
}; + } + "#}; + + let result = format_file_source( + source, + &FormatterSettings { + macro_names: vec!["foo::bar::some_view".to_string()], + ..Default::default() + }, + ) + .unwrap(); + insta::assert_snapshot!(result, @r#" + fn main() { + foo::bar::some_view! { cx, +
+ + { + let a = 12; + + foo::bar::some_view! { cx, {a} } + } + +
+ }; + } + + "#); + } + + #[test] + fn override_macro_names() { + let source = indoc! {r#" + fn main() { + html! { cx ,
{ + let a = 12; + + + html! { cx, + + {a} + } + }
}; + } + "#}; + + let result = format_file_source( + source, + &FormatterSettings { + macro_names: vec!["html".to_string()], + ..Default::default() + }, + ) + .unwrap(); + insta::assert_snapshot!(result, @r#" + fn main() { + html! { cx, +
+ + { + let a = 12; + + html! { cx, {a} } + } + +
+ }; + } + + "#); + } + #[test] fn with_comments() { let source = indoc! {r#" @@ -170,7 +324,7 @@ mod tests { // comment after view macro "#}; - let result = format_file_source(source, Default::default()).unwrap(); + let result = format_file_source(source, &Default::default()).unwrap(); insta::assert_snapshot!(result, @r#" // comment outside view macro fn main() { @@ -226,7 +380,7 @@ mod tests { } "#}; - let result = format_file_source(source, Default::default()).unwrap(); + let result = format_file_source(source, &Default::default()).unwrap(); insta::assert_snapshot!(result, @r###" fn main() { view! { cx, @@ -264,7 +418,7 @@ mod tests { } "#}; - let result = format_file_source(source, Default::default()).unwrap(); + let result = format_file_source(source, &Default::default()).unwrap(); insta::assert_snapshot!(result, @r###" fn main() { view! { cx, @@ -298,7 +452,7 @@ mod tests { } "#}; - let result = format_file_source(source, Default::default()).unwrap(); + let result = format_file_source(source, &Default::default()).unwrap(); insta::assert_snapshot!(result, @r#" fn main() { view! { cx, @@ -323,7 +477,7 @@ mod tests { } "#}; - let result = format_file_source(source, Default::default()).unwrap(); + let result = format_file_source(source, &Default::default()).unwrap(); insta::assert_snapshot!(result, @r#" fn main() { view! { cx, @@ -344,7 +498,7 @@ mod tests { } "#}; - let result = format_file_source(source, Default::default()).unwrap(); + let result = format_file_source(source, &Default::default()).unwrap(); insta::assert_snapshot!(result, @r###" #[component] fn test2(cx: Scope) -> impl IntoView { @@ -385,7 +539,7 @@ mod tests { } "#}; - let result = format_file_source(source, Default::default()).unwrap(); + let result = format_file_source(source, &Default::default()).unwrap(); insta::assert_snapshot!(result, @r#" use leptos::*; @@ -427,7 +581,7 @@ mod tests { } "#}; - let result = format_file_source(source, Default::default()).unwrap(); + let result = format_file_source(source, &Default::default()).unwrap(); insta::assert_snapshot!(result, @r#" #[component] pub fn History() -> impl IntoView { @@ -455,7 +609,7 @@ mod tests { let result = format_file_source( source, - FormatterSettings { + &FormatterSettings { tab_spaces: 1, indentation_style: IndentationStyle::Tabs, ..Default::default() @@ -490,7 +644,7 @@ mod tests { let result = format_file_source( source, - FormatterSettings { + &FormatterSettings { indentation_style: IndentationStyle::Auto, ..Default::default() }, @@ -524,7 +678,7 @@ mod tests { let result = format_file_source( source, - FormatterSettings { + &FormatterSettings { tab_spaces: 1, indentation_style: IndentationStyle::Auto, ..Default::default() diff --git a/formatter/src/test_helpers.rs b/formatter/src/test_helpers.rs index aa77dac..aee1e4d 100644 --- a/formatter/src/test_helpers.rs +++ b/formatter/src/test_helpers.rs @@ -122,14 +122,14 @@ pub fn format_with_source( let mut printer = Printer::new(settings.to_printer_settings(Some(&rope))); let tokens = ::from_str(source).unwrap(); let whitespace = crate::collect_comments::extract_whitespace_and_comments(&rope, tokens); - let mut formatter = Formatter::with_source(settings, &mut printer, &rope, whitespace); + let mut formatter = Formatter::with_source(&settings, &mut printer, &rope, whitespace); run(&mut formatter); printer.eof() } pub fn format_with(settings: FormatterSettings, run: impl FnOnce(&mut Formatter)) -> String { let mut printer = Printer::new(settings.to_printer_settings(None)); - let mut formatter = Formatter::new(settings, &mut printer); + let mut formatter = Formatter::new(&settings, &mut printer); run(&mut formatter); printer.eof() } diff --git a/formatter/src/view_macro.rs b/formatter/src/view_macro.rs index 106c542..2cad477 100644 --- a/formatter/src/view_macro.rs +++ b/formatter/src/view_macro.rs @@ -6,19 +6,19 @@ use leptosfmt_prettyplease::MacroFormatter; use crate::{Formatter, FormatterSettings, ViewMacro}; pub struct ViewMacroFormatter<'a> { - settings: FormatterSettings, + settings: &'a FormatterSettings, source: Option<&'a Rope>, line_offset: Option, comments: HashMap>, } impl ViewMacroFormatter<'_> { - pub fn new( - settings: FormatterSettings, - source: Option<&Rope>, + pub fn new<'a>( + settings: &'a FormatterSettings, + source: Option<&'a Rope>, line_offset: Option, comments: HashMap>, - ) -> ViewMacroFormatter<'_> { + ) -> ViewMacroFormatter<'a> { ViewMacroFormatter { settings, source, @@ -28,24 +28,39 @@ impl ViewMacroFormatter<'_> { } } +pub fn get_macro_full_path(mac: &syn::Macro) -> String { + mac.path + .segments + .iter() + .map(|path| path.ident.to_string()) + .collect::>() + .join("::") +} + impl MacroFormatter for ViewMacroFormatter<'_> { fn format(&self, printer: &mut leptosfmt_pretty_printer::Printer, mac: &syn::Macro) -> bool { - if !mac.path.is_ident("view") { - return false; + let mut formatted = false; + + for macro_name in &self.settings.macro_names { + if &get_macro_full_path(mac) != macro_name { + continue; + } + + let Some(m) = ViewMacro::try_parse(Default::default(), mac) else { + continue; + }; + let mut formatter = Formatter { + printer, + settings: self.settings, + source: self.source, + line_offset: self.line_offset, + whitespace_and_comments: self.comments.clone(), + }; + + formatter.view_macro(&m); + formatted = true; } - let Some(m) = ViewMacro::try_parse(Default::default(), mac) else { - return false; - }; - let mut formatter = Formatter { - printer, - settings: self.settings, - source: self.source, - line_offset: self.line_offset, - whitespace_and_comments: self.comments.clone(), - }; - - formatter.view_macro(&m); - true + formatted } }