use crate::ast::{
ArrowFunctionBody, AssignmentOperator, BinaryExpression, BinaryOperator, Expression,
ExpressionKind, LiteralValue, UnaryOperator, UpdateOperator,
};
use crate::printer::chain::child_stops_optional_chain;
use crate::printer::class_expr_has_decorators;
use crate::printer::comments::{paren_shell_close_after, printed_left_spine_step};
use crate::printer::expressions::conditional::ternary_branch_needs_parens;
use smallvec::SmallVec;
use tsv_lang::Span;
#[derive(Debug, Clone, Copy)]
pub(crate) enum ParenContext {
VariableInit,
ForInOfRight,
ExportAssignment,
EnumMemberInit,
ExpressionStatement,
BinaryLeft { parent_op: BinaryOperator },
BinaryRight { parent_op: BinaryOperator },
Callee,
NewCallee,
TaggedTemplateTag,
ChainBase,
NonNull,
TypeAssertion,
AngleBracketAssertion,
InstantiationExpression { source_pair: bool },
UnaryArgument { parent_op: UnaryOperator },
UpdateArgument { postfix: bool },
AwaitArgument,
YieldArgument,
ArrowBody,
ObjectPropertyValue,
DefaultValue,
SpreadArgument,
Argument,
TemplateLiteralExpression,
ComputedPropertyKey,
StatementTest,
SuperClass,
AssignmentTarget { operator: AssignmentOperator },
}
impl ParenContext {
#[inline]
#[must_use]
pub(crate) const fn is_operator_position(self) -> bool {
matches!(
self,
ParenContext::BinaryLeft { .. }
| ParenContext::BinaryRight { .. }
| ParenContext::TypeAssertion
| ParenContext::AngleBracketAssertion
| ParenContext::UnaryArgument { .. }
| ParenContext::UpdateArgument { .. }
| ParenContext::AwaitArgument
| ParenContext::SpreadArgument
| ParenContext::NonNull
| ParenContext::ChainBase
)
}
}
pub(crate) fn is_in_binary(expr: &Expression<'_>) -> bool {
matches!(&expr.kind, ExpressionKind::BinaryExpression(b) if b.operator == BinaryOperator::In)
}
#[derive(Clone, Copy)]
pub(crate) enum SecondTypeArgs {
Expression,
Heritage,
}
pub(crate) fn instantiation_keeps_pair_before_type_args(
source: &str,
head: &Expression<'_>,
follow: SecondTypeArgs,
) -> bool {
matches!(head.kind, ExpressionKind::TSInstantiationExpression(_))
&& (paren_shell_close_after(source, head.span().end).is_some()
|| matches!(follow, SecondTypeArgs::Heritage)
|| !ends_open_optional_chain(head))
}
pub(crate) fn instantiation_operand_is_sealed_chain(
inst_start: u32,
operand: &Expression<'_>,
) -> bool {
child_stops_optional_chain(inst_start, false, operand) || operand.is_sealing_non_null()
}
fn ends_open_optional_chain(head: &Expression<'_>) -> bool {
let mut level = head;
while let ExpressionKind::TSInstantiationExpression(inst) = &level.kind {
if level.span().start < inst.expression.span().start {
return false;
}
level = inst.expression;
}
level.has_optional_in_chain()
}
pub(super) fn needs_parens(expr: &Expression<'_>, ctx: ParenContext, in_for_init: bool) -> bool {
if in_for_init && is_in_binary(expr) {
return true;
}
match ctx {
ParenContext::VariableInit
| ParenContext::ForInOfRight
| ParenContext::ExportAssignment
| ParenContext::EnumMemberInit
| ParenContext::ObjectPropertyValue
| ParenContext::DefaultValue
| ParenContext::Argument
| ParenContext::TemplateLiteralExpression
| ParenContext::ComputedPropertyKey
| ParenContext::YieldArgument
| ParenContext::StatementTest => assignment_value_needs_parens(expr),
ParenContext::ExpressionStatement => needs_parens_expression_statement(expr),
ParenContext::BinaryLeft { parent_op } => {
needs_parens_binary_operand(expr, parent_op, false, in_for_init)
}
ParenContext::BinaryRight { parent_op } => {
needs_parens_binary_operand(expr, parent_op, true, in_for_init)
}
ParenContext::Callee | ParenContext::NewCallee | ParenContext::TaggedTemplateTag => {
if matches!(ctx, ParenContext::NewCallee) {
if matches!(expr.kind, ExpressionKind::ClassExpression(_)) {
return true;
}
}
if matches!(
ctx,
ParenContext::NewCallee | ParenContext::TaggedTemplateTag
) && ends_open_optional_chain(expr)
{
return true;
}
is_await_or_yield(expr)
|| is_type_assertion(expr)
|| is_function_like(expr)
|| is_unary_or_update(expr)
|| matches!(
expr.kind,
ExpressionKind::ConditionalExpression(_)
| ExpressionKind::BinaryExpression(_)
| ExpressionKind::AssignmentExpression(_)
)
}
ParenContext::ChainBase => {
is_lower_precedence(expr)
|| is_numeric_literal(expr)
|| is_unary_or_update(expr)
|| matches!(
expr.kind,
ExpressionKind::ArrowFunctionExpression(_)
| ExpressionKind::TSInstantiationExpression(_)
)
}
ParenContext::SpreadArgument => is_lower_precedence(expr),
ParenContext::NonNull => {
is_lower_precedence(expr)
|| is_unary_or_update(expr)
|| matches!(
expr.kind,
ExpressionKind::ArrowFunctionExpression(_)
| ExpressionKind::TSInstantiationExpression(_)
)
}
ParenContext::TypeAssertion => {
is_await_or_yield(expr)
|| ends_with_instantiation_close(expr, in_for_init)
|| matches!(
expr.kind,
ExpressionKind::BinaryExpression(_)
| ExpressionKind::ConditionalExpression(_)
| ExpressionKind::AssignmentExpression(_)
| ExpressionKind::ArrowFunctionExpression(_)
| ExpressionKind::TSTypeAssertion(_)
)
}
ParenContext::AngleBracketAssertion => needs_parens_unary_arg_common(expr),
ParenContext::UnaryArgument { parent_op } => {
needs_parens_unary_arg_common(expr) || needs_parens_unary_same_sign(expr, parent_op)
}
ParenContext::UpdateArgument { postfix } => {
is_lower_precedence(expr)
|| is_unary_or_update(expr)
|| matches!(expr.kind, ExpressionKind::ArrowFunctionExpression(_))
|| (postfix && matches!(expr.kind, ExpressionKind::TSInstantiationExpression(_)))
}
ParenContext::InstantiationExpression { source_pair } => {
source_pair
|| is_await_or_yield(expr)
|| is_type_assertion(expr)
|| is_function_like(expr)
|| is_unary_or_update(expr)
|| matches!(
expr.kind,
ExpressionKind::ConditionalExpression(_)
| ExpressionKind::AssignmentExpression(_)
| ExpressionKind::BinaryExpression(_)
)
}
ParenContext::AwaitArgument => {
is_type_assertion(expr)
|| matches!(
expr.kind,
ExpressionKind::BinaryExpression(_)
| ExpressionKind::ConditionalExpression(_)
| ExpressionKind::AssignmentExpression(_)
| ExpressionKind::YieldExpression(_)
| ExpressionKind::ArrowFunctionExpression(_)
)
}
ParenContext::ArrowBody => match &expr.kind {
ExpressionKind::ObjectExpression(_) | ExpressionKind::AssignmentExpression(_) => true,
ExpressionKind::ClassExpression(c) => class_expr_has_decorators(c),
_ => false,
},
ParenContext::SuperClass => {
let stripped = strip_non_null_wrappers(expr);
is_lower_precedence(stripped)
|| is_unary_or_update(stripped)
|| matches!(
stripped.kind,
ExpressionKind::ArrowFunctionExpression(_)
| ExpressionKind::NewExpression(_)
| ExpressionKind::TaggedTemplateExpression(_)
| ExpressionKind::ObjectExpression(_)
)
|| matches!(
&stripped.kind,
ExpressionKind::ClassExpression(c) if class_expr_has_decorators(c)
)
}
ParenContext::AssignmentTarget { operator } => {
(matches!(
operator,
AssignmentOperator::RightShiftAssign
| AssignmentOperator::UnsignedRightShiftAssign
| AssignmentOperator::DivideAssign
) && ends_with_instantiation_close(expr, in_for_init))
|| is_type_assertion(expr)
|| matches!(
expr.kind,
ExpressionKind::UnaryExpression(_)
| ExpressionKind::UpdateExpression(_)
| ExpressionKind::AwaitExpression(_)
| ExpressionKind::BinaryExpression(_)
| ExpressionKind::ConditionalExpression(_)
| ExpressionKind::ArrowFunctionExpression(_)
| ExpressionKind::YieldExpression(_)
| ExpressionKind::FunctionExpression(_)
)
}
}
}
pub(in crate::printer) fn strip_non_null_wrappers<'a>(
mut expr: &'a Expression<'a>,
) -> &'a Expression<'a> {
while let ExpressionKind::TSNonNullExpression(non_null) = &expr.kind {
expr = non_null.expression;
}
expr
}
fn is_await_or_yield(expr: &Expression<'_>) -> bool {
matches!(
expr.kind,
ExpressionKind::AwaitExpression(_) | ExpressionKind::YieldExpression(_)
)
}
fn is_lower_precedence(expr: &Expression<'_>) -> bool {
is_await_or_yield(expr)
|| is_type_assertion(expr)
|| matches!(
expr.kind,
ExpressionKind::BinaryExpression(_)
| ExpressionKind::ConditionalExpression(_)
| ExpressionKind::AssignmentExpression(_)
)
}
fn is_type_assertion(expr: &Expression<'_>) -> bool {
matches!(
expr.kind,
ExpressionKind::TSAsExpression(_)
| ExpressionKind::TSSatisfiesExpression(_)
| ExpressionKind::TSTypeAssertion(_)
)
}
fn needs_parens_unary_arg_common(expr: &Expression<'_>) -> bool {
is_await_or_yield(expr)
|| is_type_assertion(expr)
|| matches!(
expr.kind,
ExpressionKind::BinaryExpression(_)
| ExpressionKind::ConditionalExpression(_)
| ExpressionKind::AssignmentExpression(_)
| ExpressionKind::ArrowFunctionExpression(_)
)
}
fn needs_parens_unary_same_sign(expr: &Expression<'_>, parent_op: UnaryOperator) -> bool {
let (unary_sign, update_sign) = match parent_op {
UnaryOperator::Plus => (UnaryOperator::Plus, UpdateOperator::Increment),
UnaryOperator::Minus => (UnaryOperator::Minus, UpdateOperator::Decrement),
_ => return false,
};
match &expr.kind {
ExpressionKind::UnaryExpression(u) => u.operator == unary_sign,
ExpressionKind::UpdateExpression(u) => u.prefix && u.operator == update_sign,
_ => false,
}
}
fn is_function_like(expr: &Expression<'_>) -> bool {
matches!(
expr.kind,
ExpressionKind::ArrowFunctionExpression(_) | ExpressionKind::FunctionExpression(_)
)
}
fn is_unary_or_update(expr: &Expression<'_>) -> bool {
matches!(
expr.kind,
ExpressionKind::UnaryExpression(_) | ExpressionKind::UpdateExpression(_)
)
}
pub(in crate::printer) const fn joins_a_trailing_angle_bracket(op: BinaryOperator) -> bool {
matches!(
op,
BinaryOperator::Plus
| BinaryOperator::Minus
| BinaryOperator::LessThan
| BinaryOperator::GreaterThan
| BinaryOperator::GreaterThanEquals
| BinaryOperator::LeftShift
| BinaryOperator::RightShift
| BinaryOperator::UnsignedRightShift
)
}
pub(crate) fn ends_with_instantiation_close(expr: &Expression<'_>, in_for_init: bool) -> bool {
match &expr.kind {
ExpressionKind::TSInstantiationExpression(_) => true,
ExpressionKind::ConditionalExpression(conditional) => {
!ternary_branch_needs_parens(conditional.alternate)
&& ends_with_instantiation_close(conditional.alternate, in_for_init)
}
ExpressionKind::ArrowFunctionExpression(arrow) => match arrow.body {
ArrowFunctionBody::Expression(body) => {
let bare = matches!(body.kind, ExpressionKind::ConditionalExpression(_))
|| (!needs_parens(body, ParenContext::ArrowBody, in_for_init)
&& !matches!(body.kind, ExpressionKind::SequenceExpression(_)));
bare && ends_with_instantiation_close(body, in_for_init)
}
ArrowFunctionBody::BlockStatement(_) => false,
},
ExpressionKind::AwaitExpression(await_expr) => {
!needs_parens(
await_expr.argument,
ParenContext::AwaitArgument,
in_for_init,
) && ends_with_instantiation_close(await_expr.argument, in_for_init)
}
ExpressionKind::YieldExpression(yield_expr) => {
yield_expr.argument.is_some_and(|argument| {
!needs_parens(argument, ParenContext::YieldArgument, in_for_init)
&& ends_with_instantiation_close(argument, in_for_init)
})
}
ExpressionKind::BinaryExpression(binary) => {
let right = binary.rebalanced_right();
let ctx = ParenContext::BinaryRight {
parent_op: binary.operator,
};
!needs_parens(right, ctx, in_for_init)
&& ends_with_instantiation_close(right, in_for_init)
}
ExpressionKind::UnaryExpression(unary) => {
let ctx = ParenContext::UnaryArgument {
parent_op: unary.operator,
};
!needs_parens(unary.argument, ctx, in_for_init)
&& ends_with_instantiation_close(unary.argument, in_for_init)
}
ExpressionKind::UpdateExpression(update) if update.prefix => {
let ctx = ParenContext::UpdateArgument { postfix: false };
!needs_parens(update.argument, ctx, in_for_init)
&& ends_with_instantiation_close(update.argument, in_for_init)
}
ExpressionKind::TSTypeAssertion(assertion) => {
let ctx = ParenContext::AngleBracketAssertion;
!needs_parens(assertion.expression, ctx, in_for_init)
&& ends_with_instantiation_close(assertion.expression, in_for_init)
}
_ => false,
}
}
fn is_numeric_literal(expr: &Expression<'_>) -> bool {
matches!(
&expr.kind,
ExpressionKind::Literal(lit) if matches!(lit.value, LiteralValue::Number(_))
)
}
fn needs_parens_expression_statement(expr: &Expression<'_>) -> bool {
match &expr.kind {
ExpressionKind::ObjectExpression(_) => true,
ExpressionKind::FunctionExpression(_) | ExpressionKind::ClassExpression(_) => true,
ExpressionKind::AssignmentExpression(assign) => matches!(
assign.left.kind,
ExpressionKind::ObjectPattern(_) | ExpressionKind::ObjectExpression(_)
),
ExpressionKind::SequenceExpression(seq) => seq
.expressions
.first()
.copied()
.is_some_and(needs_parens_expression_statement),
_ => false,
}
}
pub(crate) fn leftmost_no_lookahead<'a>(expr: &'a Expression<'a>) -> &'a Expression<'a> {
leftmost_no_lookahead_reached(expr, LeftmostText::Printed).0
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum LeftmostText {
Printed,
Frozen,
}
impl LeftmostText {
pub(crate) const fn of_value(frozen: bool) -> Self {
if frozen { Self::Frozen } else { Self::Printed }
}
}
pub(crate) fn leftmost_no_lookahead_reached<'a>(
expr: &'a Expression<'a>,
text: LeftmostText,
) -> (&'a Expression<'a>, bool) {
fn walk<'a>(
mut expr: &'a Expression<'a>,
text: LeftmostText,
mut computed_member_object: bool,
) -> (&'a Expression<'a>, bool) {
loop {
(expr, computed_member_object) = match &expr.kind {
ExpressionKind::BinaryExpression(b) => {
if dividend_opens_on_its_pair(expr, b, text) {
return (expr, computed_member_object);
}
(b.left, false)
}
ExpressionKind::AssignmentExpression(a) => {
if !is_type_assertion(a.left)
&& needs_parens(
a.left,
ParenContext::AssignmentTarget {
operator: a.operator,
},
false,
)
{
return (expr, computed_member_object);
}
(a.left, false)
}
ExpressionKind::MemberExpression(m) => (m.object, m.computed && !m.optional),
ExpressionKind::ConditionalExpression(c) => (c.test, false),
ExpressionKind::SequenceExpression(s) => match s.expressions.first() {
Some(&first) => (first, false),
None => return (expr, computed_member_object),
},
ExpressionKind::CallExpression(call) => {
if matches!(call.callee.kind, ExpressionKind::FunctionExpression(_)) {
return (expr, computed_member_object);
}
(call.callee, false)
}
ExpressionKind::TaggedTemplateExpression(t) => {
if matches!(t.tag.kind, ExpressionKind::FunctionExpression(_)) {
return (expr, computed_member_object);
}
(t.tag, false)
}
ExpressionKind::UpdateExpression(u) if !u.prefix => (u.argument, false),
ExpressionKind::TSAsExpression(e) => (e.expression, false),
ExpressionKind::TSSatisfiesExpression(e) => (e.expression, false),
ExpressionKind::TSNonNullExpression(e) => (e.expression, false),
ExpressionKind::TSInstantiationExpression(e) => (e.expression, false),
_ => return (expr, computed_member_object),
};
}
}
walk(expr, text, false)
}
pub(crate) fn export_default_needs_parens(expr: &Expression<'_>, text: LeftmostText) -> bool {
assignment_value_needs_parens(expr)
|| matches!(
export_default_leftmost(expr, text).kind,
ExpressionKind::FunctionExpression(_) | ExpressionKind::ClassExpression(_)
)
}
fn assignment_value_needs_parens(expr: &Expression<'_>) -> bool {
matches!(expr.kind, ExpressionKind::AssignmentExpression(_))
}
fn export_default_leftmost<'a>(
mut expr: &'a Expression<'a>,
text: LeftmostText,
) -> &'a Expression<'a> {
loop {
expr = match &expr.kind {
ExpressionKind::BinaryExpression(b) if dividend_opens_on_its_pair(expr, b, text) => {
return expr;
}
ExpressionKind::BinaryExpression(b) => b.left,
ExpressionKind::AssignmentExpression(a) => a.left,
ExpressionKind::ConditionalExpression(c) => c.test,
ExpressionKind::SequenceExpression(_) => return expr,
ExpressionKind::UpdateExpression(u) if !u.prefix => u.argument,
ExpressionKind::TSAsExpression(e) => e.expression,
ExpressionKind::TSSatisfiesExpression(e) => e.expression,
ExpressionKind::TSNonNullExpression(e) => e.expression,
ExpressionKind::TSInstantiationExpression(e) => e.expression,
ExpressionKind::MemberExpression(m)
if !needs_parens(m.object, ParenContext::ChainBase, false) =>
{
m.object
}
ExpressionKind::CallExpression(call)
if !needs_parens(call.callee, ParenContext::Callee, false) =>
{
call.callee
}
ExpressionKind::TaggedTemplateExpression(t)
if !needs_parens(t.tag, ParenContext::TaggedTemplateTag, false) =>
{
t.tag
}
_ => return expr,
};
}
}
fn relational_region_opens_on_a_kept_shell(operand: &Expression<'_>, in_for_init: bool) -> bool {
let mut node = operand;
if prints_its_own_paren_pair(node)
|| needs_parens(
node,
ParenContext::BinaryRight {
parent_op: BinaryOperator::LessThan,
},
in_for_init,
)
{
return true;
}
let mut open = true;
loop {
let Some((child, parenthesized)) = printed_left_spine_step(node, in_for_init) else {
return false;
};
let hop = RegionHop::of(node);
if prints_its_own_paren_pair(child) || parenthesized {
return match hop {
RegionHop::Index => open,
RegionHop::List => {
shell_content_may_read_as_a_type(child) || prints_its_own_paren_pair(child)
}
RegionHop::Closing => false,
};
}
open &= hop != RegionHop::Closing;
node = child;
}
}
fn shell_content_may_read_as_a_type(expr: &Expression<'_>) -> bool {
match &expr.kind {
ExpressionKind::UnaryExpression(unary) => match unary.operator {
UnaryOperator::Typeof | UnaryOperator::Bang => ends_like_a_type_operand(unary.argument),
UnaryOperator::Minus => is_numeral(unary.argument),
UnaryOperator::Plus
| UnaryOperator::Void
| UnaryOperator::Delete
| UnaryOperator::Tilde => false,
},
ExpressionKind::BinaryExpression(_) => ends_like_a_type_operand(expr),
_ => false,
}
}
fn ends_like_a_type_operand(mut expr: &Expression<'_>) -> bool {
loop {
expr = match &expr.kind {
ExpressionKind::Identifier(_)
| ExpressionKind::ThisExpression(_)
| ExpressionKind::Literal(_)
| ExpressionKind::ObjectExpression(_)
| ExpressionKind::ArrayExpression(_)
| ExpressionKind::SequenceExpression(_)
| ExpressionKind::ImportExpression(_) => return true,
ExpressionKind::MemberExpression(member) if !member.optional => member.object,
ExpressionKind::TSNonNullExpression(non_null) => non_null.expression,
ExpressionKind::UnaryExpression(unary) => match unary.operator {
UnaryOperator::Typeof | UnaryOperator::Bang => unary.argument,
UnaryOperator::Minus => return is_numeral(unary.argument),
_ => return false,
},
ExpressionKind::BinaryExpression(binary)
if matches!(
binary.operator,
BinaryOperator::Pipe | BinaryOperator::Ampersand
) && ends_like_a_type_operand(binary.right) =>
{
binary.left
}
_ => return false,
};
}
}
fn is_numeral(expr: &Expression<'_>) -> bool {
matches!(
&expr.kind,
ExpressionKind::Literal(literal)
if matches!(literal.value, LiteralValue::Number(_) | LiteralValue::BigInt)
)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum RegionHop {
Index,
List,
Closing,
}
impl RegionHop {
fn of(node: &Expression<'_>) -> Self {
match &node.kind {
ExpressionKind::MemberExpression(member) if member.computed && !member.optional => {
Self::Index
}
ExpressionKind::CallExpression(call)
if call.type_arguments.is_some() && !call.optional =>
{
Self::List
}
ExpressionKind::TaggedTemplateExpression(tagged) if tagged.type_arguments.is_some() => {
Self::List
}
ExpressionKind::TSInstantiationExpression(_) => Self::List,
_ => Self::Closing,
}
}
}
fn prints_its_own_paren_pair(expr: &Expression<'_>) -> bool {
matches!(
expr.kind,
ExpressionKind::CommentBoundPair(_) | ExpressionKind::SequenceExpression(_)
)
}
fn is_function_dividend(expr: &Expression<'_>, parent_op: BinaryOperator) -> bool {
parent_op == BinaryOperator::Slash
&& matches!(
expr.kind,
ExpressionKind::FunctionExpression(_) | ExpressionKind::ClassExpression(_)
)
}
fn dividend_opens_on_its_pair(
expr: &Expression<'_>,
binary: &BinaryExpression<'_>,
text: LeftmostText,
) -> bool {
is_function_dividend(binary.left, binary.operator)
&& match text {
LeftmostText::Printed => true,
LeftmostText::Frozen => binary.left.span().start > expr.span().start,
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(in crate::printer) enum OperandPair {
None,
Owed,
Clarity,
}
fn needs_parens_binary_operand(
expr: &Expression<'_>,
parent_op: BinaryOperator,
is_right: bool,
in_for_init: bool,
) -> bool {
binary_operand_pair(expr, parent_op, is_right, in_for_init) != OperandPair::None
}
#[inline]
pub(crate) fn flattened_left_operand<'e>(
parent_op: BinaryOperator,
left: &'e Expression<'e>,
needs_pair: impl FnOnce(&Expression<'_>, ParenContext) -> bool,
) -> Option<&'e BinaryExpression<'e>> {
let ExpressionKind::BinaryExpression(left_binary) = &left.kind else {
return None;
};
let flattens = parent_op.can_flatten_with(left_binary.operator)
&& !needs_pair(left, ParenContext::BinaryLeft { parent_op });
flattens.then_some(left_binary)
}
pub(crate) fn binary_left_operand_reenters(
parent_op: BinaryOperator,
left: &Expression<'_>,
in_for_init: bool,
) -> bool {
match left.kind {
ExpressionKind::BinaryExpression(_) => {
flattened_left_operand(parent_op, left, |left, ctx| {
needs_parens(left, ctx, in_for_init)
})
.is_none()
}
ExpressionKind::TSAsExpression(_) | ExpressionKind::TSSatisfiesExpression(_) => true,
_ => false,
}
}
#[inline]
pub(crate) fn printer_adds_operand_pair(
operand: &Expression<'_>,
ctx: ParenContext,
in_for_header: bool,
) -> bool {
match operand.kind {
ExpressionKind::Identifier(_)
| ExpressionKind::Literal(_)
| ExpressionKind::AssignmentExpression(_)
| ExpressionKind::SequenceExpression(_) => return false,
_ if ctx.is_operator_position() && operator_operand_takes_no_pair(operand) => return false,
ExpressionKind::BinaryExpression(ref child) => {
if let Some(pair) = binary_operand_takes_pair_by_operators(child, ctx, in_for_header) {
return pair;
}
}
ExpressionKind::ObjectExpression(_) | ExpressionKind::ClassExpression(_) => {}
_ if matches!(ctx, ParenContext::ArrowBody) => return false,
_ => {}
}
printer_adds_operand_pair_by_rule(operand, ctx, in_for_header)
}
#[inline]
fn binary_operand_takes_pair_by_operators(
child: &BinaryExpression<'_>,
ctx: ParenContext,
in_for_header: bool,
) -> Option<bool> {
let (parent_op, is_right) = match ctx {
ParenContext::BinaryLeft { parent_op } => (parent_op, false),
ParenContext::BinaryRight { parent_op } => (parent_op, true),
_ => return None,
};
if !is_right && joins_a_trailing_angle_bracket(parent_op) {
return None;
}
if in_for_header && child.operator == BinaryOperator::In {
return Some(false);
}
Some(binary_child_pair(parent_op, child.operator, is_right) != OperandPair::None)
}
#[inline]
#[must_use]
pub(crate) const fn operator_operand_takes_no_pair(operand: &Expression<'_>) -> bool {
matches!(
operand.kind,
ExpressionKind::Identifier(_)
| ExpressionKind::Literal(_)
| ExpressionKind::AssignmentExpression(_)
| ExpressionKind::SequenceExpression(_)
| ExpressionKind::MemberExpression(_)
| ExpressionKind::CallExpression(_)
| ExpressionKind::ThisExpression(_)
| ExpressionKind::TSNonNullExpression(_)
| ExpressionKind::ArrayExpression(_)
| ExpressionKind::ObjectExpression(_)
| ExpressionKind::TemplateLiteral(_)
| ExpressionKind::NewExpression(_)
)
}
#[inline(never)]
fn printer_adds_operand_pair_by_rule(
operand: &Expression<'_>,
ctx: ParenContext,
in_for_header: bool,
) -> bool {
!(in_for_header && is_in_binary(operand)) && needs_parens(operand, ctx, in_for_header)
}
pub(crate) fn printer_parenthesizes_chain_base(object: &Expression<'_>) -> bool {
needs_parens(object, ParenContext::ChainBase, false)
}
pub(in crate::printer) fn binary_operand_pair(
expr: &Expression<'_>,
parent_op: BinaryOperator,
is_right: bool,
in_for_init: bool,
) -> OperandPair {
if matches!(
expr.kind,
ExpressionKind::ConditionalExpression(_)
| ExpressionKind::AssignmentExpression(_)
| ExpressionKind::TSAsExpression(_)
| ExpressionKind::TSSatisfiesExpression(_)
| ExpressionKind::ArrowFunctionExpression(_)
| ExpressionKind::YieldExpression(_)
) {
return OperandPair::Owed;
}
if !is_right
&& parent_op == BinaryOperator::StarStar
&& matches!(
expr.kind,
ExpressionKind::UnaryExpression(_)
| ExpressionKind::TSTypeAssertion(_)
| ExpressionKind::AwaitExpression(_)
)
{
return OperandPair::Owed;
}
if !is_right && is_function_dividend(expr, parent_op) {
return OperandPair::Owed;
}
if !is_right
&& joins_a_trailing_angle_bracket(parent_op)
&& ends_with_instantiation_close(expr, in_for_init)
{
return OperandPair::Owed;
}
if !is_right
&& parent_op == BinaryOperator::GreaterThan
&& matches!(
&expr.kind,
ExpressionKind::BinaryExpression(child)
if child.operator == BinaryOperator::LessThan
&& (child.relexes_as_type_arguments
|| relational_region_opens_on_a_kept_shell(child.right, in_for_init))
)
{
return OperandPair::Owed;
}
if !is_right
&& matches!(parent_op, BinaryOperator::In | BinaryOperator::Instanceof)
&& matches!(expr.kind, ExpressionKind::UnaryExpression(_))
{
return OperandPair::Clarity;
}
if matches!(expr.kind, ExpressionKind::AwaitExpression(_)) {
return OperandPair::Clarity;
}
let ExpressionKind::BinaryExpression(child) = &expr.kind else {
return OperandPair::None;
};
binary_child_pair(parent_op, child.operator, is_right)
}
#[inline]
fn binary_child_pair(
parent_op: BinaryOperator,
child_op: BinaryOperator,
is_right: bool,
) -> OperandPair {
if parent_op.is_logical() && child_op.is_logical() && parent_op != child_op {
return OperandPair::Owed;
}
let parent_prec = parent_op.precedence();
let child_prec = child_op.precedence();
if child_prec < parent_prec {
return OperandPair::Owed;
}
if is_right && child_prec == parent_prec {
return OperandPair::Owed;
}
if child_prec == parent_prec && !parent_op.can_flatten_with(child_op) {
return if parent_op == BinaryOperator::StarStar {
OperandPair::Owed
} else {
OperandPair::Clarity
};
}
if parent_prec < child_prec && child_op == BinaryOperator::Percent {
return if matches!(parent_op, BinaryOperator::Plus | BinaryOperator::Minus)
|| parent_op.is_bitwise()
{
OperandPair::Clarity
} else {
OperandPair::None
};
}
if parent_op.is_bitwise() && child_prec != parent_prec {
return OperandPair::Clarity;
}
OperandPair::None
}
pub(in crate::printer) fn clarity_pairs_behind_region_close(
close: &BinaryExpression<'_>,
in_for_init: bool,
source_holds_pair: impl Fn(&Expression<'_>) -> bool,
) -> SmallVec<[Span; 2]> {
let mut withheld = SmallVec::new();
let mut node = close.right;
if prints_its_own_paren_pair(node) {
return SmallVec::new();
}
let mut pair = binary_operand_pair(node, close.operator, true, in_for_init);
let mut is_operand = true;
loop {
match pair {
OperandPair::None => {}
OperandPair::Owed => return SmallVec::new(),
OperandPair::Clarity => {
if source_holds_pair(node) {
break;
}
if is_operand
&& (ends_with_instantiation_close(node, in_for_init)
|| ends_on_a_function_or_class_body(node, in_for_init))
{
return SmallVec::new();
}
withheld.push(node.span());
}
}
let Some((child, parenthesized)) = printed_left_spine_step(node, in_for_init) else {
break;
};
if prints_its_own_paren_pair(child) {
return SmallVec::new();
}
(pair, is_operand) = match &node.kind {
_ if !parenthesized => (OperandPair::None, false),
ExpressionKind::BinaryExpression(parent) => (
binary_operand_pair(child, parent.operator, false, in_for_init),
true,
),
ExpressionKind::CallExpression(_) | ExpressionKind::TaggedTemplateExpression(_)
if is_called_function(child) =>
{
(OperandPair::Clarity, false)
}
_ => (OperandPair::Owed, false),
};
node = child;
}
withheld
}
pub(in crate::printer) fn is_called_function(expr: &Expression<'_>) -> bool {
matches!(expr.kind, ExpressionKind::FunctionExpression(_))
}
fn ends_on_a_function_or_class_body(expr: &Expression<'_>, in_for_init: bool) -> bool {
let mut operand = expr;
loop {
let (child, ctx) = match &operand.kind {
ExpressionKind::FunctionExpression(_) | ExpressionKind::ClassExpression(_) => {
return true;
}
ExpressionKind::AwaitExpression(await_expr) => {
(await_expr.argument, ParenContext::AwaitArgument)
}
ExpressionKind::UnaryExpression(unary) => (
unary.argument,
ParenContext::UnaryArgument {
parent_op: unary.operator,
},
),
ExpressionKind::BinaryExpression(binary) => (
binary.rebalanced_right(),
ParenContext::BinaryRight {
parent_op: binary.operator,
},
),
ExpressionKind::TSTypeAssertion(assertion) => {
(assertion.expression, ParenContext::AngleBracketAssertion)
}
_ => return false,
};
if needs_parens(child, ctx, in_for_init) {
return false;
}
operand = child;
}
}