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

feat(rome_js_formatter): Call arguments formatting #3290

Merged
merged 25 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d0875cb
feat(rome_js_formatter): Test call formatting
MichaReiser Sep 28, 2022
68da367
feat(rome_js_formatter): Curried calls
MichaReiser Sep 28, 2022
54ad293
feat(rome_js_formatter): In the making, first/last call args
MichaReiser Sep 28, 2022
77dd302
Remove soft lines & new Format Error
Sep 29, 2022
bf405d9
Finish call args
MichaReiser Sep 29, 2022
dae9650
Add caching of function bodies
MichaReiser Sep 29, 2022
1acbf73
Factor out `FormatGroupedCallArguments`
MichaReiser Sep 30, 2022
1f581aa
Improve caching
MichaReiser Sep 30, 2022
8cee5d6
Clippy
MichaReiser Sep 30, 2022
609d357
Format JS Files
MichaReiser Sep 30, 2022
ea3c1c9
Document `RemoveSoftLineBreaks` buffer and use it for template elemen…
MichaReiser Sep 30, 2022
285e80e
Remove `rustc-hash` dependency again
MichaReiser Sep 30, 2022
5e5a3da
Restore lib.rs
MichaReiser Sep 30, 2022
f02e28b
Restore separated.rs
MichaReiser Sep 30, 2022
d0d4a7b
Pre-review cleanups
MichaReiser Sep 30, 2022
8ce0b12
Fix member chain grouping
MichaReiser Sep 30, 2022
fa262b0
Update crates/rome_formatter/src/buffer.rs
MichaReiser Sep 30, 2022
d415e31
Update crates/rome_formatter/src/lib.rs
MichaReiser Sep 30, 2022
c670901
Update crates/rome_js_formatter/src/context.rs
MichaReiser Sep 30, 2022
5d1f947
Update crates/rome_js_formatter/src/context.rs
MichaReiser Sep 30, 2022
21d4726
Update crates/rome_js_formatter/src/js/expressions/call_arguments.rs
MichaReiser Sep 30, 2022
0f4bfe9
Small formatting improvements
MichaReiser Sep 30, 2022
a5d1144
Extract test call
MichaReiser Sep 30, 2022
a5327f0
Code review feedback
MichaReiser Sep 30, 2022
0949f6d
Fix release build
MichaReiser Sep 30, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/rome_formatter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ cfg-if = "1.0.0"
schemars = { version = "0.8.10", optional = true }
rustc-hash = "1.1.0"
countme = "3.0.1"
indexmap = "1.9.1"

[dev-dependencies]
rome_js_parser = { path = "../rome_js_parser"}
Expand Down
174 changes: 174 additions & 0 deletions crates/rome_formatter/src/buffer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use super::{write, Arguments, FormatElement};
use crate::format_element::Interned;
use crate::prelude::LineMode;
use crate::{Format, FormatResult, FormatState};
use rustc_hash::FxHashMap;
use std::any::{Any, TypeId};
use std::fmt::Debug;
use std::ops::{Deref, DerefMut};
Expand Down Expand Up @@ -428,6 +431,177 @@ where
}
}

/// A Buffer that removes any soft line breaks.
///
/// * Removes [`lines`](FormatElement::Line) with the mode [`Soft`](LineMode::Soft).
/// * Replaces [`lines`](FormatElement::Line) with the mode [`Soft`](LineMode::SoftOrSpace) with a [`Space`](FormatElement::Space)
///
/// # Examples
///
/// ```
/// use rome_formatter::prelude::*;
/// use rome_formatter::{format, write};
///
/// # fn main() -> FormatResult<()> {
/// use rome_formatter::{RemoveSoftLinesBuffer, SimpleFormatContext, VecBuffer};
/// use rome_formatter::prelude::format_with;
/// let formatted = format!(
/// SimpleFormatContext::default(),
/// [format_with(|f| {
/// let mut buffer = RemoveSoftLinesBuffer::new(f);
///
/// write!(
/// buffer,
/// [
/// text("The next soft line or space gets replaced by a space"),
/// soft_line_break_or_space(),
/// text("and the line here"),
/// soft_line_break(),
/// text("is removed entirely.")
/// ]
/// )
/// })]
/// )?;
///
/// assert_eq!(
/// formatted.document().as_ref(),
/// &[
/// FormatElement::Text(Text::Static { text: "The next soft line or space gets replaced by a space" }),
/// FormatElement::Space,
/// FormatElement::Text(Text::Static { text: "and the line here" }),
/// FormatElement::Text(Text::Static { text: "is removed entirely." })
/// ]
/// );
///
/// # Ok(())
/// # }
/// ```
pub struct RemoveSoftLinesBuffer<'a, Context> {
inner: &'a mut dyn Buffer<Context = Context>,

/// Caches the interned elements after the soft line breaks have been removed.
///
/// The `key` is the [Interned] element as it has been passed to [Self::write_element] or the child of another
/// [Interned] element. The `value` is the matching document of the key where all soft line breaks have been removed.
///
/// It's fine to not snapshot the cache. The worst that can happen is that it holds on interned elements
/// that are now unused. But there's little harm in that and the cache is cleaned when dropping the buffer.
interned_cache: FxHashMap<Interned, Interned>,
}

impl<'a, Context> RemoveSoftLinesBuffer<'a, Context> {
/// Creates a new buffer that removes the soft line breaks before writing them into `buffer`.
pub fn new(inner: &'a mut dyn Buffer<Context = Context>) -> Self {
Self {
inner,
interned_cache: FxHashMap::default(),
}
}

/// Removes the soft line breaks from an interned element.
fn clean_interned(&mut self, interned: &Interned) -> Interned {
clean_interned(interned, &mut self.interned_cache)
}
}

// Extracted to function to avoid monomorphization
fn clean_interned(
interned: &Interned,
interned_cache: &mut FxHashMap<Interned, Interned>,
) -> Interned {
match interned_cache.get(interned) {
Some(cleaned) => cleaned.clone(),
None => {
// Find the first soft line break element or interned element that must be changed
let result = interned
.iter()
.enumerate()
.find_map(|(index, element)| match element {
FormatElement::Line(LineMode::Soft | LineMode::SoftOrSpace) => {
let mut cleaned = Vec::new();
cleaned.extend_from_slice(&interned[..index]);
Some((cleaned, &interned[index..]))
}
FormatElement::Interned(inner) => {
let cleaned_inner = clean_interned(inner, interned_cache);

if &cleaned_inner != inner {
let mut cleaned = Vec::with_capacity(interned.len());
cleaned.extend_from_slice(&interned[..index]);
cleaned.push(FormatElement::Interned(cleaned_inner));
Some((cleaned, &interned[index + 1..]))
} else {
None
}
}

_ => None,
});

let result = match result {
// Copy the whole interned buffer so that becomes possible to change the necessary elements.
Some((mut cleaned, rest)) => {
for element in rest {
let element = match element {
FormatElement::Line(LineMode::Soft) => continue,
FormatElement::Line(LineMode::SoftOrSpace) => FormatElement::Space,
FormatElement::Interned(interned) => {
FormatElement::Interned(clean_interned(interned, interned_cache))
}
element => element.clone(),
};
cleaned.push(element)
}

Interned::new(cleaned)
}
// No change necessary, return existing interned element
None => interned.clone(),
};

interned_cache.insert(interned.clone(), result.clone());
result
}
}
}

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

fn write_element(&mut self, element: FormatElement) -> FormatResult<()> {
let element = match element {
FormatElement::Line(LineMode::Soft) => return Ok(()),
FormatElement::Line(LineMode::SoftOrSpace) => FormatElement::Space,
FormatElement::Interned(interned) => {
FormatElement::Interned(self.clean_interned(&interned))
}
element => element,
};

self.inner.write_element(element)
}

fn elements(&self) -> &[FormatElement] {
self.inner.elements()
}

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 {
self.inner.snapshot()
}

fn restore_snapshot(&mut self, snapshot: BufferSnapshot) {
self.inner.restore_snapshot(snapshot)
}
}

pub trait BufferExtensions: Buffer + Sized {
/// Returns a new buffer that calls the passed inspector for every element that gets written to the output
#[must_use]
Expand Down
2 changes: 1 addition & 1 deletion crates/rome_formatter/src/format_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ impl BestFitting {
/// ## Safety
/// The slice must contain at least two variants.
#[doc(hidden)]
pub(crate) unsafe fn from_vec_unchecked(variants: Vec<Box<[FormatElement]>>) -> Self {
pub unsafe fn from_vec_unchecked(variants: Vec<Box<[FormatElement]>>) -> Self {
debug_assert!(
variants.len() >= 2,
"Requires at least the least expanded and most expanded variants"
Expand Down
46 changes: 42 additions & 4 deletions crates/rome_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ use crate::format_element::document::Document;
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, PreambleBuffer, RemoveSoftLinesBuffer,
VecBuffer,
};
pub use builders::BestFitting;

use crate::builders::syntax_token_cow_slice;
Expand Down Expand Up @@ -565,6 +568,14 @@ pub enum FormatError {

/// In case printing the document failed because it has an invalid structure.
InvalidDocument(InvalidDocumentError),

/// Formatting failed because some content encountered a situation where a layout
/// choice by an enclosing [`Format`] resulted in a poor layout for a child [`Format`].
///
/// It's up to an enclosing [`Format`] to handle the error and pick another layout.
/// This error should not be raised if there's no outer [`Format`] handling the poor layout error,
/// avoiding that formatting of the whole document fails.
PoorLayout,
}

impl std::fmt::Display for FormatError {
Expand All @@ -576,6 +587,9 @@ impl std::fmt::Display for FormatError {
"formatting range {input:?} is larger than syntax tree {tree:?}"
),
FormatError::InvalidDocument(error) => std::write!(fmt, "Invalid document: {error}\n\n This is an internal Rome error. Please report if necessary."),
FormatError::PoorLayout => {
std::write!(fmt, "Poor layout: The formatter wasn't able to pick a good layout for your document. This is an internal Rome error. Please report if necessary.")
}
}
}
}
Expand Down Expand Up @@ -1479,6 +1493,30 @@ impl<Context> FormatState<Context> {
}
}

#[cfg(not(debug_assertions))]
#[inline]
pub fn set_token_tracking_disabled(&mut self, _: bool) {}

/// Disables or enables token tracking for a portion of the code.
///
/// It can be useful to disable the token tracking when it is necessary to re-format a node with different parameters.
#[cfg(debug_assertions)]
pub fn set_token_tracking_disabled(&mut self, enabled: bool) {
self.printed_tokens.set_disabled(enabled)
}

#[cfg(not(debug_assertions))]
#[inline]
pub fn is_token_tracking_disabled(&self) -> bool {
false
}

/// Returns `true` if token tracking is currently disabled.
#[cfg(debug_assertions)]
pub fn is_token_tracking_disabled(&self) -> bool {
self.printed_tokens.is_disabled()
}

/// Asserts in debug builds that all tokens have been printed.
#[inline]
pub fn assert_formatted_all_tokens<L: Language>(
Expand All @@ -1500,7 +1538,7 @@ where
pub fn snapshot(&self) -> FormatStateSnapshot {
FormatStateSnapshot {
#[cfg(debug_assertions)]
printed_tokens: self.printed_tokens.clone(),
printed_tokens: self.printed_tokens.snapshot(),
}
}

Expand All @@ -1512,13 +1550,13 @@ where

cfg_if::cfg_if! {
if #[cfg(debug_assertions)] {
self.printed_tokens = printed_tokens;
self.printed_tokens.restore(printed_tokens);
}
}
}
}

pub struct FormatStateSnapshot {
#[cfg(debug_assertions)]
printed_tokens: PrintedTokens,
printed_tokens: printed_tokens::PrintedTokensSnapshot,
}
Loading