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): Member chain formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Sep 27, 2022
1 parent c2eb07c commit 3dc58c3
Show file tree
Hide file tree
Showing 58 changed files with 1,343 additions and 4,307 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ fn template_literal_contains_new_line(template: &JsTemplate) -> bool {
/// ```
///
/// Returns `false` because the template isn't on the same line as the '+' token.
fn is_multiline_template_starting_on_same_line(template: &JsTemplate) -> bool {
pub(crate) fn is_multiline_template_starting_on_same_line(template: &JsTemplate) -> bool {
let contains_new_line = template_literal_contains_new_line(template);

let starts_on_same_line = template.syntax().first_token().map_or(false, |token| {
Expand Down
190 changes: 138 additions & 52 deletions crates/rome_js_formatter/src/js/expressions/call_arguments.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::js::expressions::arrow_function_expression::is_multiline_template_starting_on_same_line;
use crate::prelude::*;
use crate::utils::{is_call_like_expression, write_arguments_multi_line};
use rome_formatter::{format_args, write, CstFormatContext};
use rome_js_syntax::{
JsAnyCallArgument, JsAnyExpression, JsAnyFunctionBody, JsAnyLiteralExpression, JsAnyName,
JsAnyStatement, JsArrayExpression, JsArrowFunctionExpression, JsCallArgumentList,
JsCallArguments, JsCallArgumentsFields, JsCallExpression, TsReferenceType,
JsCallArguments, JsCallArgumentsFields, JsCallExpression, JsExpressionStatement,
TsReferenceType,
};
use rome_rowan::{AstSeparatedList, SyntaxResult, SyntaxTokenText};

Expand Down Expand Up @@ -33,6 +35,28 @@ impl FormatNodeRule<JsCallArguments> for FormatJsCallArguments {
);
}

let call_expression = node.parent::<JsCallExpression>();

if is_commonjs_or_amd_call(node, call_expression.as_ref())?
|| is_multiline_template_only_args(node)
{
return write!(
f,
[
l_paren_token.format(),
format_with(|f| {
f.join_with(space())
.entries(
args.format_separated(",")
.with_trailing_separator(TrailingSeparator::Omit),
)
.finish()
}),
r_paren_token.format()
]
);
}

let mut iter = args.iter();
let first_argument = iter.next();
let second_argument = iter.next();
Expand Down Expand Up @@ -97,19 +121,29 @@ impl FormatNodeRule<JsCallArguments> for FormatJsCallArguments {
.map(|e| e.memoized())
.collect();

let an_argument_breaks =
separated
.iter_mut()
.enumerate()
.any(|(index, element)| match element.inspect(f) {
Ok(element) => {
let in_relevant_range = should_group_first_argument && index > 0
|| (should_group_last_argument && index < args.len() - 1);
let mut any_argument_breaks = false;
let mut first_last_breaks = false;

in_relevant_range && element.will_break()
}
Err(_) => false,
});
for (index, argument) in separated.iter_mut().enumerate() {
let breaks = argument.inspect(f)?.will_break();

any_argument_breaks = any_argument_breaks || breaks;

if (should_group_first_argument && index > 0)
|| (should_group_last_argument && index < args.len() - 1)
{
first_last_breaks = first_last_breaks || breaks;
if breaks {
break;
}
}
}

let format_flat_arguments = format_with(|f| {
f.join_with(soft_line_break_or_space())
.entries(separated.iter())
.finish()
});

// We now cache them the delimiters tokens. This is needed because `[rome_formatter::best_fitting]` will try to
// print each version first
Expand All @@ -125,27 +159,18 @@ impl FormatNodeRule<JsCallArguments> for FormatJsCallArguments {
// function, but here we use a different way to print the trailing separator
write!(
f,
[
&l_paren,
&group(&format_with(|f| {
write!(
f,
[
&soft_block_indent(&format_args![
format_with(|f| {
write_arguments_multi_line(separated.iter(), f)
}),
soft_line_break()
]),
&r_paren
]
)
[group(&format_args![
l_paren,
soft_block_indent(&format_with(|f| {
write_arguments_multi_line(separated.iter(), f)
})),
]
r_paren
])
.should_expand(true)]
)
});

if an_argument_breaks {
if first_last_breaks {
return write!(f, [all_arguments_expanded]);
}

Expand All @@ -160,39 +185,26 @@ impl FormatNodeRule<JsCallArguments> for FormatJsCallArguments {
let mut iter = separated.iter();
// SAFETY: check on the existence of at least one argument are done before
let first = iter.next().unwrap();
f.join_with(&space())
.entry(&format_with(|f| {
write!(f, [&format_args![first, expand_parent()]])
}))
.entries(iter)
.finish()?;
f.join_with(&space()).entry(&first).entries(iter).finish()?;
} else {
// special formatting of the last element
let mut iter = separated.iter();
// SAFETY: check on the existence of at least one argument are done before
let last = iter.next_back().unwrap();

f.join_with(&space())
.entries(iter)
.entry(&format_with(|f| {
write!(f, [&format_args![last, expand_parent()]])
}))
.finish()?;
f.join_with(&space()).entries(iter).entry(&last).finish()?;
}
write!(f, [r_paren])
});

if any_argument_breaks {
write!(f, [expand_parent()])?;
}

write!(
f,
[best_fitting![
format_args![
l_paren,
group(&format_with(|f| {
write_arguments_multi_line(separated.iter(), f)
})),
r_paren,
],
edge_arguments_do_not_break,
format_args![l_paren, format_flat_arguments, r_paren],
group(&edge_arguments_do_not_break).should_expand(true),
all_arguments_expanded
]]
)
Expand All @@ -209,7 +221,7 @@ impl FormatNodeRule<JsCallArguments> for FormatJsCallArguments {
write_arguments_multi_line(separated, f)
})),
r_paren,
]),]
])]
)
}
}
Expand Down Expand Up @@ -412,6 +424,80 @@ fn could_group_expression_argument(
Ok(result)
}

fn is_commonjs_or_amd_call(
arguments: &JsCallArguments,
call: Option<&JsCallExpression>,
) -> SyntaxResult<bool> {
let call = match call {
Some(call) => call,
None => return Ok(false),
};

let callee = call.callee()?;

Ok(match callee {
JsAnyExpression::JsIdentifierExpression(identifier) => {
let reference = identifier.name()?;

if reference.has_name("require") {
true
} else if reference.has_name("define") {
let in_statement = call.parent::<JsExpressionStatement>().is_some();

if in_statement {
let args = arguments.args();
match args.len() {
1 => true,
2 => matches!(
args.first(),
Some(Ok(JsAnyCallArgument::JsAnyExpression(
JsAnyExpression::JsArrayExpression(_)
)))
),
3 => {
let mut iter = args.iter();
let first = iter.next();
let second = iter.next();
matches!(
(first, second),
(
Some(Ok(JsAnyCallArgument::JsAnyExpression(
JsAnyExpression::JsAnyLiteralExpression(
JsAnyLiteralExpression::JsStringLiteralExpression(_)
)
))),
Some(Ok(JsAnyCallArgument::JsAnyExpression(
JsAnyExpression::JsArrayExpression(_)
)))
)
)
}
_ => false,
}
} else {
false
}
} else {
false
}
}
_ => false,
})
}

fn is_multiline_template_only_args(arguments: &JsCallArguments) -> bool {
let args = arguments.args();

match args.first() {
Some(Ok(JsAnyCallArgument::JsAnyExpression(JsAnyExpression::JsTemplate(template))))
if args.len() == 1 =>
{
is_multiline_template_starting_on_same_line(&template)
}
_ => false,
}
}

/// This function is used to check if the code is a hook-like code:
///
/// ```js
Expand Down
49 changes: 45 additions & 4 deletions crates/rome_js_formatter/src/js/expressions/call_expression.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,58 @@
use crate::prelude::*;
use rome_formatter::write;

use crate::parentheses::NeedsParentheses;
use crate::utils::get_member_chain;
use rome_js_syntax::{JsCallExpression, JsSyntaxKind, JsSyntaxNode};
use crate::utils::member_chain::MemberChain;
use rome_js_syntax::{
JsAnyExpression, JsCallExpression, JsCallExpressionFields, JsSyntaxKind, JsSyntaxNode,
};

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

impl FormatNodeRule<JsCallExpression> for FormatJsCallExpression {
fn fmt_fields(&self, node: &JsCallExpression, f: &mut JsFormatter) -> FormatResult<()> {
let member_chain = get_member_chain(node, f)?;
let JsCallExpressionFields {
callee,
optional_chain_token,
type_arguments,
arguments,
} = node.as_fields();

member_chain.fmt(f)
let callee = callee?;

if matches!(
callee,
JsAnyExpression::JsStaticMemberExpression(_)
| JsAnyExpression::JsComputedMemberExpression(_)
) && !callee.needs_parentheses()
{
let member_chain = MemberChain::from_call_expression(
node.clone(),
f.comments(),
f.options().tab_width(),
)?;

member_chain.fmt(f)
} else {
let format_inner = format_with(|f| {
write!(
f,
[
callee.format(),
optional_chain_token.format(),
type_arguments.format(),
arguments.format()
]
)
});

if matches!(callee, JsAnyExpression::JsCallExpression(_)) {
write!(f, [group(&format_inner)])
} else {
write!(f, [format_inner])
}
}
}

fn needs_parentheses(&self, item: &JsCallExpression) -> bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,44 @@ impl Format<JsFormatContext> for JsAnyComputedMemberLike {
fn fmt(&self, f: &mut Formatter<JsFormatContext>) -> FormatResult<()> {
write!(f, [self.object().format()])?;

match self.member()? {
FormatComputedMemberLookup(self).fmt(f)
}
}

/// Formats the lookup portion (everything except the object) of a computed member like.
pub(crate) struct FormatComputedMemberLookup<'a>(&'a JsAnyComputedMemberLike);

impl<'a> FormatComputedMemberLookup<'a> {
pub(crate) fn new(member_like: &'a JsAnyComputedMemberLike) -> Self {
Self(member_like)
}
}

impl Format<JsFormatContext> for FormatComputedMemberLookup<'_> {
fn fmt(&self, f: &mut Formatter<JsFormatContext>) -> FormatResult<()> {
match self.0.member()? {
JsAnyExpression::JsAnyLiteralExpression(
JsAnyLiteralExpression::JsNumberLiteralExpression(literal),
) => {
write!(
f,
[
self.optional_chain_token().format(),
self.l_brack_token().format(),
self.0.optional_chain_token().format(),
self.0.l_brack_token().format(),
literal.format(),
self.r_brack_token().format()
self.0.r_brack_token().format()
]
)
}
member => {
write![
f,
[group(&format_args![
self.optional_chain_token().format(),
self.l_brack_token().format(),
soft_line_break(),
self.0.optional_chain_token().format(),
self.0.l_brack_token().format(),
soft_block_indent(&member.format()),
self.r_brack_token().format()
]),]
self.0.r_brack_token().format()
])]
]
}
}
Expand Down
Loading

0 comments on commit 3dc58c3

Please sign in to comment.