Skip to content

Commit

Permalink
fix(format/html): more whitespace sensitivity fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
dyc3 committed Jan 12, 2025
1 parent 9064d92 commit 804d349
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 25 deletions.
23 changes: 19 additions & 4 deletions crates/biome_html_formatter/src/html/auxiliary/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,23 @@ use super::{
const HTML_VERBATIM_TAGS: &[&str] = &["script", "style", "pre"];

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatHtmlElement;
pub(crate) struct FormatHtmlElement {
next_sibling_is_text: bool,
}

pub(crate) struct FormatHtmlElementOptions {
/// Whether or not the element that follows this one is a text node.
pub next_sibling_is_text: bool,
}

impl FormatRuleWithOptions<HtmlElement> for FormatHtmlElement {
type Options = FormatHtmlElementOptions;

fn with_options(mut self, options: Self::Options) -> Self {
self.next_sibling_is_text = options.next_sibling_is_text;
self
}
}

impl FormatNodeRule<HtmlElement> for FormatHtmlElement {
fn fmt_fields(&self, node: &HtmlElement, f: &mut HtmlFormatter) -> FormatResult<()> {
Expand Down Expand Up @@ -90,9 +106,8 @@ impl FormatNodeRule<HtmlElement> for FormatHtmlElement {
&& !children.is_empty()
&& !content_has_leading_whitespace;
let should_borrow_closing_tag = whitespace_sensitivity.is_strict()
&& is_inline_tag
&& !children.is_empty()
&& !content_has_trailing_whitespace;
&& ((is_inline_tag && !children.is_empty() && !content_has_trailing_whitespace)
|| self.next_sibling_is_text);

let borrowed_r_angle = if should_borrow_opening_r_angle {
opening_element.r_angle_token().ok()
Expand Down
44 changes: 30 additions & 14 deletions crates/biome_html_formatter/src/html/lists/element_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ use std::cell::RefCell;

use crate::{
comments::HtmlComments,
html::auxiliary::element::{FormatHtmlElement, FormatHtmlElementOptions},
prelude::*,
utils::children::{
html_split_children, is_meaningful_html_text, HtmlChild, HtmlChildrenIterator, HtmlSpace,
},
};
use biome_formatter::{best_fitting, prelude::*, CstFormatContext, FormatRuleWithOptions};
use biome_formatter::{
best_fitting, prelude::*, CstFormatContext, FormatContext, FormatRuleWithOptions,
};
use biome_formatter::{format_args, write, VecBuffer};
use biome_html_syntax::{
AnyHtmlElement, HtmlClosingElement, HtmlClosingElementFields, HtmlElementList, HtmlRoot,
Expand Down Expand Up @@ -283,12 +286,17 @@ impl FormatHtmlElementList {
// <pre className="h-screen overflow-y-scroll" />
// adefg
// ```
if matches!(non_text, AnyHtmlElement::HtmlSelfClosingElement(_))
&& !word.is_single_character()
{
Some(LineMode::Hard)

if !f.context().options().whitespace_sensitivity().is_strict() {
if matches!(non_text, AnyHtmlElement::HtmlSelfClosingElement(_))
&& !word.is_single_character()
{
Some(LineMode::Hard)
} else {
Some(LineMode::Soft)
}
} else {
Some(LineMode::Soft)
None
}
}

Expand All @@ -308,20 +316,28 @@ impl FormatHtmlElementList {
format_with(move |f| f.write_element(FormatElement::Line(mode)))
});

let next_sibling_is_text =
matches!(children_iter.peek(), Some(HtmlChild::Word(_)));
let format_content = format_with(|f| match non_text {
AnyHtmlElement::HtmlElement(element) => FormatNodeRule::fmt(
&FormatHtmlElement::default().with_options(FormatHtmlElementOptions {
next_sibling_is_text,
}),
element,
f,
),
non_text => non_text.format().fmt(f),
});

if force_multiline {
if let Some(format_separator) = format_separator {
multiline.write_with_separator(
&non_text.format(),
&format_separator,
f,
);
multiline.write_with_separator(&format_content, &format_separator, f);
} else {
// it's safe to write without a separator because None means that next element is a separator or end of the iterator
multiline.write_content(&non_text.format(), f);
multiline.write_content(&format_content, f);
}
} else {
let mut memoized = non_text.format().memoized();

let mut memoized = format_content.memoized();
force_multiline = memoized.inspect(f)?.will_break();
flat.write(&format_args![memoized, format_separator], f);

Expand Down
2 changes: 1 addition & 1 deletion crates/biome_html_formatter/tests/language.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl TestFormatLanguage for HtmlTestFormatLanguage {
let options = Self::ServiceLanguage::resolve_format_options(
Some(&settings.formatter),
Some(&settings.override_settings),
None,
Some(&settings.languages.html.formatter),
&BiomePath::new(""),
file_source,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"html": {
"formatter": {
"whitespaceSensitivity": "ignore"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<span class="avatar"><img src="user-avatar.png" alt="profile photo"/><img src="user-avatar2.png" alt="profile alt"/></span>username
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
source: crates/biome_formatter_test/src/snapshot_builder.rs
info: elements/whitespace-sensitivity/ignore/text-after.html
---
# Input

```html
<span class="avatar"><img src="user-avatar.png" alt="profile photo"/><img src="user-avatar2.png" alt="profile alt"/></span>username
```


=============================

# 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
<span class="avatar"
><img src="user-avatar.png" alt="profile photo" />
<img src="user-avatar2.png" alt="profile alt" /></span
>username
```
## Output 1
-----
Indent style: Tab
Indent width: 2
Line ending: LF
Line width: 80
Attribute Position: Auto
Bracket same line: false
Whitespace sensitivity: ignore
Indent script and style: false
-----
```html
<span class="avatar">
<img src="user-avatar.png" alt="profile photo" />
<img src="user-avatar2.png" alt="profile alt" />
</span>
username
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<span class="avatar"><img src="user-avatar.png" alt="profile photo"/><img src="user-avatar2.png" alt="profile alt"/></span>username
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
source: crates/biome_formatter_test/src/snapshot_builder.rs
info: elements/whitespace-sensitivity/strict/text-after.html
---
# Input

```html
<span class="avatar"><img src="user-avatar.png" alt="profile photo"/><img src="user-avatar2.png" alt="profile alt"/></span>username
```


=============================

# 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
<span class="avatar"
><img src="user-avatar.png" alt="profile photo" />
<img src="user-avatar2.png" alt="profile alt" /></span
>username
```
8 changes: 3 additions & 5 deletions crates/biome_service/src/file_handlers/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,14 @@ pub struct HtmlFormatterSettings {
impl From<HtmlFormatterConfiguration> for HtmlFormatterSettings {
fn from(config: HtmlFormatterConfiguration) -> Self {
HtmlFormatterSettings {
// TODO
// uncomment once ready
// bracket_same_line: config.bracket_same_line,
// whitespace_sensitivity: config.whitespace_sensitivity,
// indent_script_and_style: config.indent_script_and_style,
enabled: config.enabled,
line_ending: config.line_ending,
line_width: config.line_width,
indent_width: config.indent_width,
indent_style: config.indent_style,
bracket_same_line: config.bracket_same_line,
whitespace_sensitivity: config.whitespace_sensitivity,
indent_script_and_style: config.indent_script_and_style,
..Default::default()
}
}
Expand Down
6 changes: 5 additions & 1 deletion crates/biome_service/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ impl Settings {
if let Some(graphql) = configuration.graphql {
self.languages.graphql = graphql.into()
}
// html settings
if let Some(html) = configuration.html {
self.languages.html = html.into()
}

// NOTE: keep this last. Computing the overrides require reading the settings computed by the parent settings.
if let Some(overrides) = configuration.overrides {
Expand Down Expand Up @@ -483,7 +487,7 @@ impl From<GritConfiguration> for LanguageSettings<GritLanguage> {
impl From<HtmlConfiguration> for LanguageSettings<HtmlLanguage> {
fn from(html: HtmlConfiguration) -> Self {
let mut language_setting: LanguageSettings<HtmlLanguage> = LanguageSettings::default();
if let Some(formatter) = html.formatter {
if let Some(formatter) = dbg!(html.formatter) {
language_setting.formatter = formatter.into();
}

Expand Down

0 comments on commit 804d349

Please sign in to comment.