diff --git a/crates/oxc_formatter/src/generated/format.rs b/crates/oxc_formatter/src/generated/format.rs index 7c0dce60de564..414ae9dfa7056 100644 --- a/crates/oxc_formatter/src/generated/format.rs +++ b/crates/oxc_formatter/src/generated/format.rs @@ -4611,9 +4611,19 @@ impl<'a> Format<'a> for AstNode<'a, TSFunctionType<'a>> { impl<'a> Format<'a> for AstNode<'a, TSConstructorType<'a>> { fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { let is_suppressed = f.comments().is_suppressed(self.span().start); + if !is_suppressed && format_type_cast_comment_node(self, false, f)? { + return Ok(()); + } self.format_leading_comments(f)?; + let needs_parentheses = self.needs_parentheses(f); + if needs_parentheses { + "(".fmt(f)?; + } let result = if is_suppressed { FormatSuppressedNode(self.span()).fmt(f) } else { self.write(f) }; + if needs_parentheses { + ")".fmt(f)?; + } self.format_trailing_comments(f)?; result } diff --git a/crates/oxc_formatter/src/parentheses/ts_type.rs b/crates/oxc_formatter/src/parentheses/ts_type.rs index 4a367ebc835ef..f0ba72518536a 100644 --- a/crates/oxc_formatter/src/parentheses/ts_type.rs +++ b/crates/oxc_formatter/src/parentheses/ts_type.rs @@ -30,31 +30,24 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, TSType<'a>> { impl<'a> NeedsParentheses<'a> for AstNode<'a, TSFunctionType<'a>> { #[inline] fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { - match self.parent { - AstNodes::TSConditionalType(ty) => { - ty.extends_type().span() == self.span() || ty.check_type().span() == self.span() - } - AstNodes::TSUnionType(_) | AstNodes::TSIntersectionType(_) => true, - _ => false, - } + function_like_type_needs_parentheses(self.span(), self.parent, Some(&self.return_type)) } } impl<'a> NeedsParentheses<'a> for AstNode<'a, TSInferType<'a>> { fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { - matches!(self.parent, AstNodes::TSArrayType(_) | AstNodes::TSTypeOperator(_)) + match self.parent { + AstNodes::TSIntersectionType(_) | AstNodes::TSUnionType(_) => true, + AstNodes::TSRestType(_) => false, + _ => operator_type_or_higher_needs_parens(self.span, self.parent), + } } } impl<'a> NeedsParentheses<'a> for AstNode<'a, TSConstructorType<'a>> { + #[inline] fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { - match self.parent { - AstNodes::TSConditionalType(ty) => { - ty.extends_type().span() == self.span() || ty.check_type().span() == self.span() - } - AstNodes::TSUnionType(_) | AstNodes::TSIntersectionType(_) => true, - _ => false, - } + function_like_type_needs_parentheses(self.span(), self.parent, Some(&self.return_type)) } } @@ -71,9 +64,60 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, TSUnionType<'a>> { } /// Returns `true` if a TS primary type needs parentheses +/// Common logic for determining if function-like types (TSFunctionType, TSConstructorType) +/// need parentheses based on their parent context. +/// +/// Ported from Biome's function_like_type_needs_parentheses +fn function_like_type_needs_parentheses<'a>( + span: Span, + parent: &'a AstNodes<'a>, + return_type: Option<&'a TSTypeAnnotation<'a>>, +) -> bool { + match parent { + // Arrow function return types need parens + AstNodes::TSTypeAnnotation(type_annotation) => { + matches!(type_annotation.parent, AstNodes::ArrowFunctionExpression(_)) + } + // In conditional types + AstNodes::TSConditionalType(conditional) => { + let is_check_type = conditional.check_type().span() == span; + if is_check_type { + return true; + } + + let is_extends_type = conditional.extends_type().span() == span; + if is_extends_type { + // Need parentheses if return type is TSInferType with constraint + // or TSTypePredicate with type annotation + if let Some(return_type) = return_type { + match &return_type.type_annotation { + TSType::TSInferType(infer_type) => { + return infer_type.type_parameter.constraint.is_some(); + } + TSType::TSTypePredicate(predicate) => { + return predicate.type_annotation.is_some(); + } + _ => {} + } + } + } + false + } + AstNodes::TSUnionType(_) | AstNodes::TSIntersectionType(_) => true, + _ => operator_type_or_higher_needs_parens(span, parent), + } +} + +/// Returns `true` if a TS primary type needs parentheses +/// This is for types that have higher precedence operators as parents fn operator_type_or_higher_needs_parens(span: Span, parent: &AstNodes) -> bool { match parent { - AstNodes::TSArrayType(_) | AstNodes::TSTypeOperator(_) | AstNodes::TSRestType(_) => true, + // These parent types always require parentheses for their operands + AstNodes::TSArrayType(_) + | AstNodes::TSTypeOperator(_) + | AstNodes::TSRestType(_) + | AstNodes::TSOptionalType(_) => true, + // Indexed access requires parens if this is the object type AstNodes::TSIndexedAccessType(indexed) => indexed.object_type.span() == span, _ => false, } @@ -97,18 +141,13 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, TSConditionalType<'a>> { ty.extends_type().span() == self.span() || ty.check_type().span() == self.span() } AstNodes::TSUnionType(_) | AstNodes::TSIntersectionType(_) => true, - _ => false, + _ => operator_type_or_higher_needs_parens(self.span, self.parent), } } } impl<'a> NeedsParentheses<'a> for AstNode<'a, TSTypeOperator<'a>> { fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { - matches!( - self.parent, - AstNodes::TSArrayType(_) - | AstNodes::TSTypeOperator(_) - | AstNodes::TSIndexedAccessType(_) - ) + operator_type_or_higher_needs_parens(self.span(), self.parent) } } diff --git a/crates/oxc_formatter/src/write/mod.rs b/crates/oxc_formatter/src/write/mod.rs index 0e22572918d34..18020752bcb83 100644 --- a/crates/oxc_formatter/src/write/mod.rs +++ b/crates/oxc_formatter/src/write/mod.rs @@ -1824,9 +1824,6 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSConstructorType<'a>> { let params = self.params(); let return_type = self.return_type(); - if self.needs_parentheses(f) { - write!(f, "(")?; - } if r#abstract { write!(f, ["abstract", space()])?; } @@ -1843,9 +1840,6 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSConstructorType<'a>> { return_type.type_annotation() ] ); - if self.needs_parentheses(f) { - write!(f, ")")?; - } Ok(()) } } diff --git a/tasks/ast_tools/src/generators/formatter/format.rs b/tasks/ast_tools/src/generators/formatter/format.rs index 3f70fc38657c1..df1f51e002e81 100644 --- a/tasks/ast_tools/src/generators/formatter/format.rs +++ b/tasks/ast_tools/src/generators/formatter/format.rs @@ -37,8 +37,14 @@ const AST_NODE_WITHOUT_PRINTING_COMMENTS_LIST: &[&str] = &[ "ExportSpecifier", ]; -const AST_NODE_NEEDS_PARENTHESES: &[&str] = - &["TSTypeAssertion", "TSInferType", "TSConditionalType", "TSUnionType", "TSIntersectionType"]; +const AST_NODE_NEEDS_PARENTHESES: &[&str] = &[ + "TSTypeAssertion", + "TSInferType", + "TSConditionalType", + "TSUnionType", + "TSIntersectionType", + "TSConstructorType", +]; const NEEDS_IMPLEMENTING_FMT_WITH_OPTIONS: phf::Map<&'static str, &'static str> = phf::phf_map! { "ArrowFunctionExpression" => "FormatJsArrowFunctionExpressionOptions", diff --git a/tasks/prettier_conformance/snapshots/prettier.ts.snap.md b/tasks/prettier_conformance/snapshots/prettier.ts.snap.md index ee19fc0b5a792..5a373626111c5 100644 --- a/tasks/prettier_conformance/snapshots/prettier.ts.snap.md +++ b/tasks/prettier_conformance/snapshots/prettier.ts.snap.md @@ -1,4 +1,4 @@ -ts compatibility: 475/573 (82.90%) +ts compatibility: 481/573 (83.94%) # Failed @@ -34,13 +34,12 @@ ts compatibility: 475/573 (82.90%) | typescript/comments/type_literals.ts | 💥 | 68.97% | | typescript/comments/union.ts | 💥 | 83.33% | | typescript/compiler/anyIsAssignableToObject.ts | 💥 | 75.00% | -| typescript/compiler/contextualSignatureInstantiation2.ts | 💥 | 88.89% | | typescript/compiler/indexSignatureWithInitializer.ts | 💥 | 75.00% | | typescript/conditional-types/comments.ts | 💥💥 | 60.21% | -| typescript/conditional-types/conditonal-types.ts | 💥💥 | 82.25% | -| typescript/conditional-types/infer-type.ts | 💥💥 | 43.22% | +| typescript/conditional-types/conditonal-types.ts | 💥✨ | 34.48% | +| typescript/conditional-types/infer-type.ts | 💥✨ | 4.76% | | typescript/conditional-types/nested-in-condition.ts | 💥✨ | 15.79% | -| typescript/conditional-types/new-ternary-spec.ts | 💥💥 | 52.64% | +| typescript/conditional-types/new-ternary-spec.ts | 💥💥 | 54.03% | | typescript/conditional-types/parentheses.ts | 💥💥 | 60.24% | | typescript/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMixedWithModifiers.ts | 💥 | 86.67% | | typescript/conformance/types/functions/functionOverloadErrorsSyntax.ts | 💥 | 0.00% | @@ -62,7 +61,6 @@ ts compatibility: 475/573 (82.90%) | typescript/function-type/consistent.ts | 💥 | 70.83% | | typescript/function-type/type-annotation.ts | 💥 | 0.00% | | typescript/index-signature/static.ts | 💥 | 66.67% | -| typescript/infer-extends/basic.ts | 💥 | 90.48% | | typescript/interface/comments-generic.ts | 💥💥 | 30.00% | | typescript/interface/ignore.ts | 💥💥 | 88.26% | | typescript/interface2/comments-declare.ts | 💥 | 66.67% | @@ -80,14 +78,10 @@ ts compatibility: 475/573 (82.90%) | typescript/non-null/optional-chain.ts | 💥 | 72.22% | | typescript/nosemi/index-signature.ts | 💥 | 75.00% | | typescript/object-multiline/multiline.ts | 💥✨ | 23.21% | -| typescript/optional-type/complex.ts | 💥 | 0.00% | -| typescript/optional-variance/basic.ts | 💥 | 98.36% | -| typescript/optional-variance/with-jsx.tsx | 💥 | 98.36% | | typescript/override-modifiers/override-modifier.ts | 💥 | 25.00% | | typescript/prettier-ignore/mapped-types.ts | 💥 | 54.72% | | typescript/prettier-ignore/prettier-ignore-nested-unions.ts | 💥 | 29.17% | | typescript/prettier-ignore/prettier-ignore-parenthesized-type.ts | 💥 | 0.00% | -| typescript/rest-type/complex.ts | 💥 | 0.00% | | typescript/satisfies-operators/expression-statement.ts | 💥💥 | 78.38% | | typescript/satisfies-operators/lhs.ts | 💥✨ | 35.00% | | typescript/tuple/trailing-comma-for-empty-tuples.ts | 💥💥💥 | 16.67% |