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

Commit

Permalink
feat(rome_json_formatter): JSON Formatting object #2570 (#4040)
Browse files Browse the repository at this point in the history
Co-authored-by: Micha Reiser <micha@rome.tools>
Fix #2570
  • Loading branch information
denbezrukov authored Dec 15, 2022
1 parent ba48b16 commit 1aee087
Show file tree
Hide file tree
Showing 29 changed files with 454 additions and 185 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ expression: content

```json
{
"formatter": {
"ignore": [
"formatter": {
"ignore": [
"test1.js",
"./test2.js",
"./test3/**/*",
Expand All @@ -16,7 +16,7 @@ expression: content
"**/test6/*.js",
"*.test7.js"
]
}
}
}

```
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::prelude::*;
use crate::utils::{node_has_leading_newline, FormatStatementSemicolon};
use crate::utils::FormatStatementSemicolon;
use rome_formatter::write;

use rome_js_syntax::JsExportNamedFromClause;
Expand Down Expand Up @@ -42,7 +42,7 @@ impl FormatNodeRule<JsExportNamedFromClause> for FormatJsExportNamedFromClause {
write!(f, [space()])?;
}
_ => {
if node_has_leading_newline(specifiers.syntax()) {
if specifiers.syntax().has_leading_newline() {
write!(f, [block_indent(&specifiers.format()),])?;
} else {
write!(
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
5 changes: 2 additions & 3 deletions crates/rome_js_formatter/src/utils/object_like.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::prelude::*;
use crate::utils::node_has_leading_newline;
use crate::JsFormatContext;
use rome_formatter::write;
use rome_formatter::{Format, FormatResult};
Expand All @@ -25,8 +24,8 @@ impl JsObjectLike {

fn members_have_leading_newline(&self) -> bool {
match self {
JsObjectLike::JsObjectExpression(oe) => node_has_leading_newline(oe.members().syntax()),
JsObjectLike::TsObjectType(ot) => node_has_leading_newline(ot.members().syntax()),
JsObjectLike::JsObjectExpression(oe) => oe.members().syntax().has_leading_newline(),
JsObjectLike::TsObjectType(ot) => ot.members().syntax().has_leading_newline(),
}
}

Expand Down
12 changes: 10 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,20 @@
use crate::prelude::*;
use rome_formatter::{format_args, 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)
write!(
f,
[group(&format_args![
&node.name().format(),
node.colon_token().format(),
space(),
node.value().format()
])]
)
}
}
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()
}
}
15 changes: 14 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,5 @@
use crate::prelude::*;
use rome_formatter::write;
use rome_json_syntax::JsonObjectValue;
use rome_rowan::AstNode;

Expand All @@ -7,6 +8,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.json_member_list().syntax().has_leading_newline();

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 1aee087

Please sign in to comment.