diff --git a/crates/air_r_formatter/src/comments.rs b/crates/air_r_formatter/src/comments.rs index 11f36566..6575d6f3 100644 --- a/crates/air_r_formatter/src/comments.rs +++ b/crates/air_r_formatter/src/comments.rs @@ -2,6 +2,7 @@ use crate::prelude::*; use air_r_syntax::AnyRExpression; use air_r_syntax::RArgument; use air_r_syntax::RArgumentNameClause; +use air_r_syntax::RElseClause; use air_r_syntax::RIfStatement; use air_r_syntax::RLanguage; use air_r_syntax::RParenthesizedExpression; @@ -59,13 +60,13 @@ impl CommentStyle for RCommentStyle { &self, comment: DecoratedComment, ) -> CommentPlacement { - // TODO: Implement more rule based comment placement, see `biome_js_formatter` match comment.text_position() { CommentTextPosition::EndOfLine => handle_for_comment(comment) .or_else(handle_function_comment) .or_else(handle_while_comment) .or_else(handle_repeat_comment) .or_else(handle_if_statement_comment) + .or_else(handle_else_clause_comment) .or_else(handle_parenthesized_expression_comment) .or_else(handle_argument_name_clause_comment) .or_else(handle_argument_comment) @@ -75,6 +76,7 @@ impl CommentStyle for RCommentStyle { .or_else(handle_while_comment) .or_else(handle_repeat_comment) .or_else(handle_if_statement_comment) + .or_else(handle_else_clause_comment) .or_else(handle_parenthesized_expression_comment) .or_else(handle_argument_name_clause_comment) .or_else(handle_argument_comment) @@ -192,55 +194,271 @@ fn handle_function_comment(comment: DecoratedComment) -> CommentPlace fn handle_if_statement_comment( comment: DecoratedComment, ) -> CommentPlacement { - match (comment.enclosing_node().kind(), comment.following_node()) { - (RSyntaxKind::R_IF_STATEMENT, Some(following)) => { - let if_statement = RIfStatement::unwrap_cast(comment.enclosing_node().clone()); - - if let Some(preceding) = comment.preceding_node() { - // Make comments directly before the condition `)` trailing - // comments of the condition itself - // - // ```r - // if ( - // cond - // # comment - // ) { - // } - // ``` - if comment - .following_token() - .is_some_and(|token| token.kind() == RSyntaxKind::R_PAREN) - { - return CommentPlacement::trailing(preceding.clone(), comment); - } - } + let Some(if_statement) = RIfStatement::cast_ref(comment.enclosing_node()) else { + return CommentPlacement::Default(comment); + }; + + let Ok(condition) = if_statement.condition() else { + // Bail with default placement if we don't have a `condition`, should + // be unexpected + return CommentPlacement::Default(comment); + }; - // Figure out if this is a comment that comes directly before the - // `consequence` and after the `)`, in which case we move it onto - // the `consequence` - // - // ```r - // if (cond) # comment - // TRUE - // ``` - if let Ok(consequence) = if_statement.consequence() { - if consequence.syntax() == following { - return place_leading_or_dangling_body_comment(consequence, comment); - } + let Ok(consequence) = if_statement.consequence() else { + // Bail with default placement if we don't have a `consequence`, should + // be unexpected + return CommentPlacement::Default(comment); + }; + + // Make comments directly after the `condition` trailing + // comments of the `condition` itself + // + // ```r + // if ( + // condition + // # comment + // ) { + // } + // ``` + // + // But don't steal comments that belong on the `consequence`, like + // below. Here the preceding node is the `condition`. To do this right, + // we also force the following token to be `)`. + // + // ```r + // if (condition) # comment + // consequence + // ``` + if let Some(preceding) = comment.preceding_node() { + if let Some(following_token) = comment.following_token() { + if condition.syntax() == preceding + && matches!(following_token.kind(), RSyntaxKind::R_PAREN) + { + return CommentPlacement::trailing(preceding.clone(), comment); } } - (RSyntaxKind::R_ELSE_CLAUSE, _) => { - // TODO: Handle else clause comments in some way? See JS for an example. - // fall through + } + + // Figure out if this is a comment that comes directly before the + // `consequence` and after the `)`, in which case we move it onto + // the `consequence` to prepare for autobracing + // + // ```r + // if (condition) # comment + // consequence + // ``` + // + // This aligns with behavior when braces already exist. Here, the comment's + // default placement makes it leading on `consequence` (the enclosing node is + // the braced expression, and there are no preceding nodes, so it attaches as leading + // on the following node). + // + // ```r + // if (condition) { # comment + // consequence + // } + // ``` + // + // It is important that we first check for comments after the `condition` + // but before the `)`, which we do above, otherwise this will steal this + // comment and place it on `consequence` when it really belongs to `condition` + // + // ```r + // if ( + // condition + // # comment + // ) { + // } + // ``` + if let Some(following) = comment.following_node() { + if consequence.syntax() == following { + return place_leading_or_dangling_body_comment(consequence, comment); } - _ => { - // fall through + } + + // Move comments directly before an `else_clause` to the correct location + // + // Most `else` related handling is done by `handle_else_clause_comment()`, + // but when the comment is right before the `else` token, it is "enclosed" + // by the if statement node instead. We handle all of those cases here. + // + // We try extremely hard to force `} else` onto the same line with nothing + // between them, for maximum portability of the if/else statement. This + // requires moving the comment onto `consequence` or `alternative`. + // + // We greatly prefer creating leading comments over dangling comments when we move + // them, as they play much nicer with idempotence. + // + // ```r + // { + // if (cond) this # becomes leading on `this` + // else that + // } + // ``` + // + // ```r + // { + // if (cond) this + // # becomes leading on `that` + // else that + // } + // ``` + // + // ```r + // { + // if (cond) { + // this + // } # becomes leading on `that` + // else { + // that + // } + // } + // ``` + // + // ```r + // { + // if (cond) { + // + // } # becomes leading on `that` + // else { + // that + // } + // } + // ``` + // + // ```r + // { + // if (cond) { + // this + // } + // # becomes leading on `that` + // else { + // that + // } + // } + // ``` + // + // ```r + // { + // if (cond) { + // this + // } + // # becomes dangling on `{}` + // else { + // } + // } + // ``` + // + // ```r + // { + // if (cond) { + // this + // } + // # becomes leading on `that` + // else if (cond) { + // that + // } + // } + // ``` + if let Some(else_clause) = if_statement.else_clause() { + let Ok(alternative) = else_clause.alternative() else { + // Bail with default placement if we don't have a `alternative`, should + // be unexpected + return CommentPlacement::Default(comment); + }; + + if let Some(following) = comment.following_node() { + if else_clause.syntax() == following { + return match comment.text_position() { + // End of line comments lead the `consequence` body + CommentTextPosition::EndOfLine => { + place_leading_or_dangling_body_comment(consequence, comment) + } + // Own line comments lead the `alternative` body + CommentTextPosition::OwnLine => { + place_leading_or_dangling_alternative_comment(alternative, comment) + } + CommentTextPosition::SameLine => { + unreachable!("Inline comments aren't possible in R") + } + }; + } + } + } + + CommentPlacement::Default(comment) +} + +fn handle_else_clause_comment(comment: DecoratedComment) -> CommentPlacement { + let Some(else_clause) = RElseClause::cast_ref(comment.enclosing_node()) else { + return CommentPlacement::Default(comment); + }; + + let Ok(alternative) = else_clause.alternative() else { + // Bail with default placement if we don't have a `alternative`, should + // be unexpected + return CommentPlacement::Default(comment); + }; + + // Comments following the `else` token but before the `alternative` are enclosed by + // the `else_clause`. We make these comments leading on the `alternative`. + // + // ```r + // { + // if (condition) a + // else # becomes leading on `b` + // { + // b + // } + // } + // ``` + // + // ```r + // { + // if (condition) a + // else + // # becomes leading on `b` + // { + // b + // } + // } + // ``` + if let Some(following) = comment.following_node() { + if alternative.syntax() == following { + return place_leading_or_dangling_alternative_comment(alternative, comment); } } CommentPlacement::Default(comment) } +/// Basically [place_leading_or_dangling_body_comment()], but moves comments on an if +/// statement `alternative` onto the body of that if statement to handle if chaining a bit +/// nicer +/// +/// ```r +/// { +/// if (condition) { +/// a +/// } else # becomes leading on `b` rather than leading on if statement `alternative` +/// if (condition) { +/// b +/// } +/// } +/// ``` +fn place_leading_or_dangling_alternative_comment( + alternative: AnyRExpression, + comment: DecoratedComment, +) -> CommentPlacement { + match alternative { + AnyRExpression::RIfStatement(node) => match node.consequence() { + Ok(consequence) => place_leading_or_dangling_body_comment(consequence, comment), + Err(_) => CommentPlacement::Default(comment), + }, + node => place_leading_or_dangling_body_comment(node, comment), + } +} + fn handle_parenthesized_expression_comment( comment: DecoratedComment, ) -> CommentPlacement { @@ -577,7 +795,7 @@ fn handle_hole_argument_comment( /// ``` /// /// ```r -/// if (cond) # becomes leading on `{}` +/// if (cond) # becomes dangling on `{}` /// { /// } /// ``` diff --git a/crates/air_r_formatter/src/r/auxiliary/else_clause.rs b/crates/air_r_formatter/src/r/auxiliary/else_clause.rs index 04d85a5c..8b430792 100644 --- a/crates/air_r_formatter/src/r/auxiliary/else_clause.rs +++ b/crates/air_r_formatter/src/r/auxiliary/else_clause.rs @@ -1,30 +1,46 @@ use crate::prelude::*; -use crate::statement_body::FormatStatementBody; +use crate::r::auxiliary::if_statement::BracedExpressions; +use crate::r::auxiliary::if_statement::FormatIfBody; use air_r_syntax::RElseClause; use air_r_syntax::RElseClauseFields; use biome_formatter::write; +use biome_formatter::FormatRuleWithOptions; #[derive(Debug, Clone, Default)] -pub(crate) struct FormatRElseClause; +pub(crate) struct FormatRElseClause { + pub(crate) braced_expressions: BracedExpressions, +} + +#[derive(Debug)] +pub(crate) struct FormatRElseClauseOptions { + pub(crate) braced_expressions: BracedExpressions, +} + +impl FormatRuleWithOptions for FormatRElseClause { + type Options = FormatRElseClauseOptions; + + fn with_options(mut self, options: Self::Options) -> Self { + self.braced_expressions = options.braced_expressions; + self + } +} + impl FormatNodeRule for FormatRElseClause { fn fmt_fields(&self, node: &RElseClause, f: &mut RFormatter) -> FormatResult<()> { - use air_r_syntax::AnyRExpression::*; - let RElseClauseFields { else_token, alternative, } = node.as_fields(); + let else_token = else_token?; let alternative = alternative?; write!( f, [ else_token.format(), - group( - &FormatStatementBody::new(&alternative) - .with_forced_space(matches!(alternative, RIfStatement(_))) - ) + space(), + &FormatIfBody::new_alternative(&alternative, self.braced_expressions) ] ) } diff --git a/crates/air_r_formatter/src/r/auxiliary/if_statement.rs b/crates/air_r_formatter/src/r/auxiliary/if_statement.rs index bfe659e7..7e42dc25 100644 --- a/crates/air_r_formatter/src/r/auxiliary/if_statement.rs +++ b/crates/air_r_formatter/src/r/auxiliary/if_statement.rs @@ -1,12 +1,36 @@ use crate::prelude::*; -use crate::statement_body::FormatStatementBody; +use crate::r::auxiliary::else_clause::FormatRElseClauseOptions; +use air_r_syntax::AnyRExpression; +use air_r_syntax::RArgument; +use air_r_syntax::RBinaryExpression; use air_r_syntax::RIfStatement; use air_r_syntax::RIfStatementFields; +use air_r_syntax::RParameterDefault; +use air_r_syntax::RSyntaxKind; use biome_formatter::format_args; use biome_formatter::write; +use biome_formatter::FormatRuleWithOptions; +use biome_rowan::SyntaxResult; #[derive(Debug, Clone, Default)] -pub(crate) struct FormatRIfStatement; +pub(crate) struct FormatRIfStatement { + pub(crate) braced_expressions: Option, +} + +#[derive(Debug)] +pub(crate) struct FormatRIfStatementOptions { + pub(crate) braced_expressions: Option, +} + +impl FormatRuleWithOptions for FormatRIfStatement { + type Options = FormatRIfStatementOptions; + + fn with_options(mut self, options: Self::Options) -> Self { + self.braced_expressions = options.braced_expressions; + self + } +} + impl FormatNodeRule for FormatRIfStatement { fn fmt_fields(&self, node: &RIfStatement, f: &mut RFormatter) -> FormatResult<()> { let RIfStatementFields { @@ -18,6 +42,33 @@ impl FormatNodeRule for FormatRIfStatement { else_clause, } = node.as_fields(); + // Compute `braced_expressions` if we are on a top level if statement, + // otherwise use the passed through `braced_expressions`, like if we are on the + // 2nd `if` statement of `if (condition) 1 else if (condition) 2`. + let braced_expressions = match self.braced_expressions { + Some(braced_expressions) => braced_expressions, + None => compute_braced_expressions(node)?, + }; + + // It's important that the `else_clause` end up in the same `group()` as the rest + // of the if statement, so we `format_once()` it to be evaluated once we are + // inside the `group()` + let else_clause = format_once(|f| { + if let Some(else_clause) = else_clause { + write!( + f, + [ + space(), + else_clause + .format() + .with_options(FormatRElseClauseOptions { braced_expressions }) + ] + )?; + } + + Ok(()) + }); + write!( f, [group(&format_args![ @@ -26,27 +77,299 @@ impl FormatNodeRule for FormatRIfStatement { l_paren_token.format(), group(&soft_block_indent(&condition.format())), r_paren_token.format(), - FormatStatementBody::new(&consequence?), + space(), + FormatIfBody::new_consequence(&consequence?, braced_expressions), + else_clause ])] - )?; - - // TODO: See more complex else handling (especially with comments) - // in `if_statement.rs` for JS - // Be careful about top level if statements. `else` has to be on the - // same line as the end of `consequence` to parse correctly! - if let Some(else_clause) = else_clause { - let else_on_same_line = true; - // let else_on_same_line = matches!(consequent, RBlockStatement(_)); - - if else_on_same_line { - write!(f, [space()])?; - } else { - write!(f, [hard_line_break()])?; - } + ) + } +} + +/// Determine if braced expressions should be forced within this if statement +/// +/// Single line if statements are only allowed in a few specific contexts: +/// - The right hand side of a `=`, `<-`, or `<<-` assignment +/// - A function call argument +/// - A function signature parameter +/// +/// If we are within one of those contexts, we must also decide if the if statement +/// is simple enough to stay on a single line. +/// - Any existing newline forces multiline +/// - A braced `consequence` or `alternative` forces multiline +/// - Nested if statements force multiline +/// +/// That ends up resulting in the following scenarios: +/// +/// ## The `consequence` or `alternative` has a leading newline +/// +/// ```r +/// # Before +/// x <- if (cond) +/// consequence +/// +/// x <- if (cond) consequence else +/// alternative +/// +/// # After +/// x <- if (cond) { +/// consequence +/// } +/// +/// x <- if (cond) { +/// consequence +/// } else { +/// alternative +/// } +/// ``` +/// +/// ## The `else` token has a leading newline +/// +/// ```r +/// # Before +/// { +/// x <- if (condition) 1 +/// else 2 +/// } +/// +/// # After +/// { +/// x <- if (condition) { +/// 1 +/// } else { +/// 2 +/// } +/// } +/// ``` +/// +/// ## The `consequence` or `alternative` has braced expressions +/// +/// ```r +/// # Before +/// x <- if (cond) { consequence } else alternative +/// x <- if (cond) consequence else { alternative } +/// +/// # After +/// x <- if (cond) { +/// consequence +/// } else { +/// alternative +/// } +/// ``` +/// +/// ## The `consequence` or `alternative` is another if statement +/// +/// ```r +/// # Before +/// x <- if (cond) if (cond) consequence +/// # |----consequence----| +/// +/// # After +/// # Note that we don't `Force` the inner if to be braced, because short +/// # ifs would be allowed there if the user wants to write it like that to begin with. +/// x <- if (cond) { +/// if (cond) consequence +/// } +/// +/// # Before +/// x <- if (cond) consequence1 else if (cond) consequence2 +/// # |-----alternative----| +/// +/// # After +/// x <- if (cond) { +/// consequence1 +/// } else if (cond) { +/// consequence2 +/// } +/// ``` +fn compute_braced_expressions(node: &RIfStatement) -> SyntaxResult { + if !in_allowed_one_line_if_statement_context(node)? { + return Ok(BracedExpressions::Force); + } + + let consequence = node.consequence()?; + + if consequence.syntax().has_leading_newline() { + return Ok(BracedExpressions::Force); + } + if matches!(consequence, AnyRExpression::RBracedExpressions(_)) { + return Ok(BracedExpressions::Force); + } + if matches!(consequence, AnyRExpression::RIfStatement(_)) { + // Disallow `if (condition) if (condition) 1` as that is too complex. + // Also shortcircuits recursion nicely. + // Notably we don't pass through `Force` to the inner if statement as well, + // it gets to compute its own `BracedExpressions` value. + return Ok(BracedExpressions::Force); + } + + if let Some(else_clause) = node.else_clause() { + let else_token = else_clause.else_token()?; + + if else_token.has_leading_newline() { + return Ok(BracedExpressions::Force); + } - write!(f, [else_clause.format()])?; + let alternative = else_clause.alternative()?; + + if alternative.syntax().has_leading_newline() { + return Ok(BracedExpressions::Force); + } + if matches!(alternative, AnyRExpression::RBracedExpressions(_)) { + return Ok(BracedExpressions::Force); + } + if matches!(alternative, AnyRExpression::RIfStatement(_)) { + // Disallow `if (condition) 1 else if (condition) 2 else 3` as that is too complex. + // Also shortcircuits recursion nicely. + return Ok(BracedExpressions::Force); } + }; - Ok(()) + Ok(BracedExpressions::IfGroupBreaks) +} + +fn in_allowed_one_line_if_statement_context(node: &RIfStatement) -> SyntaxResult { + // ```r + // # Allowed + // x = if (a) else b + // x <- if (a) else b + // x <<- if (a) else b + // ``` + if let Some(parent) = node.parent::() { + // Check for `=`, `<-`, or `<<-` as the operator + if !matches!( + parent.operator()?.kind(), + RSyntaxKind::EQUAL | RSyntaxKind::ASSIGN | RSyntaxKind::SUPER_ASSIGN + ) { + return Ok(false); + } + + // Ensure we were the right hand side + if parent.right()?.syntax() != node.syntax() { + return Ok(false); + } + + return Ok(true); + }; + + // ```r + // # Allowed (unnamed) + // fn(if (a) 1) + // fn(if (a) 1 else 2) + // + // # Allowed (named) + // fn(x = if (a) 1 else 2) + // + // # Allowed here, rejected later for being too complex + // fn(if (a) 1 else if (b) 2) + // ``` + if node.parent::().is_some() { + return Ok(true); + }; + + // ```r + // # Allowed (parameter with default) + // function(x = if (a) 1) {} + // function(x = if (a) 1 else 2) {} + // + // # Allowed here, rejected later for being too complex + // function(x = if (a) 1 else if (b) 2) {} + // ``` + if node.parent::().is_some() { + return Ok(true); + } + + // Otherwise one line if statements are not allowed + Ok(false) +} + +pub(crate) struct FormatIfBody<'a> { + node: &'a AnyRExpression, + braced_expressions: BracedExpressions, + if_body_kind: IfBodyKind, +} + +impl<'a> FormatIfBody<'a> { + pub(crate) fn new_consequence( + node: &'a AnyRExpression, + braced_expressions: BracedExpressions, + ) -> Self { + Self { + node, + braced_expressions, + if_body_kind: IfBodyKind::Consequence, + } + } + + pub(crate) fn new_alternative( + node: &'a AnyRExpression, + braced_expressions: BracedExpressions, + ) -> Self { + Self { + node, + braced_expressions, + if_body_kind: IfBodyKind::Alternative, + } + } +} + +#[derive(Debug, Copy, Clone, Default)] +pub(crate) enum BracedExpressions { + Force, + #[default] + IfGroupBreaks, +} + +pub(crate) enum IfBodyKind { + /// The body attached to `if (condition)` + Consequence, + /// The body attached to `else` + Alternative, +} + +impl Format for FormatIfBody<'_> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + match self.node { + // Body already has braces, just format it + AnyRExpression::RBracedExpressions(node) => { + write!(f, [node.format()]) + } + // Body is an `alternative` that is another if statement, pass through + // pre computed `braced_expressions` (will be `BracedExpressions::Force`) + // + // ```r + // if (TRUE) 1 else if (TRUE) 2 + // # |-----------| + // ``` + AnyRExpression::RIfStatement(node) + if matches!(self.if_body_kind, IfBodyKind::Alternative) => + { + debug_assert!(matches!(self.braced_expressions, BracedExpressions::Force)); + write!( + f, + [node.format().with_options(FormatRIfStatementOptions { + braced_expressions: Some(self.braced_expressions) + })] + ) + } + // Body does not have braces yet + node => match self.braced_expressions { + BracedExpressions::Force => write!( + f, + [ + text("{"), + block_indent(&format_args![&node.format()]), + text("}") + ] + ), + BracedExpressions::IfGroupBreaks => write!( + f, + [ + if_group_breaks(&text("{")), + soft_block_indent(&format_args![&node.format()]), + if_group_breaks(&text("}")) + ] + ), + }, + } } } diff --git a/crates/air_r_formatter/src/statement_body.rs b/crates/air_r_formatter/src/statement_body.rs index d5ce6e46..db4dd00c 100644 --- a/crates/air_r_formatter/src/statement_body.rs +++ b/crates/air_r_formatter/src/statement_body.rs @@ -4,26 +4,24 @@ use biome_formatter::write; pub(crate) struct FormatStatementBody<'a> { body: &'a AnyRExpression, - force_space: bool, } impl<'a> FormatStatementBody<'a> { pub fn new(body: &'a AnyRExpression) -> Self { - Self { - body, - force_space: false, - } - } - - /// For `if () 1 else if () 2` scenarios, ensures the second `if` is started - /// on the same line as the `else` (rather than line broken) and is - /// separated from the `else` by a single space - pub fn with_forced_space(mut self, value: bool) -> Self { - self.force_space = value; - self + Self { body } } } +// TODO!: Repurpose this as `FormatBracedBody` for use in: +// - For loops (unconditionally) +// - Repeat loops (unconditionally) +// - While loops (unconditionally) +// - Function definitions +// - Includes anonymous functions +// - Allow 1 liner function definitions +// - Definitely breaks if argument list expands over multiple lines +// - Use `if_group_breaks(&text("{"))` to add missing `{}` if the group breaks, +// like if statements. Probably won't be able to use simple `FormatBracedBody` impl Format for FormatStatementBody<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { use AnyRExpression::*; @@ -31,15 +29,15 @@ impl Format for FormatStatementBody<'_> { // We only want a single space between the construct and the statement // body if the body is a braced expression. The expression will handle // the line break as required. Otherwise, we handle the soft line - // indent for all other syntax. + // indent or space for all other syntax. // - // if (a) {} - // |--| statement body + // for (x in xs) {} + // |--| statement body // - // if (a) + // for (x in xs) // a // |--| statement body - if matches!(&self.body, RBracedExpressions(_)) || self.force_space { + if matches!(&self.body, RBracedExpressions(_)) { write!(f, [space(), self.body.format()]) } else { write!(f, [soft_line_indent_or_space(&self.body.format())]) diff --git a/crates/air_r_formatter/tests/specs/r/directives/skip.R b/crates/air_r_formatter/tests/specs/r/directives/skip.R index 494075a8..3e362302 100644 --- a/crates/air_r_formatter/tests/specs/r/directives/skip.R +++ b/crates/air_r_formatter/tests/specs/r/directives/skip.R @@ -131,3 +131,9 @@ function(){ 1+1 2+2 } + +# ----------------------------------------------------------------------------- +# If statements and comments + +# fmt: skip +if (TRUE) 1 # hi diff --git a/crates/air_r_formatter/tests/specs/r/directives/skip.R.snap b/crates/air_r_formatter/tests/specs/r/directives/skip.R.snap index c91e9ef3..b92d34d6 100644 --- a/crates/air_r_formatter/tests/specs/r/directives/skip.R.snap +++ b/crates/air_r_formatter/tests/specs/r/directives/skip.R.snap @@ -139,6 +139,12 @@ function(){ 2+2 } +# ----------------------------------------------------------------------------- +# If statements and comments + +# fmt: skip +if (TRUE) 1 # hi + ``` @@ -291,4 +297,10 @@ function() { 1+1 2 + 2 } + +# ----------------------------------------------------------------------------- +# If statements and comments + +# fmt: skip +if (TRUE) 1 # hi ``` diff --git a/crates/air_r_formatter/tests/specs/r/if_statement.R b/crates/air_r_formatter/tests/specs/r/if_statement.R index 8d39734e..01d0e2ee 100644 --- a/crates/air_r_formatter/tests/specs/r/if_statement.R +++ b/crates/air_r_formatter/tests/specs/r/if_statement.R @@ -1,18 +1,15 @@ -if (a) 1 -if (a) 1 else 2 -if (a) 1 else if (b) 2 else 3 - -# Spacing test -if(a)1 else if(b)2 else 3 - -# Line break test -if (a_really_really_long_condition_here_that_is_allowed_to_break_onto_the_next_line) 1 else 2 +# --------------------------------------------------------------------------- +# Comments if (a) # becomes leading on `1 + 1` { 1 + 1 } +if (a) { # becomes leading on `1 + 1` + 1 + 1 +} + if (a) # becomes dangling on `{}` { } @@ -25,6 +22,10 @@ if (a) # becomes dangling on `{}` if (a) # becomes leading on `TRUE` TRUE +if +# becomes leading on `a` +(a) TRUE + if ( a # becomes trailing on `a` @@ -37,6 +38,254 @@ if (a # becomes trailing on `a` TRUE } +{ + if (condition) this # becomes leading on `this` + else that +} + +{ + if (condition) this + # becomes leading on `that` + else that +} + +# With `{ this }`, it's nice that this becomes leading on `this` +{ + if (condition) { this } # becomes leading on `this` + else that +} + +# With `{\n this\n}`, it's arguable whether this should lead +# `this` or lead `that`, but we keep things simple here to have +# one code path that also handles the `{ this }` case. +{ + if (condition) { + this + } # becomes leading on `this` + else that +} + +{ + if (condition) { + + } # becomes dangling on `{}` + else that +} + +{ + if (condition) { + this + } + # becomes leading on `that` + else that +} + +{ + if (condition) { + this + } + # becomes leading on `that` + else { + that + } +} + +{ + if (condition) { + this + } + # becomes dangling on `{}` + else { + + } +} + +{ + if (condition) { + this + } + # becomes leading on `that` + else if (condition) { + that + } +} + +{ + if (condition) { + this + } + # becomes leading on `that` + else if (condition) that +} + +{ + if (condition) { + this + } + # becomes dangling on `{}` + else if (condition) {} +} + +{ + if (condition) { + this + } + # becomes leading on `that` + # becomes leading on `that` part 2 + else if (condition) { + that + } +} + +{ + if (condition) a + else # becomes leading on `b` + b +} + +{ + if (condition) a + else # becomes leading on `b` + { + b + } +} + +if (condition) { + a +} else if (condition) { + b +} else # becomes leading on `c` +{ + c +} + +if (condition) # becomes leading on `a` +{ + a +} else if (condition) # becomes leading on `b` +{ + b +} else # becomes leading on `c` +{ + c +} + +# In general, greatly prefer creating leading comments on `a` rather than +# creating trailing comments on `a`, as it is much easier to handle from +# an idempotence point of view. +{ + if (condition) { + if (condition) a + } # becomes leading on `a`'s if statement + else { + b + } +} + +{ + if (condition) a # becomes leading on `a` + else if (condition) b +} + +{ + if (condition) { a } # becomes leading on `a` + else if (condition) b +} + +{ + if (condition) { + a + } # becomes leading on `a` + else if (condition) b +} + +# --------------------------------------------------------------------------- +# Comments - these comments aren't "enclosed" by the if statement, so they +# our outside our if statement comment handling hooks. Avoid the urge to +# try and attach these comments to the last node in the if statement, as +# that causes verbatim printing (with `fmt: skip`) to break, because biome's +# formatter expects that the trailing comment trails the outermost code element +# (here, the whole if statement, not the last node of it). We have `fmt: skip` +# related test elsewhere to ensure we don't break this. + +# These stay where they are, short one liner assigned if statements don't expand +x <- if (condition) a # becomes trailing on if statement +x <- if (condition) a else b # becomes trailing on if statement + +# These expand +if (condition) a # becomes trailing on if statement +if (condition) a else b # becomes trailing on if statement + +if (condition) { a } # becomes trailing on if statement +if (condition) { a } else { b } # becomes trailing on if statement + +if (condition) a_really_really_long_name_here_that_forces_a_break_because_it_is_so_long # becomes trailing on if statement +if (condition) { a_really_really_long_name_here_that_forces_a_break_because_it_is_so_long } # becomes trailing on if statement + +if (condition) a_really_really_long_name_here else another_really_really_long_name # becomes trailing on if statement +if (condition) a_really_really_long_name_here else { another_really_really_long_name } # becomes trailing on if statement + +if (condition) a_really_really_long_name_here else if (condition2) another_really_really_long_name # becomes trailing on outer if statement +if (condition) a_really_really_long_name_here else if (condition2) { another_really_really_long_name } # becomes trailing on outer if statement + +if (condition) a_really_really_long_name_here else if (condition2) another_really_really_long_name else that_name # becomes trailing on outer if statement +if (condition) a_really_really_long_name_here else if (condition2) another_really_really_long_name else { that_name } # becomes trailing on outer if statement + +if (condition) + a # becomes trailing on if statement + +if (condition) # becomes leading on `a` + a # becomes trailing on if statement + +if (condition) { + a +} # becomes trailing on if statement + +# It would be nice to have consistent behavior, but it's basically impossible. +# Comment on `a` is enclosed by the if statement so our hooks handle it, but +# comment on `b` is not! +{ + if (condition) a # becomes leading on `a` + else b # becomes trailing on if statement +} + +{ + if (condition) a + else b # becomes trailing on if statement +} + +{ + if (condition) a + else { + b + } # becomes trailing on if statement +} + +{ + if (condition) a + else b + # Floating comment after the if statement +} + +# Nesting in `consequence` +x <- if (condition) if (condition2) this # becomes trailing on if statement +x <- if (condition) if (condition2) if (condition3) this # becomes trailing on if statement +x <- if (condition) if (condition2) this else that # becomes trailing on if statement +if (condition) if (condition2) this # becomes trailing on if statement +if (condition) if (condition2) if (condition3) this # becomes trailing on if statement +if (condition) if (condition2) this else that # becomes trailing on if statement + +# Nesting in `alternative` +x <- if (condition) this else that # stays flat, one liner if statement +if (condition) this else that # becomes trailing on if statement +if (condition) this else if (condition2) that # becomes trailing on if statement +if (condition) this else if (condition2) this2 else that # becomes trailing on if statement +if (condition) this else if (condition2) this2 else if (condition3) that # becomes trailing on if statement + +# --------------------------------------------------------------------------- +# Special + # Breaks, but the `condition` itself fits and is not expanded if (this || this || this || this || this || this || this || this || this || this) { 1 @@ -49,3 +298,76 @@ if (this || this || this || this || this || this || this || this || this || this } else { 2 } + +# --------------------------------------------------------------------------- +# Auto bracing + +# These are simple statements, but are braced because they aren't in an +# allowed context +if (a) 1 +if (a) 1 else 2 + +# These are in the allowed assignment context +x = if (a) 1 +x <- if (a) 1 +x <<- if (a) 1 + +x = if (a) 1 else 2 +x <- if (a) 1 else 2 +x <<- if (a) 1 else 2 + +# These are in the allowed function argument context +fn(if (a) 1) +fn(if (a) 1, if (a) 1 else 2) +fn(x = if (a) 1) +fn(if (a) 1, x = if (a) 1 else 2) +# Too complex +fn(if (a) 1 else if (b) 2 else 3) +# Breaks argument list, but not if/else +fn(if (a) 1, x = if (a) 1 else 2, this_is_really_really_long_and_breaks_the_group_here) + +# These are in the allowed function parameter context +function(x = if (a) 1) {} +function(x = if (a) 1, y = if (a) 1 else 2) {} +# Too complex +function(x = if (a) 1 else if (b) 2 else 3) {} +# Breaks parameter list, but not if/else +function(x = if (a) 1, y = if (a) 1 else 2, this_is_really_really_long_and_breaks_the_group_here) {} + +# The group breaking forces braces +x <- if (something_really_really_long_here_something_really_really_long_here) 1 else 2 +x <- if (a) something_really_really_long_here else and_something_really_really_long_here + +# The leading newline forces braces +x <- if (a) + 1 +x <- if (a) 1 else + 2 + +# The leading newline before `else` forces braces +{ + x <- if (a) 1 + else 2 +} + +# The nested if in `consequence` forces braces around the outer if. +# The inner ifs aren't in allowed contexts. +x <- if (a) if (b) 1 +x <- if (a) if (b) if (c) 1 +x <- if (a) if (b) 1 else 2 + +# We don't force the inner if inside the `consequence` to have braces as +# well, because a user could have writen a short if in the inner branch to +# begin with (like the second example here) and that would have been valid. +x <- if (a) if (b) 1 +x <- if (a) { + y <- if (b) 1 +} + +# The nested if in `alternative` forces braces +x <- if (a) 1 else if (b) 2 +x <- if (a) 1 else if (b) 2 else 3 + +# The braces on one piece force braces +x <- if (a) {1} else 2 +x <- if (a) {1} else {2} diff --git a/crates/air_r_formatter/tests/specs/r/if_statement.R.snap b/crates/air_r_formatter/tests/specs/r/if_statement.R.snap index 87d7d16a..fb3838ea 100644 --- a/crates/air_r_formatter/tests/specs/r/if_statement.R.snap +++ b/crates/air_r_formatter/tests/specs/r/if_statement.R.snap @@ -5,21 +5,18 @@ info: r/if_statement.R # Input ```R -if (a) 1 -if (a) 1 else 2 -if (a) 1 else if (b) 2 else 3 - -# Spacing test -if(a)1 else if(b)2 else 3 - -# Line break test -if (a_really_really_long_condition_here_that_is_allowed_to_break_onto_the_next_line) 1 else 2 +# --------------------------------------------------------------------------- +# Comments if (a) # becomes leading on `1 + 1` { 1 + 1 } +if (a) { # becomes leading on `1 + 1` + 1 + 1 +} + if (a) # becomes dangling on `{}` { } @@ -32,6 +29,10 @@ if (a) # becomes dangling on `{}` if (a) # becomes leading on `TRUE` TRUE +if +# becomes leading on `a` +(a) TRUE + if ( a # becomes trailing on `a` @@ -44,6 +45,254 @@ if (a # becomes trailing on `a` TRUE } +{ + if (condition) this # becomes leading on `this` + else that +} + +{ + if (condition) this + # becomes leading on `that` + else that +} + +# With `{ this }`, it's nice that this becomes leading on `this` +{ + if (condition) { this } # becomes leading on `this` + else that +} + +# With `{\n this\n}`, it's arguable whether this should lead +# `this` or lead `that`, but we keep things simple here to have +# one code path that also handles the `{ this }` case. +{ + if (condition) { + this + } # becomes leading on `this` + else that +} + +{ + if (condition) { + + } # becomes dangling on `{}` + else that +} + +{ + if (condition) { + this + } + # becomes leading on `that` + else that +} + +{ + if (condition) { + this + } + # becomes leading on `that` + else { + that + } +} + +{ + if (condition) { + this + } + # becomes dangling on `{}` + else { + + } +} + +{ + if (condition) { + this + } + # becomes leading on `that` + else if (condition) { + that + } +} + +{ + if (condition) { + this + } + # becomes leading on `that` + else if (condition) that +} + +{ + if (condition) { + this + } + # becomes dangling on `{}` + else if (condition) {} +} + +{ + if (condition) { + this + } + # becomes leading on `that` + # becomes leading on `that` part 2 + else if (condition) { + that + } +} + +{ + if (condition) a + else # becomes leading on `b` + b +} + +{ + if (condition) a + else # becomes leading on `b` + { + b + } +} + +if (condition) { + a +} else if (condition) { + b +} else # becomes leading on `c` +{ + c +} + +if (condition) # becomes leading on `a` +{ + a +} else if (condition) # becomes leading on `b` +{ + b +} else # becomes leading on `c` +{ + c +} + +# In general, greatly prefer creating leading comments on `a` rather than +# creating trailing comments on `a`, as it is much easier to handle from +# an idempotence point of view. +{ + if (condition) { + if (condition) a + } # becomes leading on `a`'s if statement + else { + b + } +} + +{ + if (condition) a # becomes leading on `a` + else if (condition) b +} + +{ + if (condition) { a } # becomes leading on `a` + else if (condition) b +} + +{ + if (condition) { + a + } # becomes leading on `a` + else if (condition) b +} + +# --------------------------------------------------------------------------- +# Comments - these comments aren't "enclosed" by the if statement, so they +# our outside our if statement comment handling hooks. Avoid the urge to +# try and attach these comments to the last node in the if statement, as +# that causes verbatim printing (with `fmt: skip`) to break, because biome's +# formatter expects that the trailing comment trails the outermost code element +# (here, the whole if statement, not the last node of it). We have `fmt: skip` +# related test elsewhere to ensure we don't break this. + +# These stay where they are, short one liner assigned if statements don't expand +x <- if (condition) a # becomes trailing on if statement +x <- if (condition) a else b # becomes trailing on if statement + +# These expand +if (condition) a # becomes trailing on if statement +if (condition) a else b # becomes trailing on if statement + +if (condition) { a } # becomes trailing on if statement +if (condition) { a } else { b } # becomes trailing on if statement + +if (condition) a_really_really_long_name_here_that_forces_a_break_because_it_is_so_long # becomes trailing on if statement +if (condition) { a_really_really_long_name_here_that_forces_a_break_because_it_is_so_long } # becomes trailing on if statement + +if (condition) a_really_really_long_name_here else another_really_really_long_name # becomes trailing on if statement +if (condition) a_really_really_long_name_here else { another_really_really_long_name } # becomes trailing on if statement + +if (condition) a_really_really_long_name_here else if (condition2) another_really_really_long_name # becomes trailing on outer if statement +if (condition) a_really_really_long_name_here else if (condition2) { another_really_really_long_name } # becomes trailing on outer if statement + +if (condition) a_really_really_long_name_here else if (condition2) another_really_really_long_name else that_name # becomes trailing on outer if statement +if (condition) a_really_really_long_name_here else if (condition2) another_really_really_long_name else { that_name } # becomes trailing on outer if statement + +if (condition) + a # becomes trailing on if statement + +if (condition) # becomes leading on `a` + a # becomes trailing on if statement + +if (condition) { + a +} # becomes trailing on if statement + +# It would be nice to have consistent behavior, but it's basically impossible. +# Comment on `a` is enclosed by the if statement so our hooks handle it, but +# comment on `b` is not! +{ + if (condition) a # becomes leading on `a` + else b # becomes trailing on if statement +} + +{ + if (condition) a + else b # becomes trailing on if statement +} + +{ + if (condition) a + else { + b + } # becomes trailing on if statement +} + +{ + if (condition) a + else b + # Floating comment after the if statement +} + +# Nesting in `consequence` +x <- if (condition) if (condition2) this # becomes trailing on if statement +x <- if (condition) if (condition2) if (condition3) this # becomes trailing on if statement +x <- if (condition) if (condition2) this else that # becomes trailing on if statement +if (condition) if (condition2) this # becomes trailing on if statement +if (condition) if (condition2) if (condition3) this # becomes trailing on if statement +if (condition) if (condition2) this else that # becomes trailing on if statement + +# Nesting in `alternative` +x <- if (condition) this else that # stays flat, one liner if statement +if (condition) this else that # becomes trailing on if statement +if (condition) this else if (condition2) that # becomes trailing on if statement +if (condition) this else if (condition2) this2 else that # becomes trailing on if statement +if (condition) this else if (condition2) this2 else if (condition3) that # becomes trailing on if statement + +# --------------------------------------------------------------------------- +# Special + # Breaks, but the `condition` itself fits and is not expanded if (this || this || this || this || this || this || this || this || this || this) { 1 @@ -57,6 +306,79 @@ if (this || this || this || this || this || this || this || this || this || this 2 } +# --------------------------------------------------------------------------- +# Auto bracing + +# These are simple statements, but are braced because they aren't in an +# allowed context +if (a) 1 +if (a) 1 else 2 + +# These are in the allowed assignment context +x = if (a) 1 +x <- if (a) 1 +x <<- if (a) 1 + +x = if (a) 1 else 2 +x <- if (a) 1 else 2 +x <<- if (a) 1 else 2 + +# These are in the allowed function argument context +fn(if (a) 1) +fn(if (a) 1, if (a) 1 else 2) +fn(x = if (a) 1) +fn(if (a) 1, x = if (a) 1 else 2) +# Too complex +fn(if (a) 1 else if (b) 2 else 3) +# Breaks argument list, but not if/else +fn(if (a) 1, x = if (a) 1 else 2, this_is_really_really_long_and_breaks_the_group_here) + +# These are in the allowed function parameter context +function(x = if (a) 1) {} +function(x = if (a) 1, y = if (a) 1 else 2) {} +# Too complex +function(x = if (a) 1 else if (b) 2 else 3) {} +# Breaks parameter list, but not if/else +function(x = if (a) 1, y = if (a) 1 else 2, this_is_really_really_long_and_breaks_the_group_here) {} + +# The group breaking forces braces +x <- if (something_really_really_long_here_something_really_really_long_here) 1 else 2 +x <- if (a) something_really_really_long_here else and_something_really_really_long_here + +# The leading newline forces braces +x <- if (a) + 1 +x <- if (a) 1 else + 2 + +# The leading newline before `else` forces braces +{ + x <- if (a) 1 + else 2 +} + +# The nested if in `consequence` forces braces around the outer if. +# The inner ifs aren't in allowed contexts. +x <- if (a) if (b) 1 +x <- if (a) if (b) if (c) 1 +x <- if (a) if (b) 1 else 2 + +# We don't force the inner if inside the `consequence` to have braces as +# well, because a user could have writen a short if in the inner branch to +# begin with (like the second example here) and that would have been valid. +x <- if (a) if (b) 1 +x <- if (a) { + y <- if (b) 1 +} + +# The nested if in `alternative` forces braces +x <- if (a) 1 else if (b) 2 +x <- if (a) 1 else if (b) 2 else 3 + +# The braces on one piece force braces +x <- if (a) {1} else 2 +x <- if (a) {1} else {2} + ``` @@ -76,18 +398,13 @@ Skip: None ----- ```R -if (a) 1 -if (a) 1 else 2 -if (a) 1 else if (b) 2 else 3 - -# Spacing test -if (a) 1 else if (b) 2 else 3 +# --------------------------------------------------------------------------- +# Comments -# Line break test -if ( - a_really_really_long_condition_here_that_is_allowed_to_break_onto_the_next_line -) - 1 else 2 +if (a) { + # becomes leading on `1 + 1` + 1 + 1 +} if (a) { # becomes leading on `1 + 1` @@ -103,9 +420,17 @@ if (a) { # inner comment but empty `{}` } -if (a) +if (a) { # becomes leading on `TRUE` TRUE +} + +if ( + # becomes leading on `a` + a +) { + TRUE +} if ( a @@ -120,6 +445,391 @@ if ( TRUE } +{ + if (condition) { + # becomes leading on `this` + this + } else { + that + } +} + +{ + if (condition) { + this + } else { + # becomes leading on `that` + that + } +} + +# With `{ this }`, it's nice that this becomes leading on `this` +{ + if (condition) { + # becomes leading on `this` + this + } else { + that + } +} + +# With `{\n this\n}`, it's arguable whether this should lead +# `this` or lead `that`, but we keep things simple here to have +# one code path that also handles the `{ this }` case. +{ + if (condition) { + # becomes leading on `this` + this + } else { + that + } +} + +{ + if (condition) { + # becomes dangling on `{}` + } else { + that + } +} + +{ + if (condition) { + this + } else { + # becomes leading on `that` + that + } +} + +{ + if (condition) { + this + } else { + # becomes leading on `that` + that + } +} + +{ + if (condition) { + this + } else { + # becomes dangling on `{}` + } +} + +{ + if (condition) { + this + } else if (condition) { + # becomes leading on `that` + that + } +} + +{ + if (condition) { + this + } else if (condition) { + # becomes leading on `that` + that + } +} + +{ + if (condition) { + this + } else if (condition) { + # becomes dangling on `{}` + } +} + +{ + if (condition) { + this + } else if (condition) { + # becomes leading on `that` + # becomes leading on `that` part 2 + that + } +} + +{ + if (condition) { + a + } else { + # becomes leading on `b` + b + } +} + +{ + if (condition) { + a + } else { + # becomes leading on `b` + b + } +} + +if (condition) { + a +} else if (condition) { + b +} else { + # becomes leading on `c` + c +} + +if (condition) { + # becomes leading on `a` + a +} else if (condition) { + # becomes leading on `b` + b +} else { + # becomes leading on `c` + c +} + +# In general, greatly prefer creating leading comments on `a` rather than +# creating trailing comments on `a`, as it is much easier to handle from +# an idempotence point of view. +{ + if (condition) { + # becomes leading on `a`'s if statement + if (condition) { + a + } + } else { + b + } +} + +{ + if (condition) { + # becomes leading on `a` + a + } else if (condition) { + b + } +} + +{ + if (condition) { + # becomes leading on `a` + a + } else if (condition) { + b + } +} + +{ + if (condition) { + # becomes leading on `a` + a + } else if (condition) { + b + } +} + +# --------------------------------------------------------------------------- +# Comments - these comments aren't "enclosed" by the if statement, so they +# our outside our if statement comment handling hooks. Avoid the urge to +# try and attach these comments to the last node in the if statement, as +# that causes verbatim printing (with `fmt: skip`) to break, because biome's +# formatter expects that the trailing comment trails the outermost code element +# (here, the whole if statement, not the last node of it). We have `fmt: skip` +# related test elsewhere to ensure we don't break this. + +# These stay where they are, short one liner assigned if statements don't expand +x <- if (condition) a # becomes trailing on if statement +x <- if (condition) a else b # becomes trailing on if statement + +# These expand +if (condition) { + a +} # becomes trailing on if statement +if (condition) { + a +} else { + b +} # becomes trailing on if statement + +if (condition) { + a +} # becomes trailing on if statement +if (condition) { + a +} else { + b +} # becomes trailing on if statement + +if (condition) { + a_really_really_long_name_here_that_forces_a_break_because_it_is_so_long +} # becomes trailing on if statement +if (condition) { + a_really_really_long_name_here_that_forces_a_break_because_it_is_so_long +} # becomes trailing on if statement + +if (condition) { + a_really_really_long_name_here +} else { + another_really_really_long_name +} # becomes trailing on if statement +if (condition) { + a_really_really_long_name_here +} else { + another_really_really_long_name +} # becomes trailing on if statement + +if (condition) { + a_really_really_long_name_here +} else if (condition2) { + another_really_really_long_name +} # becomes trailing on outer if statement +if (condition) { + a_really_really_long_name_here +} else if (condition2) { + another_really_really_long_name +} # becomes trailing on outer if statement + +if (condition) { + a_really_really_long_name_here +} else if (condition2) { + another_really_really_long_name +} else { + that_name +} # becomes trailing on outer if statement +if (condition) { + a_really_really_long_name_here +} else if (condition2) { + another_really_really_long_name +} else { + that_name +} # becomes trailing on outer if statement + +if (condition) { + a +} # becomes trailing on if statement + +if (condition) { + # becomes leading on `a` + a +} # becomes trailing on if statement + +if (condition) { + a +} # becomes trailing on if statement + +# It would be nice to have consistent behavior, but it's basically impossible. +# Comment on `a` is enclosed by the if statement so our hooks handle it, but +# comment on `b` is not! +{ + if (condition) { + # becomes leading on `a` + a + } else { + b + } # becomes trailing on if statement +} + +{ + if (condition) { + a + } else { + b + } # becomes trailing on if statement +} + +{ + if (condition) { + a + } else { + b + } # becomes trailing on if statement +} + +{ + if (condition) { + a + } else { + b + } + # Floating comment after the if statement +} + +# Nesting in `consequence` +x <- if (condition) { + if (condition2) { + this + } +} # becomes trailing on if statement +x <- if (condition) { + if (condition2) { + if (condition3) { + this + } + } +} # becomes trailing on if statement +x <- if (condition) { + if (condition2) { + this + } else { + that + } +} # becomes trailing on if statement +if (condition) { + if (condition2) { + this + } +} # becomes trailing on if statement +if (condition) { + if (condition2) { + if (condition3) { + this + } + } +} # becomes trailing on if statement +if (condition) { + if (condition2) { + this + } else { + that + } +} # becomes trailing on if statement + +# Nesting in `alternative` +x <- if (condition) this else that # stays flat, one liner if statement +if (condition) { + this +} else { + that +} # becomes trailing on if statement +if (condition) { + this +} else if (condition2) { + that +} # becomes trailing on if statement +if (condition) { + this +} else if (condition2) { + this2 +} else { + that +} # becomes trailing on if statement +if (condition) { + this +} else if (condition2) { + this2 +} else if (condition3) { + that +} # becomes trailing on if statement + +# --------------------------------------------------------------------------- +# Special + # Breaks, but the `condition` itself fits and is not expanded if ( this || this || this || this || this || this || this || this || this || this @@ -147,9 +857,164 @@ if ( } else { 2 } -``` -# Lines exceeding max width of 80 characters -``` - 10: a_really_really_long_condition_here_that_is_allowed_to_break_onto_the_next_line +# --------------------------------------------------------------------------- +# Auto bracing + +# These are simple statements, but are braced because they aren't in an +# allowed context +if (a) { + 1 +} +if (a) { + 1 +} else { + 2 +} + +# These are in the allowed assignment context +x = if (a) 1 +x <- if (a) 1 +x <<- if (a) 1 + +x = if (a) 1 else 2 +x <- if (a) 1 else 2 +x <<- if (a) 1 else 2 + +# These are in the allowed function argument context +fn(if (a) 1) +fn(if (a) 1, if (a) 1 else 2) +fn(x = if (a) 1) +fn(if (a) 1, x = if (a) 1 else 2) +# Too complex +fn( + if (a) { + 1 + } else if (b) { + 2 + } else { + 3 + } +) +# Breaks argument list, but not if/else +fn( + if (a) 1, + x = if (a) 1 else 2, + this_is_really_really_long_and_breaks_the_group_here +) + +# These are in the allowed function parameter context +function(x = if (a) 1) { +} +function(x = if (a) 1, y = if (a) 1 else 2) { +} +# Too complex +function( + x = if (a) { + 1 + } else if (b) { + 2 + } else { + 3 + } +) { +} +# Breaks parameter list, but not if/else +function( + x = if (a) 1, + y = if (a) 1 else 2, + this_is_really_really_long_and_breaks_the_group_here +) { +} + +# The group breaking forces braces +x <- if (something_really_really_long_here_something_really_really_long_here) { + 1 +} else { + 2 +} +x <- if (a) { + something_really_really_long_here +} else { + and_something_really_really_long_here +} + +# The leading newline forces braces +x <- if (a) { + 1 +} +x <- if (a) { + 1 +} else { + 2 +} + +# The leading newline before `else` forces braces +{ + x <- if (a) { + 1 + } else { + 2 + } +} + +# The nested if in `consequence` forces braces around the outer if. +# The inner ifs aren't in allowed contexts. +x <- if (a) { + if (b) { + 1 + } +} +x <- if (a) { + if (b) { + if (c) { + 1 + } + } +} +x <- if (a) { + if (b) { + 1 + } else { + 2 + } +} + +# We don't force the inner if inside the `consequence` to have braces as +# well, because a user could have writen a short if in the inner branch to +# begin with (like the second example here) and that would have been valid. +x <- if (a) { + if (b) { + 1 + } +} +x <- if (a) { + y <- if (b) 1 +} + +# The nested if in `alternative` forces braces +x <- if (a) { + 1 +} else if (b) { + 2 +} +x <- if (a) { + 1 +} else if (b) { + 2 +} else { + 3 +} + +# The braces on one piece force braces +x <- if (a) { + 1 +} else { + 2 +} +x <- if (a) { + 1 +} else { + 2 +} ```