use full_moon::ast::{
punctuated::{Pair, Punctuated},
span::ContainedSpan,
Block, Call, Expression, Field, FunctionArgs, FunctionBody, FunctionCall, FunctionDeclaration,
FunctionName, Index, LastStmt, LocalFunction, MethodCall, Parameter, Prefix, Stmt, Suffix,
TableConstructor, Value, Var,
};
use full_moon::tokenizer::{Token, TokenKind, TokenReference, TokenType};
use std::boxed::Box;
#[cfg(feature = "luau")]
use crate::formatters::luau::{format_generic_declaration, format_type_specifier};
use crate::{
context::{create_indent_trivia, create_newline_trivia, Context},
fmt_symbol,
formatters::{
block::{format_block, format_last_stmt_no_trivia},
expression::{format_expression, format_prefix, format_suffix, hang_expression},
general::{
format_contained_punctuated_multiline, format_contained_span, format_end_token,
format_punctuated, format_token_reference, EndTokenType,
},
stmt::format_stmt_no_trivia,
table::format_table_constructor,
trivia::{
strip_leading_trivia, strip_trivia, FormatTriviaType, UpdateLeadingTrivia,
UpdateTrailingTrivia,
},
trivia_util,
},
shape::Shape,
};
pub fn format_anonymous_function(
ctx: &Context,
function_token: &TokenReference,
function_body: &FunctionBody,
shape: Shape,
) -> (TokenReference, FunctionBody) {
const FUNCTION_LEN: usize = "function".len();
let function_token = fmt_symbol!(ctx, function_token, "function", shape);
let function_body = format_function_body(ctx, function_body, shape.add_width(FUNCTION_LEN));
(function_token, function_body)
}
pub enum FunctionCallNextNode {
ObscureWithoutParens,
None,
}
pub fn format_call(
ctx: &Context,
call: &Call,
shape: Shape,
call_next_node: FunctionCallNextNode,
) -> Call {
match call {
Call::AnonymousCall(function_args) => Call::AnonymousCall(format_function_args(
ctx,
function_args,
shape,
call_next_node,
)),
Call::MethodCall(method_call) => {
Call::MethodCall(format_method_call(ctx, method_call, shape, call_next_node))
}
other => panic!("unknown node {:?}", other),
}
}
fn is_table_constructor(expression: &Expression) -> bool {
match expression {
Expression::Value { value, .. } => matches!(**value, Value::TableConstructor(_)),
_ => false,
}
}
fn is_complex_arg(value: &Value) -> bool {
value.to_string().trim().contains('\n')
}
fn function_trivia_contains_comments(trivia: &Token) -> bool {
matches!(trivia.token_kind(), TokenKind::SingleLineComment)
|| matches!(trivia.token_type(), TokenType::MultiLineComment { comment, .. } if comment.as_str().lines().count() > 1 )
}
fn function_args_contains_comments(
parentheses: &ContainedSpan,
arguments: &Punctuated<Expression>,
) -> bool {
let (start_parens, end_parens) = parentheses.tokens();
if trivia_util::trivia_contains_comments(
start_parens.trailing_trivia(),
trivia_util::CommentSearch::Single,
) || trivia_util::trivia_contains_comments(
end_parens.leading_trivia(),
trivia_util::CommentSearch::Single,
) {
true
} else {
arguments.pairs().any(|argument| {
trivia_util::get_expression_leading_trivia(argument.value())
.iter()
.chain(trivia_util::get_expression_trailing_trivia(argument.value()).iter())
.any(function_trivia_contains_comments)
|| argument
.punctuation()
.map_or(false, |token| token.leading_trivia().chain(token.trailing_trivia()).any(function_trivia_contains_comments))
})
}
}
#[derive(Clone, Copy)]
enum ArgumentState {
None,
SeenMultilineArguments,
SeenNonMultilineArgumentAfterMultiline,
}
impl ArgumentState {
fn new() -> ArgumentState {
ArgumentState::None
}
#[must_use]
fn record_multiline_arg(self) -> ArgumentState {
match self {
ArgumentState::None => ArgumentState::SeenMultilineArguments,
_ => self,
}
}
#[must_use]
fn record_standard_arg(self) -> ArgumentState {
match self {
ArgumentState::SeenMultilineArguments => {
ArgumentState::SeenNonMultilineArgumentAfterMultiline
}
_ => self,
}
}
fn should_hang(self) -> bool {
matches!(self, ArgumentState::SeenNonMultilineArgumentAfterMultiline)
}
}
fn function_args_multiline_heuristic(
ctx: &Context,
arguments: &Punctuated<Expression>,
shape: Shape,
) -> bool {
const PAREN_LEN: usize = "(".len();
const COMMA_SPACE_LEN: usize = ", ".len();
const BRACKET_LEN: usize = "}".len();
const END_LEN: usize = "end".len();
if arguments.is_empty() {
return false;
}
if shape.using_simple_heuristics() {
return false;
}
let first_iter_formatted_arguments = arguments.clone().into_pairs().map(|value| {
value.map(|argument| {
format_expression(
ctx,
&argument,
shape.with_simple_heuristics().with_infinite_width(),
)
})
});
let mut singleline_shape = shape + PAREN_LEN;
let mut current_state = ArgumentState::new();
for pair in first_iter_formatted_arguments {
let argument = pair.value();
match argument {
Expression::Value { ref value, .. } => {
match &**value {
Value::Function((_, function_body)) => {
let is_expanded = !should_collapse_function_body(ctx, function_body);
if is_expanded {
if current_state.should_hang() {
return true;
}
current_state = current_state.record_multiline_arg();
singleline_shape = singleline_shape.take_first_line(&value);
if singleline_shape.over_budget() {
return true;
}
singleline_shape = singleline_shape.reset() + END_LEN;
} else {
singleline_shape = singleline_shape + argument.to_string().len();
}
}
Value::TableConstructor(table) => {
let is_expanded = trivia_util::trivia_contains_newline(
table.braces().tokens().0.trailing_trivia(),
);
if is_expanded {
if current_state.should_hang() {
return true;
}
current_state = current_state.record_multiline_arg();
singleline_shape = singleline_shape.reset() + BRACKET_LEN;
} else {
singleline_shape = singleline_shape + argument.to_string().len();
}
}
_ => {
current_state = current_state.record_standard_arg();
if is_complex_arg(value) && arguments.len() > 1 {
return true;
}
if singleline_shape.take_first_line(&argument).over_budget() {
return true;
}
singleline_shape = singleline_shape.take_last_line(&argument);
}
}
}
_ => {
current_state = current_state.record_standard_arg();
if singleline_shape.take_first_line(&argument).over_budget() {
return true;
}
singleline_shape = singleline_shape.take_last_line(&argument);
}
}
if singleline_shape.over_budget() {
return true;
}
if let Some(punctuation) = pair.punctuation() {
singleline_shape = singleline_shape
+ COMMA_SPACE_LEN
+ punctuation
.trailing_trivia()
.filter(|x| trivia_util::trivia_is_comment(x))
.fold(0, |acc, trivia| acc + trivia.to_string().len());
}
}
singleline_shape.add_width(PAREN_LEN).over_budget()
}
fn format_argument_multiline(ctx: &Context, argument: &Expression, shape: Shape) -> Expression {
let infinite_width_argument = format_expression(ctx, argument, shape.with_infinite_width());
if shape
.add_width(strip_trivia(&infinite_width_argument).to_string().len())
.over_budget()
{
if trivia_util::can_hang_expression(argument) {
hang_expression(ctx, argument, shape, Some(1))
} else {
format_expression(ctx, argument, shape)
}
} else {
infinite_width_argument
}
}
pub fn format_function_args(
ctx: &Context,
function_args: &FunctionArgs,
shape: Shape,
call_next_node: FunctionCallNextNode,
) -> FunctionArgs {
match function_args {
FunctionArgs::Parentheses {
parentheses,
arguments,
} => {
if (ctx.should_omit_string_parens() || ctx.should_omit_table_parens())
&& arguments.len() == 1
&& !matches!(call_next_node, FunctionCallNextNode::ObscureWithoutParens)
{
let argument = arguments.iter().next().unwrap();
let trailing_comments = parentheses.tokens().1.trailing_trivia().cloned().collect();
if let Expression::Value { value, .. } = argument {
match &**value {
Value::String(token_reference) => {
if ctx.should_omit_string_parens() {
return format_function_args(
ctx,
&FunctionArgs::String(token_reference.update_trailing_trivia(
FormatTriviaType::Append(trailing_comments),
)),
shape,
call_next_node,
);
}
}
Value::TableConstructor(table_constructor) => {
if ctx.should_omit_table_parens() {
return format_function_args(
ctx,
&FunctionArgs::TableConstructor(
table_constructor.update_trailing_trivia(
FormatTriviaType::Append(trailing_comments),
),
),
shape,
call_next_node,
);
}
}
_ => (),
}
}
}
let force_mutliline = function_args_contains_comments(parentheses, arguments);
let is_multiline =
force_mutliline || function_args_multiline_heuristic(ctx, arguments, shape);
let hug_table_constructor = is_multiline
&& !force_mutliline
&& arguments.len() == 1
&& is_table_constructor(arguments.iter().next().unwrap());
if is_multiline && !hug_table_constructor {
let (parentheses, arguments) = format_contained_punctuated_multiline(
ctx,
parentheses,
arguments,
format_argument_multiline,
trivia_util::take_expression_trailing_comments,
shape,
);
FunctionArgs::Parentheses {
parentheses,
arguments,
}
} else {
let shape_increment = if hug_table_constructor { 2 } else { 1 };
let (start_parens, end_parens) = parentheses.tokens();
let start_parens = format_token_reference(ctx, start_parens, shape);
let start_parens = if trivia_util::token_contains_trailing_comments(&start_parens)
&& !arguments.is_empty()
{
start_parens.update_trailing_trivia(FormatTriviaType::Append(vec![Token::new(
TokenType::spaces(1),
)]))
} else {
start_parens
};
let end_parens = format_token_reference(ctx, end_parens, shape);
let parentheses = ContainedSpan::new(start_parens, end_parens);
let mut arguments =
format_punctuated(ctx, arguments, shape + shape_increment, format_expression);
for argument in arguments.pairs_mut() {
let expression = argument.value_mut();
let trivia = trivia_util::get_expression_leading_trivia(expression)
.iter()
.skip_while(|trivia| trivia_util::trivia_is_whitespace(trivia))
.map(|x| x.to_owned())
.collect();
*expression =
expression.update_leading_trivia(FormatTriviaType::Replace(trivia));
}
FunctionArgs::Parentheses {
parentheses,
arguments,
}
}
}
FunctionArgs::String(token_reference) => {
if ctx.should_omit_string_parens()
&& !matches!(call_next_node, FunctionCallNextNode::ObscureWithoutParens)
{
let token_reference = format_token_reference(ctx, token_reference, shape)
.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
TokenType::spaces(1),
)]));
return FunctionArgs::String(token_reference);
}
let mut arguments = Punctuated::new();
let new_expression = format_expression(
ctx,
&Expression::Value {
value: Box::new(Value::String(token_reference.to_owned())),
#[cfg(feature = "luau")]
type_assertion: None,
},
shape + 1, );
let (new_expression, comments_buffer) =
trivia_util::take_expression_trailing_comments(&new_expression);
let parentheses = ContainedSpan::new(
TokenReference::symbol("(").unwrap(),
TokenReference::symbol(")").unwrap(),
)
.update_trailing_trivia(FormatTriviaType::Append(comments_buffer));
arguments.push(Pair::new(new_expression, None));
FunctionArgs::Parentheses {
parentheses,
arguments,
}
}
FunctionArgs::TableConstructor(table_constructor) => {
if ctx.should_omit_table_parens()
&& !matches!(call_next_node, FunctionCallNextNode::ObscureWithoutParens)
{
let table_constructor = format_table_constructor(ctx, table_constructor, shape)
.update_leading_trivia(FormatTriviaType::Append(vec![Token::new(
TokenType::spaces(1),
)]));
return FunctionArgs::TableConstructor(table_constructor);
}
let mut arguments = Punctuated::new();
let new_expression = format_expression(
ctx,
&Expression::Value {
value: Box::new(Value::TableConstructor(table_constructor.to_owned())),
#[cfg(feature = "luau")]
type_assertion: None,
},
shape + 1, );
let (new_expression, comments_buffer) =
trivia_util::take_expression_trailing_comments(&new_expression);
let parentheses = ContainedSpan::new(
TokenReference::symbol("(").unwrap(),
TokenReference::symbol(")").unwrap(),
)
.update_trailing_trivia(FormatTriviaType::Append(comments_buffer));
arguments.push(Pair::new(new_expression, None));
FunctionArgs::Parentheses {
parentheses,
arguments,
}
}
other => panic!("unknown node {:?}", other),
}
}
fn should_parameters_format_multiline(
ctx: &Context,
function_body: &FunctionBody,
shape: Shape,
should_collapse: bool,
) -> bool {
const PARENS_LEN: usize = "()".len();
const SINGLELINE_END_LEN: usize = " end".len();
let mut line_length = format_singleline_parameters(ctx, function_body, shape)
.to_string()
.len()
+ PARENS_LEN;
#[cfg(feature = "luau")]
{
let (extra_line_length, multiline_specifier_present) = function_body
.type_specifiers()
.chain(std::iter::once(function_body.return_type())) .map(|x| {
x.map_or((0, false), |specifier| {
let formatted = format_type_specifier(ctx, specifier, shape).to_string();
let length = formatted.lines().next_back().unwrap_or("").len();
let contains_newline = formatted.lines().count() > 1;
(length, contains_newline)
})
})
.fold(
(0, false),
|(acc_length, acc_multiline), (length, multiline)| {
(
acc_length + length,
if multiline { true } else { acc_multiline },
)
},
);
if multiline_specifier_present && function_body.parameters().len() > 1 {
return true;
}
line_length += extra_line_length
}
if should_collapse {
line_length += SINGLELINE_END_LEN;
}
let singleline_shape = shape + line_length;
singleline_shape.over_budget()
}
fn table_constructor_contains_nested_function(table_constructor: &TableConstructor) -> bool {
table_constructor.fields().iter().any(|field| match field {
Field::NoKey(expression) => contains_nested_function(expression),
Field::ExpressionKey { key, value, .. } => {
contains_nested_function(key) || contains_nested_function(value)
}
Field::NameKey { value, .. } => contains_nested_function(value),
other => unreachable!("unknown node {:?}", other),
})
}
fn suffix_contains_nested_function(suffix: &Suffix) -> bool {
let test_function_args = |function_args: &FunctionArgs| match function_args {
FunctionArgs::Parentheses { arguments, .. } => {
arguments.iter().any(contains_nested_function)
}
FunctionArgs::TableConstructor(table_constructor) => {
table_constructor_contains_nested_function(table_constructor)
}
_ => false,
};
match suffix {
Suffix::Index(Index::Brackets { expression, .. }) => contains_nested_function(expression),
Suffix::Call(Call::AnonymousCall(function_args)) => test_function_args(function_args),
Suffix::Call(Call::MethodCall(method_call)) => test_function_args(method_call.args()),
_ => false,
}
}
fn contains_nested_function(expression: &Expression) -> bool {
match expression {
Expression::Value { value, .. } => match &**value {
Value::Function(_) => true,
Value::FunctionCall(call) => function_call_contains_nested_function(call),
Value::ParenthesesExpression(expression) => contains_nested_function(expression),
Value::TableConstructor(table_constructor) => {
table_constructor_contains_nested_function(table_constructor)
}
Value::Var(var) => var_contains_nested_function(var),
_ => false,
},
Expression::BinaryOperator { lhs, rhs, .. } => {
contains_nested_function(lhs) || contains_nested_function(rhs)
}
Expression::Parentheses { expression, .. } => contains_nested_function(expression),
Expression::UnaryOperator { expression, .. } => contains_nested_function(expression),
other => unreachable!("unknown node {:?}", other),
}
}
fn var_contains_nested_function(var: &Var) -> bool {
match var {
Var::Expression(var_expression) => {
var_expression
.suffixes()
.any(suffix_contains_nested_function)
|| matches!(var_expression.prefix(), Prefix::Expression(expression) if contains_nested_function(expression))
}
_ => false,
}
}
fn function_call_contains_nested_function(call: &FunctionCall) -> bool {
call.suffixes().any(suffix_contains_nested_function)
|| matches!(call.prefix(), Prefix::Expression(expression) if contains_nested_function(expression))
}
fn block_contains_nested_function(block: &Block) -> bool {
debug_assert!(block.stmts().count() <= 1);
if let Some(stmt) = block.stmts().next() {
let contains_nested_function = match stmt {
Stmt::Assignment(assignment) => assignment.variables().iter().any(var_contains_nested_function) || assignment.expressions().iter().any(contains_nested_function),
Stmt::LocalAssignment(assignment) => assignment.expressions().iter().any(contains_nested_function),
Stmt::FunctionCall(function_call) => function_call_contains_nested_function(function_call),
_ => unreachable!("testing block_contains_nested_function on a stmt which isn't an assignment/function call"),
};
if contains_nested_function {
return true;
}
}
match block.last_stmt() {
Some(LastStmt::Return(r#return)) => r#return.returns().iter().any(contains_nested_function),
_ => false,
}
}
pub fn should_collapse_function_body(ctx: &Context, function_body: &FunctionBody) -> bool {
let require_multiline_function = function_body
.parameters_parentheses()
.tokens()
.1
.trailing_trivia()
.any(trivia_util::trivia_is_comment)
|| function_body
.end_token()
.leading_trivia()
.any(trivia_util::trivia_is_comment)
|| trivia_util::contains_comments(function_body.block());
!require_multiline_function
&& (trivia_util::is_block_empty(function_body.block())
|| (trivia_util::is_block_simple(function_body.block())
&& ctx.should_collapse_simple_functions()
&& !block_contains_nested_function(function_body.block())))
}
pub fn format_function_body(
ctx: &Context,
function_body: &FunctionBody,
shape: Shape,
) -> FunctionBody {
let leading_trivia = vec![create_indent_trivia(ctx, shape)];
let should_collapse = should_collapse_function_body(ctx, function_body);
let multiline_params = {
#[cfg(feature = "luau")]
let mut type_specifiers = function_body.type_specifiers();
let contains_comments = function_body.parameters().pairs().any(|pair| {
let contains_comments = pair
.punctuation()
.map_or(false, trivia_util::token_contains_comments)
|| trivia_util::contains_comments(pair.value());
#[cfg(feature = "luau")]
let type_specifier_comments = type_specifiers
.next()
.flatten()
.map_or(false, |type_specifier| {
trivia_util::contains_comments(type_specifier)
});
#[cfg(not(feature = "luau"))]
let type_specifier_comments = false;
contains_comments || type_specifier_comments
});
contains_comments
|| should_parameters_format_multiline(ctx, function_body, shape, should_collapse)
};
let mut singleline_function = !multiline_params && should_collapse;
#[cfg(feature = "luau")]
let generics = function_body
.generics()
.map(|generic_declaration| format_generic_declaration(ctx, generic_declaration, shape));
#[cfg(feature = "luau")]
let shape = shape + generics.as_ref().map_or(0, |x| x.to_string().len());
let (parameters_parentheses, formatted_parameters) = match multiline_params {
true => format_contained_punctuated_multiline(
ctx,
function_body.parameters_parentheses(),
function_body.parameters(),
format_parameter,
trivia_util::take_parameter_trailing_comments,
shape,
),
false => (
format_contained_span(ctx, function_body.parameters_parentheses(), shape),
format_singleline_parameters(ctx, function_body, shape),
),
};
#[cfg(feature = "luau")]
let (type_specifiers, return_type) = {
let parameters_shape = if multiline_params {
shape.increment_additional_indent()
} else {
shape
};
(
function_body
.type_specifiers()
.map(|x| x.map(|specifier| format_type_specifier(ctx, specifier, parameters_shape)))
.collect::<Vec<_>>(),
function_body
.return_type()
.map(|return_type| format_type_specifier(ctx, return_type, shape)),
)
};
let create_normal_block = || {
let block_shape = shape.reset().increment_block_indent();
format_block(ctx, function_body.block(), block_shape)
};
let block = if singleline_function {
if trivia_util::is_block_empty(function_body.block()) {
Block::new()
} else {
const PARENS_LEN: usize = "()".len();
let block_shape = shape.take_first_line(&formatted_parameters) + PARENS_LEN;
#[cfg(feature = "luau")]
let block_shape = block_shape
+ type_specifiers.iter().fold(0, |acc, x| {
acc + x.as_ref().map_or(0, |x| x.to_string().len())
})
+ return_type.as_ref().map_or(0, |x| x.to_string().len());
let trailing_trivia = FormatTriviaType::Append(vec![Token::new(TokenType::spaces(1))]);
let block = if let Some(last_stmt) = function_body.block().last_stmt() {
Block::new().with_last_stmt(Some((
format_last_stmt_no_trivia(ctx, last_stmt, block_shape)
.update_trailing_trivia(trailing_trivia),
None,
)))
} else if let Some(stmt) = function_body.block().stmts().next() {
let stmt = format_stmt_no_trivia(ctx, stmt, block_shape)
.update_trailing_trivia(trailing_trivia);
Block::new().with_stmts(vec![(stmt, None)])
} else {
unreachable!("Got a empty block but is_block_empty was false");
};
if block_shape.take_first_line(&block).over_budget()
|| trivia_util::spans_multiple_lines(&block)
{
singleline_function = false;
create_normal_block()
} else {
block
}
}
} else {
create_normal_block()
};
#[allow(clippy::never_loop)]
#[allow(unused_variables)]
let (parameters_parentheses, return_type) = loop {
let trailing_trivia = if singleline_function {
vec![Token::new(TokenType::spaces(1))]
} else {
vec![create_newline_trivia(ctx)]
};
#[cfg(feature = "luau")]
{
if function_body.return_type().is_some() {
break (
parameters_parentheses,
return_type.as_ref().map(|return_type| {
return_type.update_trailing_trivia(FormatTriviaType::Append(
trailing_trivia.to_owned(),
))
}),
);
}
}
#[cfg(not(feature = "luau"))]
#[allow(clippy::let_unit_value)]
let return_type = ();
break (
parameters_parentheses
.update_trailing_trivia(FormatTriviaType::Append(trailing_trivia)),
return_type,
);
};
let mut end_token = format_end_token(
ctx,
function_body.end_token(),
EndTokenType::IndentComments,
shape,
);
if !singleline_function {
end_token = end_token.update_leading_trivia(FormatTriviaType::Append(leading_trivia));
}
let function_body = function_body.to_owned();
#[cfg(feature = "luau")]
let function_body = function_body
.with_generics(generics)
.with_type_specifiers(type_specifiers)
.with_return_type(return_type);
function_body
.with_parameters_parentheses(parameters_parentheses)
.with_parameters(formatted_parameters)
.with_block(block)
.with_end_token(end_token)
}
fn should_inline_prefix(ctx: &Context, prefix: &Prefix) -> bool {
let prefix = strip_trivia(prefix).to_string();
prefix.as_str().chars().next().unwrap().is_uppercase()
|| prefix.len() <= ctx.config().indent_width
}
pub fn format_function_call(
ctx: &Context,
function_call: &FunctionCall,
shape: Shape,
) -> FunctionCall {
let formatted_prefix = format_prefix(ctx, function_call.prefix(), shape);
let num_suffixes = function_call.suffixes().count();
let must_hang = trivia_util::trivia_contains_comments(
trivia_util::prefix_trailing_trivia(function_call.prefix()).iter(),
trivia_util::CommentSearch::Single,
) || {
let mut peekable_suffixes = function_call.suffixes().peekable();
let mut must_hang = false;
while let Some(suffix) = peekable_suffixes.next() {
must_hang =
trivia_util::suffix_leading_trivia(suffix).any(trivia_util::trivia_is_comment)
|| (peekable_suffixes.peek().is_some()
&& trivia_util::suffix_trailing_trivia(suffix)
.iter()
.any(trivia_util::trivia_is_comment));
if must_hang {
break;
}
}
must_hang
};
let mut keep_first_call_inlined = false;
let should_hang = {
let mut peekable_suffixes = function_call.suffixes().peekable();
let mut call_count = 0;
while let Some(suffix) = peekable_suffixes.next() {
if matches!(suffix, Suffix::Call(Call::MethodCall(_)))
|| (matches!(suffix, Suffix::Index(_))
&& matches!(
peekable_suffixes.peek(),
Some(Suffix::Call(Call::AnonymousCall(_)))
))
{
call_count += 1;
if call_count > 1 {
break;
}
}
}
if call_count > 1 {
let formatted_suffixes: Vec<_> = function_call
.suffixes()
.map(|x| format_suffix(ctx, x, shape, FunctionCallNextNode::None)) .collect();
let preliminary_function_call = FunctionCall::new(formatted_prefix.to_owned())
.with_suffixes(formatted_suffixes.to_owned());
keep_first_call_inlined = should_inline_prefix(ctx, function_call.prefix())
&& !shape
.take_last_line(&strip_leading_trivia(&formatted_prefix))
.test_over_budget(&formatted_suffixes.into_iter().next().unwrap());
if shape
.take_first_line(&strip_trivia(&preliminary_function_call))
.over_budget()
{
true
} else if keep_first_call_inlined
&& function_call
.suffixes()
.filter(|x| matches!(x, Suffix::Index(_) | Suffix::Call(Call::MethodCall(_))))
.count()
== 2
{
false
} else {
let suffixes = preliminary_function_call.suffixes().enumerate();
let mut contains_newline = false;
for (idx, suffix) in suffixes {
let mut remaining_suffixes = preliminary_function_call.suffixes().skip(idx + 1);
if remaining_suffixes.any(|x| matches!(x, Suffix::Call(_)))
&& matches!(suffix, Suffix::Call(_))
&& strip_trivia(suffix).to_string().contains('\n')
{
contains_newline = true;
break;
}
}
contains_newline
}
} else {
false
}
};
let mut shape = shape.take_last_line(&strip_leading_trivia(&formatted_prefix));
let mut formatted_suffixes = Vec::with_capacity(num_suffixes);
let mut suffixes = function_call.suffixes().peekable();
let mut previous_suffix_was_index = true; let mut idx = 0;
while let Some(suffix) = suffixes.next() {
let will_hang = must_hang
|| (should_hang
&& (matches!(suffix, Suffix::Call(Call::MethodCall(_)))
|| (matches!(suffix, Suffix::Index(_))
&& matches!(suffixes.peek(), Some(Suffix::Call(Call::AnonymousCall(_))))))
&& !(keep_first_call_inlined && idx == 0));
let current_shape = if will_hang
|| (should_hang
&& previous_suffix_was_index
&& matches!(suffix, Suffix::Call(Call::AnonymousCall(_)))
&& !(keep_first_call_inlined && idx == 1))
{
shape = shape.reset();
shape.increment_additional_indent()
} else {
shape
};
let ambiguous_next_suffix = if matches!(
suffixes.peek(),
Some(Suffix::Index(_)) | Some(Suffix::Call(Call::MethodCall(_)))
) {
FunctionCallNextNode::ObscureWithoutParens
} else {
FunctionCallNextNode::None
};
let mut suffix = format_suffix(ctx, suffix, current_shape, ambiguous_next_suffix);
if will_hang
&& !(previous_suffix_was_index
&& matches!(suffix, Suffix::Call(Call::AnonymousCall(_))))
{
suffix = trivia_util::prepend_newline_indent(ctx, &suffix, current_shape);
}
previous_suffix_was_index = matches!(suffix, Suffix::Index(_));
shape = shape.take_last_line(&suffix);
formatted_suffixes.push(suffix);
idx += 1;
}
FunctionCall::new(formatted_prefix).with_suffixes(formatted_suffixes)
}
pub fn format_function_name(
ctx: &Context,
function_name: &FunctionName,
shape: Shape,
) -> FunctionName {
let mut formatted_names = Punctuated::new();
for pair in function_name.names().to_owned().into_pairs() {
match pair {
Pair::Punctuated(value, punctuation) => {
let formatted_punctuation = fmt_symbol!(ctx, &punctuation, ".", shape);
let formatted_value = format_token_reference(ctx, &value, shape);
formatted_names.push(Pair::new(formatted_value, Some(formatted_punctuation)));
}
Pair::End(value) => {
let formatted_value = format_token_reference(ctx, &value, shape);
formatted_names.push(Pair::new(formatted_value, None));
}
}
}
let mut formatted_method: Option<(TokenReference, TokenReference)> = None;
if let Some(method_colon) = function_name.method_colon() {
if let Some(token_reference) = function_name.method_name() {
formatted_method = Some((
fmt_symbol!(ctx, method_colon, ":", shape),
format_token_reference(ctx, token_reference, shape),
));
}
};
FunctionName::new(formatted_names).with_method(formatted_method)
}
pub fn format_function_declaration(
ctx: &Context,
function_declaration: &FunctionDeclaration,
shape: Shape,
) -> FunctionDeclaration {
let leading_trivia = vec![create_indent_trivia(ctx, shape)];
let trailing_trivia = vec![create_newline_trivia(ctx)];
let function_token = fmt_symbol!(
ctx,
function_declaration.function_token(),
"function ",
shape
)
.update_leading_trivia(FormatTriviaType::Append(leading_trivia));
let formatted_function_name = format_function_name(ctx, function_declaration.name(), shape);
let shape = shape + (9 + strip_trivia(&formatted_function_name).to_string().len()); let function_body = format_function_body(ctx, function_declaration.body(), shape)
.update_trailing_trivia(FormatTriviaType::Append(trailing_trivia));
FunctionDeclaration::new(formatted_function_name)
.with_function_token(function_token)
.with_body(function_body)
}
pub fn format_local_function(
ctx: &Context,
local_function: &LocalFunction,
shape: Shape,
) -> LocalFunction {
let leading_trivia = vec![create_indent_trivia(ctx, shape)];
let trailing_trivia = vec![create_newline_trivia(ctx)];
let local_token = fmt_symbol!(ctx, local_function.local_token(), "local ", shape)
.update_leading_trivia(FormatTriviaType::Append(leading_trivia));
let function_token = fmt_symbol!(ctx, local_function.function_token(), "function ", shape);
let formatted_name = format_token_reference(ctx, local_function.name(), shape);
let shape = shape + (6 + 9 + strip_trivia(&formatted_name).to_string().len()); let function_body = format_function_body(ctx, local_function.body(), shape)
.update_trailing_trivia(FormatTriviaType::Append(trailing_trivia));
LocalFunction::new(formatted_name)
.with_local_token(local_token)
.with_function_token(function_token)
.with_body(function_body)
}
pub fn format_method_call(
ctx: &Context,
method_call: &MethodCall,
shape: Shape,
call_next_node: FunctionCallNextNode,
) -> MethodCall {
let formatted_colon_token = format_token_reference(ctx, method_call.colon_token(), shape);
let formatted_name = format_token_reference(ctx, method_call.name(), shape);
let shape =
shape + (formatted_colon_token.to_string().len() + formatted_name.to_string().len());
let formatted_function_args =
format_function_args(ctx, method_call.args(), shape, call_next_node);
MethodCall::new(formatted_name, formatted_function_args).with_colon_token(formatted_colon_token)
}
pub fn format_parameter(ctx: &Context, parameter: &Parameter, shape: Shape) -> Parameter {
match parameter {
Parameter::Ellipse(token) => Parameter::Ellipse(fmt_symbol!(ctx, token, "...", shape)),
Parameter::Name(token_reference) => {
Parameter::Name(format_token_reference(ctx, token_reference, shape))
}
other => panic!("unknown node {:?}", other),
}
}
fn format_singleline_parameters(
ctx: &Context,
function_body: &FunctionBody,
shape: Shape,
) -> Punctuated<Parameter> {
let mut formatted_parameters = Punctuated::new();
for pair in function_body.parameters().pairs() {
let parameter = format_parameter(ctx, pair.value(), shape);
let punctuation = pair
.punctuation()
.map(|punctuation| fmt_symbol!(ctx, punctuation, ", ", shape));
formatted_parameters.push(Pair::new(parameter, punctuation));
}
formatted_parameters
}