Skip to content

Commit

Permalink
feat(prettier): init command of Fill (#1460)
Browse files Browse the repository at this point in the history
  • Loading branch information
mysteryven authored Nov 25, 2023
1 parent 13cac62 commit 924d99e
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 27 deletions.
47 changes: 47 additions & 0 deletions crates/oxc_prettier/src/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ pub enum Doc<'a> {
Hardline,
/// Print something if the current `group` or the current element of `fill` breaks and something else if it doesn't.
IfBreak(Box<'a, Doc<'a>>),
/// This is an alternative type of group which behaves like text layout:
/// it's going to add a break whenever the next element doesn't fit in the line anymore.
/// The difference with `group` is that it's not going to break all the separators, just the ones that are at the end of lines.
Fill(Fill<'a>),
}

#[derive(Debug)]
Expand All @@ -48,6 +52,38 @@ impl<'a> Group<'a> {
}
}

#[derive(Debug)]
pub struct Fill<'a> {
pub parts: Vec<'a, Doc<'a>>,
}

impl<'a> Fill<'a> {
pub fn new(docs: Vec<'a, Doc<'a>>) -> Self {
Self { parts: docs }
}
pub fn drain_out_pair(&mut self) -> (Option<Doc<'a>>, Option<Doc<'a>>) {
let content = if self.parts.len() > 0 { Some(self.parts.remove(0)) } else { None };
let whitespace = if self.parts.len() > 0 { Some(self.parts.remove(0)) } else { None };
(content, whitespace)
}
pub fn dequeue(&mut self) -> Option<Doc<'a>> {
if self.parts.len() > 0 {
Some(self.parts.remove(0))
} else {
None
}
}
pub fn enqueue(&mut self, doc: Doc<'a>) {
self.parts.insert(0, doc);
}
pub fn parts(&self) -> &[Doc<'a>] {
&self.parts
}
pub fn take_parts(self) -> Vec<'a, Doc<'a>> {
self.parts
}
}

#[derive(Clone, Copy)]
#[allow(unused)]
pub enum Separator {
Expand Down Expand Up @@ -164,6 +200,17 @@ fn print_doc_to_debug(doc: &Doc<'_>) -> std::string::String {
string.push_str(&print_doc_to_debug(break_contents));
string.push(')');
}
Doc::Fill(fill) => {
string.push_str("fill([\n");
let parts = fill.parts();
for (idx, doc) in parts.iter().enumerate() {
string.push_str(&print_doc_to_debug(doc));
if idx != parts.len() - 1 {
string.push_str(", ");
}
}
string.push_str("])");
}
}

string
Expand Down
90 changes: 81 additions & 9 deletions crates/oxc_prettier/src/format/array.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use oxc_ast::ast::*;
use oxc_span::Span;
use oxc_syntax::operator::UnaryOperator;

use crate::{
array,
comment::DanglingCommentsPrintOptions,
doc::{Doc, DocBuilder, Group},
group, indent, softline, ss, Prettier,
doc::{Doc, DocBuilder, Fill, Group},
group, if_break, indent, softline, ss, Prettier,
};

use super::Format;
Expand Down Expand Up @@ -35,6 +36,33 @@ impl<'a, 'b> Array<'a, 'b> {
Self::ArrayAssignmentTarget(array) => array.span,
}
}
fn is_concisely_printed(&self) -> bool {
match self {
Self::ArrayExpression(array) => {
if array.elements.len() <= 1 {
return false;
}

return array.elements.iter().all(|element| {
let ArrayExpressionElement::Expression(expr) = element else {
return false;
};

match expr {
Expression::NumberLiteral(_) => true,
Expression::UnaryExpression(unary_expr) => {
matches!(
unary_expr.operator,
UnaryOperator::UnaryPlus | UnaryOperator::UnaryNegation
) && matches!(unary_expr.argument, Expression::NumberLiteral(_))
}
_ => false,
}
});
}
Self::ArrayPattern(_) | Self::ArrayAssignmentTarget(_) | Self::TSTupleType(_) => false,
}
}
}

pub(super) fn print_array<'a>(p: &mut Prettier<'a>, array: &Array<'a, '_>) -> Doc<'a> {
Expand All @@ -54,16 +82,26 @@ pub(super) fn print_array<'a>(p: &mut Prettier<'a>, array: &Array<'a, '_>) -> Do
(false, false)
};

let trailing_comma = if !can_have_trailing_comma {
ss!("")
} else if needs_forced_trailing_comma {
ss!(",")
} else {
ss!("")
let should_use_concise_formatting = array.is_concisely_printed();
let trailing_comma_fn = |p: &Prettier<'a>| {
if !can_have_trailing_comma {
ss!("")
} else if needs_forced_trailing_comma {
ss!(",")
} else if should_use_concise_formatting {
if_break!(p, ",")
} else {
ss!("")
}
};

let mut parts = p.vec();
let elements = array!(p, print_elements(p, array), trailing_comma);
let elements = if should_use_concise_formatting {
print_array_elements_concisely(p, array, trailing_comma_fn)
} else {
let trailing_comma = trailing_comma_fn(p);
array!(p, print_elements(p, array), trailing_comma)
};
let parts_inner = if let Some(dangling_comments) = p.print_dangling_comments(array.span(), None)
{
indent!(p, softline!(), elements, dangling_comments)
Expand Down Expand Up @@ -147,6 +185,40 @@ fn print_elements<'a>(p: &mut Prettier<'a>, array: &Array<'a, '_>) -> Doc<'a> {
Doc::Array(parts)
}

fn print_array_elements_concisely<'a, F>(
p: &mut Prettier<'a>,
array: &Array<'a, '_>,
trailing_comma_fn: F,
) -> Doc<'a>
where
F: Fn(&Prettier<'a>) -> Doc<'a>,
{
let mut parts = p.vec();
match array {
Array::ArrayExpression(array) => {
for (i, element) in array.elements.iter().enumerate() {
let is_last = i == array.elements.len() - 1;
let part = if is_last {
array!(p, element.format(p), trailing_comma_fn(p))
} else {
array!(p, element.format(p), ss!(","))
};
parts.push(part);

if !is_last {
parts.push(Doc::Line);
}
}
}
_ => {
// TODO: implement
array!(p, print_elements(p, array), trailing_comma_fn(p));
}
}

Doc::Fill(Fill::new(parts))
}

fn should_break(array: &Array) -> bool {
if array.len() <= 1 {
return false;
Expand Down
4 changes: 4 additions & 0 deletions crates/oxc_prettier/src/printer/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ impl<'a> Command<'a> {
pub fn new(indent: Indent, mode: Mode, doc: Doc<'a>) -> Self {
Self { indent, mode, doc }
}
pub fn with_mode(mut self, mode: Mode) -> Self {
self.mode = mode;
self
}
}

#[derive(Clone, Debug, Copy, Eq, PartialEq)]
Expand Down
134 changes: 116 additions & 18 deletions crates/oxc_prettier/src/printer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use oxc_allocator::Allocator;
use std::collections::VecDeque;

use crate::{
doc::{Doc, DocBuilder, Group},
doc::{Doc, DocBuilder, Fill},
PrettierOptions,
};

Expand Down Expand Up @@ -92,12 +92,13 @@ impl<'a> Printer<'a> {
Doc::Str(s) => self.handle_str(s),
Doc::Array(docs) => self.handle_array(indent, mode, docs),
Doc::Indent(docs) => self.handle_indent(indent, mode, docs),
Doc::Group(group) => self.handle_group(indent, mode, group),
Doc::Group(_) => self.handle_group(indent, mode, doc),
Doc::IndentIfBreak(docs) => self.handle_indent_if_break(indent, mode, docs),
Doc::Line => self.handle_line(indent, mode),
Doc::Softline => self.handle_softline(indent, mode),
Doc::Hardline => self.handle_hardline(indent),
Doc::IfBreak(doc) => self.handle_if_break(doc.unbox(), indent, mode),
Doc::Fill(fill) => self.handle_fill(indent, mode, fill),
}
}
}
Expand All @@ -119,25 +120,30 @@ impl<'a> Printer<'a> {
);
}

fn handle_group(&mut self, indent: Indent, mode: Mode, group: Group<'a>) {
fn handle_group(&mut self, indent: Indent, mode: Mode, doc: Doc<'a>) {
match mode {
Mode::Flat => {
// TODO: consider supporting `group mode` e.g. Break/Flat
let Doc::Group(group) = doc else {
return;
};
self.cmds.extend(group.contents.into_iter().rev().map(|doc| {
Command::new(
indent,
if group.should_break { Mode::Break } else { Mode::Flat },
doc,
)
Command::new(indent, if group.should_break { Mode::Break } else { mode }, doc)
}));
}
Mode::Break => {
#[allow(clippy::cast_possible_wrap)]
let remaining_width = (self.options.print_width as isize) - (self.pos as isize);

if !group.should_break && self.fits(&group.contents, remaining_width) {
self.cmds.push(Command::new(indent, Mode::Flat, Doc::Group(group)));
let remaining_width = self.remaining_width();
let Doc::Group(group) = &doc else {
return;
};
let should_break = group.should_break;
let cmd = Command::new(indent, Mode::Flat, doc);
if !should_break && self.fits(&cmd, remaining_width) {
self.cmds.push(Command::new(indent, Mode::Flat, cmd.doc));
} else {
let Doc::Group(group) = cmd.doc else {
return;
};
self.cmds.extend(
group
.contents
Expand Down Expand Up @@ -175,6 +181,7 @@ impl<'a> Printer<'a> {
self.handle_hardline(indent);
} else {
self.out.push(b' ');
self.pos += 1;
}
}

Expand All @@ -196,12 +203,93 @@ impl<'a> Printer<'a> {
}
}

#[allow(clippy::cast_possible_wrap)]
fn fits(&self, docs: &oxc_allocator::Vec<'a, Doc<'a>>, remaining_width: isize) -> bool {
let mut remaining_width = remaining_width;
fn handle_fill(&mut self, indent: Indent, mode: Mode, fill: Fill<'a>) {
let mut fill = fill;
let remaining_width = self.remaining_width();
let original_parts_len = fill.parts().len();
let (content, whitespace) = fill.drain_out_pair();

let Some(content) = content else {
return;
};
let content_flat_cmd = Command::new(indent, Mode::Flat, content);
let content_fits = self.fits(&content_flat_cmd, remaining_width);

if original_parts_len == 1 {
if content_fits {
self.cmds.push(content_flat_cmd);
} else {
let content_break_cmd = content_flat_cmd.with_mode(Mode::Break);
self.cmds.push(content_break_cmd);
}
return;
}

let Some(whitespace) = whitespace else {
return;
};
let whitespace_flat_cmd = Command::new(indent, Mode::Flat, whitespace);

if original_parts_len == 2 {
if content_fits {
self.cmds.push(whitespace_flat_cmd);
self.cmds.push(content_flat_cmd);
} else {
let content_break_cmd = content_flat_cmd.with_mode(Mode::Break);
let whitespace_break_cmd = whitespace_flat_cmd.with_mode(Mode::Break);
self.cmds.push(whitespace_break_cmd);
self.cmds.push(content_break_cmd);
}
return;
}

let Some(second_content) = fill.dequeue() else {
return;
};
let mut docs = self.vec();
let content = content_flat_cmd.doc;
docs.push(content);
docs.push(whitespace_flat_cmd.doc);
docs.push(second_content);

// TODO: these should be commands
let mut queue: VecDeque<(Mode, &Doc)> = docs.iter().map(|doc| (Mode::Flat, doc)).collect();
let first_and_second_content_fit_cmd = Command::new(indent, Mode::Flat, Doc::Array(docs));
let first_and_second_content_fits =
self.fits(&first_and_second_content_fit_cmd, remaining_width);
let Doc::Array(mut doc) = first_and_second_content_fit_cmd.doc else {
return;
};
if let Some(second_content) = doc.pop() {
fill.enqueue(second_content);
}

let Some(whitespace) = doc.pop() else {
return;
};
let Some(content) = doc.pop() else {
return;
};

let remaining_cmd = Command::new(indent, mode, Doc::Fill(fill));
let whitespace_flat_cmd = Command::new(indent, Mode::Flat, whitespace);
let content_flat_cmd = Command::new(indent, Mode::Flat, content);

if first_and_second_content_fits {
self.cmds.extend(vec![remaining_cmd, whitespace_flat_cmd, content_flat_cmd]);
} else if content_fits {
let whitespace_break_cmd = whitespace_flat_cmd.with_mode(Mode::Break);
self.cmds.extend(vec![remaining_cmd, whitespace_break_cmd, content_flat_cmd]);
} else {
let content_break_cmd = content_flat_cmd.with_mode(Mode::Break);
let whitespace_break_cmd = whitespace_flat_cmd.with_mode(Mode::Break);
self.cmds.extend(vec![remaining_cmd, whitespace_break_cmd, content_break_cmd]);
};
}

#[allow(clippy::cast_possible_wrap)]
fn fits(&self, next: &Command<'a>, width: isize) -> bool {
let mut remaining_width = width;
let mut queue: VecDeque<(Mode, &Doc)> = VecDeque::new();
queue.push_front((next.mode, &next.doc));
let mut cmds = self.cmds.iter().rev();

while let Some((mode, doc)) = queue.pop_front() {
Expand Down Expand Up @@ -241,6 +329,11 @@ impl<'a> Printer<'a> {
Doc::Hardline => {
return true;
}
Doc::Fill(fill) => {
for part in fill.parts().iter().rev() {
queue.push_front((mode, part));
}
}
}

if remaining_width < 0 {
Expand Down Expand Up @@ -277,4 +370,9 @@ impl<'a> Printer<'a> {
}
}
}

#[allow(clippy::cast_possible_wrap)]
fn remaining_width(&self) -> isize {
(self.options.print_width as isize) - (self.pos as isize)
}
}

0 comments on commit 924d99e

Please sign in to comment.