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

Commit

Permalink
feat(rome_js_formatter): Call arguments formatting (#3290)
Browse files Browse the repository at this point in the history
Co-authored-by: Emanuele Stoppa <[email protected]>
  • Loading branch information
MichaReiser and ematipico authored Sep 30, 2022
1 parent 34530e7 commit 407dbbc
Show file tree
Hide file tree
Showing 90 changed files with 2,270 additions and 6,770 deletions.
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

0 comments on commit 407dbbc

Please sign in to comment.