Repository: biomejs/biome (language: rust)

Issue:
📎 HTML: Implement "Bracket same line" option
### Description

Add the option to `HtmlFormatterSettings` and implement the behavior.

See: https://prettier.io/docs/en/options.html#bracket-line

Before this can be started, this requires:
- #4017 


Discussion / hints:
This option should be a top level option now, because we already have one for JSX

https://biomejs.dev/reference/configuration/#javascriptformatterbracketsameline

We could add the top level one, deprecate the JSX option and provide a migration. This can be done in a patch release.

Then, in v2.0 we will remove the deprecated option. What do you think?
Makes sense to me, since this option will also eventually apply to all HTML ish languages.
Any progress on this? 
@JonathonRP This is a technical issue; unless you want to help or have technical opinions, please refrain from asking "when an issue is ready", we're trying to avoid such comments. Something is ready when is ready.
> @JonathonRP This is a technical issue; unless you want to help or have technical opinions, please refrain from asking "when an issue is ready", we're trying to avoid such comments. Something is ready when is ready.

Sorry, I had a use case. I'm using svelte and want the brackets on the same line. I was curious about the updates, I saw the one PR is merged but didn't see a PR for progressing this, I would be willing to help
Yeah exactly, nobody claimed the issue yet. If you're interested in helping, you're more than welcome 😁 

You can use this issue to ask any questions, or even open a draft PR and start from asking questions there 

Fix patch:
diff --git a/crates/biome_html_formatter/src/html/auxiliary/opening_element.rs b/crates/biome_html_formatter/src/html/auxiliary/opening_element.rs
index 06b60def15a0..89139c86ecae 100644
--- a/crates/biome_html_formatter/src/html/auxiliary/opening_element.rs
+++ b/crates/biome_html_formatter/src/html/auxiliary/opening_element.rs
@@ -36,14 +36,30 @@ impl FormatNodeRule<HtmlOpeningElement> for FormatHtmlOpeningElement {
             r_angle_token,
         } = node.as_fields();
 
+        let bracket_same_line = f.options().bracket_same_line().value();
         write!(f, [l_angle_token.format(), name.format()])?;
-        if attributes.len() > 0 {
-            write!(f, [space(), attributes.format()])?
-        }
-        // When these tokens are borrowed, they are managed by the sibling `HtmlElementList` formatter.
-        if !self.r_angle_is_borrowed {
-            write!(f, [r_angle_token.format()])?;
-        }
+
+        let attr_group_id = f.group_id("element-attr-group-id");
+        write!(
+            f,
+            [&group(&format_with(|f| {
+                attributes.format().fmt(f)?;
+
+                // Whitespace sensitivity takes precedence over bracketSameLine for correctness.
+                //
+                // The r_angle is placed inside this group because prettier always includes this token
+                // in the same group as the attributes, unless the token is being borrowed.
+                // When these tokens are borrowed, they are managed by the sibling `HtmlElementList` formatter.
+                if !bracket_same_line {
+                    write!(f, [soft_line_break()])?;
+                }
+                if !self.r_angle_is_borrowed {
+                    write!(f, [r_angle_token.format()])?;
+                }
+                Ok(())
+            }))
+            .with_group_id(Some(attr_group_id))]
+        )?;
 
         Ok(())
     }
diff --git a/crates/biome_html_formatter/src/html/auxiliary/self_closing_element.rs b/crates/biome_html_formatter/src/html/auxiliary/self_closing_element.rs
index 7810bd475975..fd3bcb03a580 100644
--- a/crates/biome_html_formatter/src/html/auxiliary/self_closing_element.rs
+++ b/crates/biome_html_formatter/src/html/auxiliary/self_closing_element.rs
@@ -12,24 +12,42 @@ impl FormatNodeRule<HtmlSelfClosingElement> for FormatHtmlSelfClosingElement {
             slash_token,
             r_angle_token,
         } = node.as_fields();
+        let bracket_same_line = f.options().bracket_same_line().value();
 
+        write!(f, [l_angle_token.format(), name.format(), space()])?;
+
+        let attr_group_id = f.group_id("element-attr-group-id");
         write!(
             f,
-            [
-                l_angle_token.format(),
-                name.format(),
-                space(),
-                attributes.format(),
-                space(),
-            ]
-        )?;
+            [&group(&format_with(|f| {
+                attributes.format().fmt(f)?;
+
+                // Whitespace sensitivity takes precedence over bracketSameLine for correctness.
+                //
+                // The r_angle is placed inside this group because prettier always includes this token
+                // in the same group as the attributes, unless the token is being borrowed.
+                // When these tokens are borrowed, they are managed by the sibling `HtmlElementList` formatter.
+                if bracket_same_line {
+                    write!(f, [hard_space()])?;
+                } else {
+                    write!(f, [soft_line_break_or_space()])?;
+                }
 
-        if slash_token.is_some() {
-            write!(f, [slash_token.format()])?;
-        } else {
-            write!(f, [text("/")])?;
-        }
+                // TODO: These tokens (the `/>`) are not yet borrowed by sibling elements for whitespace sensitivity.
+                // To resolve this, these tokens either need to be passed to or deferred to sibling text elements when
+                // whitespace sensitivity would require it.
+                if slash_token.is_some() {
+                    write!(f, [slash_token.format()])?;
+                } else {
+                    write!(f, [text("/")])?;
+                }
+
+                write!(f, [r_angle_token.format()])?;
+                Ok(())
+            }))
+            .with_group_id(Some(attr_group_id))]
+        )?;
 
-        write!(f, [r_angle_token.format()])
+        Ok(())
     }
 }
diff --git a/crates/biome_html_formatter/src/html/lists/attribute_list.rs b/crates/biome_html_formatter/src/html/lists/attribute_list.rs
index 9b1bf5381c58..49b9cdbf04dc 100644
--- a/crates/biome_html_formatter/src/html/lists/attribute_list.rs
+++ b/crates/biome_html_formatter/src/html/lists/attribute_list.rs
@@ -6,22 +6,32 @@ pub(crate) struct FormatHtmlAttributeList;
 impl FormatRule<HtmlAttributeList> for FormatHtmlAttributeList {
     type Context = HtmlFormatContext;
     fn fmt(&self, node: &HtmlAttributeList, f: &mut HtmlFormatter) -> FormatResult<()> {
-        let attribute_len = node.iter().len();
-        let line_break = if f.options().attribute_position() == AttributePosition::Multiline
-            && attribute_len > 1
+        let attribute_count = node.len();
+        let attribute_seperator = if f.options().attribute_position()
+            == AttributePosition::Multiline
+            && attribute_count > 1
         {
             hard_line_break()
         } else {
             soft_line_break_or_space()
         };
 
-        write!(
-            f,
-            [&group(&soft_block_indent(&format_with(|f| {
-                f.join_with(&line_break)
-                    .entries(node.iter().formatted())
-                    .finish()
-            })))]
-        )
+        if attribute_count > 0 {
+            write!(
+                f,
+                [
+                    space(),
+                    &soft_line_indent_or_space(&format_with(|f| {
+                        f.join_with(&attribute_seperator)
+                            .entries(node.iter().formatted())
+                            .finish()?;
+
+                        Ok(())
+                    }))
+                ]
+            )?;
+        }
+
+        Ok(())
     }
 }


Test patch:
diff --git a/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/element.html b/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/element.html
new file mode 100644
index 000000000000..9e69b68b13ce
--- /dev/null
+++ b/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/element.html
@@ -0,0 +1,9 @@
+<div
+  id="hello"
+  class="world really-long-class-name another-really-long-class-name"
+  style="color: red"
+  data-foo="bar"
+  data-bar="foo"
+>
+  hello world
+</div>
diff --git a/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/element.html.snap b/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/element.html.snap
new file mode 100644
index 000000000000..8d4c06008440
--- /dev/null
+++ b/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/element.html.snap
@@ -0,0 +1,68 @@
+---
+source: crates/biome_formatter_test/src/snapshot_builder.rs
+info: elements/bracket-same-line/element.html
+---
+# Input
+
+```html
+<div
+  id="hello"
+  class="world really-long-class-name another-really-long-class-name"
+  style="color: red"
+  data-foo="bar"
+  data-bar="foo"
+>
+  hello world
+</div>
+
+```
+
+
+=============================
+
+# Outputs
+
+## Output 1
+
+-----
+Indent style: Tab
+Indent width: 2
+Line ending: LF
+Line width: 80
+Attribute Position: Auto
+Bracket same line: false
+Whitespace sensitivity: strict
+Indent script and style: false
+-----
+
+```html
+<div
+	id="hello"
+	class="world really-long-class-name another-really-long-class-name"
+	style="color: red"
+	data-foo="bar"
+	data-bar="foo"
+>hello world</div>
+```
+
+## Output 1
+
+-----
+Indent style: Tab
+Indent width: 2
+Line ending: LF
+Line width: 80
+Attribute Position: Auto
+Bracket same line: true
+Whitespace sensitivity: strict
+Indent script and style: false
+-----
+
+```html
+<div
+	id="hello"
+	class="world really-long-class-name another-really-long-class-name"
+	style="color: red"
+	data-foo="bar"
+	data-bar="foo">hello world</div>
+```
diff --git a/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/options.json b/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/options.json
new file mode 100644
index 000000000000..baea0d470ac0
--- /dev/null
+++ b/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/options.json
@@ -0,0 +1,6 @@
+{
+  "$schema": "../../../../../../../../packages/@biomejs/biome/configuration_schema.json",
+  "formatter": {
+    "bracketSameLine": true
+  }
+}
diff --git a/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/self-closing.html b/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/self-closing.html
new file mode 100644
index 000000000000..50cf96f1ced3
--- /dev/null
+++ b/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/self-closing.html
@@ -0,0 +1,7 @@
+<selfclosing
+  id="hello"
+  class="world"
+  style="color: red"
+  data-foo="bar"
+  data-bar="foo"
+/>
diff --git a/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/self-closing.html.snap b/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/self-closing.html.snap
new file mode 100644
index 000000000000..ca85d98363d7
--- /dev/null
+++ b/crates/biome_html_formatter/tests/specs/html/elements/bracket-same-line/self-closing.html.snap
@@ -0,0 +1,66 @@
+---
+source: crates/biome_formatter_test/src/snapshot_builder.rs
+info: elements/bracket-same-line/self-closing.html
+---
+# Input
+
+```html
+<selfclosing
+  id="hello"
+  class="world"
+  style="color: red"
+  data-foo="bar"
+  data-bar="foo"
+/>
+
+```
+
+
+=============================
+
+# Outputs
+
+## Output 1
+
+-----
+Indent style: Tab
+Indent width: 2
+Line ending: LF
+Line width: 80
+Attribute Position: Auto
+Bracket same line: false
+Whitespace sensitivity: strict
+Indent script and style: false
+-----
+
+```html
+<selfclosing
+	id="hello"
+	class="world"
+	style="color: red"
+	data-foo="bar"
+	data-bar="foo"
+/>
+```
+
+## Output 1
+
+-----
+Indent style: Tab
+Indent width: 2
+Line ending: LF
+Line width: 80
+Attribute Position: Auto
+Bracket same line: true
+Whitespace sensitivity: strict
+Indent script and style: false
+-----
+
+```html
+<selfclosing
+	id="hello"
+	class="world"
+	style="color: red"
+	data-foo="bar"
+	data-bar="foo" />
+```