fix(formatter): keep parens around an instantiation expression ending an operand through any tight parent (#28168)

Closes #28108

Fixes `(<any>(make<T>)) + y` (#28108) case, and also `(#p in make<T>) > y` and `(<A>await make<T>) + y` cases.
This commit is contained in:
leaysgur committed 2026-10-26 06:23:43 +00:00
1 parent a5314a1c8a
commit 190bb1e67f
3 files changed
+55 -24

No files matched your search

@@ -908,7 +908,7 @@ fn class_extends_needs_parens_through_non_null(expression: &Expression<'_>) -> b
}
impl NeedsParentheses<'_> for AstNode<'_, '_, TSInstantiationExpression<'_>> {
fn needs_parentheses(&self, _f: &JsFormatter<'_, '_>) -> bool {
fn needs_parentheses(&self, f: &JsFormatter<'_, '_>) -> bool {
let object = match self.parent() {
AstNodes::StaticMemberExpression(expr) => &expr.object,
AstNodes::ComputedMemberExpression(expr) => &expr.object,
@@ -920,7 +920,7 @@ impl NeedsParentheses<'_> for AstNode<'_, '_, TSInstantiationExpression<'_>> {
AstNodes::CallExpression(expr) if expr.type_arguments.is_some() => &expr.callee,
AstNodes::NewExpression(expr) if expr.type_arguments.is_some() => &expr.callee,
AstNodes::TaggedTemplateExpression(expr) if expr.type_arguments.is_some() => &expr.tag,
_ => return is_followed_by_comparison_or_sign(self),
_ => return is_followed_by_comparison_or_sign(self, f),
};
self.span == object.span()
@@ -929,34 +929,43 @@ impl NeedsParentheses<'_> for AstNode<'_, '_, TSInstantiationExpression<'_>> {
/// Whether the instantiation expression ends the left operand of `+`, `-`, `<` or `>`-led operators:
/// TypeScript reads `f<T> + x` as `f < T > +x` and rejects `f<T> < x`,
/// also when `f<T>` only ends the operand (`(a + f<T>) - x`, `(typeof f<T>) + x`).
/// also when `f<T>` only ends that operand without parentheses (`(a + f<T>) - x`, `(<A>await f<T>) + x`).
fn is_followed_by_comparison_or_sign(
node: &AstNode<'_, '_, TSInstantiationExpression<'_>>,
f: &JsFormatter<'_, '_>,
) -> bool {
let mut span = node.span;
let mut parent = node.parent();
loop {
match parent {
AstNodes::BinaryExpression(expr) if expr.right.span() == span => {
(span, parent) = (expr.span, expr.parent());
}
AstNodes::UnaryExpression(expr) => (span, parent) = (expr.span, expr.parent()),
AstNodes::BinaryExpression(expr) => {
return expr.left.span() == span
&& matches!(
expr.operator,
BinaryOperator::Addition
| BinaryOperator::Subtraction
| BinaryOperator::LessThan
| BinaryOperator::GreaterThan
| BinaryOperator::GreaterEqualThan
| BinaryOperator::ShiftRight
| BinaryOperator::ShiftRightZeroFill
);
}
_ => return false,
for ancestor in node.ancestors() {
if let AstNodes::BinaryExpression(expr) = ancestor
&& expr.left.span() == span
{
return matches!(
expr.operator,
BinaryOperator::Addition
| BinaryOperator::Subtraction
| BinaryOperator::LessThan
| BinaryOperator::GreaterThan
| BinaryOperator::GreaterEqualThan
| BinaryOperator::ShiftRight
| BinaryOperator::ShiftRightZeroFill
);
}
// `f<T>` still ends these as the right or only operand, unless their own parentheses close it.
// Looser ones (`a && f<T>`, `yield f<T>`, ...) always print them before a binary operator.
if !matches!(
ancestor,
AstNodes::BinaryExpression(_)
| AstNodes::PrivateInExpression(_)
| AstNodes::UnaryExpression(_)
| AstNodes::AwaitExpression(_)
| AstNodes::TSTypeAssertion(_)
) || prints_own_parens(ancestor, f)
{
return false;
}
span = ancestor.span();
}
false
}
fn binary_like_needs_parens(
@@ -10,6 +10,9 @@ x = (make<T>) + y;
x = (make<T>) >> y;
x = (a + make<T>) - y;
x = (typeof make<T>) + y;
x = (<any>(make<T>)) + y;
x = (<A>await make<T>) + y;
class C { #p; m() { x = (#p in make<T>) > y; } }
export default (class {})<string>;
export default (function () {})<string>;
export default (class {})<string> as A;
@@ -14,6 +14,9 @@ x = (make<T>) + y;
x = (make<T>) >> y;
x = (a + make<T>) - y;
x = (typeof make<T>) + y;
x = (<any>(make<T>)) + y;
x = (<A>await make<T>) + y;
class C { #p; m() { x = (#p in make<T>) > y; } }
export default (class {})<string>;
export default (function () {})<string>;
export default (class {})<string> as A;
@@ -46,6 +49,14 @@ x = (make<T>) + y;
x = (make<T>) >> y;
x = a + (make<T>) - y;
x = typeof (make<T>) + y;
x = <any>(make<T>) + y;
x = <A>await (make<T>) + y;
class C {
#p;
m() {
x = #p in (make<T>) > y;
}
}
export default (class {})<string>;
export default (function () {})<string>;
export default (class {})<string> as A;
@@ -77,6 +88,14 @@ x = (make<T>) + y;
x = (make<T>) >> y;
x = a + (make<T>) - y;
x = typeof (make<T>) + y;
x = <any>(make<T>) + y;
x = <A>await (make<T>) + y;
class C {
#p;
m() {
x = #p in (make<T>) > y;
}
}
export default (class {})<string>;
export default (function () {})<string>;
export default (class {})<string> as A;