Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 52 additions & 15 deletions crates/oxc_formatter/src/write/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ impl<'a> FormatWrite<'a> for AstNode<'a, MethodDefinition<'a>> {
write!(f, "?")?;
}

format_grouped_parameters_with_return_type(
format_grouped_parameters_with_return_type_for_method(
value.type_parameters(),
value.this_param.as_deref(),
value.params(),
Expand Down Expand Up @@ -633,38 +633,75 @@ impl<'a> Format<'a> for ClassPropertySemicolon<'a, '_> {
}
}

pub fn format_grouped_parameters_with_return_type<'a>(
/// Based on <https://github.com/prettier/prettier/blob/7584432401a47a26943dd7a9ca9a8e032ead7285/src/language-js/print/function.js#L160-L176>
pub fn format_grouped_parameters_with_return_type_for_method<'a>(
type_parameters: Option<&AstNode<'a, TSTypeParameterDeclaration<'a>>>,
this_param: Option<&TSThisParameter<'a>>,
params: &AstNode<'a, FormalParameters<'a>>,
return_type: Option<&AstNode<'a, TSTypeAnnotation<'a>>>,
f: &mut Formatter<'_, 'a>,
) -> FormatResult<()> {
write!(f, type_parameters)?;

group(&format_once(|f| {
let mut format_type_parameters = type_parameters.memoized();
let mut format_parameters = params.memoized();
let mut format_return_type = return_type.map(FormatNodeWithoutTrailingComments).memoized();

// Inspect early, in case the `return_type` is formatted before `parameters`
// in `should_group_function_parameters`.
format_type_parameters.inspect(f)?;
format_parameters.inspect(f)?;

let group_parameters = should_group_function_parameters(
type_parameters.map(AsRef::as_ref),
params.parameters_count() + usize::from(this_param.is_some()),
return_type.map(AsRef::as_ref),
&mut format_return_type,
f,
)?;
let should_break_parameters = should_break_function_parameters(params, f);
let should_group_parameters = should_break_parameters
|| should_group_function_parameters(
type_parameters.map(AsRef::as_ref),
params.parameters_count() + usize::from(this_param.is_some()),
return_type.map(AsRef::as_ref),
&mut format_return_type,
f,
)?;

if group_parameters {
write!(f, [group(&format_args!(format_type_parameters, format_parameters))])
if should_group_parameters {
write!(f, [group(&format_parameters).should_expand(should_break_parameters)])?;
} else {
write!(f, [format_type_parameters, format_parameters])
}?;
write!(f, [format_parameters])?;
}

write!(f, [format_return_type])
}))
.fmt(f)
}

/// Decide if a constructor parameter list should prefer a
/// multi-line layout.
///
/// If there is more than one parameter, and any parameter has a modifier
/// (e.g. a TypeScript parameter property like `public/private/protected`, or
/// `readonly`, etc.), we break the parameters onto multiple lines.
//
/// Examples
/// --------
/// Multiple params with a modifier → break:
///
/// ```ts
/// // input
/// constructor(public x: number, y: number) {}
///
/// // preferred layout
/// constructor(
/// public x: number,
/// y: number,
/// ) {}
/// ```
///
/// Single param with a modifier → keep inline:
///
/// ```ts
/// constructor(private id: string) {}
/// ```
fn should_break_function_parameters<'a>(
params: &AstNode<'a, FormalParameters<'a>>,
f: &mut Formatter<'_, 'a>,
) -> bool {
params.parameters_count() > 1 && params.items().iter().any(|param| param.has_modifier())
}
177 changes: 177 additions & 0 deletions crates/oxc_formatter/src/write/function_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use oxc_ast::ast::*;

use crate::{
ast_nodes::AstNode,
format_args,
formatter::{Formatter, prelude::*},
utils::format_node_without_trailing_comments::FormatNodeWithoutTrailingComments,
write,
write::function::should_group_function_parameters,
};

use super::FormatWrite;

impl<'a> FormatWrite<'a> for AstNode<'a, TSFunctionType<'a>> {
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
format_grouped_parameters_with_return_type(
self.type_parameters(),
self.this_param.as_deref(),
self.params(),
Some(self.return_type()),
/* is_function_or_constructor_type */ true,
f,
)
}
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSConstructorType<'a>> {
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
let r#abstract = self.r#abstract();
let type_parameters = self.type_parameters();
let params = self.params();
let return_type = self.return_type();

write!(
f,
group(&format_args!(
r#abstract.then_some("abstract "),
"new",
space(),
&format_once(|f| {
format_grouped_parameters_with_return_type(
self.type_parameters(),
None,
self.params(),
Some(self.return_type()),
/* is_function_or_constructor_type */ true,
f,
)
})
))
)
}
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSCallSignatureDeclaration<'a>> {
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
format_grouped_parameters_with_return_type(
self.type_parameters(),
self.this_param.as_deref(),
self.params(),
self.return_type(),
/* is_function_or_constructor_type */ false,
f,
)
}
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSMethodSignature<'a>> {
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
let format_inner = format_once(|f| {
match self.kind() {
TSMethodSignatureKind::Method => {}
TSMethodSignatureKind::Get => {
write!(f, ["get", space()])?;
}
TSMethodSignatureKind::Set => {
write!(f, ["set", space()])?;
}
}
if self.computed() {
write!(f, "[")?;
}
write!(f, self.key())?;
if self.computed() {
write!(f, "]")?;
}
if self.optional() {
write!(f, "?")?;
}

let mut format_type_parameters = self.type_parameters().memoized();
let mut format_parameters = self.params().memoized();
format_type_parameters.inspect(f)?;
format_parameters.inspect(f)?;

let mut format_return_type = self.return_type().memoized();

let should_group_parameters = should_group_function_parameters(
self.type_parameters.as_deref(),
self.params.parameters_count() + usize::from(self.this_param.is_some()),
self.return_type.as_deref(),
&mut format_return_type,
f,
)?;

if should_group_parameters {
write!(f, group(&format_args!(&format_type_parameters, &format_parameters)))?;
} else {
write!(f, [format_type_parameters, format_parameters])?;
}

write!(f, group(&format_return_type))
});

write!(f, group(&format_inner))
}
}

impl<'a> FormatWrite<'a> for AstNode<'a, TSConstructSignatureDeclaration<'a>> {
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
write!(
f,
group(&format_args!(
"new",
space(),
&format_once(|f| {
format_grouped_parameters_with_return_type(
self.type_parameters(),
None,
self.params(),
self.return_type(),
/* is_function_or_constructor_type */ false,
f,
)
})
))
)
}
}

/// Based on <https://github.com/prettier/prettier/blob/7584432401a47a26943dd7a9ca9a8e032ead7285/src/language-js/print/type-annotation.js#L274-L331>
pub fn format_grouped_parameters_with_return_type<'a>(
type_parameters: Option<&AstNode<'a, TSTypeParameterDeclaration<'a>>>,
this_param: Option<&TSThisParameter<'a>>,
params: &AstNode<'a, FormalParameters<'a>>,
return_type: Option<&AstNode<'a, TSTypeAnnotation<'a>>>,
is_function_or_constructor_type: bool,
f: &mut Formatter<'_, 'a>,
) -> FormatResult<()> {
group(&format_once(|f| {
let mut format_type_parameters = type_parameters.memoized();
let mut format_parameters = params.memoized();
let mut format_return_type = return_type.map(FormatNodeWithoutTrailingComments).memoized();

// Inspect early, in case the `return_type` is formatted before `parameters`
// in `should_group_function_parameters`.
format_type_parameters.inspect(f)?;
format_parameters.inspect(f)?;

let group_parameters = should_group_function_parameters(
type_parameters.map(AsRef::as_ref),
params.parameters_count() + usize::from(this_param.is_some()),
return_type.map(AsRef::as_ref),
&mut format_return_type,
f,
)?;

if group_parameters {
write!(f, [group(&format_args!(format_type_parameters, format_parameters))])
} else {
write!(f, [format_type_parameters, format_parameters])
}?;

write!(f, [is_function_or_constructor_type.then_some(space()), format_return_type])
}))
.fmt(f)
}
Loading
Loading