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): Member chain formatting
Browse files Browse the repository at this point in the history
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
  • Loading branch information
MichaReiser and ematipico committed Sep 29, 2022
1 parent 6ad6ea9 commit a38f2ef
Show file tree
Hide file tree
Showing 74 changed files with 1,787 additions and 4,512 deletions.
2 changes: 1 addition & 1 deletion crates/rome_formatter/src/arguments.rs
Expand Up @@ -150,7 +150,7 @@ mod tests {
FormatElement::Text(Text::Static { text: "a" }),
FormatElement::Space,
// Group
FormatElement::Tag(Tag::StartGroup(None)),
FormatElement::Tag(Tag::StartGroup(tag::Group::new())),
FormatElement::Text(Text::Static { text: "(" }),
FormatElement::Text(Text::Static { text: ")" }),
FormatElement::Tag(Tag::EndGroup)
Expand Down
67 changes: 30 additions & 37 deletions crates/rome_formatter/src/builders.rs
@@ -1,5 +1,5 @@
use crate::format_element::tag::{Condition, Tag};
use crate::prelude::tag::{DedentMode, LabelId};
use crate::prelude::tag::{DedentMode, GroupMode, LabelId};
use crate::prelude::*;
use crate::{format_element, write, Argument, Arguments, GroupId, TextRange, TextSize};
use crate::{Buffer, VecBuffer};
Expand Down Expand Up @@ -1332,14 +1332,12 @@ impl<Context> Group<'_, Context> {
self
}

/// Setting the value to `true` forces the group and its enclosing group to expand regardless if it otherwise would fit on the
/// line or contains any hard line breaks.
/// Changes the [PrintMode] of the group from [`Flat`](PrintMode::Flat) to [`Expanded`](PrintMode::Expanded).
/// The result is that any soft-line break gets printed as a regular line break.
///
/// The formatter writes a [FormatElement::ExpandParent], forcing any enclosing group to expand, if `should_expand` is `true`.
/// It also omits the [`start`](Tag::StartGroup) and [`end`](Tag::EndGroup) tags because the group would be forced to expand anyway.
/// The [`start`](Tag::StartGroup) and [`end`](Tag::EndGroup) tags are only written if the `group_id` specified with [Group::with_group_id] isn't [None]
/// because other IR elements may reference the group with that group id and the printer may panic
/// if no group with the given id is present in the document.
/// This is useful for content rendered inside of a [FormatElement::BestFitting] that prints each variant
/// in [PrintMode::Flat] to change some content to be printed in [`Expanded`](PrintMode::Expanded) regardless.
/// See the documentation of the [`best_fitting`] macro for an example.
pub fn should_expand(mut self, should_expand: bool) -> Self {
self.should_expand = should_expand;
self
Expand All @@ -1348,28 +1346,18 @@ impl<Context> Group<'_, Context> {

impl<Context> Format<Context> for Group<'_, Context> {
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
if self.group_id.is_none() && self.should_expand {
write!(f, [expand_parent()])?;
return f.write_fmt(Arguments::from(&self.content));
}

let write_group = !self.should_expand || self.group_id.is_some();

if write_group {
f.write_element(FormatElement::Tag(Tag::StartGroup(self.group_id)))?;
}
let mode = match self.should_expand {
true => GroupMode::Expand,
false => GroupMode::Flat,
};

if self.should_expand {
write!(f, [expand_parent()])?;
}
f.write_element(FormatElement::Tag(StartGroup(
tag::Group::new().with_id(self.group_id).with_mode(mode),
)))?;

Arguments::from(&self.content).fmt(f)?;

if write_group {
f.write_element(FormatElement::Tag(Tag::EndGroup))?;
}

Ok(())
f.write_element(FormatElement::Tag(EndGroup))
}
}

Expand Down Expand Up @@ -1438,7 +1426,7 @@ impl<Context> Format<Context> for ExpandParent {
///
/// The element has no special meaning if used outside of a `Group`. In that case, the content is always emitted.
///
/// If you're looking for a way to only print something if the `Group` fits on a single line see [crate::if_group_fits_on_line].
/// If you're looking for a way to only print something if the `Group` fits on a single line see [self::if_group_fits_on_line].
///
/// # Examples
///
Expand Down Expand Up @@ -2119,21 +2107,26 @@ where
/// Get the number of line breaks between two consecutive SyntaxNodes in the tree
pub fn get_lines_before<L: Language>(next_node: &SyntaxNode<L>) -> usize {
// Count the newlines in the leading trivia of the next node
if let Some(leading_trivia) = next_node.first_leading_trivia() {
leading_trivia
.pieces()
.take_while(|piece| {
// Stop at the first comment or skipped piece, the comment printer
// will handle newlines between the comment and the node
!(piece.is_comments() || piece.is_skipped())
})
.filter(|piece| piece.is_newline())
.count()
if let Some(token) = next_node.first_token() {
get_lines_before_token(&token)
} else {
0
}
}

pub fn get_lines_before_token<L: Language>(token: &SyntaxToken<L>) -> usize {
token
.leading_trivia()
.pieces()
.take_while(|piece| {
// Stop at the first comment or skipped piece, the comment printer
// will handle newlines between the comment and the node
!(piece.is_comments() || piece.is_skipped())
})
.filter(|piece| piece.is_newline())
.count()
}

/// Builder to fill as many elements as possible on a single line.
#[must_use = "must eventually call `finish()` on Format builders"]
pub struct FillBuilder<'fmt, 'buf, Context> {
Expand Down
38 changes: 23 additions & 15 deletions crates/rome_formatter/src/format_element.rs
Expand Up @@ -2,31 +2,31 @@ pub mod document;
pub mod tag;

use crate::format_element::tag::{LabelId, Tag};
use std::borrow::Cow;

use crate::{TagKind, TextSize};
#[cfg(target_pointer_width = "64")]
use rome_rowan::static_assert;
use rome_rowan::SyntaxTokenText;
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::rc::Rc;

/// Language agnostic IR for formatting source code.
///
/// Use the helper functions like [crate::space], [crate::soft_line_break] etc. defined in this file to create elements.
/// Use the helper functions like [crate::builders::space], [crate::builders::soft_line_break] etc. defined in this file to create elements.
#[derive(Clone, Eq, PartialEq)]
pub enum FormatElement {
/// A space token, see [crate::space] for documentation.
/// A space token, see [crate::builders::space] for documentation.
Space,

/// A new line, see [crate::soft_line_break], [crate::hard_line_break], and [crate::soft_line_break_or_space] for documentation.
/// A new line, see [crate::builders::soft_line_break], [crate::builders::hard_line_break], and [crate::builders::soft_line_break_or_space] for documentation.
Line(LineMode),

/// Forces the parent group to print in expanded mode.
ExpandParent,

/// A text that should be printed as is, see [crate::text] for documentation and examples.
/// A text that should be printed as is, see [crate::builders::text] for documentation and examples.
Text(Text),

/// Prevents that line suffixes move past this boundary. Forces the printer to print any pending
Expand Down Expand Up @@ -66,13 +66,13 @@ impl std::fmt::Debug for FormatElement {

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum LineMode {
/// See [crate::soft_line_break_or_space] for documentation.
/// See [crate::builders::soft_line_break_or_space] for documentation.
SoftOrSpace,
/// See [crate::soft_line_break] for documentation.
/// See [crate::builders::soft_line_break] for documentation.
Soft,
/// See [crate::hard_line_break] for documentation.
/// See [crate::builders::hard_line_break] for documentation.
Hard,
/// See [crate::empty_line] for documentation.
/// See [crate::builders::empty_line] for documentation.
Empty,
}

Expand Down Expand Up @@ -140,7 +140,7 @@ impl Deref for Interned {
}
}

/// See [crate::text] for documentation
/// See [crate::builders::text] for documentation
#[derive(Eq, Clone)]
pub enum Text {
/// Token constructed by the formatter from a static string
Expand Down Expand Up @@ -274,13 +274,16 @@ impl FormatElements for FormatElement {
fn will_break(&self) -> bool {
match self {
FormatElement::ExpandParent => true,
FormatElement::Tag(Tag::StartGroup(group)) => !group.mode().is_flat(),
FormatElement::Line(line_mode) => matches!(line_mode, LineMode::Hard | LineMode::Empty),
FormatElement::Text(text) => text.contains('\n'),
FormatElement::Interned(interned) => interned.will_break(),
FormatElement::LineSuffixBoundary
| FormatElement::Space
| FormatElement::Tag(_)
| FormatElement::BestFitting(_) => false,
// Traverse into the most flat version because the content is guaranteed to expand when even
// the most flat version contains some content that forces a break.
FormatElement::BestFitting(best_fitting) => best_fitting.most_flat().will_break(),
FormatElement::LineSuffixBoundary | FormatElement::Space | FormatElement::Tag(_) => {
false
}
}
}

Expand Down Expand Up @@ -371,7 +374,7 @@ impl std::fmt::Debug for BestFitting {
pub trait FormatElements {
/// Returns true if this [FormatElement] is guaranteed to break across multiple lines by the printer.
/// This is the case if this format element recursively contains a:
/// * [crate::empty_line] or [crate::hard_line_break]
/// * [crate::builders::empty_line] or [crate::builders::hard_line_break]
/// * A token containing '\n'
///
/// Use this with caution, this is only a heuristic and the printer may print the element over multiple
Expand Down Expand Up @@ -399,8 +402,13 @@ mod tests {
#[test]
fn test_normalize_newlines() {
assert_eq!(normalize_newlines("a\nb", LINE_TERMINATORS), "a\nb");
assert_eq!(normalize_newlines("a\n\n\nb", LINE_TERMINATORS), "a\n\n\nb");
assert_eq!(normalize_newlines("a\rb", LINE_TERMINATORS), "a\nb");
assert_eq!(normalize_newlines("a\r\nb", LINE_TERMINATORS), "a\nb");
assert_eq!(
normalize_newlines("a\r\n\r\n\r\nb", LINE_TERMINATORS),
"a\n\n\nb"
);
assert_eq!(normalize_newlines("a\u{2028}b", LINE_TERMINATORS), "a\nb");
assert_eq!(normalize_newlines("a\u{2029}b", LINE_TERMINATORS), "a\nb");
}
Expand Down
105 changes: 99 additions & 6 deletions crates/rome_formatter/src/format_element/document.rs
@@ -1,5 +1,6 @@
use super::tag::Tag;
use crate::format_element::tag::DedentMode;
use crate::prelude::tag::GroupMode;
use crate::prelude::*;
use crate::printer::LineEnding;
use crate::{format, write};
Expand All @@ -8,6 +9,7 @@ use crate::{
IndentStyle, LineWidth, PrinterOptions, TransformSourceMap,
};
use rome_rowan::TextSize;
use rustc_hash::FxHashMap;
use std::collections::HashMap;
use std::ops::Deref;

Expand All @@ -17,6 +19,87 @@ pub struct Document {
elements: Vec<FormatElement>,
}

impl Document {
/// Sets [`expand`](tag::Group::expand) to [`GroupMode::Propagated`] if the group contains any of:
/// * a group with [`expand`](tag::Group::expand) set to [GroupMode::Propagated] or [GroupMode::Expand].
/// * a non-soft [line break](FormatElement::Line) with mode [LineMode::Hard], [LineMode::Empty], or [LineMode::Literal].
/// * a [FormatElement::ExpandParent]
///
/// [`BestFitting`] elements act as expand boundaries, meaning that the fact that a
/// [`BestFitting`]'s content expands is not propagated past the [`BestFitting`] element.
///
/// [`BestFitting`]: FormatElement::BestFitting
pub(crate) fn propagate_expand(&mut self) {
#[derive(Debug)]
enum Enclosing<'a> {
Group(&'a tag::Group),
BestFitting,
}

fn expand_parent(enclosing: &[Enclosing]) {
if let Some(Enclosing::Group(group)) = enclosing.last() {
group.propagate_expand();
}
}

fn propagate_expands<'a>(
elements: &'a [FormatElement],
enclosing: &mut Vec<Enclosing<'a>>,
checked_interned: &mut FxHashMap<&'a Interned, bool>,
) -> bool {
let mut expands = false;
for element in elements {
let element_expands = match element {
FormatElement::Tag(Tag::StartGroup(group)) => {
enclosing.push(Enclosing::Group(group));
false
}
FormatElement::Tag(Tag::EndGroup) => match enclosing.pop() {
Some(Enclosing::Group(group)) => !group.mode().is_flat(),
_ => false,
},
FormatElement::Interned(interned) => match checked_interned.get(interned) {
Some(interned_expands) => *interned_expands,
None => {
let interned_expands =
propagate_expands(interned, enclosing, checked_interned);
checked_interned.insert(interned, interned_expands);
interned_expands
}
},
FormatElement::BestFitting(best_fitting) => {
enclosing.push(Enclosing::BestFitting);

for variant in best_fitting.variants() {
propagate_expands(variant, enclosing, checked_interned);
}

// Best fitting acts as a boundary
expands = false;
enclosing.pop();
continue;
}
FormatElement::Text(text) => text.contains('\n'),
FormatElement::ExpandParent
| FormatElement::Line(LineMode::Hard | LineMode::Empty) => true,
_ => false,
};

if element_expands {
expands = true;
expand_parent(enclosing)
}
}

expands
}

let mut enclosing: Vec<Enclosing> = Vec::new();
let mut interned: FxHashMap<&Interned, bool> = FxHashMap::default();
propagate_expands(self, &mut enclosing, &mut interned);
}
}

impl From<Vec<FormatElement>> for Document {
fn from(elements: Vec<FormatElement>) -> Self {
Self { elements }
Expand Down Expand Up @@ -292,10 +375,10 @@ impl Format<IrFormatContext> for &[FormatElement] {
write!(f, [text("verbatim(")])?;
}

StartGroup(id) => {
StartGroup(group) => {
write!(f, [text("group(")])?;

if let Some(group_id) = id {
if let Some(group_id) = group.id() {
write!(
f,
[
Expand All @@ -308,6 +391,16 @@ impl Format<IrFormatContext> for &[FormatElement] {
]
)?;
}

match group.mode() {
GroupMode::Flat => {}
GroupMode::Expand => {
write!(f, [text("expand: true,"), space()])?;
}
GroupMode::Propagated => {
write!(f, [text("expand: propagated,"), space()])?;
}
}
}

StartIndentIfGroupBreaks(id) => {
Expand Down Expand Up @@ -351,12 +444,12 @@ impl Format<IrFormatContext> for &[FormatElement] {
write!(
f,
[
text("label(\""),
text("label("),
dynamic_text(
&std::format!("\"{label_id:?}\""),
TextSize::default()
),
text("\","),
text(","),
space(),
]
)?;
Expand Down Expand Up @@ -420,7 +513,7 @@ impl Format<IrFormatContext> for ContentArrayStart {
write!(f, [text("[")])?;

f.write_elements([
FormatElement::Tag(StartGroup(None)),
FormatElement::Tag(StartGroup(tag::Group::new())),
FormatElement::Tag(StartIndent),
FormatElement::Line(LineMode::Soft),
])
Expand Down Expand Up @@ -585,7 +678,7 @@ mod tests {

let document = Document::from(vec![
FormatElement::Text(Text::Static { text: "[" }),
FormatElement::Tag(StartGroup(None)),
FormatElement::Tag(StartGroup(tag::Group::new())),
FormatElement::Tag(StartIndent),
FormatElement::Line(LineMode::Soft),
FormatElement::Text(Text::Static { text: "a" }),
Expand Down

0 comments on commit a38f2ef

Please sign in to comment.