Skip to content

Commit 19b1151

Browse files
committed
fix(formatter): correct preserving parentheses for TSInferType and TSTypeOperator
1 parent 682e85c commit 19b1151

File tree

5 files changed

+77
-40
lines changed

5 files changed

+77
-40
lines changed

crates/oxc_formatter/src/generated/format.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4611,9 +4611,19 @@ impl<'a> Format<'a> for AstNode<'a, TSFunctionType<'a>> {
46114611
impl<'a> Format<'a> for AstNode<'a, TSConstructorType<'a>> {
46124612
fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
46134613
let is_suppressed = f.comments().is_suppressed(self.span().start);
4614+
if !is_suppressed && format_type_cast_comment_node(self, false, f)? {
4615+
return Ok(());
4616+
}
46144617
self.format_leading_comments(f)?;
4618+
let needs_parentheses = self.needs_parentheses(f);
4619+
if needs_parentheses {
4620+
"(".fmt(f)?;
4621+
}
46154622
let result =
46164623
if is_suppressed { FormatSuppressedNode(self.span()).fmt(f) } else { self.write(f) };
4624+
if needs_parentheses {
4625+
")".fmt(f)?;
4626+
}
46174627
self.format_trailing_comments(f)?;
46184628
result
46194629
}

crates/oxc_formatter/src/parentheses/ts_type.rs

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,31 +30,24 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, TSType<'a>> {
3030
impl<'a> NeedsParentheses<'a> for AstNode<'a, TSFunctionType<'a>> {
3131
#[inline]
3232
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
33-
match self.parent {
34-
AstNodes::TSConditionalType(ty) => {
35-
ty.extends_type().span() == self.span() || ty.check_type().span() == self.span()
36-
}
37-
AstNodes::TSUnionType(_) | AstNodes::TSIntersectionType(_) => true,
38-
_ => false,
39-
}
33+
function_like_type_needs_parentheses(self.span(), self.parent, Some(&self.return_type))
4034
}
4135
}
4236

4337
impl<'a> NeedsParentheses<'a> for AstNode<'a, TSInferType<'a>> {
4438
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
45-
matches!(self.parent, AstNodes::TSArrayType(_) | AstNodes::TSTypeOperator(_))
39+
match self.parent {
40+
AstNodes::TSIntersectionType(_) | AstNodes::TSUnionType(_) => true,
41+
AstNodes::TSRestType(_) => false,
42+
_ => operator_type_or_higher_needs_parens(self.span, self.parent),
43+
}
4644
}
4745
}
4846

4947
impl<'a> NeedsParentheses<'a> for AstNode<'a, TSConstructorType<'a>> {
48+
#[inline]
5049
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
51-
match self.parent {
52-
AstNodes::TSConditionalType(ty) => {
53-
ty.extends_type().span() == self.span() || ty.check_type().span() == self.span()
54-
}
55-
AstNodes::TSUnionType(_) | AstNodes::TSIntersectionType(_) => true,
56-
_ => false,
57-
}
50+
function_like_type_needs_parentheses(self.span(), self.parent, Some(&self.return_type))
5851
}
5952
}
6053

@@ -71,9 +64,60 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, TSUnionType<'a>> {
7164
}
7265

7366
/// Returns `true` if a TS primary type needs parentheses
67+
/// Common logic for determining if function-like types (TSFunctionType, TSConstructorType)
68+
/// need parentheses based on their parent context.
69+
///
70+
/// Ported from Biome's function_like_type_needs_parentheses
71+
fn function_like_type_needs_parentheses<'a>(
72+
span: Span,
73+
parent: &'a AstNodes<'a>,
74+
return_type: Option<&'a TSTypeAnnotation<'a>>,
75+
) -> bool {
76+
match parent {
77+
// Arrow function return types need parens
78+
AstNodes::TSTypeAnnotation(type_annotation) => {
79+
matches!(type_annotation.parent, AstNodes::ArrowFunctionExpression(_))
80+
}
81+
// In conditional types
82+
AstNodes::TSConditionalType(conditional) => {
83+
let is_check_type = conditional.check_type().span() == span;
84+
if is_check_type {
85+
return true;
86+
}
87+
88+
let is_extends_type = conditional.extends_type().span() == span;
89+
if is_extends_type {
90+
// Need parentheses if return type is TSInferType with constraint
91+
// or TSTypePredicate with type annotation
92+
if let Some(return_type) = return_type {
93+
match &return_type.type_annotation {
94+
TSType::TSInferType(infer_type) => {
95+
return infer_type.type_parameter.constraint.is_some();
96+
}
97+
TSType::TSTypePredicate(predicate) => {
98+
return predicate.type_annotation.is_some();
99+
}
100+
_ => {}
101+
}
102+
}
103+
}
104+
false
105+
}
106+
AstNodes::TSUnionType(_) | AstNodes::TSIntersectionType(_) => true,
107+
_ => operator_type_or_higher_needs_parens(span, parent),
108+
}
109+
}
110+
111+
/// Returns `true` if a TS primary type needs parentheses
112+
/// This is for types that have higher precedence operators as parents
74113
fn operator_type_or_higher_needs_parens(span: Span, parent: &AstNodes) -> bool {
75114
match parent {
76-
AstNodes::TSArrayType(_) | AstNodes::TSTypeOperator(_) | AstNodes::TSRestType(_) => true,
115+
// These parent types always require parentheses for their operands
116+
AstNodes::TSArrayType(_)
117+
| AstNodes::TSTypeOperator(_)
118+
| AstNodes::TSRestType(_)
119+
| AstNodes::TSOptionalType(_) => true,
120+
// Indexed access requires parens if this is the object type
77121
AstNodes::TSIndexedAccessType(indexed) => indexed.object_type.span() == span,
78122
_ => false,
79123
}
@@ -97,18 +141,13 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, TSConditionalType<'a>> {
97141
ty.extends_type().span() == self.span() || ty.check_type().span() == self.span()
98142
}
99143
AstNodes::TSUnionType(_) | AstNodes::TSIntersectionType(_) => true,
100-
_ => false,
144+
_ => operator_type_or_higher_needs_parens(self.span, self.parent),
101145
}
102146
}
103147
}
104148

105149
impl<'a> NeedsParentheses<'a> for AstNode<'a, TSTypeOperator<'a>> {
106150
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
107-
matches!(
108-
self.parent,
109-
AstNodes::TSArrayType(_)
110-
| AstNodes::TSTypeOperator(_)
111-
| AstNodes::TSIndexedAccessType(_)
112-
)
151+
operator_type_or_higher_needs_parens(self.span(), self.parent)
113152
}
114153
}

crates/oxc_formatter/src/write/mod.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,9 +1824,6 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSConstructorType<'a>> {
18241824
let params = self.params();
18251825
let return_type = self.return_type();
18261826

1827-
if self.needs_parentheses(f) {
1828-
write!(f, "(")?;
1829-
}
18301827
if r#abstract {
18311828
write!(f, ["abstract", space()])?;
18321829
}
@@ -1843,9 +1840,6 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSConstructorType<'a>> {
18431840
return_type.type_annotation()
18441841
]
18451842
);
1846-
if self.needs_parentheses(f) {
1847-
write!(f, ")")?;
1848-
}
18491843
Ok(())
18501844
}
18511845
}

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", "TSIntersectionType"];
41+
&["TSTypeAssertion", "TSInferType", "TSConditionalType", "TSUnionType", "TSIntersectionType", "TSConstructorType"];
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: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ts compatibility: 475/573 (82.90%)
1+
ts compatibility: 481/573 (83.94%)
22

33
# Failed
44

@@ -34,13 +34,12 @@ ts compatibility: 475/573 (82.90%)
3434
| typescript/comments/type_literals.ts | 💥 | 68.97% |
3535
| typescript/comments/union.ts | 💥 | 83.33% |
3636
| typescript/compiler/anyIsAssignableToObject.ts | 💥 | 75.00% |
37-
| typescript/compiler/contextualSignatureInstantiation2.ts | 💥 | 88.89% |
3837
| typescript/compiler/indexSignatureWithInitializer.ts | 💥 | 75.00% |
3938
| typescript/conditional-types/comments.ts | 💥💥 | 60.21% |
40-
| typescript/conditional-types/conditonal-types.ts | 💥💥 | 82.25% |
41-
| typescript/conditional-types/infer-type.ts | 💥💥 | 43.22% |
39+
| typescript/conditional-types/conditonal-types.ts | 💥 | 34.48% |
40+
| typescript/conditional-types/infer-type.ts | 💥 | 4.76% |
4241
| typescript/conditional-types/nested-in-condition.ts | 💥✨ | 15.79% |
43-
| typescript/conditional-types/new-ternary-spec.ts | 💥💥 | 52.64% |
42+
| typescript/conditional-types/new-ternary-spec.ts | 💥💥 | 54.03% |
4443
| typescript/conditional-types/parentheses.ts | 💥💥 | 60.24% |
4544
| typescript/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMixedWithModifiers.ts | 💥 | 86.67% |
4645
| typescript/conformance/types/functions/functionOverloadErrorsSyntax.ts | 💥 | 0.00% |
@@ -62,7 +61,6 @@ ts compatibility: 475/573 (82.90%)
6261
| typescript/function-type/consistent.ts | 💥 | 70.83% |
6362
| typescript/function-type/type-annotation.ts | 💥 | 0.00% |
6463
| typescript/index-signature/static.ts | 💥 | 66.67% |
65-
| typescript/infer-extends/basic.ts | 💥 | 90.48% |
6664
| typescript/interface/comments-generic.ts | 💥💥 | 30.00% |
6765
| typescript/interface/ignore.ts | 💥💥 | 88.26% |
6866
| typescript/interface2/comments-declare.ts | 💥 | 66.67% |
@@ -80,14 +78,10 @@ ts compatibility: 475/573 (82.90%)
8078
| typescript/non-null/optional-chain.ts | 💥 | 72.22% |
8179
| typescript/nosemi/index-signature.ts | 💥 | 75.00% |
8280
| typescript/object-multiline/multiline.ts | 💥✨ | 23.21% |
83-
| typescript/optional-type/complex.ts | 💥 | 0.00% |
84-
| typescript/optional-variance/basic.ts | 💥 | 98.36% |
85-
| typescript/optional-variance/with-jsx.tsx | 💥 | 98.36% |
8681
| typescript/override-modifiers/override-modifier.ts | 💥 | 25.00% |
8782
| typescript/prettier-ignore/mapped-types.ts | 💥 | 54.72% |
8883
| typescript/prettier-ignore/prettier-ignore-nested-unions.ts | 💥 | 29.17% |
8984
| typescript/prettier-ignore/prettier-ignore-parenthesized-type.ts | 💥 | 0.00% |
90-
| typescript/rest-type/complex.ts | 💥 | 0.00% |
9185
| typescript/satisfies-operators/expression-statement.ts | 💥💥 | 78.38% |
9286
| typescript/satisfies-operators/lhs.ts | 💥✨ | 35.00% |
9387
| typescript/tuple/trailing-comma-for-empty-tuples.ts | 💥💥💥 | 16.67% |

0 commit comments

Comments
 (0)