Skip to content

Commit d053d46

Browse files
committed
fix(rome_js_formatter): in JSX, some spaces are removed rome#3211
1 parent dd74470 commit d053d46

File tree

6 files changed

+129
-318
lines changed

6 files changed

+129
-318
lines changed

crates/rome_js_formatter/src/jsx/lists/child_list.rs

+42-13
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ impl FormatJsxChildList {
116116
flat.write(&format_args![word, separator], f);
117117

118118
if let Some(separator) = separator {
119-
multiline.write(word, &separator, f);
119+
multiline.write_with_separator(word, &separator, f);
120120
} else {
121121
multiline.write_with_empty_separator(word, f);
122122
}
@@ -151,7 +151,7 @@ impl FormatJsxChildList {
151151
// </div>
152152
// ```
153153
else if last.is_none() {
154-
multiline.write(&JsxRawSpace, &hard_line_break(), f);
154+
multiline.write_with_separator(&JsxRawSpace, &hard_line_break(), f);
155155
} else {
156156
multiline.write_with_empty_separator(&JsxSpace, f);
157157
}
@@ -205,20 +205,32 @@ impl FormatJsxChildList {
205205

206206
child_breaks = line_mode.map_or(false, |mode| mode.is_hard());
207207

208-
let format_separator = format_with(|f| match line_mode {
209-
Some(mode) => f.write_element(FormatElement::Line(mode)),
210-
None => Ok(()),
208+
let format_separator = line_mode.map(|mode| {
209+
format_with(move |f| f.write_element(FormatElement::Line(mode)))
211210
});
212211

213212
if force_multiline {
214-
multiline.write(&non_text.format(), &format_separator, f);
213+
if let Some(format_separator) = format_separator {
214+
multiline.write_with_separator(
215+
&non_text.format(),
216+
&format_separator,
217+
f,
218+
);
219+
} else {
220+
multiline.write_with_empty_separator(&non_text.format(), f);
221+
}
215222
} else {
216223
let mut memoized = non_text.format().memoized();
217224

218225
force_multiline = memoized.inspect(f)?.will_break();
219226

220-
flat.write(&format_args![memoized, format_separator], f);
221-
multiline.write(&memoized, &format_separator, f);
227+
if let Some(format_separator) = format_separator {
228+
multiline.write_with_separator(&memoized, &format_separator, f);
229+
flat.write(&format_args![memoized, format_separator], f);
230+
} else {
231+
multiline.write_with_empty_separator(&memoized, f);
232+
flat.write(&format_args![memoized], f);
233+
}
222234
}
223235
}
224236
}
@@ -481,19 +493,30 @@ impl MultilineBuilder {
481493
}
482494

483495
/// Formats an element that does not require a separator
496+
/// It is safe to omit the separator because at the call side we must guarantee that we have reached the end of the iterator
497+
/// or the next element is a space/newline that should be written into the separator "slot".
484498
fn write_with_empty_separator(
485499
&mut self,
486500
content: &dyn Format<JsFormatContext>,
487501
f: &mut JsFormatter,
488502
) {
489-
self.write(content, &format_with(|_| Ok(())), f)
503+
self.write(content, None, f);
490504
}
491505

492-
fn write(
506+
fn write_with_separator(
493507
&mut self,
494508
content: &dyn Format<JsFormatContext>,
495509
separator: &dyn Format<JsFormatContext>,
496510
f: &mut JsFormatter,
511+
) {
512+
self.write(content, Some(separator), f);
513+
}
514+
515+
fn write(
516+
&mut self,
517+
content: &dyn Format<JsFormatContext>,
518+
separator: Option<&dyn Format<JsFormatContext>>,
519+
f: &mut JsFormatter,
497520
) {
498521
let result = std::mem::replace(&mut self.result, Ok(Vec::new()));
499522

@@ -507,12 +530,18 @@ impl MultilineBuilder {
507530
write!(buffer, [content])?;
508531
buffer.write_element(FormatElement::Tag(Tag::EndEntry))?;
509532

510-
buffer.write_element(FormatElement::Tag(Tag::StartEntry))?;
511-
write!(buffer, [separator])?;
512-
buffer.write_element(FormatElement::Tag(Tag::EndEntry))?;
533+
if let Some(separator) = separator {
534+
buffer.write_element(FormatElement::Tag(Tag::StartEntry))?;
535+
write!(buffer, [separator])?;
536+
buffer.write_element(FormatElement::Tag(Tag::EndEntry))?;
537+
}
513538
}
514539
MultilineLayout::NoFill => {
515540
write!(buffer, [content, separator])?;
541+
542+
if let Some(separator) = separator {
543+
write!(buffer, [separator])?;
544+
}
516545
}
517546
};
518547
buffer.into_vec()

crates/rome_js_formatter/src/lib.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -861,8 +861,14 @@ function() {
861861
// use this test check if your snippet prints as you wish, without using a snapshot
862862
fn quick_test() {
863863
let src = r#"
864-
const a = <><b/>a{' '}{" "}{" "}{" "}c{" "}{" "}{" "}{" "}{" "}{" "}</>;
865-
864+
const b4 = (
865+
<div>
866+
Text <a data-very-long-prop-breakline-rome-playground data-other>
867+
some link
868+
</a>{" "}
869+
| some other text,{" "}
870+
</div>
871+
);
866872
867873
"#;
868874
let syntax = SourceType::jsx();

crates/rome_js_formatter/src/utils/jsx.rs

+9-6
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,9 @@ where
309309
Ok(builder.finish())
310310
}
311311

312-
/// The builder removes [JsxChild::EmptyLine], [JsxChild::Newline], [JsxChild::Whitespace]
313-
/// if a next element is [JsxChild::Whitespace]
312+
/// The builder is used to:
313+
/// 1. Remove [JsxChild::EmptyLine], [JsxChild::Newline], [JsxChild::Whitespace] if a next element is [JsxChild::Whitespace]
314+
/// 2. Don't push a new element [JsxChild::EmptyLine], [JsxChild::Newline], [JsxChild::Whitespace] if previous one is [JsxChild::EmptyLine], [JsxChild::Newline], [JsxChild::Whitespace]
314315
/// [Prettier applies]: https://github.com/prettier/prettier/blob/b0d9387b95cdd4e9d50f5999d3be53b0b5d03a97/src/language-js/print/jsx.js#L144-L180
315316
#[derive(Debug)]
316317
struct JsxSplitChildrenBuilder {
@@ -324,10 +325,12 @@ impl JsxSplitChildrenBuilder {
324325

325326
fn entry(&mut self, child: JsxChild) {
326327
match self.buffer.last_mut() {
327-
Some(last @ (JsxChild::EmptyLine | JsxChild::Newline | JsxChild::Whitespace))
328-
if matches!(child, JsxChild::Whitespace) =>
329-
{
330-
*last = child
328+
Some(last @ (JsxChild::EmptyLine | JsxChild::Newline | JsxChild::Whitespace)) => {
329+
if matches!(child, JsxChild::Whitespace) {
330+
*last = child;
331+
} else if matches!(child, JsxChild::NonText(_) | JsxChild::Word(_)) {
332+
self.buffer.push(child);
333+
}
331334
}
332335
_ => self.buffer.push(child),
333336
}

crates/rome_js_formatter/tests/specs/jsx/element.jsx.snap

+10-15
Original file line numberDiff line numberDiff line change
@@ -358,10 +358,7 @@ c2 = (
358358
// this group should fit one line jsx whitespaces are hidden
359359
b = (
360360
<div>
361-
<a></a>
362-
363-
<a></a>
364-
1
361+
<a></a> <a></a> 1
365362
</div>
366363
);
367364

@@ -374,17 +371,14 @@ b1 = (
374371
12312
375372
`}
376373
</a>{" "}
377-
378-
<a></a>
379-
1
374+
<a></a> 1
380375
</div>
381376
);
382377

383378
// this group fit one line and hide jsx whitespace
384379
b2 = (
385380
<>
386-
<a>123</a>
387-
1
381+
<a>123</a> 1
388382
</>
389383
);
390384

@@ -401,7 +395,8 @@ b3 = (
401395

402396
const b4 = (
403397
<div>
404-
Text <a data-very-long-prop-breakline-rome-playground data-other>
398+
Text{" "}
399+
<a data-very-long-prop-breakline-rome-playground data-other>
405400
some link
406401
</a>{" "}
407402
| some other text,{" "}
@@ -617,9 +612,9 @@ const breadcrumbItems = [
617612
2: <div tooltip="A very long tooltip text that would otherwise make the attribute break onto the same line but it is not because of the single string layout"></div>;
618613
7: tooltip="A very long tooltip text that would otherwise make the attribute break
619614
14: <ASuperLongComponentNameThatWouldBreakButDoesntSinceTheComponent></ASuperLongComponentNameThatWouldBreakButDoesntSinceTheComponent>
620-
179: "ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace",
621-
200: "ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace",
622-
213: "ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace",
623-
238: <pre className="h-screen overflow-y-scroll whitespace-pre-wrap text-red-500 text-xs">
624-
287: Uncle Boonmee Who Can Recall His Past Lives dir. Apichatpong Weerasethakul{" "}
615+
174: "ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace",
616+
195: "ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace",
617+
208: "ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace",
618+
233: <pre className="h-screen overflow-y-scroll whitespace-pre-wrap text-red-500 text-xs">
619+
282: Uncle Boonmee Who Can Recall His Past Lives dir. Apichatpong Weerasethakul{" "}
625620

crates/rome_js_formatter/tests/specs/prettier/jsx/significant-space/test.js.snap

+11-34
Original file line numberDiff line numberDiff line change
@@ -89,39 +89,18 @@ not_broken_begin =
8989
```diff
9090
--- Prettier
9191
+++ Rome
92-
@@ -12,8 +12,7 @@
92+
@@ -12,8 +12,9 @@
9393
9494
before_break1 = (
9595
<span>
9696
- <span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar />{" "}
9797
- foo
98-
+ <span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar /> foo
99-
</span>
100-
);
101-
102-
@@ -28,8 +27,9 @@
103-
104-
after_break = (
105-
<span>
106-
- foo{" "}
107-
- <span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar />
108-
+ foo <span
98+
+ <span
10999
+ barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar
110-
+ />
100+
+ /> foo
111101
</span>
112102
);
113103
114-
@@ -111,7 +111,8 @@
115-
116-
not_broken_begin = (
117-
<div>
118-
- <br /> long text long text long text long text long text long text long text
119-
- long text<link>url</link> long text long text
120-
+ <br /> long text long text long text long text long text long text long text long text<link>
121-
+ url
122-
+ </link> long text long text
123-
</div>
124-
);
125104
```
126105

127106
# Output
@@ -141,7 +120,9 @@ before = (
141120
142121
before_break1 = (
143122
<span>
144-
<span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar /> foo
123+
<span
124+
barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar
125+
/> foo
145126
</span>
146127
);
147128
@@ -156,9 +137,8 @@ before_break2 = (
156137
157138
after_break = (
158139
<span>
159-
foo <span
160-
barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar
161-
/>
140+
foo{" "}
141+
<span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar />
162142
</span>
163143
);
164144
@@ -240,18 +220,15 @@ not_broken_end = (
240220
241221
not_broken_begin = (
242222
<div>
243-
<br /> long text long text long text long text long text long text long text long text<link>
244-
url
245-
</link> long text long text
223+
<br /> long text long text long text long text long text long text long text
224+
long text<link>url</link> long text long text
246225
</div>
247226
);
248227
```
249228

250229

251230
# Lines exceeding max width of 80 characters
252231
```
253-
15: <span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar /> foo
254-
95: <Icon icon="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" />{" "}
255-
114: <br /> long text long text long text long text long text long text long text long text<link>
232+
96: <Icon icon="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" />{" "}
256233
```
257234

0 commit comments

Comments
 (0)