mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
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:
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user