diff --git a/deployment/schema.json b/deployment/schema.json index 50c4f059..7121980e 100644 --- a/deployment/schema.json +++ b/deployment/schema.json @@ -153,6 +153,21 @@ "description": "Forces no braces when the header is one line and body is one line. Otherwise forces braces." }] }, + "useParentheses": { + "description": "Controls how parentheses are used around expression statements.", + "type": "string", + "default": "disambiguation", + "oneOf": [{ + "const": "maintain", + "description": "Maintains parentheses as written in the source code." + }, { + "const": "disambiguation", + "description": "Forces parentheses for disambiguation (object literals, function expressions, class expressions)." + }, { + "const": "preferNone", + "description": "Prefers no parentheses - only uses them when absolutely necessary to avoid breaking code." + }] + }, "bracePosition": { "description": "Where to place the opening brace.", "type": "string", @@ -1184,6 +1199,9 @@ "conditionalType.operatorPosition": { "$ref": "#/definitions/operatorPosition" }, + "useParentheses": { + "$ref": "#/definitions/useParentheses" + }, "arguments.preferHanging": { "$ref": "#/definitions/preferHangingGranular" }, diff --git a/src/configuration/builder.rs b/src/configuration/builder.rs index aeaaa4d3..250ca0b8 100644 --- a/src/configuration/builder.rs +++ b/src/configuration/builder.rs @@ -14,6 +14,7 @@ use dprint_core::configuration::*; /// .prefer_hanging(true) /// .prefer_single_line(false) /// .quote_style(QuoteStyle::PreferSingle) +/// .use_parentheses(UseParentheses::PreferNone) /// .next_control_flow_position(NextControlFlowPosition::SameLine) /// .build(); /// ``` @@ -58,7 +59,7 @@ impl ConfigurationBuilder { .comment_line_force_space_after_slashes(false) .construct_signature_space_after_new_keyword(true) .constructor_type_space_after_new_keyword(true) - .arrow_function_use_parentheses(UseParentheses::Force) + .arrow_function_use_parentheses(ArrowFunctionUseParentheses::Force) .new_line_kind(NewLineKind::LineFeed) .function_expression_space_after_function_keyword(true) .tagged_template_space_before_literal(false) @@ -481,7 +482,7 @@ impl ConfigurationBuilder { /// Whether to use parentheses for arrow functions. /// /// Default: `UseParentheses::Maintain` - pub fn arrow_function_use_parentheses(&mut self, value: UseParentheses) -> &mut Self { + pub fn arrow_function_use_parentheses(&mut self, value: ArrowFunctionUseParentheses) -> &mut Self { self.insert("arrowFunction.useParentheses", value.to_string().into()) } @@ -1077,6 +1078,10 @@ impl ConfigurationBuilder { self.insert("whileStatement.spaceAround", value.into()) } + pub fn use_parentheses(&mut self, value: UseParentheses) -> &mut Self { + self.insert("useParentheses", value.to_string().into()) + } + #[cfg(test)] pub(super) fn get_inner_config(&self) -> ConfigKeyMap { self.config.clone() @@ -1119,7 +1124,8 @@ mod tests { .quote_props(QuoteProps::AsNeeded) .prefer_hanging(false) /* situational */ - .arrow_function_use_parentheses(UseParentheses::Maintain) + .arrow_function_use_parentheses(ArrowFunctionUseParentheses::Maintain) + .use_parentheses(UseParentheses::PreferNone) .binary_expression_line_per_expression(false) .conditional_expression_line_per_expression(true) .member_expression_line_per_expression(false) @@ -1297,7 +1303,7 @@ mod tests { .while_statement_space_around(true); let inner_config = config.get_inner_config(); - assert_eq!(inner_config.len(), 182); + assert_eq!(inner_config.len(), 183); let diagnostics = resolve_config(inner_config, &Default::default()).diagnostics; assert_eq!(diagnostics.len(), 0); } diff --git a/src/configuration/resolve_config.rs b/src/configuration/resolve_config.rs index 486b2365..9ac7a76a 100644 --- a/src/configuration/resolve_config.rs +++ b/src/configuration/resolve_config.rs @@ -94,7 +94,12 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration) semi_colons, file_indent_level: get_value(&mut config, "fileIndentLevel", 0, &mut diagnostics), /* situational */ - arrow_function_use_parentheses: get_value(&mut config, "arrowFunction.useParentheses", UseParentheses::Maintain, &mut diagnostics), + arrow_function_use_parentheses: get_value( + &mut config, + "arrowFunction.useParentheses", + ArrowFunctionUseParentheses::Maintain, + &mut diagnostics, + ), binary_expression_line_per_expression: get_value(&mut config, "binaryExpression.linePerExpression", false, &mut diagnostics), conditional_expression_line_per_expression: get_value(&mut config, "conditionalExpression.linePerExpression", true, &mut diagnostics), jsx_quote_style: get_value(&mut config, "jsx.quoteStyle", quote_style.to_jsx_quote_style(), &mut diagnostics), @@ -333,6 +338,7 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration) switch_statement_space_around: get_value(&mut config, "switchStatement.spaceAround", space_around, &mut diagnostics), tuple_type_space_around: get_value(&mut config, "tupleType.spaceAround", space_around, &mut diagnostics), while_statement_space_around: get_value(&mut config, "whileStatement.spaceAround", space_around, &mut diagnostics), + use_parentheses: get_value(&mut config, "useParentheses", UseParentheses::Disambiguation, &mut diagnostics), }; diagnostics.extend(get_unknown_property_diagnostics(config)); diff --git a/src/configuration/types.rs b/src/configuration/types.rs index 1e67d7b7..d216f4d7 100644 --- a/src/configuration/types.rs +++ b/src/configuration/types.rs @@ -173,10 +173,29 @@ generate_str_to_from![ [PreferNone, "preferNone"] ]; -/// Whether to use parentheses around a single parameter in an arrow function. +/// Controls how parentheses are used around expressions. #[derive(Clone, PartialEq, Copy, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum UseParentheses { + /// Maintains parentheses as written in the source code. + Maintain, + /// Forces parentheses for disambiguation (object literals, function expressions, class expressions at statement position). + Disambiguation, + /// Prefers no parentheses - only uses them when absolutely necessary to avoid breaking code. + PreferNone, +} + +generate_str_to_from![ + UseParentheses, + [Maintain, "maintain"], + [Disambiguation, "disambiguation"], + [PreferNone, "preferNone"] +]; + +/// Whether to use parentheses around a single parameter in an arrow function. +#[derive(Clone, PartialEq, Copy, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ArrowFunctionUseParentheses { /// Maintains the current state of the parentheses. Maintain, /// Forces parentheses. @@ -185,7 +204,12 @@ pub enum UseParentheses { PreferNone, } -generate_str_to_from![UseParentheses, [Maintain, "maintain"], [Force, "force"], [PreferNone, "preferNone"]]; +generate_str_to_from![ + ArrowFunctionUseParentheses, + [Maintain, "maintain"], + [Force, "force"], + [PreferNone, "preferNone"] +]; /// How to decide to use single or double quotes. #[derive(Clone, PartialEq, Copy, Serialize, Deserialize)] @@ -320,7 +344,7 @@ pub struct Configuration { pub file_indent_level: u32, /* situational */ #[serde(rename = "arrowFunction.useParentheses")] - pub arrow_function_use_parentheses: UseParentheses, + pub arrow_function_use_parentheses: ArrowFunctionUseParentheses, #[serde(rename = "binaryExpression.linePerExpression")] pub binary_expression_line_per_expression: bool, #[serde(rename = "conditionalExpression.linePerExpression")] @@ -662,4 +686,7 @@ pub struct Configuration { pub tuple_type_space_around: bool, #[serde(rename = "whileStatement.spaceAround")] pub while_statement_space_around: bool, + /* expression parentheses */ + #[serde(rename = "useParentheses")] + pub use_parentheses: UseParentheses, } diff --git a/src/generation/generate.rs b/src/generation/generate.rs index d5090dc9..c494560a 100644 --- a/src/generation/generate.rs +++ b/src/generation/generate.rs @@ -1846,11 +1846,11 @@ fn gen_arrow_func_expr<'a>(node: &'a ArrowExpr<'a>, context: &mut Context<'a>) - fn get_should_use_parens<'a>(node: &ArrowSignature<'a>, context: &Context<'a>) -> bool { return match context.config.arrow_function_use_parentheses { - UseParentheses::Force => true, - UseParentheses::PreferNone => { + ArrowFunctionUseParentheses::Force => true, + ArrowFunctionUseParentheses::PreferNone => { node.params().len() != 1 || node.return_type().is_some() || is_first_param_not_identifier_or_has_type_annotation(node.params(), node, context.program) } - UseParentheses::Maintain => has_parens(node, context.program), + ArrowFunctionUseParentheses::Maintain => has_parens(node, context.program), }; fn is_first_param_not_identifier_or_has_type_annotation<'a>(params: &[Pat<'a>], arrow: &ArrowSignature<'a>, program: Program<'a>) -> bool { @@ -2661,6 +2661,62 @@ fn gen_fn_expr<'a>(node: &FnExpr<'a>, context: &mut Context<'a>) -> PrintItems { } } +// unwrap type assertions to find the underlying expression +fn unwrap_assertion_node(mut node: Node) -> Node { + loop { + node = match node { + Node::TsAsExpr(n) => n.expr.into(), + Node::TsSatisfiesExpr(n) => n.expr.into(), + Node::TsConstAssertion(n) => n.expr.into(), + Node::TsTypeAssertion(n) => n.expr.into(), + Node::TsNonNullExpr(n) => n.expr.into(), + _ => return node, + }; + } +} + +/// Check if a node kind is a type assertion (as, satisfies, const assertion, type assertion) +/// Note: TsNonNullExpr (!) is NOT included; it's handled separately where needed +fn is_type_assertion(kind: NodeKind) -> bool { + matches!( + kind, + NodeKind::TsAsExpr | NodeKind::TsSatisfiesExpr | NodeKind::TsConstAssertion | NodeKind::TsTypeAssertion + ) +} + +/// Check if a node kind is a statement +fn is_stmt(kind: NodeKind) -> bool { + matches!( + kind, + NodeKind::BlockStmt + | NodeKind::BreakStmt + | NodeKind::ContinueStmt + | NodeKind::DebuggerStmt + | NodeKind::DoWhileStmt + | NodeKind::EmptyStmt + | NodeKind::ExprStmt + | NodeKind::ForInStmt + | NodeKind::ForOfStmt + | NodeKind::ForStmt + | NodeKind::IfStmt + | NodeKind::LabeledStmt + | NodeKind::ReturnStmt + | NodeKind::SwitchStmt + | NodeKind::ThrowStmt + | NodeKind::TryStmt + | NodeKind::WhileStmt + | NodeKind::WithStmt + ) +} + +/// Unwrap nested ParenExprs to get the innermost expression +fn get_innermost_expr<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> { + while let Expr::Paren(paren_expr) = expr { + expr = &paren_expr.expr; + } + expr +} + fn should_add_parens_around_expr<'a>(node: Node<'a>, context: &Context<'a>) -> bool { let original_node = node; for node in node.ancestors() { @@ -2688,7 +2744,11 @@ fn should_add_parens_around_expr<'a>(node: Node<'a>, context: &Context<'a>) -> b return false; } } - Node::ExprStmt(_) => return true, + Node::ExprStmt(_) => { + return context.config.use_parentheses != UseParentheses::Maintain + && (context.config.use_parentheses == UseParentheses::Disambiguation + || matches!(unwrap_assertion_node(original_node), Node::ObjectLit(_) | Node::FnExpr(_) | Node::ClassExpr(_))); + } Node::MemberExpr(expr) => { if matches!(expr.prop, MemberProp::Computed(_)) && expr.prop.range().contains(&original_node.range()) { return false; @@ -2776,20 +2836,35 @@ fn gen_new_expr<'a>(node: &NewExpr<'a>, context: &mut Context<'a>) -> PrintItems if let Some(type_args) = node.type_args { items.extend(gen_node(type_args.into(), context)); } - let args = match node.args.as_ref() { - Some(args) => args.iter().map(|&node| node.into()).collect(), - None => Vec::new(), + + // Generate parentheses based on useParentheses mode: + // - Disambiguation: always add parens (even if callee is parenthesized) + // - Maintain: keep as-is (only add if args are present in the AST) + // - PreferNone: only add if needed for member/optional access + let should_generate_parens = match context.config.use_parentheses { + UseParentheses::Disambiguation => true, + UseParentheses::Maintain => node.args.is_some(), + UseParentheses::PreferNone => is_node_accessed_by_member_or_chain(node.parent()), }; - items.extend(gen_parameters_or_arguments( - GenParametersOrArgumentsOptions { - node: node.into(), - range: node.get_parameters_range(context), - nodes: args, - custom_close_paren: |_| None, - is_parameters: false, - }, - context, - )); + + // Generate arguments if present or if should_generate_parens is true + let nodes: Vec<_> = node.args.as_ref() + .map(|args_ref| args_ref.iter().map(|&n| n.into()).collect()) + .unwrap_or_default(); + + if !nodes.is_empty() || should_generate_parens { + items.extend(gen_parameters_or_arguments( + GenParametersOrArgumentsOptions { + node: node.into(), + range: node.get_parameters_range(context), + nodes, + custom_close_paren: |_| None, + is_parameters: false, + }, + context, + )); + } + items } @@ -2867,11 +2942,91 @@ fn gen_paren_expr<'a>(node: &'a ParenExpr<'a>, context: &mut Context<'a>) -> Pri } } +/// Find the kind of the first ancestor statement for a given node +fn get_context_stmt_kind(node: &ParenExpr) -> Option { + for ancestor in node.ancestors() { + let kind = ancestor.kind(); + if is_stmt(kind) { + return Some(kind); + } + } + None +} + +/// Walks up the tree from the given node to find the first ClassDecl or ClassExpr ancestor. +fn find_ancestor_class<'a>(node: Node<'a>) -> Option<&'a Class<'a>> { + for ancestor in node.ancestors() { + match ancestor { + Node::ClassDecl(class_decl) => return Some(class_decl.class), + Node::ClassExpr(class_expr) => return Some(class_expr.class), + _ => {} + } + } + None +} + +/// Left-associative operators requiring right-side parens when nested. +fn is_non_commutative_or_comparison(op: &BinaryOp) -> bool { + matches!( + op, + BinaryOp::Add // Not associative due to type coercion: "x" + (4 + 2) ≠ "x" + 4 + 2 + | BinaryOp::Mul // Not associative due to floating-point precision + | BinaryOp::Div + | BinaryOp::Mod + | BinaryOp::Sub + | BinaryOp::LShift + | BinaryOp::RShift + | BinaryOp::ZeroFillRShift + | BinaryOp::EqEq + | BinaryOp::NotEq + | BinaryOp::EqEqEq + | BinaryOp::NotEqEq + | BinaryOp::Lt + | BinaryOp::LtEq + | BinaryOp::Gt + | BinaryOp::GtEq + ) +} + +/// Checks if a node (or any ParenExpr wrapping it) is accessed via member/optional chaining. +fn is_node_accessed_by_member_or_chain(mut node: Node) -> bool { + loop { + match node.kind() { + NodeKind::MemberExpr | NodeKind::OptChainExpr => return true, + NodeKind::ParenExpr => match node.parent() { + Some(parent) => node = parent, + None => return false, + }, + _ => return false, + } + } +} + fn should_skip_paren_expr<'a>(node: &'a ParenExpr<'a>, context: &Context<'a>) -> bool { - if node_helpers::has_surrounding_different_line_comments(node.expr.into(), context.program) { + // Keep parens if: maintain mode, sequence expression, or comments on different lines + if context.config.use_parentheses == UseParentheses::Maintain + || matches!(node.expr.kind(), NodeKind::SeqExpr) + || node_helpers::has_surrounding_different_line_comments(node.expr.into(), context.program) + { + return false; + } + + let parent = node.parent(); + + // keep for `(val as number)++` or `(val)++` or `(val!)++` + if parent.kind() == NodeKind::UpdateExpr && (is_type_assertion(node.expr.kind()) || expr_ends_with_non_null_assertion(&node.expr)) { return false; } + // Remove parens when directly inside these parent types (unless JSXExprContainer has leading comments) + if matches!( + parent.kind(), + NodeKind::ParenExpr | NodeKind::JSXElement | NodeKind::JSXFragment | NodeKind::UpdateExpr | NodeKind::ComputedPropName + ) || (parent.kind() == NodeKind::JSXExprContainer && node.expr.as_node().leading_comments_fast(context.program).next().is_none()) + { + return true; + } + // keep parens around any destructuring assignments if let Node::AssignExpr(assign_expr) = node.expr.as_node() { let left_kind = assign_expr.left.kind(); @@ -2880,11 +3035,6 @@ fn should_skip_paren_expr<'a>(node: &'a ParenExpr<'a>, context: &Context<'a>) -> } } - if matches!(node.expr.kind(), NodeKind::SeqExpr) { - // don't care about extra logic for sequence expressions - return false; - } - // keep when there is a JSDoc because it could be a type assertion or satisfies for c in node.leading_comments_fast(context.program) { if c.kind == CommentKind::Block && c.text.starts_with('*') { @@ -2892,32 +3042,61 @@ fn should_skip_paren_expr<'a>(node: &'a ParenExpr<'a>, context: &Context<'a>) -> } } - // keep for `(val as number)++` or `(val)++` - let parent = node.parent(); - if parent.kind() == NodeKind::UpdateExpr && matches!(node.expr.kind(), NodeKind::TsAsExpr | NodeKind::TsTypeAssertion) { - return false; + // preferNone mode: remove all unnecessary parens + if context.config.use_parentheses == UseParentheses::PreferNone { + // In expression statements, keep parens for disambiguation + if get_context_stmt_kind(node).is_some_and(|kind| kind == NodeKind::ExprStmt) { + match unwrap_assertion_node(node.expr.into()) { + Node::ObjectLit(_) | Node::FnExpr(_) | Node::ClassExpr(_) => return false, + Node::ArrowExpr(_) => { + if matches!( + parent.kind(), + NodeKind::TsAsExpr + | NodeKind::TsSatisfiesExpr + | NodeKind::TsConstAssertion + | NodeKind::TsTypeAssertion + | NodeKind::TsNonNullExpr + | NodeKind::CallExpr + | NodeKind::MemberExpr + | NodeKind::OptChainExpr + ) { + return false; + } + } + _ => {} + } + } + + // Remove redundant outer parens in nested assertion chains + if is_type_assertion(parent.kind()) { + return is_type_assertion(node.expr.kind()); + } } - if matches!(node.expr.kind(), NodeKind::ArrayLit) || matches!(node.expr, Expr::Ident(_)) { - return true; + // Keep parens when parent is unary and child has lower precedence than unary operators + // Examples: !(a ? b : c), !(a, b), !(x => x), !(a = b), !(yield a) + if parent.kind() == NodeKind::UnaryExpr + && matches!( + node.expr, + Expr::Bin(_) | Expr::Assign(_) | Expr::Cond(_) | Expr::Seq(_) | Expr::Arrow(_) | Expr::Yield(_) + ) + { + return false; } - if parent.kind() == NodeKind::MemberExpr && node.expr.kind() == NodeKind::MemberExpr { + if matches!(get_innermost_expr(&node.expr), Expr::Array(_) | Expr::Ident(_)) + || (parent.kind() == NodeKind::MemberExpr && node.expr.kind() == NodeKind::MemberExpr) + { return true; } - // skip over any paren exprs within paren exprs and needless paren exprs - if matches!( - parent.kind(), - NodeKind::ParenExpr - | NodeKind::ExprStmt - | NodeKind::JSXElement - | NodeKind::JSXFragment - | NodeKind::JSXExprContainer - | NodeKind::UpdateExpr - | NodeKind::ComputedPropName - ) { - return true; + // keep parens for object/function/class expressions (required for disambiguation) + if parent.kind() == NodeKind::ExprStmt { + return context.config.use_parentheses == UseParentheses::PreferNone + || !matches!( + unwrap_assertion_node(node.expr.into()), + Node::ObjectLit(_) | Node::FnExpr(_) | Node::ClassExpr(_) + ); } // skip explicitly parsing this as a paren expr as that will be handled @@ -2930,6 +3109,12 @@ fn should_skip_paren_expr<'a>(node: &'a ParenExpr<'a>, context: &Context<'a>) -> if assign_expr.right.range().contains(&node.range()) { return true; } + // Keep parens on left side of assignment if: + // - Contains type assertion: (obj.prop as Type) = value is valid, but obj.prop as Type = value is not + // - Ends with !: (x!) = y avoids ambiguity with != + if assign_expr.left.range().contains(&node.range()) && (is_type_assertion(node.expr.kind()) || expr_ends_with_non_null_assertion(&node.expr)) { + return false; + } } if let Node::VarDeclarator(var_decl) = parent { @@ -2955,13 +3140,241 @@ fn should_skip_paren_expr<'a>(node: &'a ParenExpr<'a>, context: &Context<'a>) -> } } + // Keep parens in spread with conditional expression + // ...( cond ? a : b ) is valid, but ...cond ? a : b is parsed as (...cond) ? a : b + if parent.kind() == NodeKind::SpreadElement && matches!(node.expr, Expr::Cond(_)) { + return false; + } + if let Node::MemberExpr(member_expr) = parent { if matches!(member_expr.prop, MemberProp::Computed(_)) && member_expr.prop.range().contains(&node.range()) { return true; } } - false + // In non-PreferNone modes, we've handled all cases where parens should be removed + if context.config.use_parentheses != UseParentheses::PreferNone { + return false; + } + + // Everything below is PreferNone mode only - we aggressively remove parens where safe + + // Keep parens for `new` when called: (new Foo())() vs new Foo()() + if matches!(node.expr, Expr::New(_)) && matches!(parent.kind(), NodeKind::CallExpr | NodeKind::OptCall) { + return false; + } + + // Keep parens for arrow/function/class when called or member accessed: (function() {})() + if matches!(node.expr, Expr::Arrow(_) | Expr::Fn(_) | Expr::Class(_)) + && matches!( + parent.kind(), + NodeKind::CallExpr | NodeKind::OptCall | NodeKind::OptChainExpr | NodeKind::MemberExpr + ) + { + return false; + } + + // Keep parens for unary and update expressions in specific contexts + if matches!(node.expr, Expr::Unary(_) | Expr::Update(_)) { + // Keep parens when member accessed: (typeof x).toUpperCase(), (x++).toString() + if matches!(parent.kind(), NodeKind::MemberExpr | NodeKind::OptChainExpr) { + return false; + } + + // JavaScript spec requires parens here - unary operators cannot appear unparenthesized before ** + if let Node::BinExpr(bin_expr) = parent { + if bin_expr.op() == BinaryOp::Exp && bin_expr.left.range().contains(&node.range()) { + return false; + } + } + } + + // Keep parens for await/yield in binary/member/call/new contexts: (await x) + 1, new (await x) + if matches!(node.expr, Expr::Await(_) | Expr::Yield(_)) + && matches!( + parent.kind(), + NodeKind::BinExpr | NodeKind::MemberExpr | NodeKind::OptChainExpr | NodeKind::CallExpr | NodeKind::OptCall | NodeKind::NewExpr + ) + { + return false; + } + + // Check if parens are needed for operator precedence in binary/logical expressions + if let Node::BinExpr(parent_bin) = parent { + // Keep parens for assignment expressions in binary context: (a = b) && c + if matches!(node.expr, Expr::Assign(_) | Expr::Update(_)) { + return false; + } + + // Keep parens for arrow functions in binary/logical expressions: a || (() => b) + if matches!(node.expr, Expr::Arrow(_)) { + return false; + } + + // Keep parens when expr ends with ! and is followed by =, ==, or === + // to avoid ambiguity with !=, !==: (a + b!) === x should not become a + b! === x + if parent_bin.left.range().contains(&node.range()) + && matches!(parent_bin.op(), BinaryOp::EqEq | BinaryOp::EqEqEq) + && expr_ends_with_non_null_assertion(&node.expr) + { + return false; + } + + if let Expr::Bin(inner_bin) = node.expr { + // Keep parens when mixing logical operators (&&, ||) with nullish coalescing (??) + // JavaScript requires parens: (a && b) ?? c or a && (b ?? c), but not a && b ?? c + if (matches!(parent_bin.op(), BinaryOp::NullishCoalescing) && matches!(inner_bin.op(), BinaryOp::LogicalAnd | BinaryOp::LogicalOr)) + || (matches!(parent_bin.op(), BinaryOp::LogicalAnd | BinaryOp::LogicalOr) && matches!(inner_bin.op(), BinaryOp::NullishCoalescing)) + { + return false; + } + + let parent_prec = get_precedence(&parent_bin.op()); + let inner_prec = get_precedence(&inner_bin.op()); + + // Keep parens if the inner expression has LOWER precedence than parent + if inner_prec < parent_prec { + return false; + } + + // Also keep parens when precedences are equal and associativity matters + if inner_prec == parent_prec { + // Different ops at same precedence level - parens always matter: a / (b * c) + if parent_bin.op() != inner_bin.op() { + return false; + } + + if parent_bin.right.range().contains(&node.range()) { + // RIGHT SIDE: Keep parens for left-associative operators + if is_non_commutative_or_comparison(&parent_bin.op()) { + return false; + } + } else { + // LEFT SIDE: Exponentiation is right-associative + if matches!(parent_bin.op(), BinaryOp::Exp) { + return false; + } + // LEFT SIDE: Remove redundant parens for left-associative operators + if is_non_commutative_or_comparison(&parent_bin.op()) { + return true; + } + } + } + } + } + + // Keep parens for object/function/class in arrow body: () => ({ a: 1 }) + if let Node::ArrowExpr(arrow) = parent { + if arrow.body.range().contains(&node.range()) + && matches!( + unwrap_assertion_node(node.expr.into()), + Node::ObjectLit(_) | Node::FnExpr(_) | Node::ClassExpr(_) + ) + { + return false; + } + } + + // Keep parens for type assertions in various contexts + if is_type_assertion(node.expr.kind()) { + // Keep parens when followed by member/optional access/call/non-null: (expr as Type).member or (expr as Type)?.optional or (expr as Type)() or (expr as Type)! + if matches!( + parent.kind(), + NodeKind::MemberExpr | NodeKind::OptChainExpr | NodeKind::CallExpr | NodeKind::OptCall | NodeKind::TsNonNullExpr + ) { + return false; + } + + // Keep parens in extends clause: class X extends (Y as typeof Z) {} + if let Some(class) = find_ancestor_class(node.into()) { + if let Some(super_class) = class.super_class { + if super_class.range().contains(&node.range()) { + return false; + } + } + } + + // Keep parens in new expression constructor: new (X as any)() + if parent.kind() == NodeKind::NewExpr { + if let Node::NewExpr(new_expr) = parent { + if new_expr.callee.range().contains(&node.range()) { + return false; + } + } + } + } + + // Keep parens for conditional expressions in binary context: (a ? b : c) + d + if matches!(node.expr, Expr::Cond(_)) && matches!(parent.kind(), NodeKind::BinExpr | NodeKind::MemberExpr | NodeKind::CallExpr | NodeKind::OptCall) { + return false; + } + + // Keep parens for binary expressions in various contexts + if matches!(node.expr, Expr::Bin(_)) { + // Member access and call have higher precedence than binary operators; (a / 1000).toString() or (a || b)(c) + if matches!( + parent.kind(), + NodeKind::MemberExpr | NodeKind::OptChainExpr | NodeKind::CallExpr | NodeKind::OptCall + ) { + return false; + } + + // Keep parens in new expression constructor: new (A || B)() + if let Node::NewExpr(new_expr) = parent { + if new_expr.callee.range().contains(&node.range()) { + return false; + } + } + } + + // Member access, call, non-null, and new have higher precedence than assignment, so parens are needed + if matches!(node.expr, Expr::Assign(_)) + && matches!( + parent.kind(), + NodeKind::MemberExpr | NodeKind::OptChainExpr | NodeKind::CallExpr | NodeKind::OptCall | NodeKind::NewExpr | NodeKind::TsNonNullExpr + ) + { + return false; + } + + // All other parens can be removed in PreferNone mode + true +} + +fn get_precedence(op: &BinaryOp) -> u8 { + match op { + BinaryOp::LogicalOr | BinaryOp::NullishCoalescing => 1, + BinaryOp::LogicalAnd => 2, + BinaryOp::BitOr => 3, + BinaryOp::BitXor => 4, + BinaryOp::BitAnd => 5, + BinaryOp::EqEq | BinaryOp::NotEq | BinaryOp::EqEqEq | BinaryOp::NotEqEq => 6, + BinaryOp::Lt | BinaryOp::LtEq | BinaryOp::Gt | BinaryOp::GtEq | BinaryOp::In | BinaryOp::InstanceOf => 7, + BinaryOp::LShift | BinaryOp::RShift | BinaryOp::ZeroFillRShift => 8, + BinaryOp::Add | BinaryOp::Sub => 9, + BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => 10, + BinaryOp::Exp => 11, + } +} + +/// Check if an expression ends with a non-null assertion (!) +/// This recursively checks the rightmost part of the expression +fn expr_ends_with_non_null_assertion(expr: &Expr) -> bool { + match expr { + Expr::TsNonNull(_) => true, + Expr::Paren(paren) => expr_ends_with_non_null_assertion(&paren.expr), + Expr::Bin(bin) => expr_ends_with_non_null_assertion(&bin.right), + Expr::Cond(cond) => expr_ends_with_non_null_assertion(&cond.alt), + Expr::Assign(assign) => expr_ends_with_non_null_assertion(&assign.right), + Expr::Seq(seq) => { + if let Some(last) = seq.exprs.last() { + expr_ends_with_non_null_assertion(last) + } else { + false + } + } + _ => false, + } } fn gen_sequence_expr<'a>(node: &SeqExpr<'a>, context: &mut Context<'a>) -> PrintItems { diff --git a/tests/specs/expressions/AsExpression/AsExpression_All.txt b/tests/specs/expressions/AsExpression/AsExpression_All.txt index e7c824a8..a196c794 100644 --- a/tests/specs/expressions/AsExpression/AsExpression_All.txt +++ b/tests/specs/expressions/AsExpression/AsExpression_All.txt @@ -24,3 +24,17 @@ testingtesting() .find(p => p.testing) as | string | undefined; + +== should format type assertion on left side of assignment == +(obj.prop as unknown) = { + handler: (data: string) => { + processData(data); + } +}; + +[expect] +(obj.prop as unknown) = { + handler: (data: string) => { + processData(data); + }, +}; diff --git a/tests/specs/expressions/AssignmentExpression/AssignmentExpression_All.txt b/tests/specs/expressions/AssignmentExpression/AssignmentExpression_All.txt index 70e6af12..616c814e 100644 --- a/tests/specs/expressions/AssignmentExpression/AssignmentExpression_All.txt +++ b/tests/specs/expressions/AssignmentExpression/AssignmentExpression_All.txt @@ -57,3 +57,15 @@ a = c = d = e; + +== should format object destructuring assignment == +({x} = obj); + +[expect] +({ x } = obj); + +== should format array destructuring assignment == +([a] = arr); + +[expect] +[a] = arr; diff --git a/tests/specs/expressions/SpreadElement/SpreadElement_All.txt b/tests/specs/expressions/SpreadElement/SpreadElement_All.txt index df56cd1b..7ce58ca7 100644 --- a/tests/specs/expressions/SpreadElement/SpreadElement_All.txt +++ b/tests/specs/expressions/SpreadElement/SpreadElement_All.txt @@ -3,3 +3,31 @@ const t = { ...obj }; [expect] const t = { ...obj }; + +== should format spread with conditional expression == +const o = { + ...(flag ? {} : { + key: { + value: flag || defaultValue + } + }) +}; + +[expect] +const o = { + ...(flag ? {} : { + key: { + value: flag || defaultValue, + }, + }), +}; + +== should format spread with sequence expression == +const o = { + ...(a, b) +}; + +[expect] +const o = { + ...(a, b), +}; diff --git a/tests/specs/general/UseParentheses_Disambiguation.txt b/tests/specs/general/UseParentheses_Disambiguation.txt new file mode 100644 index 00000000..30d988a5 --- /dev/null +++ b/tests/specs/general/UseParentheses_Disambiguation.txt @@ -0,0 +1,2175 @@ +~~ useParentheses: disambiguation ~~ +== should retain multi-line parentheses in expression statement without comment == +if (a) + (f + ()); + +[expect] +if (a) { + f(); +} + +== should keep multi-line parentheses in expression statement with comment == +if (a) + (f // + ()); + +[expect] +if (a) { + f // + (); +} + +== should retain multi-line parentheses in return without comment == +if (a) + return (f + ()); + +[expect] +if (a) { + return (f()); +} + +== should keep multi-line parentheses in return with comment == +if (a) + return (f // + ()); + +[expect] +if (a) { + return (f // + ()); +} + +== should retain multi-line parentheses in return with function call on next line == +if (a) + return (f +(veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongIdentifierName1234567890123456789012345678901234567890)); + +[expect] +if (a) { + return (f( + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongIdentifierName1234567890123456789012345678901234567890, + )); +} + +== should retain multi-line parentheses around long variable in return == +return ( + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongIdentifierName1234567890123456789012345678901234567890); + +[expect] +return veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongIdentifierName1234567890123456789012345678901234567890; + +== should retain multi-line parentheses in throw without comment == +if (a) + throw (f + ()); + +[expect] +if (a) { + throw (f()); +} + +== should keep multi-line parentheses in throw with comment == +if (a) + throw (f // + ()); + +[expect] +if (a) { + throw (f // + ()); +} + +== should retain multi-line parentheses in yield without comment == +function* gen() { + if (a) + yield (f + ()); +} + +[expect] +function* gen() { + if (a) { + yield (f()); + } +} + +== should keep multi-line parentheses in yield with comment == +function* gen() { + if (a) + yield (f // + ()); +} + +[expect] +function* gen() { + if (a) { + yield (f // + ()); + } +} + +== should keep multi-line parentheses in call argument with comment == +foo((arg // +)); + +[expect] +foo( + ( + arg // + ), +); + +== should retain multi-line parentheses in call argument without comment == +foo((arg +)); + +[expect] +foo(arg); + +== should keep multi-line parentheses in array element with comment == +[(item // +)]; + +[expect] +[ + ( + item // + ), +]; + +== should retain multi-line parentheses in array element without comment == +[(item +)]; + +[expect] +[item]; + +== should use parentheses for object literal expression statement == +({ foo: 1 }); + +[expect] +({ foo: 1 }); + +== should keep multi-line parentheses around optional chain object with comment == +(f // +)?.prop; + +[expect] +( + f // +)?.prop; + +== should retain multi-line parentheses around optional chain object without comment == +(f +)?.prop; + +[expect] +f?.prop; + +== should keep multi-line parentheses with comment before optional chain member access == +(f // +?.prop); + +[expect] +f // + ?.prop; + +== should retain multi-line parentheses around optional chain member access without comment == +(f +?.prop); + +[expect] +f + ?.prop; + +== should keep multi-line parentheses around member access object with comment == +(f // +).prop; + +[expect] +( + f // +).prop; + +== should retain multi-line parentheses around member access object without comment == +(f +).prop; + +[expect] +f.prop; + +== should keep multi-line parentheses with comment before member access == +(f // +.prop); + +[expect] +f // + .prop; + +== should retain multi-line parentheses around member access without comment == +(f +.prop); + +[expect] +f + .prop; + +== should keep multi-line parentheses around call callee with comment == +(f // +)(); + +[expect] +( + f // +)(); + +== should retain multi-line parentheses around call callee without comment == +(f +)(); + +[expect] +f(); + +== should keep multi-line parentheses with comment before call == +(f // +()); + +[expect] +f // +(); + +== should retain multi-line parentheses around call without comment == +(f +()); + +[expect] +f(); + +== should keep multi-line parentheses around optional call callee with comment == +(f // +)?.(); + +[expect] +( + f // +)?.(); + +== should retain multi-line parentheses around optional call callee without comment == +(f +)?.(); + +[expect] +f?.(); + +== should keep multi-line parentheses with comment before optional chain == +(f // +?.()); + +[expect] +f // +?.(); + +== should retain multi-line parentheses around optional chain call without comment == +(f +?.()); + +[expect] +f?.(); + +== TEST: single line array access should retain parens == +(a[2]); + +[expect] +a[2]; + +== should keep multi-line parentheses around call expression == +(f // +()); + +[expect] +f // +(); + +== should keep multi-line parentheses around function callee == +(f // +)(); + +[expect] +( + f // +)(); + +== should remove parentheses inside call arguments == +f((1 + 2)); + +[expect] +f(1 + 2); + +== should remove parentheses inside return statement == +function test() { + return (value); +} + +[expect] +function test() { + return value; +} + +== should keep parentheses when they affect operator precedence == +const result = (a + b) * c; + +[expect] +const result = (a + b) * c; + +== should retain parentheses that enforce grouping == +const ambiguous = a / (b * c); + +[expect] +const ambiguous = a / (b * c); + +== should remove parentheses that do not affect precedence == +const precedenceOk = (a * b) + c; + +[expect] +const precedenceOk = (a * b) + c; + +== should remove parentheses in ternary branches == +const value = condition ? (left) : (right); + +[expect] +const value = condition ? left : right; + +== should remove parentheses inside array elements == +const items = [(value), ((other))]; + +[expect] +const items = [value, other]; + +== should retain parentheses in array destructuring pattern when required == +const [value = (await load())] = source; + +[expect] +const [value = (await load())] = source; + +== should remove parentheses inside object properties == +const obj = { + prop: (value), + nested: ((other)), +}; + +[expect] +const obj = { + prop: value, + nested: other, +}; + +== should keep parentheses around object literal arrow body == +const arrow = () => ({ value: 1 }); + +[expect] +const arrow = () => ({ value: 1 }); + +== should remove parentheses inside template expression == +const message = `hello ${ (name) }`; + +[expect] +const message = `hello ${name}`; + +== should remove parentheses around optional chaining == +const maybe = (obj?.prop); + +[expect] +const maybe = obj?.prop; + +== should retain parentheses around optional chaining when combined with exponentiation == +const complex = (obj?.value) ** 2; + +[expect] +const complex = (obj?.value) ** 2; + +== should keep parentheses around function expression callees == +const iifeResult = (function () {})(); + +[expect] +const iifeResult = (function() {})(); + +== should keep parentheses around optional chained function expression callees == +const optionalIife = (function () {})?.(); + +[expect] +const optionalIife = (function() {})?.(); + +== should keep parentheses around optional chained arrow callees == +const optionalArrowIife = (() => {})?.(); + +[expect] +const optionalArrowIife = (() => {})?.(); + +== should remove redundant parens in nested optional arrow calls == +const nestedOptionalArrow = ((() => {})?.())?.(); + +[expect] +const nestedOptionalArrow = ((() => {})?.())?.(); + +== should keep parentheses around optional chained class expression callees == +const optionalClassIife = (class {})?.(); + +[expect] +const optionalClassIife = (class {})?.(); + +== should keep parentheses around optional chained class expression property access == +const optionalClassProp = (class { constructor() { this.prop = 1; } })?.prop; + +[expect] +const optionalClassProp = (class { + constructor() { + this.prop = 1; + } +})?.prop; + +== should keep parentheses around optional chained function expression property access == +const optionalFuncProp = (function () {})?.prop; + +[expect] +const optionalFuncProp = (function() {})?.prop; + +== should keep parentheses around optional chained arrow function property access == +const optionalArrowProp = (() => {})?.prop; + +[expect] +const optionalArrowProp = (() => {})?.prop; + +== should keep parentheses around multi-line function expression property access == +const functionPropMultiline = (function () {} + ).prop; + +[expect] +const functionPropMultiline = (function() {}).prop; + +== should keep parentheses around multi-line class expression property access == +const classPropMultiline = (class {} + ).prop; + +[expect] +const classPropMultiline = (class {}).prop; + +== should keep parentheses around multi-line arrow function property access == +const arrowPropMultiline = (() => {} + ).prop; + +[expect] +const arrowPropMultiline = (() => {}).prop; + +== should keep parentheses around multi-line optional chained function property access == +const optionalFunctionPropMultiline = (function () {} + )?.prop; + +[expect] +const optionalFunctionPropMultiline = (function() {})?.prop; + +== should keep parentheses around multi-line optional chained function property access with comment == +const optionalFunctionPropMultilineComment = (function () {} + )?.prop; // comment + +[expect] +const optionalFunctionPropMultilineComment = (function() {})?.prop; // comment + +== should keep parentheses around multi-line optional chained arrow call == +const optionalArrowCallMultiline = (() => {} + )?.(); + +[expect] +const optionalArrowCallMultiline = (() => {})?.(); + +== should keep parentheses around multi-line optional chained function call == +const optionalFunctionCallMultiline = (function () {} + )?.(); + +[expect] +const optionalFunctionCallMultiline = (function() {})?.(); + +== should remove redundant parens around optional chained new expression access == +const optionalNewProp = (new Foo())?.prop; + +[expect] +const optionalNewProp = (new Foo())?.prop; + +== should keep parentheses around multi-line optional chained new expression access == +const optionalNewPropMultiline = (new Foo + ())?.prop; + +[expect] +const optionalNewPropMultiline = (new Foo())?.prop; + +== should remove parentheses around nullish coalescing inside assignments == +const fallback = (primary ?? secondary); + +[expect] +const fallback = primary ?? secondary; + +== should remove parentheses around await inside expressions == +async function load() { + const data = (await fetchData()); +} + +[expect] +async function load() { + const data = await fetchData(); +} + +== should keep parentheses around awaited call when accessing members == +async function useData() { + return (await loadData()).map(item => item); +} + +[expect] +async function useData() { + return (await loadData()).map(item => item); +} + +== should remove parentheses around yield expression == +function* gen() { + yield (value); +} + +[expect] +function* gen() { + yield value; +} + +== should retain parentheses around yield used in expression == +function* collect() { + const collected = [ (yield value) ]; +} + +[expect] +function* collect() { + const collected = [yield value]; +} + +== should keep parentheses around yield when accessing members == +function* project() { + const result = (yield getValue()).prop; +} + +[expect] +function* project() { + const result = (yield getValue()).prop; +} + +== should keep parentheses around multi-line yield expression == +function* yieldMultiline() { + yield (getGenerator + ()); +} + +[expect] +function* yieldMultiline() { + yield (getGenerator()); +} + +== should keep parentheses around multi-line yield optional call == +function* yieldOptionalMultiline() { + return (yield getFactory + ())?.(); +} + +[expect] +function* yieldOptionalMultiline() { + return (yield getFactory())?.(); +} + +== should keep parentheses around multi-line yield property access == +function* yieldPropertyMultiline() { + return (yield getFactory + ()).value; +} + +[expect] +function* yieldPropertyMultiline() { + return (yield getFactory()).value; +} + +== should remove parentheses around throw expression == +throw (error); + +[expect] +throw error; + +== should keep parentheses around multi-line throw expression == +function throwMultiline() { + throw (createError + ()); +} + +[expect] +function throwMultiline() { + throw (createError()); +} + +== should keep parentheses around multi-line await expression in throw == +async function throwAwaitMultiline() { + throw (await createAsyncError + ()); +} + +[expect] +async function throwAwaitMultiline() { + throw (await createAsyncError()); +} + +== should keep parentheses around multi-line throw optional call == +function throwOptionalMultiline() { + throw (getFactory + ())?.(); +} + +[expect] +function throwOptionalMultiline() { + throw (getFactory())?.(); +} + +== should keep parentheses around multi-line throw property access == +function throwPropertyMultiline() { + throw (createError + ()).code; +} + +[expect] +function throwPropertyMultiline() { + throw (createError()).code; +} + +== should retain parentheses around throw in IIFE == +const result = condition ? value : (() => { throw error; })(); + +[expect] +const result = condition ? value : (() => { + throw error; +})(); + +== should remove parentheses around export default expression == +export default (component); + +[expect] +export default component; + +== should retain parentheses around export default when template literal == +export default (`value`); + +[expect] +export default (`value`); + +== should remove parentheses in if condition == +if ((condition)) { + doSomething(); +} + +[expect] +if (condition) { + doSomething(); +} + +== should keep parentheses around multi-line await in if condition == +async function ifAwaitMultiline() { + if ((await shouldProceed + ())) { + doSomething(); + } +} + +[expect] +async function ifAwaitMultiline() { + if ((await shouldProceed())) { + doSomething(); + } +} + +== should remove parentheses in while condition == +while ((ready)) { + run(); +} + +[expect] +while (ready) { + run(); +} + +== should remove parentheses in do while condition == +do { + work(); +} while ((shouldContinue)); + +[expect] +do { + work(); +} while (shouldContinue); + +== should keep parentheses around multi-line await in while condition == +async function whileAwaitMultiline() { + while ((await shouldContinue + ())) { + run(); + } +} + +[expect] +async function whileAwaitMultiline() { + while ((await shouldContinue())) { + run(); + } +} + +== should remove parentheses in for-of iterable == +for (const item of (collection)) { + consume(item); +} + +[expect] +for (const item of collection) { + consume(item); +} + +== should keep parentheses around multi-line await in for-of iterable == +async function forOfAwaitMultiline() { + for (const item of (await loadItems + ())) { + consume(item); + } +} + +[expect] +async function forOfAwaitMultiline() { + for (const item of (await loadItems())) { + consume(item); + } +} + +== should remove parentheses in for head == +for (let i = (0); i < (max); i++) { + step(i); +} + +[expect] +for (let i = 0; i < max; i++) { + step(i); +} + +== should remove parentheses in switch discriminant and cases == +switch ((value)) { +case (1): + break; +} + +[expect] +switch (value) { + case (1): + break; +} + +== should remove parentheses around unary operations == +const increment = (++(value)); + +[expect] +const increment = ++value; + +== should remove parentheses around typeof in binary expressions == +const typeCheck = (typeof value) + " suffix"; + +[expect] +const typeCheck = (typeof value) + " suffix"; + +== should keep parentheses around delete when followed by member access == +const deleteResult = (delete obj.prop).toString(); + +[expect] +const deleteResult = (delete obj.prop).toString(); + +== should keep parentheses around typeof when accessing members == +const typeInfo = (typeof value).toUpperCase(); + +[expect] +const typeInfo = (typeof value).toUpperCase(); + +== should keep parentheses around void when accessing members == +const voidInfo = (void 0).toString(); + +[expect] +const voidInfo = (void 0).toString(); + +== should remove parentheses in default parameter initializers == +function configure(option = (defaultValue)) {} + +[expect] +function configure(option = defaultValue) {} + +== should remove parentheses inside logical expressions == +const combined = (a && b) || (c && d); + +[expect] +const combined = (a && b) || (c && d); + +== should retain parentheses in logical expression when required == +const precedence = a && (b || c); + +[expect] +const precedence = a && (b || c); + +== should remove parentheses around new expression == +const instance = (new Foo()); + +[expect] +const instance = new Foo(); + +== should retain parentheses around new with call when needed == +const construct = (new Foo())(); + +[expect] +const construct = (new Foo())(); + +== should remove parentheses around typeof/void/delete == +const info = { + type: typeof (value), + cleared: void (0), + removed: delete (obj.prop), +}; + +[expect] +const info = { + type: typeof value, + cleared: void (0), + removed: delete (obj.prop), +}; + +== should remove parentheses around tagged template expressions == +const tagged = tag((value)); + +[expect] +const tagged = tag(value); + +== should remove parentheses inside class field initializer == +class Example { + value = (initial); +} + +[expect] +class Example { + value = initial; +} + +== should remove parentheses in array destructuring defaults == +const [first = (fallback)] = source; + +[expect] +const [first = fallback] = source; + +== should remove parentheses in object destructuring defaults == +const { value = (fallback) } = source; + +[expect] +const { value = fallback } = source; + +== should remove parentheses around dynamic import specifier == +async function load() { + return import((moduleName)); +} + +[expect] +async function load() { + return import(moduleName); +} + +== should retain parentheses around dynamic import with assertions == +async function load() { + return import(moduleName, { with: (options) }); +} + +[expect] +async function load() { + return import(moduleName, { with: options }); +} + +== should remove parentheses around class heritage == +class Derived extends (Base) {} + +[expect] +class Derived extends Base {} + +== should remove parentheses in super call == +class Child extends Base { + constructor() { + super((arg)); + } +} + +[expect] +class Child extends Base { + constructor() { + super(arg); + } +} + +== should remove parentheses around yield star expression == +function* forward() { + yield* (iterable); +} + +[expect] +function* forward() { + yield* iterable; +} + +== should keep parentheses around multi-line yield star expression == +function* forwardMultiline() { + yield* (getIterable + ()); +} + +[expect] +function* forwardMultiline() { + yield* (getIterable()); +} + +== should keep parentheses around sequence expressions == +doSomething((a, b)); + +[expect] +doSomething((a, b)); + +== should remove parentheses around call expression results == +const resultCall = (compute()) + 1; + +[expect] +const resultCall = (compute()) + 1; + +== should remove parentheses around expressions in for loop headers == +for (let i = (0); i < (max); i++) {} + +[expect] +for (let i = 0; i < max; i++) {} + +== should retain parentheses when mixing nullish coalescing with logical OR == +const fallbackOr = (x ?? y) || z; + +[expect] +const fallbackOr = (x ?? y) || z; + +== should retain parentheses when mixing nullish coalescing with logical AND == +const fallbackAnd = (x ?? y) && z; + +[expect] +const fallbackAnd = (x ?? y) && z; + +== should retain parentheses when mixing logical OR with nullish coalescing == +const orWithNull = x || (y ?? z); + +[expect] +const orWithNull = x || (y ?? z); + +== should retain parentheses when mixing logical AND with nullish coalescing == +const andWithNull = x && (y ?? z); + +[expect] +const andWithNull = x && (y ?? z); + +== should remove parentheses around delete in logical expressions == +const deleteCheck = (delete obj.prop) && other; + +[expect] +const deleteCheck = (delete obj.prop) && other; + +== should remove parentheses around typeof in binary expressions == +const typeCheck = (typeof value) + " suffix"; + +[expect] +const typeCheck = (typeof value) + " suffix"; + +== should remove parentheses around void in logical expressions == +const voidCheck = (void 0) || fallback; + +[expect] +const voidCheck = (void 0) || fallback; + +== should keep parentheses around await in binary expressions == +async function asyncBinary() { + const result = (await fetch()) + 1; +} + +[expect] +async function asyncBinary() { + const result = (await fetch()) + 1; +} + +== should remove redundant parentheses around await when returned == +async function returnAwait(condition) { + return (await resolveValue(condition)); +} + +[expect] +async function returnAwait(condition) { + return (await resolveValue(condition)); +} + +== should keep parentheses around multi-line return call == +function returnMultilineCall() { + return (getFactory + ()); +} + +[expect] +function returnMultilineCall() { + return (getFactory()); +} + +== should keep parentheses around multi-line return call with comment == +function returnMultilineCallWithComment() { + return (createFactory // comment + ()); +} + +[expect] +function returnMultilineCallWithComment() { + return (createFactory // comment + ()); +} + +== should keep parentheses around multi-line throw call with comment == +function throwMultilineWithComment() { + throw (createError // comment + ()); +} + +[expect] +function throwMultilineWithComment() { + throw (createError // comment + ()); +} + +== should keep parentheses around multi-line await expression == +async function awaitMultiline() { + return (await loadData + ()).map(item => item); +} + +[expect] +async function awaitMultiline() { + return (await loadData()).map(item => item); +} + +== should keep parentheses around multi-line await optional call == +async function awaitOptionalMultiline() { + return (await loadFactory + ())?.(); +} + +[expect] +async function awaitOptionalMultiline() { + return (await loadFactory())?.(); +} + +== should keep parentheses around multi-line await binary expression == +async function awaitBinaryMultiline() { + const total = (await computeValue + ()) + 1; + return total; +} + +[expect] +async function awaitBinaryMultiline() { + const total = (await computeValue()) + 1; + return total; +} + +== should keep parentheses around multi-line return property access == +function returnPropertyMultiline() { + return (getObject + ()).value; +} + +[expect] +function returnPropertyMultiline() { + return (getObject()).value; +} + +== should keep parentheses around multi-line return optional call == +function returnOptionalMultiline() { + return (getCallback + ())?.(); +} + +[expect] +function returnOptionalMultiline() { + return (getCallback())?.(); +} + +== should keep parentheses around multi-line return property access with comment == +function returnPropertyMultilineComment() { + return (getObject // comment + ()).value; +} + +[expect] +function returnPropertyMultilineComment() { + return (getObject // comment + ()).value; +} + +== should remove redundant parentheses around await in ternary expressions == +async function asyncTernary() { + const value = condition ? (await foo()) : bar; +} + +[expect] +async function asyncTernary() { + const value = condition ? (await foo()) : bar; +} + +== should retain parentheses around yield in binary expressions == +function* yieldBinary() { + const result = (yield value) + 1; +} + +[expect] +function* yieldBinary() { + const result = (yield value) + 1; +} + +== should keep parentheses around multi-line yield binary expression == +function* yieldBinaryMultiline() { + const result = (yield computeValue + ()) + 1; + return result; +} + +[expect] +function* yieldBinaryMultiline() { + const result = (yield computeValue()) + 1; + return result; +} + +== should remove redundant parens around member access on await in ternary condition == +async function ternaryAccess(cond) { + return ((await fetchData()).value) ? (await fetchData()).value : defaultValue; +} + +[expect] +async function ternaryAccess(cond) { + return ((await fetchData()).value) ? (await fetchData()).value : defaultValue; +} + +== should keep parentheses around object literal (disambiguation required) == +({ + prop: value +}); + +[expect] +({ + prop: value, +}); + +== should keep parentheses around function expression (disambiguation required) == +(function test() { + return 1; +}); + +[expect] +(function test() { + return 1; +}); + +== should NOT add parentheses around arrow function == +() => 42; + +[expect] +(() => 42); + +== should keep simple call expression statement == +foo(); + +[expect] +foo(); + +== should keep await expression statement == +async function run() { + await doSomething(); +} + +[expect] +async function run() { + await doSomething(); +} + +== should remove parentheses around arrow function == +(() => 42); + +[expect] +(() => 42); + +== should NOT add parentheses around simple identifier (not supported) == +someVariable; + +[expect] +someVariable; + +== should NOT add parentheses around call expression (not supported) == +someFunction(); + +[expect] +someFunction(); + +== should remove parentheses around call expression == +(someFunction()); + +[expect] +someFunction(); + +== should NOT add parentheses around member expression (not supported) == +obj.prop; + +[expect] +obj.prop; + +== should NOT add parentheses around array literal (not supported) == +[1, 2, 3]; + +[expect] +[1, 2, 3]; + +== should NOT add parentheses around template literal (not supported) == +`hello world`; + +[expect] +`hello world`; + +== should NOT add parentheses around binary expression (not supported) == +a + b; + +[expect] +a + b; + +== should remove parentheses around binary expression == +(a + b); + +[expect] +a + b; + +== should remove redundant outer parens from nested binary expressions == +((a || b) && c); + +[expect] +(a || b) && c; + +== should remove redundant outer parens from simple expression == +(value); + +[expect] +value; + +== should keep parentheses that affect operator precedence == +((a + b) * c); + +[expect] +(a + b) * c; + +== should keep parentheses around arrow function used as callee == +(() => 42)(); + +[expect] +(() => 42)(); + +== should keep parentheses around arrow function used in optional chain == +(() => 42)?.prop; + +[expect] +(() => 42)?.prop; + +== should keep parentheses around arrow function with type assertion == +(() => 42) as NumberFunction; + +[expect] +(() => 42) as NumberFunction; + +== should remove parentheses around await expression == +(await value()); + +[expect] +await value(); + +== should remove parentheses around unary expression == +(+value); + +[expect] ++value; + +== should remove parentheses around prefix increment == ++(value); + +[expect] ++value; + +== should remove parentheses around new expression == +(new Foo()); + +[expect] +new Foo(); + +== should remove parentheses around optional chain expression == +(obj?.prop); + +[expect] +obj?.prop; + +== should remove parentheses around nullish coalescing expression == +(a ?? b); + +[expect] +a ?? b; + +== should keep parentheses around object literal with as assertion == +({}) as Foo; + +[expect] +({}) as Foo; + +== should keep parentheses around object literal with satisfies assertion == +({}) satisfies Foo; + +[expect] +({}) satisfies Foo; + +== should keep parentheses around object literal with as const assertion == +({}) as const; + +[expect] +({}) as const; + +== should NOT add parentheses around object literal with type assertion == +{}; + +[expect] + {}; + +== should keep parentheses around object literal with non-null assertion == +({})!; + +[expect] +({})!; + +== should keep parentheses around object literal with multiple assertions == +({}) as Foo as Bar; + +[expect] +({}) as Foo as Bar; + +== should keep parentheses around object literal with as assertion inside while loop == +while (true) { + ({} as X); +} + +[expect] +while (true) { + ({} as X); +} + +== should keep parentheses around function expression with as assertion == +(function() {}) as Foo; + +[expect] +(function() {}) as Foo; + +== should keep parentheses around function expression with satisfies assertion == +(function() {}) satisfies Foo; + +[expect] +(function() {}) satisfies Foo; + +== should keep parentheses around function expression with as const assertion == +(function() {}) as const; + +[expect] +(function() {}) as const; + +== should keep parentheses around function expression with non-null assertion == +(function() {})!; + +[expect] +(function() {})!; + +== should keep parentheses around function expression with multiple assertions == +(function() {}) as Foo as Bar; + +[expect] +(function() {}) as Foo as Bar; + +== should NOT add parentheses around arrow function with as assertion == +(() => 42) as Foo; + +[expect] +(() => 42) as Foo; + +== should NOT add parentheses around call expression with as assertion == +foo() as Bar; + +[expect] +foo() as Bar; + +== should keep parentheses around nested object literal with assertions == +({prop: {}}) as Foo; + +[expect] +({ prop: {} }) as Foo; + +== should keep parentheses around object literal used as member expression base == +({}).foo; + +[expect] +({}).foo; + +== should keep parentheses around object literal used as call expression callee == +({})(); + +[expect] +({})(); + +== should keep parentheses around object literal used as optional chain base == +({})?.prop; + +[expect] +({})?.prop; + +== should keep parentheses around function expression used as member expression base == +(function() {}).foo; + +[expect] +(function() {}).foo; + +== should keep parentheses around function expression used as call expression callee == +(function() {})(); + +[expect] +(function() {})(); + +== should keep parentheses around function expression used as optional chain base == +(function() {})?.prop; + +[expect] +(function() {})?.prop; + +== should keep parentheses with mixed assertion wrappers on object literal == +({})! as Foo; + +[expect] +({})! as Foo; + +== should keep parentheses with mixed assertion wrappers on function expression == +(function() {})! as Foo; + +[expect] +(function() {})! as Foo; + +== should keep parentheses with chained mixed assertions on object literal == +({}) as Foo satisfies Bar; + +[expect] +({}) as Foo satisfies Bar; + +== should keep parentheses with chained mixed assertions on function expression == +(function() {}) as Foo satisfies Bar; + +[expect] +(function() {}) as Foo satisfies Bar; + +== should remove redundant outer parens from nested assertion chains on object literal == +(({} as X) as Y); + +[expect] +({} as X) as Y; + +== should remove redundant outer parens from nested assertion chains on function expression == +((function() {}) as X) as Y; + +[expect] +((function() {}) as X) as Y; + +== should remove redundant outer parens from nested assertion chains on class expression == +((class {}) as X) as Y; + +[expect] +((class {}) as X) as Y; + +== should keep parentheses around sequence expression == +(a, b); + +[expect] +(a, b); + +== should keep parentheses around anonymous class expression == +(class {}); + +[expect] +(class {}); + +== should keep parentheses around named class expression == +(class Foo {}); + +[expect] +(class Foo {}); + +== should keep parentheses around class expression with as assertion == +(class {}) as Foo; + +[expect] +(class {}) as Foo; + +== should keep parentheses around class expression with satisfies assertion == +(class {}) satisfies Foo; + +[expect] +(class {}) satisfies Foo; + +== should keep parentheses around class expression with as const assertion == +(class {}) as const; + +[expect] +(class {}) as const; + +== should keep parentheses around class expression with non-null assertion == +(class {})!; + +[expect] +(class {})!; + +== should keep parentheses around class expression with multiple assertions == +(class {}) as Foo as Bar; + +[expect] +(class {}) as Foo as Bar; + +== should keep parentheses with mixed assertion wrappers on class expression == +(class {})! as Foo; + +[expect] +(class {})! as Foo; + +== should keep parentheses around class expression with chained mixed assertions == +(class {}) as Foo satisfies Bar; + +[expect] +(class {}) as Foo satisfies Bar; + +== should keep parentheses around class expression used as member expression base == +(class {}).foo; + +[expect] +(class {}).foo; + +== should keep parentheses around class expression used as call expression callee == +(class {})(); + +[expect] +(class {})(); + +== should keep parentheses around class expression used as optional chain base == +(class {})?.prop; + +[expect] +(class {})?.prop; + +== should keep parentheses with nested assignments in logical expression == +if (this.leaving && (distanceL2 = (dlx = this.leaving.x - x) ** 2 + (dly = this.leaving.y - y) ** 2) > this.far2) {} + +[expect] +if (this.leaving && (distanceL2 = (dlx = this.leaving.x - x) ** 2 + (dly = this.leaving.y - y) ** 2) > this.far2) {} + +== should keep parentheses around simple nested assignment == +const x = (y = 2); + +[expect] +const x = (y = 2); + +== should keep parentheses around type assertion with optional chaining == +expect((result as Payload).options?.debug).toBe(true); + +[expect] +expect((result as Payload).options?.debug).toBe(true); + +== should keep parentheses around type assertion with optional chaining in assignment == +const errorCode = (event.target as IDBOpenDBRequest).error?.name || 'Unknown'; + +[expect] +const errorCode = (event.target as IDBOpenDBRequest).error?.name || "Unknown"; + +== should keep parentheses around type assertion with method call == +return Object.keys(object).every(key => (keys as string[]).includes(key)); + +[expect] +return Object.keys(object).every(key => (keys as string[]).includes(key)); + +== should keep parentheses around negation of in expression == +const onlyColor = computed(() => 'color' in props.colorData && !('word' in props.colorData)); + +[expect] +const onlyColor = computed(() => "color" in props.colorData && !("word" in props.colorData)); + +== should keep parentheses around negation of in expression with member access == +const onlyWord = computed(() => !('color' in props.colorData) && 'word' in props.colorData); + +[expect] +const onlyWord = computed(() => !("color" in props.colorData) && "word" in props.colorData); + +== should keep parentheses around identifier in ternary operator == +const errorTitle = computed(() => 'Fehler' + (anyErrors.value ? '' : '(leer)')); + +[expect] +const errorTitle = computed(() => "Fehler" + (anyErrors.value ? "" : "(leer)")); + +== should keep parentheses around complex arithmetic expression == +const duration = computed(() => (props.data.entries.flatMap(x => x.log[x.log.length - 1].t - x.log[0].t).reduce((a, b) => a + b) / 1000).toString()); + +[expect] +const duration = computed(() => + (props.data.entries.flatMap(x => x.log[x.log.length - 1].t - x.log[0].t).reduce((a, b) => a + b) / 1000).toString() +); + +== should keep parentheses around negation of instanceof expression == +const isNotInstance = !(obj instanceof MyClass); + +[expect] +const isNotInstance = !(obj instanceof MyClass); + +== should keep parentheses around negation of comparison expression == +const isNotLess = !(a < b); + +[expect] +const isNotLess = !(a < b); + +== should keep parentheses around negation of equality expression == +const isNotEqual = !(a == b); + +[expect] +const isNotEqual = !(a == b); + +== should keep parentheses around negation of strict equality expression == +const isNotStrictEqual = !(a === b); + +[expect] +const isNotStrictEqual = !(a === b); + +== should keep parentheses around negation of addition expression == +const notSum = !(a + b); + +[expect] +const notSum = !(a + b); + +== should keep parentheses around negation of subtraction expression == +const notDiff = !(a - b); + +[expect] +const notDiff = !(a - b); + +== should keep parentheses around negation of logical and expression == +const notBoth = !(a && b); + +[expect] +const notBoth = !(a && b); + +== should keep parentheses around negation of logical or expression == +const notEither = !(a || b); + +[expect] +const notEither = !(a || b); + +== should keep parentheses around typeof with binary expression == +const typeOfSum = typeof (a + b); + +[expect] +const typeOfSum = typeof (a + b); + +== should keep parentheses around void with logical and expression == +const voidResult = void (a && b); + +[expect] +const voidResult = void (a && b); + +== should keep parentheses around unary minus with exponentiation == +const negativeSquared = (-2) ** 3; + +[expect] +const negativeSquared = (-2) ** 3; + +== should keep parentheses around unary plus with exponentiation == +const positiveSquared = (+x) ** 2; + +[expect] +const positiveSquared = (+x) ** 2; + +== should keep parentheses around typeof with exponentiation == +const typeofExp = (typeof x) ** 2; + +[expect] +const typeofExp = (typeof x) ** 2; + +== should keep parentheses around void with exponentiation == +const voidExp = (void 0) ** 2; + +[expect] +const voidExp = (void 0) ** 2; + +== should keep parentheses around bitwise not with exponentiation == +const bitwiseNotExp = (~flags) ** 2; + +[expect] +const bitwiseNotExp = (~flags) ** 2; + +== should keep parentheses around logical not with exponentiation == +const logicalNotExp = (!flag) ** 2; + +[expect] +const logicalNotExp = (!flag) ** 2; + +== should keep parentheses around delete with exponentiation == +const deleteExp = (delete obj.prop) ** 2; + +[expect] +const deleteExp = (delete obj.prop) ** 2; + +== should keep parentheses around await with exponentiation == +async function test() { + const awaitExp = (await getValue()) ** 2; +} + +[expect] +async function test() { + const awaitExp = (await getValue()) ** 2; +} + +== should keep parentheses for left-to-right exponentiation (associativity override) == +const leftToRight = (2 ** 3) ** 2; + +[expect] +const leftToRight = (2 ** 3) ** 2; + +== should keep parentheses for right-to-left exponentiation for clarity == +const rightToLeft = 2 ** (3 ** 2); + +[expect] +const rightToLeft = 2 ** (3 ** 2); + +== should keep required parentheses around spread with conditional expression == +const o = { + ...(flag ? {} : { + key: { + value: flag || defaultValue + } + }) +}; + +[expect] +const o = { + ...(flag ? {} : { + key: { + value: flag || defaultValue, + }, + }), +}; + +== should keep required parentheses around type assertion on left side of assignment == +(obj.prop as unknown) = { + handler: (data: string) => { + processData(data); + } +}; + +[expect] +(obj.prop as unknown) = { + handler: (data: string) => { + processData(data); + }, +}; + +== should keep required parentheses around spread with sequence expression == +const o = { + ...(a, b) +}; + +[expect] +const o = { + ...(a, b), +}; + +== should keep required parentheses around object destructuring assignment == +({x} = obj); + +[expect] +({ x } = obj); + +== should keep required parentheses around object destructuring assignment with trailing comment == +({x} = obj); // comment + +[expect] +({ x } = obj); // comment + +== should remove parentheses around array destructuring assignment == +([a] = arr); + +[expect] +[a] = arr; + +== should keep required parentheses around assignment with member access in while condition == +while ((current = startElement.previousSibling!)._offset! >= threshold && current !== endElement) + process(current); + +[expect] +while ((current = startElement.previousSibling!)._offset! >= threshold && current !== endElement) { + process(current); +} + +== should keep required parentheses around assignment with member access and multiple conditions == +while ( + (current = firstElement.nextSibling!)._offset! + current!.height < targetValue + && current !== lastElement && current.nextSibling !== lastElement +) + process(current); + +[expect] +while ( + (current = firstElement.nextSibling!)._offset! + current!.height < targetValue + && current !== lastElement && current.nextSibling !== lastElement +) { + process(current); +} + +== should keep required parentheses around assignment in comparison in while condition == +while ((item = getNext()) !== null) + process(item); + +[expect] +while ((item = getNext()) !== null) { + process(item); +} + +== should keep required parentheses around assignment with call == +(fn = () => 42)(); + +[expect] +(fn = () => 42)(); + +== should keep required parentheses around assignment with optional call == +(maybeFn = () => 99)?.(); + +[expect] +(maybeFn = () => 99)?.(); + +== should keep required parentheses around assignment with computed member access == +(obj = {x: 5, y: 10})["x"]; + +[expect] +(obj = { x: 5, y: 10 })["x"]; + +== should keep required parentheses around assignment with optional computed member access == +(obj = getValue())?.[key]; + +[expect] +(obj = getValue())?.[key]; + +== should keep required parentheses around assignment with new expression == +const instance = new (Constructor = MyClass)(); + +[expect] +const instance = new (Constructor = MyClass)(); + +== should keep required parentheses around assignment with non-null assertion == +const result = (maybeValue = getValue())!; + +[expect] +const result = (maybeValue = getValue())!; + +== should keep required parentheses around assignment with type assertion == +const result = (value = 5) as number; + +[expect] +const result = (value = 5) as number; + +== should keep parentheses around arrow function in logical expression == +f(a() || (() => "")); + +[expect] +f(a() || (() => "")); + +== should keep parentheses around assignment with member access in logical expression == +f(!a || !(b = a!.b)); + +[expect] +f(!a || !(b = a!.b)); + +== should keep parentheses around arrow function with type annotation in logical expression == +f(a && ((b: string) => c!(a))); + +[expect] +f(a && ((b: string) => c!(a))); + +== should keep parentheses around type assertion in extends clause == +return class extends (a as typeof B) {}; + +[expect] +return class extends (a as typeof B) {}; + +== should keep parentheses around type assertion followed by call == +proxy[key] = (...args: any[]) => (ls[key] as (...args: any[]) => any)(...args); + +[expect] +proxy[key] = (...args: any[]) => (ls[key] as (...args: any[]) => any)(...args); + +== should keep parentheses around assignment with new expression == +return a || (a = new (X as any)()); + +[expect] +return a || (a = new (X as any)()); + +== should remove parentheses around arrow function in variable declarator == +const x = (a => a); + +[expect] +const x = a => a; + +== should keep parentheses for call expression after nullish coalescing == +(a.b ?? c)(d); + +[expect] +(a.b ?? c)(d); + +== should keep parentheses around logical expression before nullish coalescing == +(a && b(c)?.d) ?? "..."; + +[expect] +(a && b(c)?.d) ?? "..."; + +== should keep parentheses in new expression with logical assignment == +new (A || (A = b.c()))(d); + +[expect] +new (A || (A = b.c()))(d); + +== should keep parentheses for call expression after logical OR == +a = (b || c)(d); + +[expect] +a = (b || c)(d); + +== should keep parentheses for call expression after member and logical OR == +(a.b || c)(d); + +[expect] +(a.b || c)(d); + +== should keep parentheses for nullish coalescing with logical AND in ternary == +return a.b ? (a.b && f(a.b, d)) ?? (a.c && f(a.c, d)) : undefined; + +[expect] +return a.b ? (a.b && f(a.b, d)) ?? (a.c && f(a.c, d)) : undefined; + +== should keep parentheses for member access after logical OR == +(a || b).c + +[expect] +(a || b).c; + +== should keep parentheses for computed member access after logical AND == +(a && b)[c] + +[expect] +(a && b)[c]; + +== should keep parentheses for member access after nullish coalescing == +(a ?? b).c + +[expect] +(a ?? b).c; + +== should retain parentheses around addition with non-null assertion before comparison == +(a + b!) === x + +[expect] +(a + b!) === x; + +== should retain parentheses around non-null assertion before loose equality == +(a + b!) == x + +[expect] +(a + b!) == x; + +== should retain parentheses around non-null assertion before assignment == +let y = (a + b!) = x; + +[expect] +let y = (a + b!) = x; + +== should retain parentheses around direct non-null assertion before assignment == +(x!) = y; + +[expect] +(x!) = y; + +== should keep inner parentheses for nested comparisons == +(a !== b || c === (d === e)) + +[expect] +a !== b || c === (d === e); + +== should retain parentheses in associative addition == +const x = a + (b + c); + +[expect] +const x = a + (b + c); + +== should retain parentheses in left-associative addition == +const x = (a + b) + c; + +[expect] +const x = (a + b) + c; + +== should retain parentheses in left-associative subtraction == +const x = (a - b) - c; + +[expect] +const x = (a - b) - c; + +== should retain parentheses in left-associative multiplication == +const x = (a * b) * c; + +[expect] +const x = (a * b) * c; + +== should retain parentheses in left-associative division == +const x = (a / b) / c; + +[expect] +const x = (a / b) / c; + +== should keep parentheses around conditional expression after unary operator == +!(a ? b : c) + +[expect] +!(a ? b : c); + +== should keep parentheses around sequence expression after unary operator == +!(a, b) + +[expect] +!(a, b); + +== should keep parentheses around arrow function after unary operator == +!(x => x) + +[expect] +!(x => x); + +== should keep parentheses around yield expression after unary operator == +function* test() { + return !(yield a); +} + +[expect] +function* test() { + return !(yield a); +} + +== should keep parentheses around assignment in unary expression == +!(a = b) + +[expect] +!(a = b); + +== should keep parentheses around binary expression after unary operator == +!(a + b) + +[expect] +!(a + b); + +== should keep parentheses around logical OR after unary operator == +!(a || b) + +[expect] +!(a || b); + +== should keep parentheses around nullish coalescing after unary operator == +!(a ?? b) + +[expect] +!(a ?? b); + +== should keep parentheses in method call with post-increment and member access == +o.f({ a: (X.y++).toString(), b }); + +[expect] +o.f({ a: (X.y++).toString(), b }); + +== should keep parentheses around type assertion with non-null assertions in function call == +f((a.b!.c as D)!.e, g); + +[expect] +f((a.b!.c as D)!.e, g); + +== should keep parentheses around type assertion with generic type and non-null assertion == +f((a as B)!.d, e); + +[expect] +f((a as B)!.d, e); + +== should add parentheses to new expression without arguments == +try { + throw new TypeError +} +catch { +} + +[expect] +try { + throw new TypeError(); +} catch { +} + +== should keep parentheses in new expression with arguments == +try { + throw new TypeError() +} +catch { +} + +[expect] +try { + throw new TypeError(); +} catch { +} + +== should add parentheses to new expression with parenthesized callee == +const a = new (await this._getB()) + +[expect] +const a = new (await this._getB())(); + +== should keep parenthesized callee with args in new expression == +const a = new (await this._getB())() + +[expect] +const a = new (await this._getB())(); + +== should keep parentheses around type assertions in binary expression (with parens) == +const x = (a as any as number) >= (b as any as number); + +[expect] +const x = (a as any as number) >= (b as any as number); + +== should keep parentheses around type assertions in binary expression (without parens) == +const x = a as any as number >= b as any as number; + +[expect] +const x = (a as any as number) >= (b as any as number); + +== should remove outer parentheses with type assertion and member access == +(([] as X[])).f(g); + +[expect] +([] as X[]).f(g); diff --git a/tests/specs/general/UseParentheses_Maintain.txt b/tests/specs/general/UseParentheses_Maintain.txt new file mode 100644 index 00000000..3b0eaace --- /dev/null +++ b/tests/specs/general/UseParentheses_Maintain.txt @@ -0,0 +1,2183 @@ +~~ useParentheses: maintain ~~ +== should retain multi-line parentheses in expression statement without comment == +if (a) + (f + ()); + +[expect] +if (a) { + (f()); +} + +== should keep multi-line parentheses in expression statement with comment == +if (a) + (f // + ()); + +[expect] +if (a) { + (f // + ()); +} + +== should retain multi-line parentheses in return without comment == +if (a) + return (f + ()); + +[expect] +if (a) { + return (f()); +} + +== should keep multi-line parentheses in return with comment == +if (a) + return (f // + ()); + +[expect] +if (a) { + return (f // + ()); +} + +== should retain multi-line parentheses in return with function call on next line == +if (a) + return (f +(veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongIdentifierName1234567890123456789012345678901234567890)); + +[expect] +if (a) { + return (f( + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongIdentifierName1234567890123456789012345678901234567890, + )); +} + +== should retain multi-line parentheses around long variable in return == +return ( + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongIdentifierName1234567890123456789012345678901234567890); + +[expect] +return ( + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongIdentifierName1234567890123456789012345678901234567890 +); + +== should retain multi-line parentheses in throw without comment == +if (a) + throw (f + ()); + +[expect] +if (a) { + throw (f()); +} + +== should keep multi-line parentheses in throw with comment == +if (a) + throw (f // + ()); + +[expect] +if (a) { + throw (f // + ()); +} + +== should retain multi-line parentheses in yield without comment == +function* gen() { + if (a) + yield (f + ()); +} + +[expect] +function* gen() { + if (a) { + yield (f()); + } +} + +== should keep multi-line parentheses in yield with comment == +function* gen() { + if (a) + yield (f // + ()); +} + +[expect] +function* gen() { + if (a) { + yield (f // + ()); + } +} + +== should keep multi-line parentheses in call argument with comment == +foo((arg // +)); + +[expect] +foo( + ( + arg // + ), +); + +== should retain multi-line parentheses in call argument without comment == +foo((arg +)); + +[expect] +foo((arg)); + +== should keep multi-line parentheses in array element with comment == +[(item // +)]; + +[expect] +[ + ( + item // + ), +]; + +== should retain multi-line parentheses in array element without comment == +[(item +)]; + +[expect] +[(item)]; + +== should use parentheses for object literal expression statement == +({ foo: 1 }); + +[expect] +({ foo: 1 }); + +== should keep multi-line parentheses around optional chain object with comment == +(f // +)?.prop; + +[expect] +( + f // +)?.prop; + +== should retain multi-line parentheses around optional chain object without comment == +(f +)?.prop; + +[expect] +(f)?.prop; + +== should keep multi-line parentheses with comment before optional chain member access == +(f // +?.prop); + +[expect] +(f // + ?.prop); + +== should retain multi-line parentheses around optional chain member access without comment == +(f +?.prop); + +[expect] +(f + ?.prop); + +== should keep multi-line parentheses around member access object with comment == +(f // +).prop; + +[expect] +( + f // +).prop; + +== should retain multi-line parentheses around member access object without comment == +(f +).prop; + +[expect] +(f).prop; + +== should keep multi-line parentheses with comment before member access == +(f // +.prop); + +[expect] +(f // + .prop); + +== should retain multi-line parentheses around member access without comment == +(f +.prop); + +[expect] +(f + .prop); + +== should keep multi-line parentheses around call callee with comment == +(f // +)(); + +[expect] +( + f // +)(); + +== should retain multi-line parentheses around call callee without comment == +(f +)(); + +[expect] +(f)(); + +== should keep multi-line parentheses with comment before call == +(f // +()); + +[expect] +(f // +()); + +== should retain multi-line parentheses around call without comment == +(f +()); + +[expect] +(f()); + +== should keep multi-line parentheses around optional call callee with comment == +(f // +)?.(); + +[expect] +( + f // +)?.(); + +== should retain multi-line parentheses around optional call callee without comment == +(f +)?.(); + +[expect] +(f)?.(); + +== should keep multi-line parentheses with comment before optional chain == +(f // +?.()); + +[expect] +(f // +?.()); + +== should retain multi-line parentheses around optional chain call without comment == +(f +?.()); + +[expect] +(f?.()); + +== TEST: single line array access should retain parens == +(a[2]); + +[expect] +(a[2]); + +== should keep multi-line parentheses around call expression == +(f // +()); + +[expect] +(f // +()); + +== should keep multi-line parentheses around function callee == +(f // +)(); + +[expect] +( + f // +)(); + +== should remove parentheses inside call arguments == +f((1 + 2)); + +[expect] +f((1 + 2)); + +== should remove parentheses inside return statement == +function test() { + return (value); +} + +[expect] +function test() { + return (value); +} + +== should keep parentheses when they affect operator precedence == +const result = (a + b) * c; + +[expect] +const result = (a + b) * c; + +== should retain parentheses that enforce grouping == +const ambiguous = a / (b * c); + +[expect] +const ambiguous = a / (b * c); + +== should remove parentheses that do not affect precedence == +const precedenceOk = (a * b) + c; + +[expect] +const precedenceOk = (a * b) + c; + +== should remove parentheses in ternary branches == +const value = condition ? (left) : (right); + +[expect] +const value = condition ? (left) : (right); + +== should remove parentheses inside array elements == +const items = [(value), ((other))]; + +[expect] +const items = [(value), ((other))]; + +== should retain parentheses in array destructuring pattern when required == +const [value = (await load())] = source; + +[expect] +const [value = (await load())] = source; + +== should remove parentheses inside object properties == +const obj = { + prop: (value), + nested: ((other)), +}; + +[expect] +const obj = { + prop: (value), + nested: ((other)), +}; + +== should keep parentheses around object literal arrow body == +const arrow = () => ({ value: 1 }); + +[expect] +const arrow = () => ({ value: 1 }); + +== should remove parentheses inside template expression == +const message = `hello ${ (name) }`; + +[expect] +const message = `hello ${(name)}`; + +== should remove parentheses around optional chaining == +const maybe = (obj?.prop); + +[expect] +const maybe = (obj?.prop); + +== should retain parentheses around optional chaining when combined with exponentiation == +const complex = (obj?.value) ** 2; + +[expect] +const complex = (obj?.value) ** 2; + +== should keep parentheses around function expression callees == +const iifeResult = (function () {})(); + +[expect] +const iifeResult = (function() {})(); + +== should keep parentheses around optional chained function expression callees == +const optionalIife = (function () {})?.(); + +[expect] +const optionalIife = (function() {})?.(); + +== should keep parentheses around optional chained arrow callees == +const optionalArrowIife = (() => {})?.(); + +[expect] +const optionalArrowIife = (() => {})?.(); + +== should remove redundant parens in nested optional arrow calls == +const nestedOptionalArrow = ((() => {})?.())?.(); + +[expect] +const nestedOptionalArrow = ((() => {})?.())?.(); + +== should keep parentheses around optional chained class expression callees == +const optionalClassIife = (class {})?.(); + +[expect] +const optionalClassIife = (class {})?.(); + +== should keep parentheses around optional chained class expression property access == +const optionalClassProp = (class { constructor() { this.prop = 1; } })?.prop; + +[expect] +const optionalClassProp = (class { + constructor() { + this.prop = 1; + } +})?.prop; + +== should keep parentheses around optional chained function expression property access == +const optionalFuncProp = (function () {})?.prop; + +[expect] +const optionalFuncProp = (function() {})?.prop; + +== should keep parentheses around optional chained arrow function property access == +const optionalArrowProp = (() => {})?.prop; + +[expect] +const optionalArrowProp = (() => {})?.prop; + +== should keep parentheses around multi-line function expression property access == +const functionPropMultiline = (function () {} + ).prop; + +[expect] +const functionPropMultiline = (function() {}).prop; + +== should keep parentheses around multi-line class expression property access == +const classPropMultiline = (class {} + ).prop; + +[expect] +const classPropMultiline = (class {}).prop; + +== should keep parentheses around multi-line arrow function property access == +const arrowPropMultiline = (() => {} + ).prop; + +[expect] +const arrowPropMultiline = (() => {}).prop; + +== should keep parentheses around multi-line optional chained function property access == +const optionalFunctionPropMultiline = (function () {} + )?.prop; + +[expect] +const optionalFunctionPropMultiline = (function() {})?.prop; + +== should keep parentheses around multi-line optional chained function property access with comment == +const optionalFunctionPropMultilineComment = (function () {} + )?.prop; // comment + +[expect] +const optionalFunctionPropMultilineComment = (function() {})?.prop; // comment + +== should keep parentheses around multi-line optional chained arrow call == +const optionalArrowCallMultiline = (() => {} + )?.(); + +[expect] +const optionalArrowCallMultiline = (() => {})?.(); + +== should keep parentheses around multi-line optional chained function call == +const optionalFunctionCallMultiline = (function () {} + )?.(); + +[expect] +const optionalFunctionCallMultiline = (function() {})?.(); + +== should remove redundant parens around optional chained new expression access == +const optionalNewProp = (new Foo())?.prop; + +[expect] +const optionalNewProp = (new Foo())?.prop; + +== should keep parentheses around multi-line optional chained new expression access == +const optionalNewPropMultiline = (new Foo + ())?.prop; + +[expect] +const optionalNewPropMultiline = (new Foo())?.prop; + +== should remove parentheses around nullish coalescing inside assignments == +const fallback = (primary ?? secondary); + +[expect] +const fallback = (primary ?? secondary); + +== should remove parentheses around await inside expressions == +async function load() { + const data = (await fetchData()); +} + +[expect] +async function load() { + const data = (await fetchData()); +} + +== should keep parentheses around awaited call when accessing members == +async function useData() { + return (await loadData()).map(item => item); +} + +[expect] +async function useData() { + return (await loadData()).map(item => item); +} + +== should remove parentheses around yield expression == +function* gen() { + yield (value); +} + +[expect] +function* gen() { + yield (value); +} + +== should retain parentheses around yield used in expression == +function* collect() { + const collected = [ (yield value) ]; +} + +[expect] +function* collect() { + const collected = [(yield value)]; +} + +== should keep parentheses around yield when accessing members == +function* project() { + const result = (yield getValue()).prop; +} + +[expect] +function* project() { + const result = (yield getValue()).prop; +} + +== should keep parentheses around multi-line yield expression == +function* yieldMultiline() { + yield (getGenerator + ()); +} + +[expect] +function* yieldMultiline() { + yield (getGenerator()); +} + +== should keep parentheses around multi-line yield optional call == +function* yieldOptionalMultiline() { + return (yield getFactory + ())?.(); +} + +[expect] +function* yieldOptionalMultiline() { + return (yield getFactory())?.(); +} + +== should keep parentheses around multi-line yield property access == +function* yieldPropertyMultiline() { + return (yield getFactory + ()).value; +} + +[expect] +function* yieldPropertyMultiline() { + return (yield getFactory()).value; +} + +== should remove parentheses around throw expression == +throw (error); + +[expect] +throw (error); + +== should keep parentheses around multi-line throw expression == +function throwMultiline() { + throw (createError + ()); +} + +[expect] +function throwMultiline() { + throw (createError()); +} + +== should keep parentheses around multi-line await expression in throw == +async function throwAwaitMultiline() { + throw (await createAsyncError + ()); +} + +[expect] +async function throwAwaitMultiline() { + throw (await createAsyncError()); +} + +== should keep parentheses around multi-line throw optional call == +function throwOptionalMultiline() { + throw (getFactory + ())?.(); +} + +[expect] +function throwOptionalMultiline() { + throw (getFactory())?.(); +} + +== should keep parentheses around multi-line throw property access == +function throwPropertyMultiline() { + throw (createError + ()).code; +} + +[expect] +function throwPropertyMultiline() { + throw (createError()).code; +} + +== should retain parentheses around throw in IIFE == +const result = condition ? value : (() => { throw error; })(); + +[expect] +const result = condition ? value : (() => { + throw error; +})(); + +== should remove parentheses around export default expression == +export default (component); + +[expect] +export default (component); + +== should retain parentheses around export default when template literal == +export default (`value`); + +[expect] +export default (`value`); + +== should remove parentheses in if condition == +if ((condition)) { + doSomething(); +} + +[expect] +if ((condition)) { + doSomething(); +} + +== should keep parentheses around multi-line await in if condition == +async function ifAwaitMultiline() { + if ((await shouldProceed + ())) { + doSomething(); + } +} + +[expect] +async function ifAwaitMultiline() { + if ((await shouldProceed())) { + doSomething(); + } +} + +== should remove parentheses in while condition == +while ((ready)) { + run(); +} + +[expect] +while ((ready)) { + run(); +} + +== should remove parentheses in do while condition == +do { + work(); +} while ((shouldContinue)); + +[expect] +do { + work(); +} while ((shouldContinue)); + +== should keep parentheses around multi-line await in while condition == +async function whileAwaitMultiline() { + while ((await shouldContinue + ())) { + run(); + } +} + +[expect] +async function whileAwaitMultiline() { + while ((await shouldContinue())) { + run(); + } +} + +== should remove parentheses in for-of iterable == +for (const item of (collection)) { + consume(item); +} + +[expect] +for (const item of (collection)) { + consume(item); +} + +== should keep parentheses around multi-line await in for-of iterable == +async function forOfAwaitMultiline() { + for (const item of (await loadItems + ())) { + consume(item); + } +} + +[expect] +async function forOfAwaitMultiline() { + for (const item of (await loadItems())) { + consume(item); + } +} + +== should remove parentheses in for head == +for (let i = (0); i < (max); i++) { + step(i); +} + +[expect] +for (let i = (0); i < (max); i++) { + step(i); +} + +== should remove parentheses in switch discriminant and cases == +switch ((value)) { +case (1): + break; +} + +[expect] +switch ((value)) { + case (1): + break; +} + +== should remove parentheses around unary operations == +const increment = (++(value)); + +[expect] +const increment = (++(value)); + +== should remove parentheses around typeof in binary expressions == +const typeCheck = (typeof value) + " suffix"; + +[expect] +const typeCheck = (typeof value) + " suffix"; + +== should keep parentheses around delete when followed by member access == +const deleteResult = (delete obj.prop).toString(); + +[expect] +const deleteResult = (delete obj.prop).toString(); + +== should keep parentheses around typeof when accessing members == +const typeInfo = (typeof value).toUpperCase(); + +[expect] +const typeInfo = (typeof value).toUpperCase(); + +== should keep parentheses around void when accessing members == +const voidInfo = (void 0).toString(); + +[expect] +const voidInfo = (void 0).toString(); + +== should remove parentheses in default parameter initializers == +function configure(option = (defaultValue)) {} + +[expect] +function configure(option = (defaultValue)) {} + +== should remove parentheses inside logical expressions == +const combined = (a && b) || (c && d); + +[expect] +const combined = (a && b) || (c && d); + +== should retain parentheses in logical expression when required == +const precedence = a && (b || c); + +[expect] +const precedence = a && (b || c); + +== should remove parentheses around new expression == +const instance = (new Foo()); + +[expect] +const instance = (new Foo()); + +== should retain parentheses around new with call when needed == +const construct = (new Foo())(); + +[expect] +const construct = (new Foo())(); + +== should remove parentheses around typeof/void/delete == +const info = { + type: typeof (value), + cleared: void (0), + removed: delete (obj.prop), +}; + +[expect] +const info = { + type: typeof (value), + cleared: void (0), + removed: delete (obj.prop), +}; + +== should remove parentheses around tagged template expressions == +const tagged = tag((value)); + +[expect] +const tagged = tag((value)); + +== should remove parentheses inside class field initializer == +class Example { + value = (initial); +} + +[expect] +class Example { + value = (initial); +} + +== should remove parentheses in array destructuring defaults == +const [first = (fallback)] = source; + +[expect] +const [first = (fallback)] = source; + +== should remove parentheses in object destructuring defaults == +const { value = (fallback) } = source; + +[expect] +const { value = (fallback) } = source; + +== should remove parentheses around dynamic import specifier == +async function load() { + return import((moduleName)); +} + +[expect] +async function load() { + return import((moduleName)); +} + +== should retain parentheses around dynamic import with assertions == +async function load() { + return import(moduleName, { with: (options) }); +} + +[expect] +async function load() { + return import(moduleName, { with: (options) }); +} + +== should remove parentheses around class heritage == +class Derived extends (Base) {} + +[expect] +class Derived extends (Base) {} + +== should remove parentheses in super call == +class Child extends Base { + constructor() { + super((arg)); + } +} + +[expect] +class Child extends Base { + constructor() { + super((arg)); + } +} + +== should remove parentheses around yield star expression == +function* forward() { + yield* (iterable); +} + +[expect] +function* forward() { + yield* (iterable); +} + +== should keep parentheses around multi-line yield star expression == +function* forwardMultiline() { + yield* (getIterable + ()); +} + +[expect] +function* forwardMultiline() { + yield* (getIterable()); +} + +== should keep parentheses around sequence expressions == +doSomething((a, b)); + +[expect] +doSomething((a, b)); + +== should remove parentheses around call expression results == +const resultCall = (compute()) + 1; + +[expect] +const resultCall = (compute()) + 1; + +== should remove parentheses around expressions in for loop headers == +for (let i = (0); i < (max); i++) {} + +[expect] +for (let i = (0); i < (max); i++) {} + +== should retain parentheses when mixing nullish coalescing with logical OR == +const fallbackOr = (x ?? y) || z; + +[expect] +const fallbackOr = (x ?? y) || z; + +== should retain parentheses when mixing nullish coalescing with logical AND == +const fallbackAnd = (x ?? y) && z; + +[expect] +const fallbackAnd = (x ?? y) && z; + +== should retain parentheses when mixing logical OR with nullish coalescing == +const orWithNull = x || (y ?? z); + +[expect] +const orWithNull = x || (y ?? z); + +== should retain parentheses when mixing logical AND with nullish coalescing == +const andWithNull = x && (y ?? z); + +[expect] +const andWithNull = x && (y ?? z); + +== should remove parentheses around delete in logical expressions == +const deleteCheck = (delete obj.prop) && other; + +[expect] +const deleteCheck = (delete obj.prop) && other; + +== should remove parentheses around typeof in binary expressions == +const typeCheck = (typeof value) + " suffix"; + +[expect] +const typeCheck = (typeof value) + " suffix"; + +== should remove parentheses around void in logical expressions == +const voidCheck = (void 0) || fallback; + +[expect] +const voidCheck = (void 0) || fallback; + +== should keep parentheses around await in binary expressions == +async function asyncBinary() { + const result = (await fetch()) + 1; +} + +[expect] +async function asyncBinary() { + const result = (await fetch()) + 1; +} + +== should remove redundant parentheses around await when returned == +async function returnAwait(condition) { + return (await resolveValue(condition)); +} + +[expect] +async function returnAwait(condition) { + return (await resolveValue(condition)); +} + +== should keep parentheses around multi-line return call == +function returnMultilineCall() { + return (getFactory + ()); +} + +[expect] +function returnMultilineCall() { + return (getFactory()); +} + +== should keep parentheses around multi-line return call with comment == +function returnMultilineCallWithComment() { + return (createFactory // comment + ()); +} + +[expect] +function returnMultilineCallWithComment() { + return (createFactory // comment + ()); +} + +== should keep parentheses around multi-line throw call with comment == +function throwMultilineWithComment() { + throw (createError // comment + ()); +} + +[expect] +function throwMultilineWithComment() { + throw (createError // comment + ()); +} + +== should keep parentheses around multi-line await expression == +async function awaitMultiline() { + return (await loadData + ()).map(item => item); +} + +[expect] +async function awaitMultiline() { + return (await loadData()).map(item => item); +} + +== should keep parentheses around multi-line await optional call == +async function awaitOptionalMultiline() { + return (await loadFactory + ())?.(); +} + +[expect] +async function awaitOptionalMultiline() { + return (await loadFactory())?.(); +} + +== should keep parentheses around multi-line await binary expression == +async function awaitBinaryMultiline() { + const total = (await computeValue + ()) + 1; + return total; +} + +[expect] +async function awaitBinaryMultiline() { + const total = (await computeValue()) + 1; + return total; +} + +== should keep parentheses around multi-line return property access == +function returnPropertyMultiline() { + return (getObject + ()).value; +} + +[expect] +function returnPropertyMultiline() { + return (getObject()).value; +} + +== should keep parentheses around multi-line return optional call == +function returnOptionalMultiline() { + return (getCallback + ())?.(); +} + +[expect] +function returnOptionalMultiline() { + return (getCallback())?.(); +} + +== should keep parentheses around multi-line return property access with comment == +function returnPropertyMultilineComment() { + return (getObject // comment + ()).value; +} + +[expect] +function returnPropertyMultilineComment() { + return (getObject // comment + ()).value; +} + +== should remove redundant parentheses around await in ternary expressions == +async function asyncTernary() { + const value = condition ? (await foo()) : bar; +} + +[expect] +async function asyncTernary() { + const value = condition ? (await foo()) : bar; +} + +== should retain parentheses around yield in binary expressions == +function* yieldBinary() { + const result = (yield value) + 1; +} + +[expect] +function* yieldBinary() { + const result = (yield value) + 1; +} + +== should keep parentheses around multi-line yield binary expression == +function* yieldBinaryMultiline() { + const result = (yield computeValue + ()) + 1; + return result; +} + +[expect] +function* yieldBinaryMultiline() { + const result = (yield computeValue()) + 1; + return result; +} + +== should remove redundant parens around member access on await in ternary condition == +async function ternaryAccess(cond) { + return ((await fetchData()).value) ? (await fetchData()).value : defaultValue; +} + +[expect] +async function ternaryAccess(cond) { + return ((await fetchData()).value) ? (await fetchData()).value : defaultValue; +} + +== should keep parentheses around object literal (disambiguation required) == +({ + prop: value +}); + +[expect] +({ + prop: value, +}); + +== should keep parentheses around function expression (disambiguation required) == +(function test() { + return 1; +}); + +[expect] +(function test() { + return 1; +}); + +== should NOT add parentheses around arrow function == +() => 42; + +[expect] +() => 42; + +== should keep simple call expression statement == +foo(); + +[expect] +foo(); + +== should keep await expression statement == +async function run() { + await doSomething(); +} + +[expect] +async function run() { + await doSomething(); +} + +== should remove parentheses around arrow function == +(() => 42); + +[expect] +(() => 42); + +== should NOT add parentheses around simple identifier (not supported) == +someVariable; + +[expect] +someVariable; + +== should NOT add parentheses around call expression (not supported) == +someFunction(); + +[expect] +someFunction(); + +== should remove parentheses around call expression == +(someFunction()); + +[expect] +(someFunction()); + +== should NOT add parentheses around member expression (not supported) == +obj.prop; + +[expect] +obj.prop; + +== should NOT add parentheses around array literal (not supported) == +[1, 2, 3]; + +[expect] +[1, 2, 3]; + +== should NOT add parentheses around template literal (not supported) == +`hello world`; + +[expect] +`hello world`; + +== should NOT add parentheses around binary expression (not supported) == +a + b; + +[expect] +a + b; + +== should remove parentheses around binary expression == +(a + b); + +[expect] +(a + b); + +== should remove redundant outer parens from nested binary expressions == +((a || b) && c); + +[expect] +((a || b) && c); + +== should remove redundant outer parens from simple expression == +(value); + +[expect] +(value); + +== should keep parentheses that affect operator precedence == +((a + b) * c); + +[expect] +((a + b) * c); + +== should keep parentheses around arrow function used as callee == +(() => 42)(); + +[expect] +(() => 42)(); + +== should keep parentheses around arrow function used in optional chain == +(() => 42)?.prop; + +[expect] +(() => 42)?.prop; + +== should keep parentheses around arrow function with type assertion == +(() => 42) as NumberFunction; + +[expect] +(() => 42) as NumberFunction; + +== should remove parentheses around await expression == +(await value()); + +[expect] +(await value()); + +== should remove parentheses around unary expression == +(+value); + +[expect] +(+value); + +== should remove parentheses around prefix increment == ++(value); + +[expect] ++(value); + +== should remove parentheses around new expression == +(new Foo()); + +[expect] +(new Foo()); + +== should remove parentheses around optional chain expression == +(obj?.prop); + +[expect] +(obj?.prop); + +== should remove parentheses around nullish coalescing expression == +(a ?? b); + +[expect] +(a ?? b); + +== should keep parentheses around object literal with as assertion == +({}) as Foo; + +[expect] +({}) as Foo; + +== should keep parentheses around object literal with satisfies assertion == +({}) satisfies Foo; + +[expect] +({}) satisfies Foo; + +== should keep parentheses around object literal with as const assertion == +({}) as const; + +[expect] +({}) as const; + +== should NOT add parentheses around object literal with type assertion == +{}; + +[expect] + {}; + +== should keep parentheses around object literal with non-null assertion == +({})!; + +[expect] +({})!; + +== should keep parentheses around object literal with multiple assertions == +({}) as Foo as Bar; + +[expect] +({}) as Foo as Bar; + +== should keep parentheses around object literal with as assertion inside while loop == +while (true) { + ({} as X); +} + +[expect] +while (true) { + ({} as X); +} + +== should keep parentheses around function expression with as assertion == +(function() {}) as Foo; + +[expect] +(function() {}) as Foo; + +== should keep parentheses around function expression with satisfies assertion == +(function() {}) satisfies Foo; + +[expect] +(function() {}) satisfies Foo; + +== should keep parentheses around function expression with as const assertion == +(function() {}) as const; + +[expect] +(function() {}) as const; + +== should keep parentheses around function expression with non-null assertion == +(function() {})!; + +[expect] +(function() {})!; + +== should keep parentheses around function expression with multiple assertions == +(function() {}) as Foo as Bar; + +[expect] +(function() {}) as Foo as Bar; + +== should NOT add parentheses around arrow function with as assertion == +(() => 42) as Foo; + +[expect] +(() => 42) as Foo; + +== should NOT add parentheses around call expression with as assertion == +foo() as Bar; + +[expect] +foo() as Bar; + +== should keep parentheses around nested object literal with assertions == +({prop: {}}) as Foo; + +[expect] +({ prop: {} }) as Foo; + +== should keep parentheses around object literal used as member expression base == +({}).foo; + +[expect] +({}).foo; + +== should keep parentheses around object literal used as call expression callee == +({})(); + +[expect] +({})(); + +== should keep parentheses around object literal used as optional chain base == +({})?.prop; + +[expect] +({})?.prop; + +== should keep parentheses around function expression used as member expression base == +(function() {}).foo; + +[expect] +(function() {}).foo; + +== should keep parentheses around function expression used as call expression callee == +(function() {})(); + +[expect] +(function() {})(); + +== should keep parentheses around function expression used as optional chain base == +(function() {})?.prop; + +[expect] +(function() {})?.prop; + +== should keep parentheses with mixed assertion wrappers on object literal == +({})! as Foo; + +[expect] +({})! as Foo; + +== should keep parentheses with mixed assertion wrappers on function expression == +(function() {})! as Foo; + +[expect] +(function() {})! as Foo; + +== should keep parentheses with chained mixed assertions on object literal == +({}) as Foo satisfies Bar; + +[expect] +({}) as Foo satisfies Bar; + +== should keep parentheses with chained mixed assertions on function expression == +(function() {}) as Foo satisfies Bar; + +[expect] +(function() {}) as Foo satisfies Bar; + +== should remove redundant outer parens from nested assertion chains on object literal == +(({} as X) as Y); + +[expect] +(({} as X) as Y); + +== should remove redundant outer parens from nested assertion chains on function expression == +((function() {}) as X) as Y; + +[expect] +((function() {}) as X) as Y; + +== should remove redundant outer parens from nested assertion chains on class expression == +((class {}) as X) as Y; + +[expect] +((class {}) as X) as Y; + +== should keep parentheses around sequence expression == +(a, b); + +[expect] +(a, b); + +== should keep parentheses around anonymous class expression == +(class {}); + +[expect] +(class {}); + +== should keep parentheses around named class expression == +(class Foo {}); + +[expect] +(class Foo {}); + +== should keep parentheses around class expression with as assertion == +(class {}) as Foo; + +[expect] +(class {}) as Foo; + +== should keep parentheses around class expression with satisfies assertion == +(class {}) satisfies Foo; + +[expect] +(class {}) satisfies Foo; + +== should keep parentheses around class expression with as const assertion == +(class {}) as const; + +[expect] +(class {}) as const; + +== should keep parentheses around class expression with non-null assertion == +(class {})!; + +[expect] +(class {})!; + +== should keep parentheses around class expression with multiple assertions == +(class {}) as Foo as Bar; + +[expect] +(class {}) as Foo as Bar; + +== should keep parentheses with mixed assertion wrappers on class expression == +(class {})! as Foo; + +[expect] +(class {})! as Foo; + +== should keep parentheses around class expression with chained mixed assertions == +(class {}) as Foo satisfies Bar; + +[expect] +(class {}) as Foo satisfies Bar; + +== should keep parentheses around class expression used as member expression base == +(class {}).foo; + +[expect] +(class {}).foo; + +== should keep parentheses around class expression used as call expression callee == +(class {})(); + +[expect] +(class {})(); + +== should keep parentheses around class expression used as optional chain base == +(class {})?.prop; + +[expect] +(class {})?.prop; + +== should retain parentheses with nested assignments in logical expression == +if (this.leaving && (distanceL2 = (dlx = this.leaving.x - x) ** 2 + (dly = this.leaving.y - y) ** 2) > this.far2) {} + +[expect] +if (this.leaving && (distanceL2 = (dlx = this.leaving.x - x) ** 2 + (dly = this.leaving.y - y) ** 2) > this.far2) {} + +== should retain parentheses around simple nested assignment == +const x = (y = 2); + +[expect] +const x = (y = 2); + +== should retain parentheses around type assertion with optional chaining == +expect((result as Payload).options?.debug).toBe(true); + +[expect] +expect((result as Payload).options?.debug).toBe(true); + +== should retain parentheses around type assertion with optional chaining in assignment == +const errorCode = (event.target as IDBOpenDBRequest).error?.name || 'Unknown'; + +[expect] +const errorCode = (event.target as IDBOpenDBRequest).error?.name || "Unknown"; + +== should retain parentheses around type assertion with method call == +return Object.keys(object).every(key => (keys as string[]).includes(key)); + +[expect] +return Object.keys(object).every(key => (keys as string[]).includes(key)); + +== should retain parentheses around negation of in expression == +const onlyColor = computed(() => 'color' in props.colorData && !('word' in props.colorData)); + +[expect] +const onlyColor = computed(() => "color" in props.colorData && !("word" in props.colorData)); + +== should retain parentheses around negation of in expression with member access == +const onlyWord = computed(() => !('color' in props.colorData) && 'word' in props.colorData); + +[expect] +const onlyWord = computed(() => !("color" in props.colorData) && "word" in props.colorData); + +== should retain parentheses around identifier in ternary operator == +const errorTitle = computed(() => 'Fehler' + (anyErrors.value ? '' : '(leer)')); + +[expect] +const errorTitle = computed(() => "Fehler" + (anyErrors.value ? "" : "(leer)")); + +== should retain parentheses around complex arithmetic expression == +const duration = computed(() => (props.data.entries.flatMap(x => x.log[x.log.length - 1].t - x.log[0].t).reduce((a, b) => a + b) / 1000).toString()); + +[expect] +const duration = computed(() => + (props.data.entries.flatMap(x => x.log[x.log.length - 1].t - x.log[0].t).reduce((a, b) => a + b) / 1000).toString() +); + +== should retain parentheses around negation of instanceof expression == +const isNotInstance = !(obj instanceof MyClass); + +[expect] +const isNotInstance = !(obj instanceof MyClass); + +== should retain parentheses around negation of comparison expression == +const isNotLess = !(a < b); + +[expect] +const isNotLess = !(a < b); + +== should retain parentheses around negation of equality expression == +const isNotEqual = !(a == b); + +[expect] +const isNotEqual = !(a == b); + +== should retain parentheses around negation of strict equality expression == +const isNotStrictEqual = !(a === b); + +[expect] +const isNotStrictEqual = !(a === b); + +== should retain parentheses around negation of addition expression == +const notSum = !(a + b); + +[expect] +const notSum = !(a + b); + +== should retain parentheses around negation of subtraction expression == +const notDiff = !(a - b); + +[expect] +const notDiff = !(a - b); + +== should retain parentheses around negation of logical and expression == +const notBoth = !(a && b); + +[expect] +const notBoth = !(a && b); + +== should retain parentheses around negation of logical or expression == +const notEither = !(a || b); + +[expect] +const notEither = !(a || b); + +== should retain parentheses around typeof with binary expression == +const typeOfSum = typeof (a + b); + +[expect] +const typeOfSum = typeof (a + b); + +== should retain parentheses around void with logical and expression == +const voidResult = void (a && b); + +[expect] +const voidResult = void (a && b); + +== should retain parentheses around unary minus with exponentiation == +const negativeSquared = (-2) ** 3; + +[expect] +const negativeSquared = (-2) ** 3; + +== should retain parentheses around unary plus with exponentiation == +const positiveSquared = (+x) ** 2; + +[expect] +const positiveSquared = (+x) ** 2; + +== should retain parentheses around typeof with exponentiation == +const typeofExp = (typeof x) ** 2; + +[expect] +const typeofExp = (typeof x) ** 2; + +== should retain parentheses around void with exponentiation == +const voidExp = (void 0) ** 2; + +[expect] +const voidExp = (void 0) ** 2; + +== should retain parentheses around bitwise not with exponentiation == +const bitwiseNotExp = (~flags) ** 2; + +[expect] +const bitwiseNotExp = (~flags) ** 2; + +== should retain parentheses around logical not with exponentiation == +const logicalNotExp = (!flag) ** 2; + +[expect] +const logicalNotExp = (!flag) ** 2; + +== should retain parentheses around delete with exponentiation == +const deleteExp = (delete obj.prop) ** 2; + +[expect] +const deleteExp = (delete obj.prop) ** 2; + +== should retain parentheses around await with exponentiation == +async function test() { + const awaitExp = (await getValue()) ** 2; +} + +[expect] +async function test() { + const awaitExp = (await getValue()) ** 2; +} + +== should retain parentheses for left-to-right exponentiation (associativity override) == +const leftToRight = (2 ** 3) ** 2; + +[expect] +const leftToRight = (2 ** 3) ** 2; + +== should retain parentheses for right-to-left exponentiation (preserve user intent) == +const rightToLeft = 2 ** (3 ** 2); + +[expect] +const rightToLeft = 2 ** (3 ** 2); + +== should retain required parentheses around spread with conditional expression == +const o = { + ...(flag ? {} : { + key: { + value: flag || defaultValue + } + }) +}; + +[expect] +const o = { + ...(flag ? {} : { + key: { + value: flag || defaultValue, + }, + }), +}; + +== should retain required parentheses around type assertion on left side of assignment == +(obj.prop as unknown) = { + handler: (data: string) => { + processData(data); + } +}; + +[expect] +(obj.prop as unknown) = { + handler: (data: string) => { + processData(data); + }, +}; + +== should retain required parentheses around spread with sequence expression == +const o = { + ...(a, b) +}; + +[expect] +const o = { + ...(a, b), +}; + +== should retain required parentheses around object destructuring assignment == +({x} = obj); + +[expect] +({ x } = obj); + +== should retain required parentheses around object destructuring assignment with trailing comment == +({x} = obj); // comment + +[expect] +({ x } = obj); // comment + +== should retain parentheses around array destructuring assignment == +([a] = arr); + +[expect] +([a] = arr); + +== should retain required parentheses around assignment with member access in while condition == +while ((current = startElement.previousSibling!)._offset! >= threshold && current !== endElement) + process(current); + +[expect] +while ((current = startElement.previousSibling!)._offset! >= threshold && current !== endElement) { + process(current); +} + +== should retain required parentheses around assignment with member access and multiple conditions == +while ( + (current = firstElement.nextSibling!)._offset! + current!.height < targetValue + && current !== lastElement && current.nextSibling !== lastElement +) + process(current); + +[expect] +while ( + (current = firstElement.nextSibling!)._offset! + current!.height < targetValue + && current !== lastElement && current.nextSibling !== lastElement +) { + process(current); +} + +== should retain parentheses around assignment in comparison in while condition == +while ((item = getNext()) !== null) + process(item); + +[expect] +while ((item = getNext()) !== null) { + process(item); +} + +== should retain required parentheses around assignment with call == +(fn = () => 42)(); + +[expect] +(fn = () => 42)(); + +== should retain required parentheses around assignment with optional call == +(maybeFn = () => 99)?.(); + +[expect] +(maybeFn = () => 99)?.(); + +== should retain required parentheses around assignment with computed member access == +(obj = {x: 5, y: 10})["x"]; + +[expect] +(obj = { x: 5, y: 10 })["x"]; + +== should retain required parentheses around assignment with optional computed member access == +(obj = getValue())?.[key]; + +[expect] +(obj = getValue())?.[key]; + +== should retain required parentheses around assignment with new expression == +const instance = new (Constructor = MyClass)(); + +[expect] +const instance = new (Constructor = MyClass)(); + +== should retain required parentheses around assignment with non-null assertion == +const result = (maybeValue = getValue())!; + +[expect] +const result = (maybeValue = getValue())!; + +== should retain required parentheses around assignment with type assertion == +const result = (value = 5) as number; + +[expect] +const result = (value = 5) as number; + +== should retain parentheses around arrow function in logical expression == +f(a() || (() => "")); + +[expect] +f(a() || (() => "")); + +== should retain parentheses around assignment with member access in logical expression == +f(!a || !(b = a!.b)); + +[expect] +f(!a || !(b = a!.b)); + +== should retain parentheses around arrow function with type annotation in logical expression == +f(a && ((b: string) => c!(a))); + +[expect] +f(a && ((b: string) => c!(a))); + +== should retain parentheses around type assertion in extends clause == +return class extends (a as typeof B) {}; + +[expect] +return class extends (a as typeof B) {}; + +== should retain parentheses around type assertion followed by call == +proxy[key] = (...args: any[]) => (ls[key] as (...args: any[]) => any)(...args); + +[expect] +proxy[key] = (...args: any[]) => (ls[key] as (...args: any[]) => any)(...args); + +== should retain parentheses around assignment with new expression == +return a || (a = new (X as any)()); + +[expect] +return a || (a = new (X as any)()); + +== should retain parentheses around arrow function in variable declarator == +const x = (a => a); + +[expect] +const x = (a => a); + +== should retain parentheses for call expression after nullish coalescing == +(a.b ?? c)(d); + +[expect] +(a.b ?? c)(d); + +== should retain parentheses around logical expression before nullish coalescing == +(a && b(c)?.d) ?? "..."; + +[expect] +(a && b(c)?.d) ?? "..."; + +== should retain parentheses in new expression with logical assignment == +new (A || (A = b.c()))(d); + +[expect] +new (A || (A = b.c()))(d); + +== should retain parentheses for call expression after logical OR == +a = (b || c)(d); + +[expect] +a = (b || c)(d); + +== should retain parentheses for call expression after member and logical OR == +(a.b || c)(d); + +[expect] +(a.b || c)(d); + +== should retain parentheses for nullish coalescing with logical AND in ternary == +return a.b ? (a.b && f(a.b, d)) ?? (a.c && f(a.c, d)) : undefined; + +[expect] +return a.b ? (a.b && f(a.b, d)) ?? (a.c && f(a.c, d)) : undefined; + +== should retain parentheses for member access after logical OR == +(a || b).c + +[expect] +(a || b).c; + +== should retain parentheses for computed member access after logical AND == +(a && b)[c] + +[expect] +(a && b)[c]; + +== should retain parentheses for member access after nullish coalescing == +(a ?? b).c + +[expect] +(a ?? b).c; + +== should retain parentheses around addition with non-null assertion before comparison == +(a + b!) === x + +[expect] +(a + b!) === x; + +== should retain parentheses around non-null assertion before loose equality == +(a + b!) == x + +[expect] +(a + b!) == x; + +== should retain parentheses around non-null assertion before assignment == +let y = (a + b!) = x; + +[expect] +let y = (a + b!) = x; + +== should retain parentheses around direct non-null assertion before assignment == +(x!) = y; + +[expect] +(x!) = y; + +== should retain all parentheses for nested comparisons == +(a !== b || c === (d === e)) + +[expect] +(a !== b || c === (d === e)); + +== should retain parentheses in associative addition == +const x = a + (b + c); + +[expect] +const x = a + (b + c); + +== should retain parentheses in left-associative addition == +const x = (a + b) + c; + +[expect] +const x = (a + b) + c; + +== should retain parentheses in left-associative subtraction == +const x = (a - b) - c; + +[expect] +const x = (a - b) - c; + +== should retain parentheses in left-associative multiplication == +const x = (a * b) * c; + +[expect] +const x = (a * b) * c; + +== should retain parentheses in left-associative division == +const x = (a / b) / c; + +[expect] +const x = (a / b) / c; + +== should retain parentheses around conditional expression after unary operator == +!(a ? b : c) + +[expect] +!(a ? b : c); + +== should retain parentheses around sequence expression after unary operator == +!(a, b) + +[expect] +!(a, b); + +== should retain parentheses around arrow function after unary operator == +!(x => x) + +[expect] +!(x => x); + +== should retain parentheses around yield expression after unary operator == +function* test() { + return !(yield a); +} + +[expect] +function* test() { + return !(yield a); +} + +== should retain parentheses around assignment in unary expression == +!(a = b) + +[expect] +!(a = b); + +== should retain parentheses around binary expression after unary operator == +!(a + b) + +[expect] +!(a + b); + +== should retain parentheses around logical OR after unary operator == +!(a || b) + +[expect] +!(a || b); + +== should retain parentheses around nullish coalescing after unary operator == +!(a ?? b) + +[expect] +!(a ?? b); + +== should retain parentheses in method call with post-increment and member access == +o.f({ a: (X.y++).toString(), b }); + +[expect] +o.f({ a: (X.y++).toString(), b }); + +== should retain parentheses in new expression with parenthesized callee == +const a = new (await this._getB()); + +[expect] +const a = new (await this._getB()); + +== should retain parentheses around type assertion with non-null assertions in function call == +f((a.b!.c as D)!.e, g); + +[expect] +f((a.b!.c as D)!.e, g); + +== should retain parentheses around type assertion with generic type and non-null assertion == +f((a as B)!.d, e); + +[expect] +f((a as B)!.d, e); + +== should retain new expression without arguments == +try { + throw new TypeError +} +catch { +} + +[expect] +try { + throw new TypeError; +} catch { +} + +== should retain parentheses in new expression with arguments == +try { + throw new TypeError() +} +catch { +} + +[expect] +try { + throw new TypeError(); +} catch { +} + +== should retain parenthesized callee in new expression == +const a = new (await this._getB()) + +[expect] +const a = new (await this._getB()); + +== should retain parenthesized callee with args in new expression == +const a = new (await this._getB())() + +[expect] +const a = new (await this._getB())(); + +== should maintain parentheses around type assertions in binary expression (with parens) == +const x = (a as any as number) >= (b as any as number); + +[expect] +const x = (a as any as number) >= (b as any as number); + +== should maintain parentheses around type assertions in binary expression (without parens) == +const x = a as any as number >= b as any as number; + +[expect] +const x = a as any as number >= b as any as number; + +== should keep double parentheses with type assertion and member access == +(([] as X[])).f(g); + +[expect] +(([] as X[])).f(g); diff --git a/tests/specs/general/UseParentheses_PreferNone.txt b/tests/specs/general/UseParentheses_PreferNone.txt new file mode 100644 index 00000000..939b8a01 --- /dev/null +++ b/tests/specs/general/UseParentheses_PreferNone.txt @@ -0,0 +1,2211 @@ +~~ useParentheses: preferNone, useBraces: preferNone ~~ +== should remove multi-line parentheses in expression statement without comment == +if (a) + (f + ()); + +[expect] +if (a) + f(); + +== should keep multi-line parentheses in expression statement with comment == +if (a) + (f // + ()); + +[expect] +if (a) { + f // + (); +} + +== should remove multi-line parentheses in return without braces == +if (a) + return (f + ()); + +[expect] +if (a) + return f(); + +== should keep multi-line parentheses in return with comment == +if (a) + return (f // + ()); + +[expect] +if (a) { + return f // + (); +} + +== should remove multi-line parentheses in return with function call on next line == +if (a) + return (f +(veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongIdentifierName1234567890123456789012345678901234567890)); + +[expect] +if (a) { + return f( + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongIdentifierName1234567890123456789012345678901234567890, + ); +} + +== should remove multi-line parentheses around long variable in return == +return ( + veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongIdentifierName1234567890123456789012345678901234567890); + +[expect] +return veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongIdentifierName1234567890123456789012345678901234567890; + +== should remove multi-line parentheses in throw without braces == +if (a) + throw (f + ()); + +[expect] +if (a) + throw f(); + +== should keep multi-line parentheses in throw with comment == +if (a) + throw (f // + ()); + +[expect] +if (a) { + throw f // + (); +} + +== should remove multi-line parentheses in yield without braces == +function* gen() { + if (a) + yield (f + ()); +} + +[expect] +function* gen() { + if (a) + yield f(); +} + +== should keep multi-line parentheses in yield with comment == +function* gen() { + if (a) + yield (f // + ()); +} + +[expect] +function* gen() { + if (a) { + yield f // + (); + } +} + +== should keep multi-line parentheses in call argument with comment == +foo((arg // +)); + +[expect] +foo( + ( + arg // + ), +); + +== should remove multi-line parentheses in call argument without comment == +foo((arg +)); + +[expect] +foo(arg); + +== should keep multi-line parentheses in array element with comment == +[(item // +)]; + +[expect] +[ + ( + item // + ), +]; + +== should remove multi-line parentheses in array element without comment == +[(item +)]; + +[expect] +[item]; + +== should use parentheses for object literal expression statement == +({ foo: 1 }); + +[expect] +({ foo: 1 }); + +== should keep multi-line parentheses around optional chain object with comment == +(f // +)?.prop; + +[expect] +( + f // +)?.prop; + +== should remove multi-line parentheses around optional chain object without comment == +(f +)?.prop; + +[expect] +f?.prop; + +== should keep multi-line parentheses with comment before optional chain member access == +(f // +?.prop); + +[expect] +f // + ?.prop; + +== should remove multi-line parentheses around optional chain member access without comment == +(f +?.prop); + +[expect] +f + ?.prop; + +== should keep multi-line parentheses around member access object with comment == +(f // +).prop; + +[expect] +( + f // +).prop; + +== should remove multi-line parentheses around member access object without comment == +(f +).prop; + +[expect] +f.prop; + +== should keep multi-line parentheses with comment before member access == +(f // +.prop); + +[expect] +f // + .prop; + +== should remove multi-line parentheses around member access without comment == +(f +.prop); + +[expect] +f + .prop; + +== should keep multi-line parentheses around call callee with comment == +(f // +)(); + +[expect] +( + f // +)(); + +== should remove multi-line parentheses around call callee without comment == +(f +)(); + +[expect] +f(); + +== should keep multi-line parentheses with comment before call == +(f // +()); + +[expect] +f // +(); + +== should remove multi-line parentheses around call without comment == +(f +()); + +[expect] +f(); + +== should keep multi-line parentheses around optional call callee with comment == +(f // +)?.(); + +[expect] +( + f // +)?.(); + +== should remove multi-line parentheses around optional call callee without comment == +(f +)?.(); + +[expect] +f?.(); + +== should keep multi-line parentheses with comment before optional chain == +(f // +?.()); + +[expect] +f // +?.(); + +== should remove multi-line parentheses around optional chain call without comment == +(f +?.()); + +[expect] +f?.(); + +== TEST: single line array access should remove parens == +(a[2]); + +[expect] +a[2]; + +== should keep multi-line parentheses around call expression == +(f // +()); + +[expect] +f // +(); + +== should keep multi-line parentheses around function callee == +(f // +)(); + +[expect] +( + f // +)(); + +== should remove parentheses inside call arguments == +f((1 + 2)); + +[expect] +f(1 + 2); + +== should remove parentheses inside return statement == +function test() { + return (value); +} + +[expect] +function test() { + return value; +} + +== should keep parentheses when they affect operator precedence == +const result = (a + b) * c; + +[expect] +const result = (a + b) * c; + +== should retain parentheses that enforce grouping == +const ambiguous = a / (b * c); + +[expect] +const ambiguous = a / (b * c); + +== should remove parentheses that do not affect precedence == +const precedenceOk = (a * b) + c; + +[expect] +const precedenceOk = a * b + c; + +== should remove parentheses in ternary branches == +const value = condition ? (left) : (right); + +[expect] +const value = condition ? left : right; + +== should remove parentheses inside array elements == +const items = [(value), ((other))]; + +[expect] +const items = [value, other]; + +== should remove parentheses in array destructuring pattern == +const [value = (await load())] = source; + +[expect] +const [value = await load()] = source; + +== should remove parentheses inside object properties == +const obj = { + prop: (value), + nested: ((other)), +}; + +[expect] +const obj = { + prop: value, + nested: other, +}; + +== should keep parentheses around object literal arrow body == +const arrow = () => ({ value: 1 }); + +[expect] +const arrow = () => ({ value: 1 }); + +== should remove parentheses inside template expression == +const message = `hello ${ (name) }`; + +[expect] +const message = `hello ${name}`; + +== should remove parentheses around optional chaining == +const maybe = (obj?.prop); + +[expect] +const maybe = obj?.prop; + +== should remove parentheses around optional chaining with exponentiation == +const complex = (obj?.value) ** 2; + +[expect] +const complex = obj?.value ** 2; + +== should keep parentheses around function expression callees == +const iifeResult = (function () {})(); + +[expect] +const iifeResult = (function() {})(); + +== should keep parentheses around optional chained function expression callees == +const optionalIife = (function () {})?.(); + +[expect] +const optionalIife = (function() {})?.(); + +== should keep parentheses around optional chained arrow callees == +const optionalArrowIife = (() => {})?.(); + +[expect] +const optionalArrowIife = (() => {})?.(); + +== should remove redundant parens in nested optional arrow calls == +const nestedOptionalArrow = ((() => {})?.())?.(); + +[expect] +const nestedOptionalArrow = (() => {})?.()?.(); + +== should keep parentheses around optional chained class expression callees == +const optionalClassIife = (class {})?.(); + +[expect] +const optionalClassIife = (class {})?.(); + +== should keep parentheses around optional chained class expression property access == +const optionalClassProp = (class { constructor() { this.prop = 1; } })?.prop; + +[expect] +const optionalClassProp = (class { + constructor() { + this.prop = 1; + } +})?.prop; + +== should keep parentheses around optional chained function expression property access == +const optionalFuncProp = (function () {})?.prop; + +[expect] +const optionalFuncProp = (function() {})?.prop; + +== should keep parentheses around optional chained arrow function property access == +const optionalArrowProp = (() => {})?.prop; + +[expect] +const optionalArrowProp = (() => {})?.prop; + +== should keep parentheses around multi-line function expression property access == +const functionPropMultiline = (function () {} + ).prop; + +[expect] +const functionPropMultiline = (function() {}).prop; + +== should keep parentheses around multi-line class expression property access == +const classPropMultiline = (class {} + ).prop; + +[expect] +const classPropMultiline = (class {}).prop; + +== should keep parentheses around multi-line arrow function property access == +const arrowPropMultiline = (() => {} + ).prop; + +[expect] +const arrowPropMultiline = (() => {}).prop; + +== should keep parentheses around multi-line optional chained function property access == +const optionalFunctionPropMultiline = (function () {} + )?.prop; + +[expect] +const optionalFunctionPropMultiline = (function() {})?.prop; + +== should keep parentheses around multi-line optional chained function property access with comment == +const optionalFunctionPropMultilineComment = (function () {} + )?.prop; // comment + +[expect] +const optionalFunctionPropMultilineComment = (function() {})?.prop; // comment + +== should keep parentheses around multi-line optional chained arrow call == +const optionalArrowCallMultiline = (() => {} + )?.(); + +[expect] +const optionalArrowCallMultiline = (() => {})?.(); + +== should keep parentheses around multi-line optional chained function call == +const optionalFunctionCallMultiline = (function () {} + )?.(); + +[expect] +const optionalFunctionCallMultiline = (function() {})?.(); + +== should remove redundant parens around optional chained new expression access == +const optionalNewProp = (new Foo())?.prop; + +[expect] +const optionalNewProp = new Foo()?.prop; + +== should keep parentheses around multi-line optional chained new expression access == +const optionalNewPropMultiline = (new Foo + ())?.prop; + +[expect] +const optionalNewPropMultiline = new Foo()?.prop; + +== should remove parentheses around nullish coalescing inside assignments == +const fallback = (primary ?? secondary); + +[expect] +const fallback = primary ?? secondary; + +== should remove parentheses around await inside expressions == +async function load() { + const data = (await fetchData()); +} + +[expect] +async function load() { + const data = await fetchData(); +} + +== should keep parentheses around awaited call when accessing members == +async function useData() { + return (await loadData()).map(item => item); +} + +[expect] +async function useData() { + return (await loadData()).map(item => item); +} + +== should remove parentheses around yield expression == +function* gen() { + yield (value); +} + +[expect] +function* gen() { + yield value; +} + +== should remove parentheses around yield used in array == +function* collect() { + const collected = [ (yield value) ]; +} + +[expect] +function* collect() { + const collected = [yield value]; +} + +== should keep parentheses around yield when accessing members == +function* project() { + const result = (yield getValue()).prop; +} + +[expect] +function* project() { + const result = (yield getValue()).prop; +} + +== should keep parentheses around multi-line yield expression == +function* yieldMultiline() { + yield (getGenerator + ()); +} + +[expect] +function* yieldMultiline() { + yield getGenerator(); +} + +== should keep parentheses around multi-line yield optional call == +function* yieldOptionalMultiline() { + return (yield getFactory + ())?.(); +} + +[expect] +function* yieldOptionalMultiline() { + return (yield getFactory())?.(); +} + +== should keep parentheses around multi-line yield property access == +function* yieldPropertyMultiline() { + return (yield getFactory + ()).value; +} + +[expect] +function* yieldPropertyMultiline() { + return (yield getFactory()).value; +} + +== should remove parentheses around throw expression == +throw (error); + +[expect] +throw error; + +== should keep parentheses around multi-line throw expression == +function throwMultiline() { + throw (createError + ()); +} + +[expect] +function throwMultiline() { + throw createError(); +} + +== should keep parentheses around multi-line await expression in throw == +async function throwAwaitMultiline() { + throw (await createAsyncError + ()); +} + +[expect] +async function throwAwaitMultiline() { + throw await createAsyncError(); +} + +== should keep parentheses around multi-line throw optional call == +function throwOptionalMultiline() { + throw (getFactory + ())?.(); +} + +[expect] +function throwOptionalMultiline() { + throw getFactory()?.(); +} + +== should keep parentheses around multi-line throw property access == +function throwPropertyMultiline() { + throw (createError + ()).code; +} + +[expect] +function throwPropertyMultiline() { + throw createError().code; +} + +== should retain parentheses around throw in IIFE == +const result = condition ? value : (() => { throw error; })(); + +[expect] +const result = condition ? value : (() => { + throw error; +})(); + +== should remove parentheses around export default expression == +export default (component); + +[expect] +export default component; + +== should remove parentheses around export default template literal == +export default (`value`); + +[expect] +export default `value`; + +== should remove parentheses in if condition == +if ((condition)) { + doSomething(); +} + +[expect] +if (condition) + doSomething(); + +== should keep parentheses around multi-line await in if condition == +async function ifAwaitMultiline() { + if ((await shouldProceed + ())) { + doSomething(); + } +} + +[expect] +async function ifAwaitMultiline() { + if (await shouldProceed()) + doSomething(); +} + +== should remove parentheses in while condition == +while ((ready)) { + run(); +} + +[expect] +while (ready) + run(); + +== should remove parentheses in do while condition == +do { + work(); +} while ((shouldContinue)); + +[expect] +do { + work(); +} while (shouldContinue); + +== should keep parentheses around multi-line await in while condition == +async function whileAwaitMultiline() { + while ((await shouldContinue + ())) { + run(); + } +} + +[expect] +async function whileAwaitMultiline() { + while (await shouldContinue()) + run(); +} + +== should remove parentheses in for-of iterable == +for (const item of (collection)) { + consume(item); +} + +[expect] +for (const item of collection) + consume(item); + +== should keep parentheses around multi-line await in for-of iterable == +async function forOfAwaitMultiline() { + for (const item of (await loadItems + ())) { + consume(item); + } +} + +[expect] +async function forOfAwaitMultiline() { + for (const item of await loadItems()) + consume(item); +} + +== should remove parentheses in for head == +for (let i = (0); i < (max); i++) { + step(i); +} + +[expect] +for (let i = 0; i < max; i++) + step(i); + +== should remove parentheses in switch discriminant and cases == +switch ((value)) { +case (1): + break; +} + +[expect] +switch (value) { + case 1: + break; +} + +== should remove parentheses around unary operations == +const increment = (++(value)); + +[expect] +const increment = ++value; + +== should remove parentheses around typeof in binary expressions == +const typeCheck = (typeof value) + " suffix"; + +[expect] +const typeCheck = typeof value + " suffix"; + +== should keep parentheses around delete when followed by member access == +const deleteResult = (delete obj.prop).toString(); + +[expect] +const deleteResult = (delete obj.prop).toString(); + +== should keep parentheses around typeof when accessing members == +const typeInfo = (typeof value).toUpperCase(); + +[expect] +const typeInfo = (typeof value).toUpperCase(); + +== should keep parentheses around void when accessing members == +const voidInfo = (void 0).toString(); + +[expect] +const voidInfo = (void 0).toString(); + +== should remove parentheses in default parameter initializers == +function configure(option = (defaultValue)) {} + +[expect] +function configure(option = defaultValue) {} + +== should remove parentheses inside logical expressions == +const combined = (a && b) || (c && d); + +[expect] +const combined = a && b || c && d; + +== should retain parentheses in logical expression when required == +const precedence = a && (b || c); + +[expect] +const precedence = a && (b || c); + +== should remove parentheses around new expression == +const instance = (new Foo()); + +[expect] +const instance = new Foo; + +== should retain parentheses around new with call when needed == +const construct = (new Foo())(); + +[expect] +const construct = (new Foo)(); + +== should remove parentheses around typeof/void/delete == +const info = { + type: typeof (value), + cleared: void (0), + removed: delete (obj.prop), +}; + +[expect] +const info = { + type: typeof value, + cleared: void 0, + removed: delete obj.prop, +}; + +== should remove parentheses around tagged template expressions == +const tagged = tag((value)); + +[expect] +const tagged = tag(value); + +== should remove parentheses inside class field initializer == +class Example { + value = (initial); +} + +[expect] +class Example { + value = initial; +} + +== should remove parentheses in array destructuring defaults == +const [first = (fallback)] = source; + +[expect] +const [first = fallback] = source; + +== should remove parentheses in object destructuring defaults == +const { value = (fallback) } = source; + +[expect] +const { value = fallback } = source; + +== should remove parentheses around dynamic import specifier == +async function load() { + return import((moduleName)); +} + +[expect] +async function load() { + return import(moduleName); +} + +== should retain parentheses around dynamic import with assertions == +async function load() { + return import(moduleName, { with: (options) }); +} + +[expect] +async function load() { + return import(moduleName, { with: options }); +} + +== should remove parentheses around class heritage == +class Derived extends (Base) {} + +[expect] +class Derived extends Base {} + +== should remove parentheses in super call == +class Child extends Base { + constructor() { + super((arg)); + } +} + +[expect] +class Child extends Base { + constructor() { + super(arg); + } +} + +== should remove parentheses around yield star expression == +function* forward() { + yield* (iterable); +} + +[expect] +function* forward() { + yield* iterable; +} + +== should keep parentheses around multi-line yield star expression == +function* forwardMultiline() { + yield* (getIterable + ()); +} + +[expect] +function* forwardMultiline() { + yield* getIterable(); +} + +== should keep parentheses around sequence expressions == +doSomething((a, b)); + +[expect] +doSomething((a, b)); + +== should remove parentheses around call expression results == +const resultCall = (compute()) + 1; + +[expect] +const resultCall = compute() + 1; + +== should remove parentheses around expressions in for loop headers == +for (let i = (0); i < (max); i++) {} + +[expect] +for (let i = 0; i < max; i++) {} + +== should retain parentheses when mixing nullish coalescing with logical OR == +const fallbackOr = (x ?? y) || z; + +[expect] +const fallbackOr = (x ?? y) || z; + +== should retain parentheses when mixing nullish coalescing with logical AND == +const fallbackAnd = (x ?? y) && z; + +[expect] +const fallbackAnd = (x ?? y) && z; + +== should retain parentheses when mixing logical OR with nullish coalescing == +const orWithNull = x || (y ?? z); + +[expect] +const orWithNull = x || (y ?? z); + +== should retain parentheses when mixing logical AND with nullish coalescing == +const andWithNull = x && (y ?? z); + +[expect] +const andWithNull = x && (y ?? z); + +== should remove parentheses around delete in logical expressions == +const deleteCheck = (delete obj.prop) && other; + +[expect] +const deleteCheck = delete obj.prop && other; + +== should remove parentheses around typeof in binary expressions == +const typeCheck = (typeof value) + " suffix"; + +[expect] +const typeCheck = typeof value + " suffix"; + +== should remove parentheses around void in logical expressions == +const voidCheck = (void 0) || fallback; + +[expect] +const voidCheck = void 0 || fallback; + +== should keep parentheses around await in binary expressions == +async function asyncBinary() { + const result = (await fetch()) + 1; +} + +[expect] +async function asyncBinary() { + const result = (await fetch()) + 1; +} + +== should remove redundant parentheses around await when returned == +async function returnAwait(condition) { + return (await resolveValue(condition)); +} + +[expect] +async function returnAwait(condition) { + return await resolveValue(condition); +} + +== should keep parentheses around multi-line return call == +function returnMultilineCall() { + return (getFactory + ()); +} + +[expect] +function returnMultilineCall() { + return getFactory(); +} + +== should keep parentheses around multi-line return call with comment == +function returnMultilineCallWithComment() { + return (createFactory // comment + ()); +} + +[expect] +function returnMultilineCallWithComment() { + return createFactory // comment + (); +} + +== should keep parentheses around multi-line throw call with comment == +function throwMultilineWithComment() { + throw (createError // comment + ()); +} + +[expect] +function throwMultilineWithComment() { + throw createError // comment + (); +} + +== should keep parentheses around multi-line await expression == +async function awaitMultiline() { + return (await loadData + ()).map(item => item); +} + +[expect] +async function awaitMultiline() { + return (await loadData()).map(item => item); +} + +== should keep parentheses around multi-line await optional call == +async function awaitOptionalMultiline() { + return (await loadFactory + ())?.(); +} + +[expect] +async function awaitOptionalMultiline() { + return (await loadFactory())?.(); +} + +== should keep parentheses around multi-line await binary expression == +async function awaitBinaryMultiline() { + const total = (await computeValue + ()) + 1; + return total; +} + +[expect] +async function awaitBinaryMultiline() { + const total = (await computeValue()) + 1; + return total; +} + +== should keep parentheses around multi-line return property access == +function returnPropertyMultiline() { + return (getObject + ()).value; +} + +[expect] +function returnPropertyMultiline() { + return getObject().value; +} + +== should keep parentheses around multi-line return optional call == +function returnOptionalMultiline() { + return (getCallback + ())?.(); +} + +[expect] +function returnOptionalMultiline() { + return getCallback()?.(); +} + +== should keep parentheses around multi-line return property access with comment == +function returnPropertyMultilineComment() { + return (getObject // comment + ()).value; +} + +[expect] +function returnPropertyMultilineComment() { + return getObject // comment + ().value; +} + +== should remove redundant parentheses around await in ternary expressions == +async function asyncTernary() { + const value = condition ? (await foo()) : bar; +} + +[expect] +async function asyncTernary() { + const value = condition ? await foo() : bar; +} + +== should retain parentheses around yield in binary expressions == +function* yieldBinary() { + const result = (yield value) + 1; +} + +[expect] +function* yieldBinary() { + const result = (yield value) + 1; +} + +== should keep parentheses around multi-line yield binary expression == +function* yieldBinaryMultiline() { + const result = (yield computeValue + ()) + 1; + return result; +} + +[expect] +function* yieldBinaryMultiline() { + const result = (yield computeValue()) + 1; + return result; +} + +== should remove redundant parens around member access on await in ternary condition == +async function ternaryAccess(cond) { + return ((await fetchData()).value) ? (await fetchData()).value : defaultValue; +} + +[expect] +async function ternaryAccess(cond) { + return (await fetchData()).value ? (await fetchData()).value : defaultValue; +} + +== should keep parentheses around object literal (disambiguation required) == +({ + prop: value +}); + +[expect] +({ + prop: value, +}); + +== should keep parentheses around function expression (disambiguation required) == +(function test() { + return 1; +}); + +[expect] +(function test() { + return 1; +}); + +== should NOT add parentheses around arrow function == +() => 42; + +[expect] +() => 42; + +== should keep simple call expression statement == +foo(); + +[expect] +foo(); + +== should keep await expression statement == +async function run() { + await doSomething(); +} + +[expect] +async function run() { + await doSomething(); +} + +== should remove parentheses around arrow function == +(() => 42); + +[expect] +() => 42; + +== should NOT add parentheses around simple identifier (not supported) == +someVariable; + +[expect] +someVariable; + +== should NOT add parentheses around call expression (not supported) == +someFunction(); + +[expect] +someFunction(); + +== should remove parentheses around call expression == +(someFunction()); + +[expect] +someFunction(); + +== should NOT add parentheses around member expression (not supported) == +obj.prop; + +[expect] +obj.prop; + +== should NOT add parentheses around array literal (not supported) == +[1, 2, 3]; + +[expect] +[1, 2, 3]; + +== should NOT add parentheses around template literal (not supported) == +`hello world`; + +[expect] +`hello world`; + +== should NOT add parentheses around binary expression (not supported) == +a + b; + +[expect] +a + b; + +== should remove parentheses around binary expression == +(a + b); + +[expect] +a + b; + +== should remove redundant outer parens from nested binary expressions == +((a || b) && c); + +[expect] +(a || b) && c; + +== should remove redundant outer parens from simple expression == +(value); + +[expect] +value; + +== should keep parentheses that affect operator precedence == +((a + b) * c); + +[expect] +(a + b) * c; + +== should keep parentheses around arrow function used as callee == +(() => 42)(); + +[expect] +(() => 42)(); + +== should keep parentheses around arrow function used in optional chain == +(() => 42)?.prop; + +[expect] +(() => 42)?.prop; + +== should keep parentheses around arrow function with type assertion == +(() => 42) as NumberFunction; + +[expect] +(() => 42) as NumberFunction; + +== should remove parentheses around await expression == +(await value()); + +[expect] +await value(); + +== should remove parentheses around unary expression == +(+value); + +[expect] ++value; + +== should remove parentheses around prefix increment == ++(value); + +[expect] ++value; + +== should remove parentheses around new expression == +(new Foo()); + +[expect] +new Foo; + +== should remove parentheses around optional chain expression == +(obj?.prop); + +[expect] +obj?.prop; + +== should remove parentheses around nullish coalescing expression == +(a ?? b); + +[expect] +a ?? b; + +== should keep parentheses around object literal with as assertion == +({}) as Foo; + +[expect] +({}) as Foo; + +== should keep parentheses around object literal with satisfies assertion == +({}) satisfies Foo; + +[expect] +({}) satisfies Foo; + +== should keep parentheses around object literal with as const assertion == +({}) as const; + +[expect] +({}) as const; + +== should NOT add parentheses around object literal with type assertion == +{}; + +[expect] + {}; + +== should keep parentheses around object literal with non-null assertion == +({})!; + +[expect] +({})!; + +== should keep parentheses around object literal with multiple assertions == +({}) as Foo as Bar; + +[expect] +({}) as Foo as Bar; + +== should keep parentheses around object literal with as assertion inside while loop == +while (true) { + ({} as X); +} + +[expect] +while (true) + ({} as X); + +== should keep parentheses around function expression with as assertion == +(function() {}) as Foo; + +[expect] +(function() {}) as Foo; + +== should keep parentheses around function expression with satisfies assertion == +(function() {}) satisfies Foo; + +[expect] +(function() {}) satisfies Foo; + +== should keep parentheses around function expression with as const assertion == +(function() {}) as const; + +[expect] +(function() {}) as const; + +== should keep parentheses around function expression with non-null assertion == +(function() {})!; + +[expect] +(function() {})!; + +== should keep parentheses around function expression with multiple assertions == +(function() {}) as Foo as Bar; + +[expect] +(function() {}) as Foo as Bar; + +== should NOT add parentheses around arrow function with as assertion == +(() => 42) as Foo; + +[expect] +(() => 42) as Foo; + +== should NOT add parentheses around call expression with as assertion == +foo() as Bar; + +[expect] +foo() as Bar; + +== should keep parentheses around nested object literal with assertions == +({prop: {}}) as Foo; + +[expect] +({ prop: {} }) as Foo; + +== should keep parentheses around object literal used as member expression base == +({}).foo; + +[expect] +({}).foo; + +== should keep parentheses around object literal used as call expression callee == +({})(); + +[expect] +({})(); + +== should keep parentheses around object literal used as optional chain base == +({})?.prop; + +[expect] +({})?.prop; + +== should keep parentheses around function expression used as member expression base == +(function() {}).foo; + +[expect] +(function() {}).foo; + +== should keep parentheses around function expression used as call expression callee == +(function() {})(); + +[expect] +(function() {})(); + +== should keep parentheses around function expression used as optional chain base == +(function() {})?.prop; + +[expect] +(function() {})?.prop; + +== should keep parentheses with mixed assertion wrappers on object literal == +({})! as Foo; + +[expect] +({})! as Foo; + +== should keep parentheses with mixed assertion wrappers on function expression == +(function() {})! as Foo; + +[expect] +(function() {})! as Foo; + +== should keep parentheses with chained mixed assertions on object literal == +({}) as Foo satisfies Bar; + +[expect] +({}) as Foo satisfies Bar; + +== should keep parentheses with chained mixed assertions on function expression == +(function() {}) as Foo satisfies Bar; + +[expect] +(function() {}) as Foo satisfies Bar; + +== should remove redundant outer parens from nested assertion chains on object literal == +(({} as X) as Y); + +[expect] +({} as X) as Y; + +== should remove redundant outer parens from nested assertion chains on function expression == +((function() {}) as X) as Y; + +[expect] +(function() {}) as X as Y; + +== should remove redundant outer parens from nested assertion chains on class expression == +((class {}) as X) as Y; + +[expect] +(class {}) as X as Y; + +== should keep parentheses around sequence expression == +(a, b); + +[expect] +(a, b); + +== should keep parentheses around anonymous class expression == +(class {}); + +[expect] +(class {}); + +== should keep parentheses around named class expression == +(class Foo {}); + +[expect] +(class Foo {}); + +== should keep parentheses around class expression with as assertion == +(class {}) as Foo; + +[expect] +(class {}) as Foo; + +== should keep parentheses around class expression with satisfies assertion == +(class {}) satisfies Foo; + +[expect] +(class {}) satisfies Foo; + +== should keep parentheses around class expression with as const assertion == +(class {}) as const; + +[expect] +(class {}) as const; + +== should keep parentheses around class expression with non-null assertion == +(class {})!; + +[expect] +(class {})!; + +== should keep parentheses around class expression with multiple assertions == +(class {}) as Foo as Bar; + +[expect] +(class {}) as Foo as Bar; + +== should keep parentheses with mixed assertion wrappers on class expression == +(class {})! as Foo; + +[expect] +(class {})! as Foo; + +== should keep parentheses around class expression with chained mixed assertions == +(class {}) as Foo satisfies Bar; + +[expect] +(class {}) as Foo satisfies Bar; + +== should keep parentheses around class expression used as member expression base == +(class {}).foo; + +[expect] +(class {}).foo; + +== should keep parentheses around class expression used as call expression callee == +(class {})(); + +[expect] +(class {})(); + +== should keep parentheses around class expression used as optional chain base == +(class {})?.prop; + +[expect] +(class {})?.prop; + +== should keep parentheses with nested assignments in logical expression == +if (this.leaving && (distanceL2 = (dlx = this.leaving.x - x) ** 2 + (dly = this.leaving.y - y) ** 2) > this.far2) {} + +[expect] +if (this.leaving && (distanceL2 = (dlx = this.leaving.x - x) ** 2 + (dly = this.leaving.y - y) ** 2) > this.far2) {} + +== should remove parentheses around simple nested assignment == +const x = (y = 2); + +[expect] +const x = y = 2; + +== should keep parentheses around type assertion with optional chaining == +expect((result as Payload).options?.debug).toBe(true); + +[expect] +expect((result as Payload).options?.debug).toBe(true); + +== should keep parentheses around type assertion with optional chaining in assignment == +const errorCode = (event.target as IDBOpenDBRequest).error?.name || 'Unknown'; + +[expect] +const errorCode = (event.target as IDBOpenDBRequest).error?.name || "Unknown"; + +== should keep parentheses around type assertion with method call == +return Object.keys(object).every(key => (keys as string[]).includes(key)); + +[expect] +return Object.keys(object).every(key => (keys as string[]).includes(key)); + +== should keep parentheses around negation of in expression == +const onlyColor = computed(() => 'color' in props.colorData && !('word' in props.colorData)); + +[expect] +const onlyColor = computed(() => "color" in props.colorData && !("word" in props.colorData)); + +== should keep parentheses around negation of in expression with member access == +const onlyWord = computed(() => !('color' in props.colorData) && 'word' in props.colorData); + +[expect] +const onlyWord = computed(() => !("color" in props.colorData) && "word" in props.colorData); + +== should keep parentheses around identifier in ternary operator == +const errorTitle = computed(() => 'Fehler' + (anyErrors.value ? '' : '(leer)')); + +[expect] +const errorTitle = computed(() => "Fehler" + (anyErrors.value ? "" : "(leer)")); + +== should keep parentheses around complex arithmetic expression == +const duration = computed(() => (props.data.entries.flatMap(x => x.log[x.log.length - 1].t - x.log[0].t).reduce((a, b) => a + b) / 1000).toString()); + +[expect] +const duration = computed(() => + (props.data.entries.flatMap(x => x.log[x.log.length - 1].t - x.log[0].t).reduce((a, b) => a + b) / 1000).toString() +); + +== should keep parentheses around negation of instanceof expression == +const isNotInstance = !(obj instanceof MyClass); + +[expect] +const isNotInstance = !(obj instanceof MyClass); + +== should keep parentheses around negation of comparison expression == +const isNotLess = !(a < b); + +[expect] +const isNotLess = !(a < b); + +== should keep parentheses around negation of equality expression == +const isNotEqual = !(a == b); + +[expect] +const isNotEqual = !(a == b); + +== should keep parentheses around negation of strict equality expression == +const isNotStrictEqual = !(a === b); + +[expect] +const isNotStrictEqual = !(a === b); + +== should keep parentheses around negation of addition expression == +const notSum = !(a + b); + +[expect] +const notSum = !(a + b); + +== should keep parentheses around negation of subtraction expression == +const notDiff = !(a - b); + +[expect] +const notDiff = !(a - b); + +== should keep parentheses around negation of logical and expression == +const notBoth = !(a && b); + +[expect] +const notBoth = !(a && b); + +== should keep parentheses around negation of logical or expression == +const notEither = !(a || b); + +[expect] +const notEither = !(a || b); + +== should keep parentheses around typeof with binary expression == +const typeOfSum = typeof (a + b); + +[expect] +const typeOfSum = typeof (a + b); + +== should keep parentheses around void with logical and expression == +const voidResult = void (a && b); + +[expect] +const voidResult = void (a && b); + +== should keep parentheses around unary minus with exponentiation == +const negativeSquared = (-2) ** 3; + +[expect] +const negativeSquared = (-2) ** 3; + +== should keep parentheses around unary plus with exponentiation == +const positiveSquared = (+x) ** 2; + +[expect] +const positiveSquared = (+x) ** 2; + +== should keep parentheses around typeof with exponentiation == +const typeofExp = (typeof x) ** 2; + +[expect] +const typeofExp = (typeof x) ** 2; + +== should keep parentheses around void with exponentiation == +const voidExp = (void 0) ** 2; + +[expect] +const voidExp = (void 0) ** 2; + +== should keep parentheses around bitwise not with exponentiation == +const bitwiseNotExp = (~flags) ** 2; + +[expect] +const bitwiseNotExp = (~flags) ** 2; + +== should keep parentheses around logical not with exponentiation == +const logicalNotExp = (!flag) ** 2; + +[expect] +const logicalNotExp = (!flag) ** 2; + +== should keep parentheses around delete with exponentiation == +const deleteExp = (delete obj.prop) ** 2; + +[expect] +const deleteExp = (delete obj.prop) ** 2; + +== should keep parentheses around await with exponentiation == +async function test() { + const awaitExp = (await getValue()) ** 2; +} + +[expect] +async function test() { + const awaitExp = (await getValue()) ** 2; +} + +== should keep parentheses for left-to-right exponentiation (associativity override) == +const leftToRight = (2 ** 3) ** 2; + +[expect] +const leftToRight = (2 ** 3) ** 2; + +== should remove redundant parentheses for right-to-left exponentiation (natural associativity) == +const rightToLeft = 2 ** (3 ** 2); + +[expect] +const rightToLeft = 2 ** 3 ** 2; + +== should keep required parentheses around spread with conditional expression == +const o = { + ...(flag ? {} : { + key: { + value: flag || defaultValue + } + }) +}; + +[expect] +const o = { + ...(flag ? {} : { + key: { + value: flag || defaultValue, + }, + }), +}; + +== should keep required parentheses around type assertion on left side of assignment == +(obj.prop as unknown) = { + handler: (data: string) => { + processData(data); + } +}; + +[expect] +(obj.prop as unknown) = { + handler: (data: string) => { + processData(data); + }, +}; + +== should keep required parentheses around spread with sequence expression == +const o = { + ...(a, b) +}; + +[expect] +const o = { + ...(a, b), +}; + +== should keep required parentheses around object destructuring assignment == +({x} = obj); + +[expect] +({ x } = obj); + +== should keep required parentheses around object destructuring assignment with trailing comment == +({x} = obj); // comment + +[expect] +({ x } = obj); // comment + +== should remove parentheses around array destructuring assignment == +([a] = arr); + +[expect] +[a] = arr; + +== should keep required parentheses around assignment with member access in while condition == +while ((current = startElement.previousSibling!)._offset! >= threshold && current !== endElement) + process(current); + +[expect] +while ((current = startElement.previousSibling!)._offset! >= threshold && current !== endElement) + process(current); + +== should keep required parentheses around assignment with member access and multiple conditions == +while ( + (current = firstElement.nextSibling!)._offset! + current!.height < targetValue + && current !== lastElement && current.nextSibling !== lastElement +) + process(current); + +[expect] +while ( + (current = firstElement.nextSibling!)._offset! + current!.height < targetValue + && current !== lastElement && current.nextSibling !== lastElement +) { + process(current); +} + +== should keep required parentheses around assignment in comparison in while condition == +while ((item = getNext()) !== null) + process(item); + +[expect] +while ((item = getNext()) !== null) + process(item); + +== should keep required parentheses around assignment with call == +(fn = () => 42)(); + +[expect] +(fn = () => 42)(); + +== should keep required parentheses around assignment with optional call == +(maybeFn = () => 99)?.(); + +[expect] +(maybeFn = () => 99)?.(); + +== should keep required parentheses around assignment with computed member access == +(obj = {x: 5, y: 10})["x"]; + +[expect] +(obj = { x: 5, y: 10 })["x"]; + +== should keep required parentheses around assignment with optional computed member access == +(obj = getValue())?.[key]; + +[expect] +(obj = getValue())?.[key]; + +== should keep required parentheses around assignment with new expression == +const instance = new (Constructor = MyClass)(); + +[expect] +const instance = new (Constructor = MyClass); + +== should keep required parentheses around assignment with non-null assertion == +const result = (maybeValue = getValue())!; + +[expect] +const result = (maybeValue = getValue())!; + +== should keep required parentheses around assignment with type assertion == +const result = (value = 5) as number; + +[expect] +const result = (value = 5) as number; + +== should remove parentheses around assignment in variable declarator == +const y = (x = 5); + +[expect] +const y = x = 5; + +== should remove parentheses around assignment in return statement == +function test() { + return (value = 10); +} + +[expect] +function test() { + return value = 10; +} + +== should remove parentheses around assignment in function argument == +process((x = getValue())); + +[expect] +process(x = getValue()); + +== should remove parentheses around assignment in array element == +const arr = [(x = 1), (y = 2)]; + +[expect] +const arr = [x = 1, y = 2]; + +== should remove parentheses around assignment in object property == +const obj = { + prop: (x = 5) +}; + +[expect] +const obj = { + prop: x = 5, +}; + +== should remove parentheses around assignment in ternary branch == +const result = condition ? (x = 1) : (y = 2); + +[expect] +const result = condition ? x = 1 : y = 2; + +== should keep parentheses around arrow function in logical expression == +f(a() || (() => "")); + +[expect] +f(a() || (() => "")); + +== should keep parentheses around assignment with member access in logical expression == +f(!a || !(b = a!.b)); + +[expect] +f(!a || !(b = a!.b)); + +== should keep parentheses around arrow function with type annotation in logical expression == +f(a && ((b: string) => c!(a))); + +[expect] +f(a && ((b: string) => c!(a))); + +== should keep parentheses around type assertion in extends clause == +return class extends (a as typeof B) {}; + +[expect] +return class extends (a as typeof B) {}; + +== should keep parentheses around type assertion followed by call == +proxy[key] = (...args: any[]) => (ls[key] as (...args: any[]) => any)(...args); + +[expect] +proxy[key] = (...args: any[]) => (ls[key] as (...args: any[]) => any)(...args); + +== should keep parentheses around assignment with new expression == +return a || (a = new (X as any)()); + +[expect] +return a || (a = new (X as any)); + +== should remove parentheses around arrow function in variable declarator == +const x = (a => a); + +[expect] +const x = a => a; + +== should keep parentheses for call expression after nullish coalescing == +(a.b ?? c)(d); + +[expect] +(a.b ?? c)(d); + +== should keep parentheses around logical expression before nullish coalescing == +(a && b(c)?.d) ?? "..."; + +[expect] +(a && b(c)?.d) ?? "..."; + +== should keep parentheses in new expression with logical assignment == +new (A || (A = b.c()))(d); + +[expect] +new (A || (A = b.c()))(d); + +== should keep parentheses for call expression after logical OR == +a = (b || c)(d); + +[expect] +a = (b || c)(d); + +== should keep parentheses for call expression after member and logical OR == +(a.b || c)(d); + +[expect] +(a.b || c)(d); + +== should keep parentheses for nullish coalescing with logical AND in ternary == +return a.b ? (a.b && f(a.b, d)) ?? (a.c && f(a.c, d)) : undefined; + +[expect] +return a.b ? (a.b && f(a.b, d)) ?? (a.c && f(a.c, d)) : undefined; + +== should keep parentheses for member access after logical OR == +(a || b).c + +[expect] +(a || b).c; + +== should keep parentheses for computed member access after logical AND == +(a && b)[c] + +[expect] +(a && b)[c]; + +== should keep parentheses for member access after nullish coalescing == +(a ?? b).c + +[expect] +(a ?? b).c; + +== should keep parentheses around addition with non-null assertion before comparison == +(a + b!) === x + +[expect] +(a + b!) === x; + +== should keep parentheses around non-null assertion before loose equality == +(a + b!) == x + +[expect] +(a + b!) == x; + +== should keep parentheses around non-null assertion before assignment == +let y = (a + b!) = x; + +[expect] +let y = (a + b!) = x; + +== should keep parentheses around direct non-null assertion before assignment == +(x!) = y; + +[expect] +(x!) = y; + +== should keep inner parentheses for nested comparisons == +(a !== b || c === (d === e)) + +[expect] +a !== b || c === (d === e); + +== should keep parentheses in right-associative addition == +const x = a + (b + c); + +[expect] +const x = a + (b + c); + +== should remove redundant parentheses in left-associative addition == +const x = (a + b) + c; + +[expect] +const x = a + b + c; + +== should remove redundant parentheses in left-associative subtraction == +const x = (a - b) - c; + +[expect] +const x = a - b - c; + +== should remove redundant parentheses in left-associative multiplication == +const x = (a * b) * c; + +[expect] +const x = a * b * c; + +== should remove redundant parentheses in left-associative division == +const x = (a / b) / c; + +[expect] +const x = a / b / c; + +== should keep parentheses around conditional expression after unary operator == +!(a ? b : c) + +[expect] +!(a ? b : c); + +== should keep parentheses around sequence expression after unary operator == +!(a, b) + +[expect] +!(a, b); + +== should keep parentheses around arrow function after unary operator == +!(x => x) + +[expect] +!(x => x); + +== should keep parentheses around yield expression after unary operator == +function* test() { + return !(yield a); +} + +[expect] +function* test() { + return !(yield a); +} + +== should keep parentheses around assignment in unary expression == +!(a = b) + +[expect] +!(a = b); + +== should keep parentheses around binary expression after unary operator == +!(a + b) + +[expect] +!(a + b); + +== should keep parentheses around logical OR after unary operator == +!(a || b) + +[expect] +!(a || b); + +== should keep parentheses around nullish coalescing after unary operator == +!(a ?? b) + +[expect] +!(a ?? b); + +== should keep parentheses in method call with post-increment and member access == +o.f({ a: (X.y++).toString(), b }); + +[expect] +o.f({ a: (X.y++).toString(), b }); + +== should not add parentheses to new expression with parenthesized callee == +const a = new (await this._getB()); + +[expect] +const a = new (await this._getB()); + +== should keep parentheses around type assertion with non-null assertions in function call == +f((a.b!.c as D)!.e, g); + +[expect] +f((a.b!.c as D)!.e, g); + +== should keep parentheses around type assertion with generic type and non-null assertion == +f((a as B)!.d, e); + +[expect] +f((a as B)!.d, e); + +== should not add parentheses to new expression without arguments == +try { + throw new TypeError +} +catch { +} + +[expect] +try { + throw new TypeError; +} catch { +} + +== should remove parentheses from new expression with arguments == +try { + throw new TypeError() +} +catch { +} + +[expect] +try { + throw new TypeError; +} catch { +} + +== should not add parentheses to new expression with parenthesized callee == +const a = new (await this._getB()) + +[expect] +const a = new (await this._getB()); + +== should remove parentheses from new expression with parenthesized callee and args == +const a = new (await this._getB())() + +[expect] +const a = new (await this._getB()); + +== should remove parentheses around type assertions in binary expression (with parens) == +const x = (a as any as number) >= (b as any as number); + +[expect] +const x = a as any as number >= b as any as number; + +== should remove parentheses around type assertions in binary expression (without parens) == +const x = a as any as number >= b as any as number; + +[expect] +const x = a as any as number >= b as any as number; + +== should remove outer parentheses with type assertion and member access == +(([] as X[])).f(g); + +[expect] +([] as X[]).f(g);