Skip to content

Commit

Permalink
feat(rome_json_formatter): JSON Formatting object rome#2570
Browse files Browse the repository at this point in the history
  • Loading branch information
denbezrukov committed Dec 12, 2022
1 parent 7ba67ce commit 581b823
Show file tree
Hide file tree
Showing 28 changed files with 456 additions and 179 deletions.
1 change: 1 addition & 0 deletions crates/rome_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub mod printed_tokens;
pub mod printer;
mod source_map;
pub mod trivia;
pub mod utils;
mod verbatim;

use crate::formatter::Formatter;
Expand Down
13 changes: 13 additions & 0 deletions crates/rome_formatter/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use rome_rowan::{Language, SyntaxNode};

/// Returns true if this node contains newlines in trivias.
pub fn node_has_leading_newline<L: Language>(node: &SyntaxNode<L>) -> bool {
if let Some(leading_trivia) = node.first_leading_trivia() {
for piece in leading_trivia.pieces() {
if piece.is_newline() {
return true;
}
}
}
false
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::prelude::*;
use crate::utils::{node_has_leading_newline, FormatStatementSemicolon};
use crate::utils::FormatStatementSemicolon;
use rome_formatter::utils::node_has_leading_newline;
use rome_formatter::write;

use rome_js_syntax::JsExportNamedFromClause;
Expand Down
14 changes: 1 addition & 13 deletions crates/rome_js_formatter/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ pub(crate) use conditional::{AnyJsConditional, ConditionalJsxChain};
pub(crate) use object_like::JsObjectLike;
pub(crate) use object_pattern_like::JsObjectPatternLike;
use rome_formatter::{format_args, write, Buffer};
use rome_js_syntax::JsSyntaxToken;
use rome_js_syntax::{
AnyJsExpression, AnyJsStatement, JsCallExpression, JsInitializerClause, JsLanguage, Modifiers,
};
use rome_js_syntax::{JsSyntaxNode, JsSyntaxToken};
use rome_rowan::{AstNode, AstNodeList};
pub(crate) use string_utils::*;
pub(crate) use typescript::{
Expand Down Expand Up @@ -142,18 +142,6 @@ impl Format<JsFormatContext> for FormatInterpreterToken<'_> {
}
}

/// Returns true if this node contains newlines in trivias.
pub(crate) fn node_has_leading_newline(node: &JsSyntaxNode) -> bool {
if let Some(leading_trivia) = node.first_leading_trivia() {
for piece in leading_trivia.pieces() {
if piece.is_newline() {
return true;
}
}
}
false
}

/// Formats the body of a statement where it can either be a single statement, an empty statement,
/// or a block statement.
pub(crate) struct FormatStatementBody<'a> {
Expand Down
2 changes: 1 addition & 1 deletion crates/rome_js_formatter/src/utils/object_like.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::prelude::*;
use crate::utils::node_has_leading_newline;
use crate::JsFormatContext;
use rome_formatter::utils::node_has_leading_newline;
use rome_formatter::write;
use rome_formatter::{Format, FormatResult};
use rome_js_syntax::{JsObjectExpression, JsSyntaxToken, TsObjectType};
Expand Down
16 changes: 14 additions & 2 deletions crates/rome_json_formatter/src/json/auxiliary/member.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
use crate::prelude::*;
use rome_formatter::write;
use rome_json_syntax::JsonMember;
use rome_rowan::AstNode;

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatJsonMember;

impl FormatNodeRule<JsonMember> for FormatJsonMember {
fn fmt_fields(&self, node: &JsonMember, f: &mut JsonFormatter) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
let content = format_with(move |f| {
write!(
f,
[
group(&node.name().format()),
node.colon_token().format(),
space(),
node.value().format()
]
)
});

write!(f, [group(&content)])
}
}
10 changes: 9 additions & 1 deletion crates/rome_json_formatter/src/json/lists/member_list.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
use crate::prelude::*;
use crate::separated::FormatAstSeparatedListExtension;
use rome_json_syntax::JsonMemberList;
use rome_rowan::{AstNode, AstSeparatedList};

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatJsonMemberList;

impl FormatRule<JsonMemberList> for FormatJsonMemberList {
type Context = JsonFormatContext;
fn fmt(&self, node: &JsonMemberList, f: &mut JsonFormatter) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
let mut join = f.join_nodes_with_soft_line();

for (element, formatted) in node.elements().zip(node.format_separated(",")) {
join.entry(element.node()?.syntax(), &formatted);
}

join.finish()
}
}
16 changes: 15 additions & 1 deletion crates/rome_json_formatter/src/json/value/object_value.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::prelude::*;
use rome_formatter::utils::node_has_leading_newline;
use rome_formatter::write;
use rome_json_syntax::JsonObjectValue;
use rome_rowan::AstNode;

Expand All @@ -7,6 +9,18 @@ pub(crate) struct FormatJsonObjectValue;

impl FormatNodeRule<JsonObjectValue> for FormatJsonObjectValue {
fn fmt_fields(&self, node: &JsonObjectValue, f: &mut JsonFormatter) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
write!(f, [node.l_curly_token().format(),])?;

let should_expand = node_has_leading_newline(node.json_member_list().syntax());

write!(
f,
[group(&soft_space_or_block_indent(
&node.json_member_list().format()
))
.should_expand(should_expand)]
)?;

write!(f, [node.r_curly_token().format()])
}
}
18 changes: 4 additions & 14 deletions crates/rome_json_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod cst;
mod generated;
mod json;
mod prelude;
mod separated;

pub(crate) use crate::context::JsonFormatContext;
use crate::context::JsonFormatOptions;
Expand Down Expand Up @@ -170,21 +171,10 @@ mod tests {
"#;
let parse = parse_json(src, FileId::zero());
let options = JsonFormatOptions::default();
let result = format_node(options, &parse.syntax())
.unwrap()
.print()
.unwrap();

let formatted = format_node(options, &parse.syntax()).unwrap();
assert_eq!(
result.as_code(),
r#"{
"a": 5,
"b": [1, 2, 3, 4],
"c": null,
"d": true,
"e": false
}
"#
formatted.print().unwrap().as_code(),
"{\n\t\"a\": 5,\n\t\"b\": [1, 2, 3, 4],\n\t\"c\": null,\n\t\"d\": true,\n\t\"e\": false\n}\n"
);
}
}
159 changes: 159 additions & 0 deletions crates/rome_json_formatter/src/separated.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
use crate::prelude::*;
use crate::AsFormat;
use rome_formatter::{write, GroupId};
use rome_json_syntax::JsonLanguage;
use rome_rowan::{
AstNode, AstSeparatedElement, AstSeparatedList, AstSeparatedListElementsIterator, Language,
SyntaxResult,
};
use std::iter::FusedIterator;

/// Formats a single element inside of a separated list.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct FormatSeparatedElement<L: Language, N> {
element: AstSeparatedElement<L, N>,
is_last: bool,
/// The separator to write if the element has no separator yet.
separator: &'static str,
options: FormatSeparatedOptions,
}

impl<L: Language, N: AstNode<Language = L>> FormatSeparatedElement<L, N> {
/// Returns the node belonging to the element.
#[allow(unused)]
pub fn node(&self) -> SyntaxResult<&N> {
self.element.node()
}
}

impl<N> Format<JsonFormatContext> for FormatSeparatedElement<JsonLanguage, N>
where
N: AstNode<Language = JsonLanguage> + AsFormat<JsonFormatContext>,
{
fn fmt(&self, f: &mut Formatter<JsonFormatContext>) -> FormatResult<()> {
let node = self.element.node()?;
let separator = self.element.trailing_separator()?;

if !self.options.nodes_grouped {
node.format().fmt(f)?;
} else {
group(&node.format()).fmt(f)?;
}

// Reuse the existing trailing separator or create it if it wasn't in the
// input source. Only print the last trailing token if the outer group breaks
if let Some(separator) = separator {
if self.is_last {
return Err(FormatError::SyntaxError);
} else {
write!(f, [separator.format()])?;
}
} else if self.is_last {
/* no op */
} else {
unreachable!(
"This is a syntax error, separator must be present between every two elements"
);
};

Ok(())
}
}

/// Iterator for formatting separated elements. Prints the separator between each element and
/// inserts a trailing separator if necessary
pub struct FormatSeparatedIter<I, Language, Node>
where
Language: rome_rowan::Language,
{
next: Option<AstSeparatedElement<Language, Node>>,
inner: I,
separator: &'static str,
options: FormatSeparatedOptions,
}

impl<I, L, Node> FormatSeparatedIter<I, L, Node>
where
L: Language,
{
fn new(inner: I, separator: &'static str) -> Self {
Self {
inner,
separator,
next: None,
options: FormatSeparatedOptions::default(),
}
}

/// Wraps every node inside of a group
#[allow(unused)]
pub fn nodes_grouped(mut self) -> Self {
self.options.nodes_grouped = true;
self
}

#[allow(unused)]
pub fn with_group_id(mut self, group_id: Option<GroupId>) -> Self {
self.options.group_id = group_id;
self
}
}

impl<I, N> Iterator for FormatSeparatedIter<I, JsonLanguage, N>
where
I: Iterator<Item = AstSeparatedElement<JsonLanguage, N>>,
{
type Item = FormatSeparatedElement<JsonLanguage, N>;

fn next(&mut self) -> Option<Self::Item> {
let element = self.next.take().or_else(|| self.inner.next())?;

self.next = self.inner.next();
let is_last = self.next.is_none();

Some(FormatSeparatedElement {
element,
is_last,
separator: self.separator,
options: self.options,
})
}
}

impl<I, N> FusedIterator for FormatSeparatedIter<I, JsonLanguage, N> where
I: Iterator<Item = AstSeparatedElement<JsonLanguage, N>> + FusedIterator
{
}

impl<I, N> ExactSizeIterator for FormatSeparatedIter<I, JsonLanguage, N> where
I: Iterator<Item = AstSeparatedElement<JsonLanguage, N>> + ExactSizeIterator
{
}

/// AST Separated list formatting extension methods
pub trait FormatAstSeparatedListExtension: AstSeparatedList<Language = JsonLanguage> {
/// Prints a separated list of nodes
///
/// Trailing separators will be reused from the original list or
/// created by calling the `separator_factory` function.
/// The last trailing separator in the list will only be printed
/// if the outer group breaks.
fn format_separated(
&self,
separator: &'static str,
) -> FormatSeparatedIter<
AstSeparatedListElementsIterator<JsonLanguage, Self::Node>,
JsonLanguage,
Self::Node,
> {
FormatSeparatedIter::new(self.elements(), separator)
}
}

impl<T> FormatAstSeparatedListExtension for T where T: AstSeparatedList<Language = JsonLanguage> {}

#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct FormatSeparatedOptions {
group_id: Option<GroupId>,
nodes_grouped: bool,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"number": 1123123213123123123123122311231232131231231231231223,
"string": "some-long-long-long-long-long-long-long-string",
"array": [12312321, {"another-number": 12321321, "string": "some-string"}],
"object": {"array": [123214123, "some-long-long-string", [12312312, "some-long-long-string"]]},
"null": null
}
Loading

0 comments on commit 581b823

Please sign in to comment.