Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add possibility to have non void self closing HTML tags #123

Merged
merged 9 commits into from
Jul 15, 2024
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ 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
allow_non_void_self_closing_tags = true # false by default

# Attribute values can be formatted by custom formatters
# Every attribute name may only select one formatter (this might change later on)
Expand Down
279 changes: 277 additions & 2 deletions formatter/src/formatter/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ impl Formatter<'_> {
let is_void = is_void_element(&name, !element.children.is_empty());
V4ldum marked this conversation as resolved.
Show resolved Hide resolved
self.opening_tag(element, is_void);

if !is_void {
if !(is_void
|| (self.settings.allow_non_void_self_closing_tags && element.children.is_empty()))
{
V4ldum marked this conversation as resolved.
Show resolved Hide resolved
self.children(&element.children, element.attributes().len());
self.flush_comments(element.close_tag.span().end().line - 1);
self.closing_tag(element)
Expand All @@ -22,7 +24,9 @@ impl Formatter<'_> {

self.attributes(element.attributes());

if is_void {
if is_void
|| (self.settings.allow_non_void_self_closing_tags && element.children.is_empty())
{
V4ldum marked this conversation as resolved.
Show resolved Hide resolved
self.printer.word("/>");
} else {
self.printer.word(">")
Expand Down Expand Up @@ -155,6 +159,14 @@ mod tests {
})
}};
}
macro_rules! format_element_with_self_closing_tag {
V4ldum marked this conversation as resolved.
Show resolved Hide resolved
($($tt:tt)*) => {{
let element = element! { $($tt)* };
format_with(FormatterSettings { max_width: 40, allow_non_void_self_closing_tags: true, ..Default::default() }, |formatter| {
formatter.element(&element)
})
}};
}
macro_rules! format_element_from_string {
($val:expr) => {{
format_element_from_string(
Expand All @@ -166,6 +178,18 @@ mod tests {
)
}};
}
macro_rules! format_element_from_string_with_self_closing_tag {
($val:expr) => {{
format_element_from_string(
FormatterSettings {
max_width: 40,
allow_non_void_self_closing_tags: true,
..Default::default()
},
$val,
)
}};
}

#[test]
fn no_children() {
Expand Down Expand Up @@ -409,4 +433,255 @@ mod tests {
</div>
"#);
}

// Self Closing Tag
V4ldum marked this conversation as resolved.
Show resolved Hide resolved

#[test]
fn self_closing_no_children() {
let formatted = format_element_with_self_closing_tag! { < div /> };
insta::assert_snapshot!(formatted, @"<div/>");
}

#[test]
fn self_closing_no_children_single_attr() {
let formatted = format_element_with_self_closing_tag! { < div width=12 > < / div > };
insta::assert_snapshot!(formatted, @"<div width=12/>");
}

#[test]
fn self_closing_no_children_multi_attr() {
let formatted = format_element_with_self_closing_tag! { <div key=23 width=100></div> };
insta::assert_snapshot!(formatted, @"<div key=23 width=100/>");
}

#[test]
fn self_closing_no_children_single_long_attr() {
let formatted = format_element_with_self_closing_tag! { <div key=a::very::deeply::nested::module::generate_key()></div> };

insta::assert_snapshot!(formatted, @"<div key=a::very::deeply::nested::module::generate_key()/>");
}

#[test]
fn self_closing_no_children_multi_long_attr() {
let formatted = format_element_with_self_closing_tag! { <div key=a::very::deeply::nested::module::generate_key() width=100></div> };
insta::assert_snapshot!(formatted, @r###"
<div
key=a::very::deeply::nested::module::generate_key()
width=100
/>
"###);
}

#[test]
fn self_closing_no_children_multi_attr_with_comment() {
println!("test");
let formatted = format_element_from_string_with_self_closing_tag!(indoc! {"
<div key=a
// width
width=100></div>
"});

insta::assert_snapshot!(formatted, @r###"
<div
key=a
// width
width=100
/>
"###);
}

#[test]
fn self_closing_child_element() {
let formatted = format_element_with_self_closing_tag! { <div><span>"hello"</span></div> };
insta::assert_snapshot!(formatted, @r#"
<div>
<span>"hello"</span>
</div>
"#);
}

#[test]
fn self_closing_child_element_single_textual() {
let formatted = format_element_with_self_closing_tag! { <div>"hello"</div> };
insta::assert_snapshot!(formatted, @r#"<div>"hello"</div>"#);
}

#[test]
fn self_closing_child_element_single_textual_unquoted() {
let formatted = format_element_from_string_with_self_closing_tag!("<div>hello</div>");
insta::assert_snapshot!(formatted, @r###"<div>hello</div>"###);
}

#[test]
fn self_closing_child_element_single_textual_single_attr() {
let formatted = format_element_with_self_closing_tag! { <div key=12>"hello"</div> };
insta::assert_snapshot!(formatted, @r#"<div key=12>"hello"</div>"#);
}

#[test]
fn self_closing_child_element_single_textual_multi_attr() {
let formatted =
format_element_with_self_closing_tag! { <div key=12 width=100>"hello"</div> };
insta::assert_snapshot!(formatted, @r#"
<div key=12 width=100>
"hello"
</div>
"#);
}

#[test]
fn self_closing_child_element_two_textual() {
let formatted =
format_element_with_self_closing_tag! { <div>"The count is " {count}</div> };
insta::assert_snapshot!(formatted, @r#"<div>"The count is " {count}</div>"#);
}

#[test]
fn self_closing_child_element_many_textual() {
let formatted = format_element_with_self_closing_tag! { <div>"The current count is: " {count} ". Increment by one is this: " {count + 1}</div> };
insta::assert_snapshot!(formatted, @r#"
<div>
"The current count is: " {count}
". Increment by one is this: " {count + 1}
</div>
"#);
}

#[test]
fn self_closing_child_element_two_textual_unquoted() {
let formatted = format_element_from_string_with_self_closing_tag! { "<div>The count is {count}.</div>" };
insta::assert_snapshot!(formatted, @r#"<div>The count is {count}.</div>"#);
}

#[test]
fn self_closing_child_element_two_textual_unquoted_no_trailingspace() {
let formatted =
format_element_from_string_with_self_closing_tag! { "<div>The count is{count}</div>" };
insta::assert_snapshot!(formatted, @r#"<div>The count is{count}</div>"#);
}

#[test]
fn self_closing_child_element_many_textual_unquoted() {
let formatted = format_element_from_string_with_self_closing_tag! { "<div>The current count is: {count}. Increment by one is this: {count + 1}</div>" };
insta::assert_snapshot!(formatted, @r###"
<div>
The current count is: {count}. Increment by one is this:
{count + 1}
</div>
"###);
}
// view! { <p>Something: {something} .</p> }

#[test]
fn self_closing_html_unquoted_text() {
let formatted =
format_element_from_string_with_self_closing_tag!(r##"<div>Unquoted text</div>"##);
insta::assert_snapshot!(formatted, @"<div>Unquoted text</div>");
}

#[test]
fn self_closing_html_unquoted_text_with_surrounding_spaces() {
let formatted = format_element_from_string_with_self_closing_tag!(
r##"<div> Unquoted text with spaces </div>"##
);
insta::assert_snapshot!(formatted, @"<div>Unquoted text with spaces</div>");
}

#[test]
fn self_closing_html_unquoted_text_multiline() {
let formatted = format_element_from_string_with_self_closing_tag!(indoc! {"
<div>
Unquoted text
with spaces
</div>
"});

insta::assert_snapshot!(formatted, @r###"
<div>
Unquoted text
with spaces
</div>"###);
}

#[test]
fn self_closing_single_empty_line() {
let formatted = format_element_from_string_with_self_closing_tag!(indoc! {r#"
<div>
<Nav/>

<Main/>
</div>
"#});

insta::assert_snapshot!(formatted, @r###"
<div>
<Nav/>

<Main/>
</div>
"###);
}

#[test]
fn self_closing_multiple_empty_lines() {
let formatted = format_element_from_string_with_self_closing_tag!(indoc! {r#"
<div>
<Nav/>



<Main/>
</div>
"#});

insta::assert_snapshot!(formatted, @r###"
<div>
<Nav/>

<Main/>
</div>
"###);
}

#[test]
fn self_closing_surrounded_by_empty_lines() {
let formatted = format_element_from_string_with_self_closing_tag!(indoc! {r#"

<div>
<Nav/>
<Main/>
</div>

"#});

insta::assert_snapshot!(formatted, @r###"
<div>
<Nav/>
<Main/>
</div>
"###);
}

#[test]
fn self_closing_other_test() {
let formatted = format_element_from_string_with_self_closing_tag!(indoc! {r#"
<div>
<div
class="foo"
>
<i class="bi-google"/>
"Sign in with google"
</div>
</div>
"#});

insta::assert_snapshot!(formatted, @r#"
<div>
<div class="foo">
<i class="bi-google"/>
"Sign in with google"
</div>
</div>
"#);
}
}
4 changes: 4 additions & 0 deletions formatter/src/formatter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ pub struct FormatterSettings {
/// Determines placement of braces around single expression attribute values
pub attr_value_brace_style: AttributeValueBraceStyle,

/// Allows non void HTML tags like div to be self closing. If set to true, <div /> will no longer format to <div></div>
pub allow_non_void_self_closing_tags: bool,

/// Determines macros to be formatted. Default: leptos::view, view
pub macro_names: Vec<String>,

Expand All @@ -88,6 +91,7 @@ impl Default for FormatterSettings {
attr_value_brace_style: AttributeValueBraceStyle::WhenRequired,
indentation_style: IndentationStyle::Auto,
newline_style: NewlineStyle::Auto,
allow_non_void_self_closing_tags: false,
macro_names: vec!["leptos::view".to_string(), "view".to_string()],
attr_values: HashMap::new(),
}
Expand Down