fix(formatter): keep more parens around TSInstantiationExpression (#27811)

Like `(make<T>)<U>();`.
This commit is contained in:
leaysgur committed 2026-10-16 05:26:31 +00:00
1 parent 5b6ae62ffa
commit de110ced8b
4 files changed
+80 -3

No files matched your search

+10
View File
@@ -813,16 +813,22 @@ Before an optional call's `?.`, Prettier moves an own-line comment into the argu
```ts
// input
const a = (make<T>)!;
(make<T>)<U>();
x = (make<T>) + y;
export default (class {})<string>;
(class {})<string>;
// ours
const a = (make<T>)!;
(make<T>)<U>();
x = (make<T>) + y;
export default (class {})<string>;
(class {})<string>;
// prettier
const a = make<T>!;
make<T><U>();
x = make<T> + y;
export default class {}<string>;
class {}<string>;
```
@@ -831,6 +837,10 @@ Prettier drops the parens around an instantiation expression before `!`,
and the ones that keep a leading `class`/`function` from starting a declaration or statement.
Fixed in Prettier `main` by prettier/prettier#20177, which we follow except after `export default` (DIVERGENCES.md#export-default-leftmost-parens).
Prettier `main` still drops them before type arguments (`make<T><U>()` does not parse)
and before a binary `+`, `-`, `<` or `>`-led operator, also when `make<T>` only ends the left operand
(`make<T> + y` and `a + make<T> - y` parse as `make < T > +y`).
## export-default-leftmost-parens
- Why: uniform-rule (same construct, same output: expression statement)
@@ -921,15 +921,50 @@ impl NeedsParentheses<'_> for AstNode<'_, TSInstantiationExpression<'_>> {
AstNodes::StaticMemberExpression(expr) => &expr.object,
AstNodes::ComputedMemberExpression(expr) => &expr.object,
AstNodes::PrivateFieldExpression(expr) => &expr.object,
// `(f<T>)!`, since `f<T>!` does not parse
AstNodes::TSNonNullExpression(_) => return true,
_ => return false,
// `(f<T>)!`, `(f<T>)<U>` and `(f<T>)<U>()`, since `f<T>!`, `f<T><U>` and `f<T><U>()` do not parse
AstNodes::TSNonNullExpression(_) | AstNodes::TSInstantiationExpression(_) => {
return true;
}
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),
};
self.span == object.span()
}
}
/// 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`).
fn is_followed_by_comparison_or_sign(node: &AstNode<'_, TSInstantiationExpression<'_>>) -> 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,
}
}
}
fn binary_like_needs_parens(
binary_like: BinaryLikeExpression<'_, '_>,
f: &JsFormatter<'_, '_>,
@@ -2,6 +2,14 @@
const a = (make<T>)!;
(make<T>)!.b;
(make<T>)!();
(make<T>)<U>;
(make<T>)<U>();
new (make<T>)<U>();
(make<T>)<U>``;
x = (make<T>) + y;
x = (make<T>) >> y;
x = (a + make<T>) - y;
x = (typeof make<T>) + y;
export default (class {})<string>;
export default (function () {})<string>;
export default (class {})<string> as A;
@@ -6,6 +6,14 @@ source: crates/oxc_formatter/tests/fixtures/mod.rs
const a = (make<T>)!;
(make<T>)!.b;
(make<T>)!();
(make<T>)<U>;
(make<T>)<U>();
new (make<T>)<U>();
(make<T>)<U>``;
x = (make<T>) + y;
x = (make<T>) >> y;
x = (a + make<T>) - y;
x = (typeof make<T>) + y;
export default (class {})<string>;
export default (function () {})<string>;
export default (class {})<string> as A;
@@ -20,6 +28,14 @@ export default (class {})<string> as A;
const a = (make<T>)!;
(make<T>)!.b;
(make<T>)!();
(make<T>)<U>;
(make<T>)<U>();
new (make<T>)<U>();
(make<T>)<U>``;
x = (make<T>) + y;
x = (make<T>) >> y;
x = a + (make<T>) - y;
x = typeof (make<T>) + y;
export default (class {})<string>;
export default (function () {})<string>;
export default (class {})<string> as A;
@@ -33,6 +49,14 @@ export default (class {})<string> as A;
const a = (make<T>)!;
(make<T>)!.b;
(make<T>)!();
(make<T>)<U>;
(make<T>)<U>();
new (make<T>)<U>();
(make<T>)<U>``;
x = (make<T>) + y;
x = (make<T>) >> y;
x = a + (make<T>) - y;
x = typeof (make<T>) + y;
export default (class {})<string>;
export default (function () {})<string>;
export default (class {})<string> as A;