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): Format Member Chain
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Sep 27, 2022
1 parent 107c9f9 commit 3733287
Show file tree
Hide file tree
Showing 78 changed files with 2,093 additions and 4,619 deletions.
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
99 changes: 62 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 @@ -100,6 +100,38 @@ pub const fn hard_line_break() -> Line {
Line::new(LineMode::Hard)
}

/// A plain line break that does not add any indent.
/// Useful for line breaks in a spacing sensitive context.
///
/// # Examples
///
/// ```
/// use rome_formatter::{format, format_args};
/// use rome_formatter::prelude::*;
///
/// # fn main() -> FormatResult<()> {
/// let block = format!(SimpleFormatContext::default(), [
/// text("a ="),
/// block_indent(&format_args![
/// text("`abcd"),
/// literal_line(),
/// text("...no indent at the beginning`")
/// ]),
/// text(";"),
/// ])?;
///
/// assert_eq!(
/// "a =\n\t`abcd\n...no indent at the beginning`\n;",
/// block.print()?.as_code()
/// );
/// # Ok(())
/// # }
/// ```
#[inline]
pub const fn literal_line() -> Line {
Line::new(LineMode::Literal)
}

/// A forced empty line. An empty line inserts enough line breaks in the output for
/// the previous and next element to be separated by an empty line.
///
Expand Down Expand Up @@ -1332,14 +1364,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 +1378,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 +1458,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 +2139,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
86 changes: 25 additions & 61 deletions crates/rome_formatter/src/format_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,25 @@ 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,20 +65,26 @@ 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::literal_line] for documentation.
Literal,
/// See [crate::builders::empty_line] for documentation.
Empty,
}

impl LineMode {
pub const fn is_hard(&self) -> bool {
matches!(self, LineMode::Hard)
}

pub const fn is_literal(&self) -> bool {
matches!(self, LineMode::Literal)
}
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
Expand Down Expand Up @@ -140,7 +145,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 @@ -202,38 +207,6 @@ impl PartialEq for Text {
}
}

const LINE_SEPARATOR: char = '\u{2028}';
const PARAGRAPH_SEPARATOR: char = '\u{2029}';
pub const LINE_TERMINATORS: [char; 3] = ['\r', LINE_SEPARATOR, PARAGRAPH_SEPARATOR];

/// Replace the line terminators matching the provided list with "\n"
/// since its the only line break type supported by the printer
pub fn normalize_newlines<const N: usize>(text: &str, terminators: [char; N]) -> Cow<str> {
let mut result = String::new();
let mut last_end = 0;

for (start, part) in text.match_indices(terminators) {
result.push_str(&text[last_end..start]);
result.push('\n');

last_end = start + part.len();
// If the current character is \r and the
// next is \n, skip over the entire sequence
if part == "\r" && text[last_end..].starts_with('\n') {
last_end += 1;
}
}

// If the result is empty no line terminators were matched,
// return the entire input text without allocating a new String
if result.is_empty() {
Cow::Borrowed(text)
} else {
result.push_str(&text[last_end..text.len()]);
Cow::Owned(result)
}
}

impl Deref for Text {
type Target = str;
fn deref(&self) -> &Self::Target {
Expand Down Expand Up @@ -274,13 +247,19 @@ impl FormatElements for FormatElement {
fn will_break(&self) -> bool {
match self {
FormatElement::ExpandParent => true,
FormatElement::Line(line_mode) => matches!(line_mode, LineMode::Hard | LineMode::Empty),
FormatElement::Text(text) => text.contains('\n'),
FormatElement::Tag(Tag::StartGroup(group)) => !group.mode().is_flat(),
FormatElement::Line(line_mode) => matches!(
line_mode,
LineMode::Hard | LineMode::Empty | LineMode::Literal
),
FormatElement::Interned(interned) => interned.will_break(),
// 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(_)
| FormatElement::BestFitting(_) => false,
| FormatElement::Text(_) => false,
}
}

Expand Down Expand Up @@ -371,7 +350,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 All @@ -391,21 +370,6 @@ pub trait FormatElements {
fn end_tag(&self, kind: TagKind) -> Option<&Tag>;
}

#[cfg(test)]
mod tests {

use crate::format_element::{normalize_newlines, LINE_TERMINATORS};

#[test]
fn test_normalize_newlines() {
assert_eq!(normalize_newlines("a\nb", LINE_TERMINATORS), "a\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\u{2028}b", LINE_TERMINATORS), "a\nb");
assert_eq!(normalize_newlines("a\u{2029}b", LINE_TERMINATORS), "a\nb");
}
}

#[cfg(target_pointer_width = "64")]
static_assert!(std::mem::size_of::<rome_rowan::TextRange>() == 8usize);

Expand Down
Loading

0 comments on commit 3733287

Please sign in to comment.