Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions deployment/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@
}, {
"const": "preferNone",
"description": "Forces no braces when the header is one line and body is one line. Otherwise forces braces."
}, {
"const": "whenNeeded",
"description": "Uses braces when needed for clarity or safety, avoiding them when the statement is simple and unambiguous."
}, {
"const": "whenFormattedMultiLine",
"description": "Uses braces when the formatted code spans multiple lines, removing them for single-line statements."
}]
},
"bracePosition": {
Expand Down
1 change: 1 addition & 0 deletions src/configuration/resolve_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration)
quote_props,
semi_colons,
file_indent_level: get_value(&mut config, "fileIndentLevel", 0, &mut diagnostics),
prefer_single_line,
/* situational */
arrow_function_use_parentheses: get_value(&mut config, "arrowFunction.useParentheses", UseParentheses::Maintain, &mut diagnostics),
binary_expression_line_per_expression: get_value(&mut config, "binaryExpression.linePerExpression", false, &mut diagnostics),
Expand Down
9 changes: 8 additions & 1 deletion src/configuration/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,20 @@ pub enum UseBraces {
Always,
/// Forces no braces when the header is one line and body is one line. Otherwise forces braces.
PreferNone,
/// Only uses braces when syntactically required (empty blocks, declarations, multiple statements).
WhenNeeded,
/// Uses braces when the formatted body spans multiple lines.
WhenFormattedMultiLine,
}

generate_str_to_from![
UseBraces,
[Maintain, "maintain"],
[WhenNotSingleLine, "whenNotSingleLine"],
[Always, "always"],
[PreferNone, "preferNone"]
[PreferNone, "preferNone"],
[WhenNeeded, "whenNeeded"],
[WhenFormattedMultiLine, "whenFormattedMultiLine"]
];

/// Whether to use parentheses around a single parameter in an arrow function.
Expand Down Expand Up @@ -318,6 +324,7 @@ pub struct Configuration {
pub quote_props: QuoteProps,
pub semi_colons: SemiColons,
pub file_indent_level: u32,
pub prefer_single_line: bool,
/* situational */
#[serde(rename = "arrowFunction.useParentheses")]
pub arrow_function_use_parentheses: UseParentheses,
Expand Down
194 changes: 150 additions & 44 deletions src/generation/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5174,7 +5174,11 @@ fn gen_if_stmt<'a>(node: &IfStmt<'a>, context: &mut Context<'a>) -> PrintItems {
));
items
},
use_braces: context.config.if_statement_use_braces,
use_braces: if use_braces_for_then(cons, node.alt) {
UseBraces::Always
} else {
context.config.if_statement_use_braces
},
brace_position: context.config.if_statement_brace_position,
single_body_position: Some(context.config.if_statement_single_body_position),
requires_braces_condition_ref: context.take_if_stmt_last_brace_condition_ref(),
Expand Down Expand Up @@ -5229,7 +5233,11 @@ fn gen_if_stmt<'a>(node: &IfStmt<'a>, context: &mut Context<'a>) -> PrintItems {
gen_conditional_brace_body(
GenConditionalBraceBodyOptions {
body_node: alt.into(),
use_braces: context.config.if_statement_use_braces,
use_braces: if use_braces_for_else(alt) {
UseBraces::Always
} else {
context.config.if_statement_use_braces
},
brace_position: context.config.if_statement_brace_position,
single_body_position: Some(context.config.if_statement_single_body_position),
requires_braces_condition_ref: Some(result.open_brace_condition_ref),
Expand Down Expand Up @@ -8579,7 +8587,10 @@ fn gen_header_with_conditional_brace_body<'a>(
let result = gen_conditional_brace_body(
GenConditionalBraceBodyOptions {
body_node: opts.body_node.into(),
use_braces: if force_use_braces_for_stmt(opts.body_node) {
use_braces: if opts.use_braces != UseBraces::WhenNeeded
&& opts.use_braces != UseBraces::WhenFormattedMultiLine
&& get_use_braces_for_stmt(opts.body_node, false)
{
UseBraces::Always
} else {
opts.use_braces
Expand All @@ -8601,29 +8612,95 @@ fn gen_header_with_conditional_brace_body<'a>(
}
}

fn force_use_braces_for_stmt(stmt: Stmt) -> bool {
/// Returns the single non-empty statement from an iterator of statements, if there is exactly one.
/// Empty statements are automatically filtered out.
fn single_non_empty_stmt<'a, I>(iter: I) -> Option<Stmt<'a>>
where
I: IntoIterator<Item = &'a Stmt<'a>>,
{
let mut non_empty = iter.into_iter().filter(|s| !matches!(s, Stmt::Empty(_)));
match (non_empty.next(), non_empty.next()) {
(Some(first), None) => Some(*first),
_ => None,
}
}

fn get_use_braces_for_node(body_node: Node, preferred: bool) -> bool {
match body_node {
Node::BlockStmt(block) => match single_non_empty_stmt(block.stmts) {
Some(stmt) => get_use_braces_for_stmt(stmt, preferred),
None => true,
},
Node::ExprStmt(expr_stmt) => preferred && matches!(expr_stmt.expr, Expr::Await(_)),
Node::DoWhileStmt(_)
| Node::ForStmt(_)
| Node::ForInStmt(_)
| Node::ForOfStmt(_)
| Node::IfStmt(_)
| Node::LabeledStmt(_)
| Node::SwitchStmt(_)
| Node::TryStmt(_)
| Node::WhileStmt(_)
| Node::WithStmt(_) => preferred,
_ => false,
}
}

fn get_use_braces_for_stmt(stmt: Stmt, preferred: bool) -> bool {
match stmt {
Stmt::Block(block) => {
if block.stmts.len() != 1 {
true
} else {
force_use_braces_for_stmt(block.stmts[0])
}
}
// force braces for any children where no braces could be ambiguous
Stmt::Empty(_)
| Stmt::DoWhile(_)
Stmt::Block(block) => match single_non_empty_stmt(block.stmts) {
Some(stmt) => get_use_braces_for_stmt(stmt, preferred),
None => true,
},
Stmt::Expr(expr_stmt) => preferred && matches!(expr_stmt.expr, Expr::Await(_)),
Stmt::DoWhile(_)
| Stmt::For(_)
| Stmt::ForIn(_)
| Stmt::ForOf(_)
| Stmt::Decl(_)
| Stmt::If(_) // especially force for this as it may cause a bug
| Stmt::If(_)
| Stmt::Labeled(_)
| Stmt::Switch(_)
| Stmt::Try(_)
| Stmt::While(_)
| Stmt::With(_) => true,
Stmt::Break(_) | Stmt::Continue(_) | Stmt::Debugger(_) | Stmt::Expr(_) | Stmt::Return(_) | Stmt::Throw(_) => false,
| Stmt::With(_) => preferred,
Stmt::Decl(_) => true,
_ => false,
}
}

fn contains_dangling_if(stmt: Stmt) -> bool {
match stmt {
Stmt::If(if_stmt) => if_stmt.alt.is_none(),
Stmt::For(for_stmt) => contains_dangling_if(for_stmt.body),
Stmt::ForIn(for_in_stmt) => contains_dangling_if(for_in_stmt.body),
Stmt::ForOf(for_of_stmt) => contains_dangling_if(for_of_stmt.body),
Stmt::While(while_stmt) => contains_dangling_if(while_stmt.body),
Stmt::Block(block_stmt) => single_non_empty_stmt(block_stmt.stmts).map_or(false, contains_dangling_if),
Stmt::With(with_stmt) => contains_dangling_if(with_stmt.body),
Stmt::Labeled(labeled_stmt) => contains_dangling_if(labeled_stmt.body),
_ => false,
}
}

fn use_braces_for_then(then_node: Stmt, else_node: Option<Stmt>) -> bool {
match then_node {
Stmt::Block(block_stmt) => match single_non_empty_stmt(block_stmt.stmts) {
Some(stmt) => use_braces_for_then(stmt, else_node),
None => true,
},
Stmt::Decl(_) => true,
_ => else_node.is_some() && contains_dangling_if(then_node),
}
}

fn use_braces_for_else(else_node: Stmt) -> bool {
match else_node {
Stmt::Block(block_stmt) => match single_non_empty_stmt(block_stmt.stmts) {
Some(stmt) => use_braces_for_else(stmt),
None => true,
},
Stmt::Decl(_) => true,
_ => false,
}
}

Expand Down Expand Up @@ -8651,6 +8728,7 @@ fn gen_conditional_brace_body<'a>(opts: GenConditionalBraceBodyOptions<'a>, cont
let start_header_lsil = opts.start_header_info.map(|v| v.1);
let end_header_ln = opts.end_header_info;
let requires_braces_condition = opts.requires_braces_condition_ref;
let prefer_single_line = context.config.prefer_single_line;
let start_inner_text_lc = LineAndColumn::new("startInnerText");
let start_statements_lc = LineAndColumn::new("startStatements");
let end_statements_lc = LineAndColumn::new("endStatements");
Expand Down Expand Up @@ -8686,17 +8764,27 @@ fn gen_conditional_brace_body<'a>(opts: GenConditionalBraceBodyOptions<'a>, cont
if should_use_new_line {
return Some(true);
}
let end_header_ln = condition_context.resolved_line_number(end_header_ln?)?;
if end_header_ln < condition_context.writer_info.line_number {
return Some(true);

// Check if we have end_header_ln, if not fall back to checking from start
if let Some(end_header_ln_info) = end_header_ln {
let end_header_ln = condition_context.resolved_line_number(end_header_ln_info)?;
if end_header_ln < condition_context.writer_info.line_number {
return Some(true);
}
let resolved_end_statements_ln = condition_context.resolved_line_number(end_statements_lc.line)?;
Some(resolved_end_statements_ln > end_header_ln)
} else {
// No end header (e.g., for "else" keyword), check if statements span multiple lines
let resolved_start_statements_ln = condition_context.resolved_line_number(start_statements_lc.line)?;
let resolved_end_statements_ln = condition_context.resolved_line_number(end_statements_lc.line)?;
Some(resolved_end_statements_ln > resolved_start_statements_ln)
}
let resolved_end_statements_ln = condition_context.resolved_line_number(end_statements_lc.line)?;
Some(resolved_end_statements_ln > end_header_ln)
}),
Signal::NewLine.into(),
);
let newline_condition_ref = newline_condition.create_reference();
let force_braces = get_force_braces(opts.body_node);
let when_needed_use_braces = opts.use_braces == UseBraces::WhenNeeded && get_use_braces_for_node(opts.body_node, false);
let mut open_brace_condition = if_true(
"openBrace",
{
Expand All @@ -8707,43 +8795,42 @@ fn gen_conditional_brace_body<'a>(opts: GenConditionalBraceBodyOptions<'a>, cont
return Some(false);
}

if force_braces {
return Some(true);
}

match use_braces {
UseBraces::WhenNotSingleLine => {
if force_braces {
Some(true)
} else {
let is_multiple_lines = condition_helpers::is_multiple_lines(condition_context, end_header_ln.unwrap_or(start_lc.line), end_ln)?;
Some(is_multiple_lines)
}
let is_multiple_lines = condition_helpers::is_multiple_lines(condition_context, end_header_ln.unwrap_or(start_lc.line), end_ln)?;
Some(is_multiple_lines)
}
UseBraces::Maintain => Some(force_braces || has_open_brace_token),
UseBraces::Maintain => Some(has_open_brace_token),
UseBraces::Always => Some(true),
UseBraces::PreferNone => {
if force_braces || body_should_be_multi_line {
return Some(true);
}
if let Some(start_header_ln) = start_header_ln {
if let Some(end_header_ln) = end_header_ln {
let is_header_multiple_lines = condition_helpers::is_multiple_lines(condition_context, start_header_ln, end_header_ln)?;
if is_header_multiple_lines {
return Some(true);
}
if let (Some(start_header_ln), Some(end_header_ln)) = (start_header_ln, end_header_ln) {
if condition_helpers::is_multiple_lines(condition_context, start_header_ln, end_header_ln)? {
return Some(true);
}
}
let is_statements_multiple_lines = condition_helpers::is_multiple_lines(condition_context, start_statements_lc.line, end_statements_lc.line)?;
if is_statements_multiple_lines {

if body_should_be_multi_line || condition_helpers::is_multiple_lines(condition_context, start_statements_lc.line, end_statements_lc.line)? {
return Some(true);
}

if let Some(requires_braces_condition) = &requires_braces_condition {
let requires_braces = condition_context.resolved_condition(requires_braces_condition)?;
if requires_braces {
if let Some(requires_braces_condition) = requires_braces_condition {
if !prefer_single_line && condition_context.resolved_condition(&requires_braces_condition)? {
return Some(true);
}
}

Some(false)
}
UseBraces::WhenNeeded => Some(when_needed_use_braces),
UseBraces::WhenFormattedMultiLine => Some(condition_helpers::is_multiple_lines(
condition_context,
start_statements_lc.line,
end_statements_lc.line,
)?),
}
})
},
Expand Down Expand Up @@ -8855,11 +8942,30 @@ fn gen_conditional_brace_body<'a>(opts: GenConditionalBraceBodyOptions<'a>, cont
.into(),
));
items.push_sc(sc!("}"));
// Generate trailing comments for BlockStmt nodes when braces are always present
if use_braces == UseBraces::Always {
if let Node::BlockStmt(block_node) = opts.body_node {
items.extend(gen_trailing_comments(&block_node.range(), context));
}
}
items
},
);
let close_brace_condition_ref = close_brace_condition.create_reference();
items.push_condition(close_brace_condition);

// Generate trailing comments for BlockStmt nodes when braces can be removed
// (so comments appear even when braces are not present)
// These should be generated as statement comments (on separate lines) not trailing comments
if use_braces == UseBraces::PreferNone {
if let Node::BlockStmt(_) = opts.body_node {
let trailing_comments = opts.body_node.range().trailing_comments_fast(context.program);
if !trailing_comments.is_empty() {
items.extend(gen_trailing_comments_as_statements(&opts.body_node.range(), context));
}
}
}

items.push_info(end_ln);
items.push_reevaluation(open_brace_condition_reevaluation);

Expand Down
Loading