Skip to content

Commit

Permalink
Formatter: TS Intersection & Union types rome#3162
Browse files Browse the repository at this point in the history
  • Loading branch information
denbezrukov committed Sep 12, 2022
1 parent e315553 commit 9c790af
Show file tree
Hide file tree
Showing 23 changed files with 347 additions and 1,496 deletions.
8 changes: 3 additions & 5 deletions crates/rome_js_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -750,11 +750,9 @@ function() {
// use this test check if your snippet prints as you wish, without using a snapshot
fn quick_test() {
let src = r#"
type Example = {
[A in B]: T;
} & {
[A in B]: T;
};
type A =
/*das*/B | C;
"#;
let syntax = SourceType::tsx();
Expand Down
7 changes: 2 additions & 5 deletions crates/rome_js_formatter/src/ts/expressions/type_arguments.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::prelude::*;
use crate::utils::should_hug_type;
use crate::{prelude::*, utils::is_object_like_type};
use rome_formatter::write;
use rome_js_syntax::{
JsAnyExpression, JsSyntaxKind, JsVariableDeclarator, TsType, TsTypeArguments,
Expand Down Expand Up @@ -28,10 +28,7 @@ impl FormatNodeRule<TsTypeArguments> for FormatTsTypeArguments {
let first_argument = first_argument?;

// first argument is not mapped type or object type
if !matches!(
first_argument,
TsType::TsObjectType(_) | TsType::TsMappedType(_)
) {
if !is_object_like_type(&first_argument) {
// we then go up until we can find a potential type annotation,
// meaning four levels up
let maybe_type_annotation = first_argument.syntax().ancestors().nth(4);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::prelude::*;
use crate::{prelude::*, utils::is_object_like_type};
use rome_formatter::{format_args, write};
use rome_js_syntax::{TsIntersectionTypeElementList, TsType};
use rome_js_syntax::TsIntersectionTypeElementList;
use rome_rowan::AstSeparatedList;

#[derive(Debug, Clone, Default)]
Expand All @@ -19,8 +19,7 @@ impl FormatRule<TsIntersectionTypeElementList> for FormatTsIntersectionTypeEleme
for (index, element) in node.elements().enumerate() {
let node = element.node()?;

let is_object_type_like =
matches!(node, TsType::TsMappedType(_) | TsType::TsObjectType(_));
let is_object_type_like = is_object_like_type(node);

// always inline first element
if index == 0 {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::prelude::*;
use crate::utils::should_hug_type;
use rome_formatter::write;
use rome_js_syntax::{JsLanguage, JsSyntaxKind, TsType, TsUnionTypeVariantList};
use rome_js_factory::make::token;
use rome_js_syntax::{JsLanguage, TsType, TsUnionTypeVariantList};
use rome_rowan::{AstSeparatedElement, AstSeparatedList};

#[derive(Debug, Clone, Default)]
Expand Down
77 changes: 5 additions & 72 deletions crates/rome_js_formatter/src/ts/types/intersection_type.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
use crate::prelude::*;

use crate::parentheses::{
is_in_many_type_union_or_intersection_list, operator_type_or_higher_needs_parens,
NeedsParentheses,
use crate::parentheses::NeedsParentheses;
use crate::utils::{
union_or_intersection_type_needs_parentheses, FormatTypeMemberSeparator,
TsIntersectionOrUnionTypeList,
};
use crate::utils::FormatTypeMemberSeparator;
use rome_formatter::{format_args, write};
use rome_js_syntax::{
JsSyntaxKind, JsSyntaxNode, TsIntersectionTypeElementList, TsIntersectionTypeFields,
TsUnionTypeVariantList,
};
use rome_js_syntax::{JsSyntaxToken, TsIntersectionType};
use rome_js_syntax::{TsIntersectionType, TsIntersectionTypeFields, JsSyntaxNode};

#[derive(Debug, Clone, Default)]
pub struct FormatTsIntersectionType;
Expand All @@ -35,38 +31,6 @@ impl FormatNodeRule<TsIntersectionType> for FormatTsIntersectionType {
}
}

pub struct FormatTypeSetLeadingSeparator<'a> {
pub(crate) separator: JsSyntaxKind,
pub(crate) leading_separator: Option<&'a JsSyntaxToken>,
pub(crate) new_line: bool,
}

impl Format<JsFormatContext> for FormatTypeSetLeadingSeparator<'_> {
fn fmt(&self, f: &mut JsFormatter) -> FormatResult<()> {
match &self.leading_separator {
Some(token) => {
let content = format_with(|f| {
if self.new_line {
write!(f, [soft_line_break_or_space()])?;
}
write!(f, [token.format(), space()])
});
format_only_if_breaks(token, &content).fmt(f)
}
None => {
let content = format_with(|f| {
if self.new_line {
write!(f, [soft_line_break_or_space()])?;
}
write!(f, [format_inserted(self.separator), space()])
});

write!(f, [if_group_breaks(&content)])
}
}
}
}

impl NeedsParentheses for TsIntersectionType {
fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool {
union_or_intersection_type_needs_parentheses(
Expand All @@ -77,37 +41,6 @@ impl NeedsParentheses for TsIntersectionType {
}
}

pub(super) fn union_or_intersection_type_needs_parentheses(
node: &JsSyntaxNode,
parent: &JsSyntaxNode,
types: &TsIntersectionOrUnionTypeList,
) -> bool {
debug_assert!(matches!(
node.kind(),
JsSyntaxKind::TS_INTERSECTION_TYPE | JsSyntaxKind::TS_UNION_TYPE
));

if is_in_many_type_union_or_intersection_list(node, parent) {
types.len() > 1
} else {
operator_type_or_higher_needs_parens(node, parent)
}
}

pub(super) enum TsIntersectionOrUnionTypeList {
TsIntersectionTypeElementList(TsIntersectionTypeElementList),
TsUnionTypeVariantList(TsUnionTypeVariantList),
}

impl TsIntersectionOrUnionTypeList {
fn len(&self) -> usize {
match self {
TsIntersectionOrUnionTypeList::TsIntersectionTypeElementList(list) => list.len(),
TsIntersectionOrUnionTypeList::TsUnionTypeVariantList(list) => list.len(),
}
}
}

#[cfg(test)]
mod tests {

Expand Down
100 changes: 77 additions & 23 deletions crates/rome_js_formatter/src/ts/types/union_type.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use crate::parentheses::NeedsParentheses;
use crate::prelude::*;
use crate::ts::types::intersection_type::{
union_or_intersection_type_needs_parentheses, FormatTypeSetLeadingSeparator,
TsIntersectionOrUnionTypeList,
use crate::utils::{
has_leading_own_line_comment, should_hug_type, union_or_intersection_type_needs_parentheses,
FormatTypeMemberSeparator, TsIntersectionOrUnionTypeList,
};
use crate::utils::{should_hug_type, FormatTypeMemberSeparator};
use rome_formatter::{format_args, write, Buffer};
use rome_js_factory::make::token;
use rome_js_syntax::{JsSyntaxKind, TsTupleType, TsType, TsUnionType};
use rome_js_syntax::{JsSyntaxKind, JsSyntaxToken, TsTupleTypeElementList, TsType, TsUnionType};
use rome_js_syntax::{JsSyntaxNode, TsUnionTypeFields};

#[derive(Debug, Clone, Default)]
Expand All @@ -20,7 +18,7 @@ impl FormatNodeRule<TsUnionType> for FormatTsUnionType {
types,
} = node.as_fields();

let should_hug = should_hug_type(&node.clone().into());
let should_hug = should_hug_type(&TsType::from(node.clone()));

if should_hug {
return write!(
Expand All @@ -32,29 +30,41 @@ impl FormatNodeRule<TsUnionType> for FormatTsUnionType {
);
}

let has_leading_own_line_comment = has_leading_own_line_comment(node.syntax());

let should_indent = {
let parent_kind = node.syntax().parent().map(|p| p.kind());

// TODO more conditions??
!matches!(
parent_kind,
Some(
JsSyntaxKind::TS_REFERENCE_TYPE
| JsSyntaxKind::TS_TYPE_ASSERTION_EXPRESSION
| JsSyntaxKind::TS_TUPLE_TYPE
| JsSyntaxKind::TS_TYPE_ASSERTION_ASSIGNMENT
| JsSyntaxKind::TS_FUNCTION_TYPE
| JsSyntaxKind::TS_TYPE_ARGUMENTS
)
)
// These parents have indent for their content, so we don't need to indent here
!match parent_kind {
Some(JsSyntaxKind::TS_TYPE_ALIAS_DECLARATION) => {
has_leading_own_line_comment
}
parent_kind => {
matches!(
parent_kind,
Some(
JsSyntaxKind::TS_TYPE_ASSERTION_EXPRESSION
| JsSyntaxKind::TS_TUPLE_TYPE_ELEMENT_LIST
| JsSyntaxKind::TS_TYPE_ASSERTION_ASSIGNMENT
| JsSyntaxKind::TS_TYPE_ARGUMENT_LIST
)
)
}
}
};

let body = format_with(|f| {
write!(
f,
[
FormatTypeSetLeadingSeparator {
new_line: should_indent, //shouldIndent && !hasLeadingOwnLineComment(options.originalText, node);
// stange comment
// type A = [
// /*lol*/
// A | B,
// ]
new_line: should_indent && !has_leading_own_line_comment,
separator: JsSyntaxKind::PIPE,
leading_separator: leading_separator_token.as_ref()
},
Expand All @@ -63,17 +73,29 @@ impl FormatNodeRule<TsUnionType> for FormatTsUnionType {
)
});

// we add parentheses if union is in intersaction
//
if node.needs_parentheses() {
return write!(f, [group(&format_args![indent(&body), soft_line_break()])]);
}

//rename
let is_tuple_with_args = node
.parent::<TsTupleType>()
.map_or(false, |tuple| tuple.elements().len() > 1);
.parent::<TsTupleTypeElementList>()
.map_or(false, |tuple| tuple.len() > 1);

if is_tuple_with_args {
write![f, [group(&format_args!(if_group_breaks(token(""))))]]
write!(
f,
[group(&format_args![
indent(&format_args![
if_group_breaks(&format_args![text("("), soft_line_break()]),
body
]),
soft_line_break(),
if_group_breaks(&text(")"))
])]
)
} else {
write![
f,
Expand Down Expand Up @@ -102,3 +124,35 @@ impl NeedsParentheses for TsUnionType {
)
}
}

pub struct FormatTypeSetLeadingSeparator<'a> {
pub(crate) separator: JsSyntaxKind,
pub(crate) leading_separator: Option<&'a JsSyntaxToken>,
pub(crate) new_line: bool,
}

impl Format<JsFormatContext> for FormatTypeSetLeadingSeparator<'_> {
fn fmt(&self, f: &mut JsFormatter) -> FormatResult<()> {
match &self.leading_separator {
Some(token) => {
let content = format_with(|f| {
if self.new_line {
write!(f, [soft_line_break_or_space()])?;
}
write!(f, [token.format(), space()])
});
format_only_if_breaks(token, &content).fmt(f)
}
None => {
let content = format_with(|f| {
if self.new_line {
write!(f, [soft_line_break_or_space()])?;
}
write!(f, [format_inserted(self.separator), space()])
});

write!(f, [if_group_breaks(&content)])
}
}
}
}
32 changes: 3 additions & 29 deletions crates/rome_js_formatter/src/utils/assignment_like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::utils::member_chain::is_member_call_chain;
use crate::utils::object::write_member_name;
use crate::utils::{JsAnyBinaryLikeExpression, JsAnyBinaryLikeLeftExpression};
use rome_formatter::{format_args, write, CstFormatContext, FormatOptions, VecBuffer};
use rome_js_syntax::JsAnyLiteralExpression;
use rome_js_syntax::{
JsAnyAssignmentPattern, JsAnyBindingPattern, JsAnyCallArgument, JsAnyClassMemberName,
JsAnyExpression, JsAnyFunctionBody, JsAnyObjectAssignmentPatternMember,
Expand All @@ -15,10 +16,11 @@ use rome_js_syntax::{
TsAnyVariableAnnotation, TsIdentifierBinding, TsPropertySignatureClassMember,
TsPropertySignatureClassMemberFields, TsType, TsTypeAliasDeclaration, TsTypeArguments,
};
use rome_js_syntax::{JsAnyLiteralExpression, JsSyntaxNode};
use rome_rowan::{declare_node_union, AstNode, SyntaxResult};
use std::iter;

use super::has_leading_own_line_comment;

declare_node_union! {
pub(crate) JsAnyAssignmentLike =
JsPropertyObjectMember |
Expand Down Expand Up @@ -874,34 +876,6 @@ pub(crate) fn should_break_after_operator(right: &JsAnyExpression) -> SyntaxResu

Ok(result)
}
/// Tests if the node has any leading comment that will be placed on its own line.
pub(crate) fn has_leading_own_line_comment(node: &JsSyntaxNode) -> bool {
if let Some(leading_trivia) = node.first_leading_trivia() {
let mut first_comment = true;
let mut after_comment = false;
let mut after_new_line = false;

for piece in leading_trivia.pieces() {
if piece.is_comments() {
if after_new_line && first_comment {
return true;
} else {
first_comment = false;
after_comment = true;
}
} else if piece.is_newline() {
if after_comment {
return true;
} else {
after_new_line = true;
}
} else if piece.is_skipped() {
return false;
}
}
}
false
}

impl Format<JsFormatContext> for JsAnyAssignmentLike {
fn fmt(&self, f: &mut JsFormatter) -> FormatResult<()> {
Expand Down
3 changes: 2 additions & 1 deletion crates/rome_js_formatter/src/utils/binary_like_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,13 @@ use crate::parentheses::{
};

use crate::js::expressions::static_member_expression::JsAnyStaticMemberLike;
use crate::utils::assignment_like::has_leading_own_line_comment;
use rome_rowan::{declare_node_union, AstNode, SyntaxResult};
use std::fmt::Debug;
use std::hash::Hash;
use std::iter::FusedIterator;

use super::has_leading_own_line_comment;

declare_node_union! {
pub(crate) JsAnyBinaryLikeExpression = JsLogicalExpression | JsBinaryExpression | JsInstanceofExpression | JsInExpression
}
Expand Down
Loading

0 comments on commit 9c790af

Please sign in to comment.