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
10 changes: 10 additions & 0 deletions crates/oxc_formatter/src/generated/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
85 changes: 62 additions & 23 deletions crates/oxc_formatter/src/parentheses/ts_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}

Expand All @@ -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,
}
Expand All @@ -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)
}
}
6 changes: 0 additions & 6 deletions crates/oxc_formatter/src/write/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()])?;
}
Expand All @@ -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(())
}
}
Expand Down
10 changes: 8 additions & 2 deletions tasks/ast_tools/src/generators/formatter/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 4 additions & 10 deletions tasks/prettier_conformance/snapshots/prettier.ts.snap.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ts compatibility: 475/573 (82.90%)
ts compatibility: 481/573 (83.94%)

# Failed

Expand Down Expand Up @@ -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% |
Expand All @@ -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% |
Expand All @@ -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% |
Expand Down
Loading