fix(codegen): preserve instantiation expression precedence (#26424)

Preserve parentheses around TypeScript instantiation expressions in postfix contexts. Previously, `(f<T>).x` and `(f<T>)[x]` were emitted as `f<T>.x` and `f<T>[x]`, producing invalid syntax or changing how the expression is parsed.

The printer now respects the surrounding precedence while retaining grouping for lower-precedence operands.
This commit is contained in:
camc314
2026-09-07 18:20:11 +00:00
parent cfa47ab396
commit 32d00c55bc
4 changed files with 40 additions and 6 deletions
+9 -5
View File
@@ -2498,11 +2498,15 @@ impl GenExpr for TSNonNullExpression<'_> {
}
impl GenExpr for TSInstantiationExpression<'_> {
fn gen_expr(&self, p: &mut Codegen, _precedence: Precedence, ctx: Context) {
// Wrap a lower-precedence operand so `(a ?? b)<T>` isn't emitted as `a ?? b<T>`.
self.expression.print_expr(p, Precedence::Prefix, ctx);
self.type_arguments.print(p, ctx);
if p.options.minify {
fn gen_expr(&self, p: &mut Codegen, precedence: Precedence, ctx: Context) {
// Member access and other postfix operations need grouping around the type arguments.
let wrap = precedence >= Precedence::Postfix;
p.wrap(wrap, |p| {
// Wrap a lower-precedence operand so `(a ?? b)<T>` isn't emitted as `a ?? b<T>`.
self.expression.print_expr(p, Precedence::Prefix, ctx);
self.type_arguments.print(p, ctx);
});
if p.options.minify && !wrap {
p.print_hard_space();
}
}
@@ -486,6 +486,25 @@ fn ts_instantiation_expression() {
test_same("p = a()<T>;\n");
}
#[test]
fn ts_instantiation_expression_precedence() {
for source in
["(f<T>).x", "(f<T>)[x]", "(f<T>)()", "new (f<T>)()", "(f<T>)`text`", "((a ?? b)<T>).x"]
{
test_ts(source, &format!("{source};\n"));
test_idempotency(source);
test_idempotency_options(source, &CodegenOptions::minify());
}
for source in ["(f<T>).x", "(f<T>)[x]"] {
test_options_with_source_type(
source,
&format!("{source};"),
SourceType::ts(),
CodegenOptions::minify(),
);
}
}
#[test]
fn ts_satisfies_expression() {
test_same("(foo satisfies null) | ~\"\";\n");
+5 -1
View File
@@ -232,10 +232,14 @@ export function printExpression(
printExpression(node.expression, state, PREC_POSTFIX, ctx);
write(state, "!", CAT_OP_UN_NOT);
break;
case "TSInstantiationExpression":
case "TSInstantiationExpression": {
const wrap = precedence >= PREC_POSTFIX;
if (wrap) write(state, "(", CAT_OTHER);
printExpression(node.expression, state, PREC_PREFIX, ctx);
printTypeArguments(node.typeArguments, state);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
break;
}
case "TSTypeAssertion":
printTSTypeAssertion(node, state, precedence, ctx);
break;
@@ -0,0 +1,7 @@
(f<T>).x;
(f<T>)[x];
(f<T>)();
(a<b>)?.();
new (f<T>)();
(f<T>)`text`;
((a ?? b)<T>).x;