Skip to content

Commit 2e20a98

Browse files
committed
feat(formatter): implement formatting for TSIntersectiontype
1 parent b3703d8 commit 2e20a98

File tree

5 files changed

+125
-30
lines changed

5 files changed

+125
-30
lines changed

crates/oxc_formatter/src/generated/format.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3807,9 +3807,19 @@ impl<'a> Format<'a> for AstNode<'a, TSUnionType<'a>> {
38073807
impl<'a> Format<'a> for AstNode<'a, TSIntersectionType<'a>> {
38083808
fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
38093809
let is_suppressed = f.comments().is_suppressed(self.span().start);
3810+
if !is_suppressed && format_type_cast_comment_node(self, false, f)? {
3811+
return Ok(());
3812+
}
38103813
self.format_leading_comments(f)?;
3814+
let needs_parentheses = self.needs_parentheses(f);
3815+
if needs_parentheses {
3816+
"(".fmt(f)?;
3817+
}
38113818
let result =
38123819
if is_suppressed { FormatSuppressedNode(self.span()).fmt(f) } else { self.write(f) };
3820+
if needs_parentheses {
3821+
")".fmt(f)?;
3822+
}
38133823
self.format_trailing_comments(f)?;
38143824
result
38153825
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use oxc_allocator::Vec;
2+
use oxc_ast::ast::*;
3+
use oxc_span::GetSpan;
4+
5+
use crate::{
6+
format_args,
7+
formatter::{FormatResult, Formatter, prelude::*},
8+
generated::ast_nodes::{AstNode, AstNodes},
9+
parentheses::NeedsParentheses,
10+
write,
11+
write::FormatWrite,
12+
};
13+
14+
impl<'a> FormatWrite<'a> for AstNode<'a, TSIntersectionType<'a>> {
15+
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
16+
let types = self.types();
17+
18+
if types.len() == 1 {
19+
return write!(f, self.types().first());
20+
}
21+
22+
let content = format_with(|f| {
23+
if self.needs_parentheses(f) {
24+
return write!(
25+
f,
26+
[
27+
indent(&format_once(|f| format_intersection_types(types, f))),
28+
soft_line_break()
29+
]
30+
);
31+
}
32+
33+
let is_inside_complex_tuple_type = match self.parent {
34+
AstNodes::TSTupleType(tuple) => tuple.element_types().len() > 1,
35+
_ => false,
36+
};
37+
38+
if is_inside_complex_tuple_type {
39+
write!(
40+
f,
41+
[
42+
indent(&format_args!(
43+
if_group_breaks(&format_args!(text("("), soft_line_break())),
44+
format_once(|f| format_intersection_types(types, f))
45+
)),
46+
soft_line_break(),
47+
if_group_breaks(&text(")"))
48+
]
49+
)
50+
} else {
51+
format_intersection_types(types, f)
52+
}
53+
});
54+
55+
write!(f, [group(&content)])
56+
}
57+
}
58+
59+
/// Check if a TSType is object-like (object literal, mapped type, etc.)
60+
fn is_object_like_type(ty: &TSType) -> bool {
61+
matches!(ty, TSType::TSTypeLiteral(_) | TSType::TSMappedType(_))
62+
}
63+
64+
// [Prettier applies]: https://github.com/prettier/prettier/blob/cd3e530c2e51fb8296c0fb7738a9afdd3a3a4410/src/language-js/print/type-annotation.js#L93-L120
65+
fn format_intersection_types<'a>(
66+
node: &AstNode<'a, Vec<'a, TSType<'a>>>,
67+
f: &mut Formatter<'_, 'a>,
68+
) -> FormatResult<()> {
69+
let last_index = node.len().saturating_sub(1);
70+
let mut is_prev_object_like = false;
71+
let mut is_chain_indented = false;
72+
73+
for (index, item) in node.iter().enumerate() {
74+
let is_object_like = is_object_like_type(item.as_ref());
75+
76+
// always inline first element
77+
if index == 0 {
78+
write!(f, item)?;
79+
} else {
80+
// If no object is involved, go to the next line if it breaks
81+
if !is_prev_object_like && !is_object_like {
82+
write!(f, [indent(&format_args!(soft_line_break_or_space(), item))])?;
83+
} else {
84+
write!(f, space())?;
85+
86+
if !is_prev_object_like || !is_object_like {
87+
// indent if we move from object to non-object or vice versa, otherwise keep inline
88+
is_chain_indented = index > 1;
89+
}
90+
91+
if is_chain_indented {
92+
write!(f, [indent(&item)])?;
93+
} else {
94+
write!(f, item)?;
95+
}
96+
}
97+
}
98+
99+
// Add separator if not the last element
100+
if index < last_index {
101+
write!(f, [space(), "&"])?;
102+
}
103+
104+
is_prev_object_like = is_object_like;
105+
}
106+
107+
Ok(())
108+
}

crates/oxc_formatter/src/write/mod.rs

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod export_declarations;
1313
mod function;
1414
mod import_declaration;
1515
mod import_expression;
16+
mod intersection_type;
1617
mod jsx;
1718
mod member_expression;
1819
mod object_like;
@@ -1210,27 +1211,6 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSConditionalType<'a>> {
12101211
}
12111212
}
12121213

1213-
impl<'a> FormatWrite<'a> for AstNode<'a, TSIntersectionType<'a>> {
1214-
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
1215-
let mut types = self.types().iter();
1216-
let needs_parentheses = self.needs_parentheses(f);
1217-
if needs_parentheses {
1218-
write!(f, "(")?;
1219-
}
1220-
if let Some(item) = types.next() {
1221-
write!(f, item)?;
1222-
1223-
for item in types {
1224-
write!(f, [" & ", item])?;
1225-
}
1226-
}
1227-
if needs_parentheses {
1228-
write!(f, ")")?;
1229-
}
1230-
Ok(())
1231-
}
1232-
}
1233-
12341214
impl<'a> FormatWrite<'a> for AstNode<'a, TSParenthesizedType<'a>> {
12351215
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
12361216
write!(f, ["(", self.type_annotation(), ")"])

tasks/ast_tools/src/generators/formatter/format.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const AST_NODE_WITHOUT_PRINTING_COMMENTS_LIST: &[&str] = &[
3838
];
3939

4040
const AST_NODE_NEEDS_PARENTHESES: &[&str] =
41-
&["TSTypeAssertion", "TSInferType", "TSConditionalType", "TSUnionType"];
41+
&["TSTypeAssertion", "TSInferType", "TSConditionalType", "TSUnionType", "TSIntersectionType"];
4242

4343
const NEEDS_IMPLEMENTING_FMT_WITH_OPTIONS: phf::Map<&'static str, &'static str> = phf::phf_map! {
4444
"ArrowFunctionExpression" => "FormatJsArrowFunctionExpressionOptions",

tasks/prettier_conformance/snapshots/prettier.ts.snap.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ts compatibility: 414/573 (72.25%)
1+
ts compatibility: 417/573 (72.77%)
22

33
# Failed
44

@@ -17,9 +17,8 @@ ts compatibility: 414/573 (72.25%)
1717
| typescript/arrow/16067.ts | 💥💥 | 95.92% |
1818
| typescript/arrow/comments.ts | 💥✨ | 44.44% |
1919
| typescript/as/as.ts | 💥 | 89.06% |
20-
| typescript/as/assignment2.ts | 💥 | 94.12% |
2120
| typescript/as/expression-statement.ts | 💥 | 75.00% |
22-
| typescript/assignment/issue-10846.ts | 💥 | 60.00% |
21+
| typescript/assignment/issue-10846.ts | 💥 | 57.14% |
2322
| typescript/assignment/issue-10848.tsx | 💥 | 52.12% |
2423
| typescript/assignment/issue-10850.ts | 💥 | 50.00% |
2524
| typescript/cast/generic-cast.ts | 💥 | 39.24% |
@@ -91,18 +90,18 @@ ts compatibility: 414/573 (72.25%)
9190
| typescript/export/export.ts | 💥 | 85.71% |
9291
| typescript/function-type/consistent.ts | 💥 | 70.83% |
9392
| typescript/function-type/type-annotation.ts | 💥 | 0.00% |
94-
| typescript/generic/arrow-return-type.ts | 💥 | 80.00% |
93+
| typescript/generic/arrow-return-type.ts | 💥 | 79.25% |
9594
| typescript/generic/issue-6899.ts | 💥 | 21.05% |
9695
| typescript/generic/object-method.ts | 💥 | 72.73% |
9796
| typescript/index-signature/static.ts | 💥 | 66.67% |
9897
| typescript/infer-extends/basic.ts | 💥 | 71.43% |
9998
| typescript/interface/comments-generic.ts | 💥💥 | 30.00% |
10099
| typescript/interface/ignore.ts | 💥💥 | 88.26% |
101-
| typescript/interface/long-type-parameters/long-type-parameters.ts | 💥💥 | 45.00% |
100+
| typescript/interface/long-type-parameters/long-type-parameters.ts | 💥💥 | 43.05% |
102101
| typescript/interface2/comments-declare.ts | 💥 | 66.67% |
103102
| typescript/interface2/comments.ts | 💥 | 78.87% |
104103
| typescript/interface2/break/break.ts | 💥💥💥 | 80.23% |
105-
| typescript/intersection/intersection-parens.ts | 💥💥 | 59.14% |
104+
| typescript/intersection/intersection-parens.ts | 💥💥 | 72.25% |
106105
| typescript/intersection/type-arguments.ts | 💥💥 | 46.67% |
107106
| typescript/intersection/consistent-with-flow/intersection-parens.ts | 💥 | 69.77% |
108107
| typescript/key-remapping-in-mapped-types/key-remapping.ts | 💥 | 23.53% |
@@ -130,7 +129,6 @@ ts compatibility: 414/573 (72.25%)
130129
| typescript/prettier-ignore/prettier-ignore-parenthesized-type.ts | 💥 | 0.00% |
131130
| typescript/rest-type/complex.ts | 💥 | 0.00% |
132131
| typescript/rest-type/infer-type.ts | 💥 | 80.00% |
133-
| typescript/satisfies-operators/assignment.ts | 💥💥 | 90.91% |
134132
| typescript/satisfies-operators/expression-statement.ts | 💥💥 | 78.38% |
135133
| typescript/satisfies-operators/lhs.ts | 💥✨ | 35.00% |
136134
| typescript/template-literal-types/template-literal-types.ts | 💥 | 80.00% |
@@ -156,7 +154,6 @@ ts compatibility: 414/573 (72.25%)
156154
| typescript/typeparams/long-function-arg.ts | 💥 | 76.92% |
157155
| typescript/typeparams/empty-parameters-with-arrow-function/issue-13817.ts | 💥 | 66.67% |
158156
| typescript/typeparams/trailing-comma/type-paramters.ts | 💥💥💥 | 28.57% |
159-
| typescript/union/comments.ts | 💥 | 84.21% |
160157
| typescript/union/inlining.ts | 💥 | 77.37% |
161158
| typescript/union/union-parens.ts | 💥 | 92.59% |
162159
| typescript/union/with-type-params.ts | 💥 | 37.50% |

0 commit comments

Comments
 (0)