Skip to content

Commit

Permalink
feat: add indentation style (tabs/spaces) & newline style (LF/CRLF) s…
Browse files Browse the repository at this point in the history
…ettings (#90)
  • Loading branch information
bram209 authored Oct 10, 2023
1 parent 85762ed commit a7dae84
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 49 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,15 @@ edition = "2021"
You can configure all settings through a `leptosfmt.toml` file.

```toml
max_width = 100
tab_spaces = 4
max_width = 100 # Maximum width of each line
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"

```

To see what each setting does, the see [configuration docs](./docs/configuration.md)



## Examples

**Single file**
Expand Down
17 changes: 10 additions & 7 deletions formatter/src/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use syn::{
File, Macro,
};

use crate::ViewMacro;
use crate::{ParentIdent, ViewMacro};

struct ViewMacroVisitor<'ast> {
macros: Vec<ViewMacro<'ast>>,
Expand All @@ -16,14 +16,17 @@ impl<'ast> Visit<'ast> for ViewMacroVisitor<'ast> {
fn visit_macro(&mut self, node: &'ast Macro) {
if node.path.is_ident("view") {
let span_line = node.span().start().line;
let indent = self
.source
.line(span_line - 1)
let line = self.source.line(span_line - 1);

let indent_chars: Vec<_> = line
.chars()
.take_while(|&c| c == ' ')
.count();
.take_while(|&c| c == ' ' || c == '\t')
.collect();

let tabs = indent_chars.iter().filter(|&&c| c == '\t').count();
let spaces = indent_chars.iter().filter(|&&c| c == ' ').count();

if let Some(view_mac) = ViewMacro::try_parse(Some(indent), node) {
if let Some(view_mac) = ViewMacro::try_parse(ParentIdent { tabs, spaces }, node) {
self.macros.push(view_mac);
}
}
Expand Down
34 changes: 14 additions & 20 deletions formatter/src/formatter/mac.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use crop::Rope;
use leptosfmt_pretty_printer::{Printer, PrinterSettings};
use leptosfmt_pretty_printer::Printer;
use proc_macro2::{token_stream, Span, TokenStream, TokenTree};
use rstml::node::Node;
use syn::{spanned::Spanned, Macro};

use super::{Formatter, FormatterSettings};

pub struct ViewMacro<'a> {
pub parent_ident: Option<usize>,
pub parent_ident: ParentIdent,
pub cx: Option<TokenTree>,
pub global_class: Option<TokenTree>,
pub nodes: Vec<Node>,
Expand All @@ -16,8 +16,14 @@ pub struct ViewMacro<'a> {
pub comma: Option<TokenTree>,
}

#[derive(Default)]
pub struct ParentIdent {
pub tabs: usize,
pub spaces: usize,
}

impl<'a> ViewMacro<'a> {
pub fn try_parse(parent_ident: Option<usize>, mac: &'a Macro) -> Option<Self> {
pub fn try_parse(parent_ident: ParentIdent, mac: &'a Macro) -> Option<Self> {
let mut tokens = mac.tokens.clone().into_iter();
let (cx, comma) = (tokens.next(), tokens.next());

Expand Down Expand Up @@ -75,7 +81,8 @@ impl Formatter<'_> {
..
} = view_mac;

self.printer.cbox(parent_indent.unwrap_or(0) as isize);
self.printer
.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! {");
Expand Down Expand Up @@ -155,30 +162,17 @@ pub fn format_macro(
settings: &FormatterSettings,
source: Option<&Rope>,
) -> String {
let mut printer: Printer;
let mut printer = Printer::new(settings.to_printer_settings(source));
let mut formatter = match source {
Some(source) => {
let whitespace = crate::collect_comments::extract_whitespace_and_comments(
source,
mac.mac.tokens.clone(),
);

let crlf_line_endings = source
.raw_lines()
.next()
.map(|raw_line| raw_line.to_string().ends_with("\r\n"))
.unwrap_or_default();

printer = Printer::new(PrinterSettings {
crlf_line_endings,
..settings.into()
});
Formatter::with_source(*settings, &mut printer, source, whitespace)
}
None => {
printer = Printer::new(settings.into());
Formatter::new(*settings, &mut printer)
}
None => Formatter::new(*settings, &mut printer),
};

formatter.view_macro(mac);
Expand All @@ -195,7 +189,7 @@ mod tests {
macro_rules! view_macro {
($($tt:tt)*) => {{
let mac: Macro = syn::parse2(quote! { $($tt)* }).unwrap();
format_macro(&ViewMacro::try_parse(None, &mac).unwrap(), &Default::default(), None)
format_macro(&ViewMacro::try_parse(Default::default(), &mac).unwrap(), &Default::default(), None)
}}
}

Expand Down
63 changes: 56 additions & 7 deletions formatter/src/formatter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod mac;
mod node;

pub use mac::format_macro;
pub use mac::ViewMacro;
pub use mac::{ParentIdent, ViewMacro};

use serde::Deserialize;
use serde::Serialize;
Expand All @@ -25,6 +25,21 @@ pub enum AttributeValueBraceStyle {
Preserve,
}

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub enum IndentationStyle {
Auto,
Spaces,
Tabs,
}

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub enum NewlineStyle {
Auto,
Native,
Unix,
Windows,
}

#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(default)]
pub struct FormatterSettings {
Expand All @@ -34,6 +49,12 @@ pub struct FormatterSettings {
// Number of spaces per tab
pub tab_spaces: usize,

// Determines indentation style (tabs or spaces)
pub indentation_style: IndentationStyle,

// Determines line ending (unix or windows)
pub newline_style: NewlineStyle,

// Determines placement of braces around single expression attribute values
pub attr_value_brace_style: AttributeValueBraceStyle,
}
Expand All @@ -44,17 +65,45 @@ impl Default for FormatterSettings {
max_width: 100,
tab_spaces: 4,
attr_value_brace_style: AttributeValueBraceStyle::WhenRequired,
indentation_style: IndentationStyle::Auto,
newline_style: NewlineStyle::Auto,
}
}
}

impl From<&FormatterSettings> for PrinterSettings {
fn from(value: &FormatterSettings) -> Self {
Self {
margin: value.max_width as isize,
indent: value.tab_spaces as isize,
fn uses_crlf_line_ending(source: &Rope) -> bool {
source
.raw_lines()
.next()
.map(|raw_line| raw_line.to_string().ends_with("\r\n"))
.unwrap_or_default()
}

fn uses_tabs_for_indentation(source: &Rope) -> bool {
source
.lines()
.find(|line| matches!(line.chars().next(), Some('\t') | Some(' ')))
.map(|line| matches!(line.chars().next(), Some('\t')))
.unwrap_or_default()
}

impl FormatterSettings {
pub fn to_printer_settings(&self, source: Option<&Rope>) -> PrinterSettings {
PrinterSettings {
margin: self.max_width as isize,
spaces: self.tab_spaces as isize,
min_space: 60,
crlf_line_endings: false,
crlf_line_endings: match self.newline_style {
NewlineStyle::Auto => source.map(uses_crlf_line_ending).unwrap_or_default(),
NewlineStyle::Native => cfg!(windows),
NewlineStyle::Unix => false,
NewlineStyle::Windows => true,
},
hard_tabs: match self.indentation_style {
IndentationStyle::Auto => source.map(uses_tabs_for_indentation).unwrap_or_default(),
IndentationStyle::Spaces => false,
IndentationStyle::Tabs => true,
},
}
}
}
Expand Down
106 changes: 106 additions & 0 deletions formatter/src/source_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ fn format_source(
mod tests {
use indoc::indoc;

use crate::IndentationStyle;

use super::*;

#[test]
Expand Down Expand Up @@ -438,4 +440,108 @@ mod tests {
}
"#);
}

#[test]
fn indent_with_tabs() {
let source = indoc! {"
fn main() {
\u{0020}view! { cx,
<div>
<div>Example</div>
</div>
}
}
"};

let result = format_file_source(
source,
FormatterSettings {
tab_spaces: 1,
indentation_style: IndentationStyle::Tabs,
..Default::default()
},
)
.unwrap();

let expected = indoc! {"
fn main() {
\u{0020}view! { cx,
\t\t<div>
\t\t\t<div>Example</div>
\t\t</div>
\t}
}
"};

assert_eq!(result, expected);
}

#[test]
fn auto_detect_tabs() {
let source = indoc! {"
fn main() {
\tview! { cx,
<div>
<div>Example</div>
</div>
}
}
"};

let result = format_file_source(
source,
FormatterSettings {
indentation_style: IndentationStyle::Auto,
..Default::default()
},
)
.unwrap();

let expected = indoc! {"
fn main() {
\tview! { cx,
\t\t<div>
\t\t\t<div>Example</div>
\t\t</div>
\t}
}
"};

assert_eq!(result, expected);
}

#[test]
fn auto_detect_spaces() {
let source = indoc! {"
fn main() {
\u{0020}view! { cx,
<div>
<div>Example</div>
</div>
}
}
"};

let result = format_file_source(
source,
FormatterSettings {
tab_spaces: 1,
indentation_style: IndentationStyle::Auto,
..Default::default()
},
)
.unwrap();

let expected = indoc! {"
fn main() {
\u{0020}view! { cx,
\u{0020}\u{0020}<div>
\u{0020}\u{0020}\u{0020}<div>Example</div>
\u{0020}\u{0020}</div>
\u{0020}}
}
"};

assert_eq!(result, expected);
}
}
4 changes: 2 additions & 2 deletions formatter/src/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ pub fn format_with_source(
source: &str,
run: impl FnOnce(&mut Formatter),
) -> String {
let mut printer = Printer::new((&settings).into());
let rope = Rope::from_str(source).unwrap();
let mut printer = Printer::new(settings.to_printer_settings(Some(&rope)));
let tokens = <proc_macro2::TokenStream as std::str::FromStr>::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);
Expand All @@ -128,7 +128,7 @@ pub fn format_with_source(
}

pub fn format_with(settings: FormatterSettings, run: impl FnOnce(&mut Formatter)) -> String {
let mut printer = Printer::new((&settings).into());
let mut printer = Printer::new(settings.to_printer_settings(None));
let mut formatter = Formatter::new(settings, &mut printer);
run(&mut formatter);
printer.eof()
Expand Down
2 changes: 1 addition & 1 deletion formatter/src/view_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl MacroFormatter for ViewMacroFormatter<'_> {
return false;
}

let Some(m) = ViewMacro::try_parse(None, mac) else {
let Some(m) = ViewMacro::try_parse(Default::default(), mac) else {
return false;
};
let mut formatter = Formatter {
Expand Down
Loading

0 comments on commit a7dae84

Please sign in to comment.