Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
feat(rome_formatter): will_break utility (#2771)
Browse files Browse the repository at this point in the history
* fix(rome_js_formatter): custom formatting for test calls

* chore: snapshots

* chore: documentation with tests

* chore: clippy

* chore: fix test

* chore: code review

* chore: remove `will_break` functionality

* feat(rome_formatter): `will_break` utility

* chore: wip

* chore: fix call arguments and more APIs

* chore: code review
  • Loading branch information
ematipico committed Jun 29, 2022
1 parent d1dca4f commit c518066
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 28 deletions.
194 changes: 194 additions & 0 deletions crates/rome_formatter/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,200 @@ pub trait BufferExtensions: Buffer + Sized {

Ok(())
}

/// It emits a custom buffer called [WillBreakBuffer], which tracks
/// it he last element written in the main buffer breaks, it does so by
/// checking if their IR emits an [element](FormatElement) that breaks.
///
/// This functionality can be used only one element and only after the element
/// is written in the buffer.
///
/// ## Examples
///
/// ```
/// use rome_formatter::{format, format_args, write, LineWidth};
/// use rome_formatter::prelude::*;
///
/// let context = SimpleFormatContext {
/// line_width: LineWidth::try_from(20).unwrap(),
/// ..SimpleFormatContext::default()
/// };
///
///
/// let formatted = format!(context, [format_with(|f| {
///
/// let element = format_with(|f| {
/// write!(f, [
/// token("hello"),
/// hard_line_break(),
/// token("world!")
/// ])
/// });
/// let mut buffer = f.inspect_will_break();
/// write!(buffer, [element])?;
/// let does_element_break = buffer.will_break();
///
/// if does_element_break {
/// write!(f, [hard_line_break(), token("break")])
/// } else {
/// write!(f, [token("did not break")])
/// }
///
/// })]).unwrap();
///
/// assert_eq!(
/// "hello\nworld!\nbreak",
/// formatted.print().as_code()
/// );
/// ```
fn inspect_will_break(&mut self) -> WillBreakBuffer<Self::Context> {
WillBreakBuffer::new(self)
}

/// It creates a buffer where all the elements are ignored, so the elements
/// are not written anywhere at all.
///
/// This can be useful when formatters are not yet written inside the main buffer
/// and the consumer needs to inspect them, to decide the formatting layout in advance.
///
/// ## Examples
///
/// The following example shows how to use it with the `will_break` functionality
///
/// ```
/// use rome_formatter::{format, format_args, write, LineWidth};
/// use rome_formatter::prelude::*;
///
/// let context = SimpleFormatContext {
/// line_width: LineWidth::try_from(20).unwrap(),
/// ..SimpleFormatContext::default()
/// };
///
///
/// let formatted = format!(context, [format_with(|f| {
///
/// let element = format_with(|f| {
/// write!(f, [
/// token("hello"),
/// hard_line_break(),
/// token("world!")
/// ])
/// }).memoized();
///
/// let will_break = {
/// let mut null_buffer = f.inspect_null();
/// let mut buffer = null_buffer.inspect_will_break();
/// write!(buffer, [element])?;
/// buffer.will_break()
/// };
///
///
/// if will_break {
/// write!(f, [token("break"), hard_line_break(), &element])
/// } else {
/// write!(f, [token("did not break")])
/// }
///
/// })]).unwrap();
///
/// assert_eq!(
/// "break\nhello\nworld!",
/// formatted.print().as_code()
/// );
/// ```
#[must_use]
fn inspect_null(&mut self) -> NullBuffer<Self::Context> {
NullBuffer::new(self)
}
}

impl<T> BufferExtensions for T where T: Buffer {}

#[must_use = "must eventually call `finish()` to retrieve the information"]
pub struct WillBreakBuffer<'buffer, Context> {
breaks: bool,
inner: &'buffer mut dyn Buffer<Context = Context>,
}

impl<'buffer, Context> WillBreakBuffer<'buffer, Context> {
pub fn new(buffer: &'buffer mut dyn Buffer<Context = Context>) -> Self {
Self {
breaks: false,
inner: buffer,
}
}

pub fn will_break(&self) -> bool {
self.breaks
}
}

impl<Context> Buffer for WillBreakBuffer<'_, Context> {
type Context = Context;

fn write_element(&mut self, element: FormatElement) -> FormatResult<()> {
self.breaks = self.breaks || element.will_break();
self.inner.write_element(element)
}

fn state(&self) -> &FormatState<Self::Context> {
self.inner.state()
}

fn state_mut(&mut self) -> &mut FormatState<Self::Context> {
self.inner.state_mut()
}

fn snapshot(&self) -> BufferSnapshot {
BufferSnapshot::Any(Box::new(WillBreakSnapshot {
inner: self.inner.snapshot(),
breaks: self.breaks,
}))
}

fn restore_snapshot(&mut self, snapshot: BufferSnapshot) {
let snapshot = snapshot.unwrap_any::<WillBreakSnapshot>();
self.inner.restore_snapshot(snapshot.inner);
self.breaks = snapshot.breaks;
}
}

struct WillBreakSnapshot {
inner: BufferSnapshot,
breaks: bool,
}

pub struct NullBuffer<'buffer, Context> {
inner: &'buffer mut dyn Buffer<Context = Context>,
}

impl<'buffer, Context> NullBuffer<'buffer, Context> {
pub fn new(buffer: &'buffer mut dyn Buffer<Context = Context>) -> Self {
Self { inner: buffer }
}
}

impl<Context> Buffer for NullBuffer<'_, Context> {
type Context = Context;

fn write_element(&mut self, element: FormatElement) -> FormatResult<()> {
drop(element);
Ok(())
}

fn state(&self) -> &FormatState<Self::Context> {
self.inner.state()
}

fn state_mut(&mut self) -> &mut FormatState<Self::Context> {
self.inner.state_mut()
}

fn snapshot(&self) -> BufferSnapshot {
BufferSnapshot::Position(0)
}

fn restore_snapshot(&mut self, snapshot: BufferSnapshot) {
drop(snapshot);
}
}
4 changes: 2 additions & 2 deletions crates/rome_formatter/src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ impl<'buf, Context> Formatter<'buf, Context> {
impl<Context> Formatter<'_, Context> {
/// Take a snapshot of the state of the formatter
#[inline]
pub fn snapshot(&self) -> FormatterSnapshot {
pub fn state_snapshot(&self) -> FormatterSnapshot {
FormatterSnapshot {
buffer: self.buffer.snapshot(),
state: self.state().snapshot(),
Expand All @@ -185,7 +185,7 @@ impl<Context> Formatter<'_, Context> {

#[inline]
/// Restore the state of the formatter to a previous snapshot
pub fn restore_snapshot(&mut self, snapshot: FormatterSnapshot) {
pub fn restore_state_snapshot(&mut self, snapshot: FormatterSnapshot) {
self.state_mut().restore_snapshot(snapshot.state);
self.buffer.restore_snapshot(snapshot.buffer);
}
Expand Down
5 changes: 4 additions & 1 deletion crates/rome_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ use std::any::TypeId;
use crate::printed_tokens::PrintedTokens;
use crate::printer::{Printer, PrinterOptions};
pub use arguments::{Argument, Arguments};
pub use buffer::{Buffer, BufferExtensions, BufferSnapshot, Inspect, PreambleBuffer, VecBuffer};
pub use buffer::{
Buffer, BufferExtensions, BufferSnapshot, Inspect, NullBuffer, PreambleBuffer, VecBuffer,
WillBreakBuffer,
};
pub use builders::{
block_indent, comment, empty_line, get_lines_before, group_elements, hard_line_break,
if_group_breaks, if_group_fits_on_line, indent, line_suffix, soft_block_indent,
Expand Down
4 changes: 2 additions & 2 deletions crates/rome_js_formatter/src/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ where
Node: AstNode<Language = JsLanguage> + AsFormat<'a>,
{
fn fmt(&self, f: &mut JsFormatter) -> FormatResult<()> {
let snapshot = Formatter::snapshot(f);
let snapshot = Formatter::state_snapshot(f);

match self.node.format().fmt(f) {
Ok(result) => Ok(result),

Err(_) => {
f.restore_snapshot(snapshot);
f.restore_state_snapshot(snapshot);

// Lists that yield errors are formatted as they were unknown nodes.
// Doing so, the formatter formats the nodes/tokens as is.
Expand Down
13 changes: 10 additions & 3 deletions crates/rome_js_formatter/src/js/expressions/call_arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ fn should_group_first_argument(list: &JsCallArgumentList) -> SyntaxResult<bool>

/// Checks if the last group requires grouping
fn should_group_last_argument(list: &JsCallArgumentList) -> SyntaxResult<bool> {
let list_len = list.len();
let mut iter = list.iter().rev();
let last = iter.next();
let penultimate = iter.next();
Expand All @@ -270,9 +271,15 @@ fn should_group_last_argument(list: &JsCallArgumentList) -> SyntaxResult<bool> {
let last = last?;
let check_with_penultimate = if let Some(penultimate) = penultimate {
let penultimate = penultimate?;
(last.syntax().kind() != penultimate.syntax().kind())
&& !JsArrayExpression::can_cast(penultimate.syntax().kind())
|| !JsArrowFunctionExpression::can_cast(last.syntax().kind())
let different_kind = last.syntax().kind() != penultimate.syntax().kind();

let no_array_and_arrow_function = list_len != 2
|| !JsArrayExpression::can_cast(penultimate.syntax().kind())
|| !JsArrowFunctionExpression::can_cast(last.syntax().kind());

let _no_poor_printed_array =
!list_len > 1 && JsArrayExpression::can_cast(last.syntax().kind());
different_kind && no_array_and_arrow_function
} else {
true
};
Expand Down
13 changes: 5 additions & 8 deletions crates/rome_js_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,14 +457,11 @@ mod test {
// use this test check if your snippet prints as you wish, without using a snapshot
fn quick_test() {
let src = r#"
export type a =
// foo
| foo1 & foo2
// bar
| bar1 & bar2
// prettier-ignore
| qux1 & qux2;
deepCopyAndAsyncMapLeavesC(
{ source: sourceedeefeffeefefefValue, destination: destination[sourceKey] },
1337,
{ valueMapper, overwriteExistingKeys }
)
"#;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,16 @@ h(
),
);
deepCopyAndAsyncMapLeavesA({
source: sourceValue,
destination: destination[sourceKey],
}, { valueMapper, overwriteExistingKeys });
deepCopyAndAsyncMapLeavesA(
{ source: sourceValue, destination: destination[sourceKey] },
{ valueMapper, overwriteExistingKeys },
);
deepCopyAndAsyncMapLeavesB(1337, {
source: sourceValue,
destination: destination[sourceKey],
}, { valueMapper, overwriteExistingKeys });
deepCopyAndAsyncMapLeavesB(
1337,
{ source: sourceValue, destination: destination[sourceKey] },
{ valueMapper, overwriteExistingKeys },
);
deepCopyAndAsyncMapLeavesC({
source: sourceValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ const Query: FunctionComponent<QueryProps> = (
},
) =>
children(
useQuery({ type, resource, payload }, {
...options,
withDeclarativeSideEffectsSupport: true,
}),
useQuery(
{ type, resource, payload },
{ ...options, withDeclarativeSideEffectsSupport: true },
),
);
```
Expand Down

0 comments on commit c518066

Please sign in to comment.