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

feat(rome_js_formatter): Member chain formatting #3283

Merged
merged 5 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion crates/rome_formatter/src/arguments.rs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Loading