Skip to content

Commit

Permalink
Fix indentation of assigned pipelines (#227)
Browse files Browse the repository at this point in the history
  • Loading branch information
lionel- authored Feb 19, 2025
1 parent a649af0 commit 2144caa
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

# Development version

- Assigned pipelines no longer double-indent when a persistent line break is used (#220).


# 0.3.0

- Air has gained support for excluding files and folders (#128).
Expand All @@ -21,6 +24,7 @@

- ARM Windows binaries are now available (#170).


# 0.2.0

- Initial public release, yay!
Expand Down
22 changes: 22 additions & 0 deletions crates/air_r_formatter/src/either.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use biome_formatter::{prelude::Formatter, Format, FormatResult};

/// An owned value that generically represents two `Format` types
///
/// Can be chained.
pub enum Either<L, R> {
Left(L),
Right(R),
}

impl<L, R, Context> Format<Context> for Either<L, R>
where
L: Format<Context>,
R: Format<Context>,
{
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
match self {
Either::Left(left) => left.fmt(f),
Either::Right(right) => right.fmt(f),
}
}
}
1 change: 1 addition & 0 deletions crates/air_r_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::cst::FormatRSyntaxNode;
pub mod comments;
pub mod context;
mod cst;
pub mod either;
mod prelude;
mod r;
pub(crate) mod separated;
Expand Down
71 changes: 63 additions & 8 deletions crates/air_r_formatter/src/r/auxiliary/binary_expression.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::context::RFormatOptions;
use crate::either::Either;
use crate::prelude::*;
use air_r_syntax::AnyRExpression;
use air_r_syntax::RBinaryExpression;
Expand All @@ -8,12 +9,55 @@ use air_r_syntax::RSyntaxKind;
use biome_formatter::format_args;
use biome_formatter::write;
use biome_formatter::CstFormatContext;
use biome_formatter::FormatRuleWithOptions;
use biome_rowan::AstNode;
use biome_rowan::SyntaxResult;
use biome_rowan::SyntaxToken;

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatRBinaryExpression;
#[derive(Default, Debug, Clone, Copy)]
pub(crate) enum ChainAlignment {
#[default]
Indented,

/// Used in the RHS of assign operators to align pipelines.
LeftAligned,
}

#[derive(Default, Debug, Clone)]
pub(crate) struct FormatRBinaryExpression {
/// Alignment to use with chained expressions
///
/// Left alignment is used to prevent "double-indenting" an
/// assigned pipeline, e.g.:
///
/// ```r
/// foo <-
/// bar |>
/// baz()
///
/// foo <-
/// bar +
/// baz()
/// ```
///
/// See https://github.com/posit-dev/air/issues/220.
pub(crate) alignment: ChainAlignment,
}

#[derive(Default, Debug, Clone)]
pub(crate) struct FormatRBinaryExpressionOptions {
pub(crate) alignment: ChainAlignment,
}

impl FormatRuleWithOptions<RBinaryExpression> for FormatRBinaryExpression {
type Options = FormatRBinaryExpressionOptions;

fn with_options(mut self, options: Self::Options) -> Self {
self.alignment = options.alignment;
self
}
}

impl FormatNodeRule<RBinaryExpression> for FormatRBinaryExpression {
fn fmt_fields(&self, node: &RBinaryExpression, f: &mut RFormatter) -> FormatResult<()> {
let RBinaryExpressionFields {
Expand Down Expand Up @@ -42,7 +86,7 @@ impl FormatNodeRule<RBinaryExpression> for FormatRBinaryExpression {
| RSyntaxKind::SUPER_ASSIGN_RIGHT => fmt_binary_assignment(left, operator, right, f),

// Chainable (pipes, logical, arithmetic)
kind if is_chainable_binary_operator(kind) => fmt_binary_chain(left, operator, right, f),
kind if is_chainable_binary_operator(kind) => fmt_binary_chain(left, operator, right, self.alignment, f),

// Not chainable
// Formulas (debatable)
Expand Down Expand Up @@ -147,10 +191,15 @@ fn fmt_binary_assignment(
) -> FormatResult<()> {
let right = format_with(|f| {
if binary_assignment_has_persistent_line_break(&operator, &right, f.options()) {
write!(
f,
[indent(&format_args![hard_line_break(), right.format()])]
)
let right = match &right {
AnyRExpression::RBinaryExpression(right) => {
Either::Left(right.format().with_options(FormatRBinaryExpressionOptions {
alignment: ChainAlignment::LeftAligned,
}))
}
right => Either::Right(right.format()),
};
write!(f, [indent(&format_args![hard_line_break(), right])])
} else {
write!(f, [space(), right.format()])
}
Expand Down Expand Up @@ -424,6 +473,7 @@ fn fmt_binary_chain(
mut left: AnyRExpression,
operator: SyntaxToken<RLanguage>,
right: AnyRExpression,
alignment: ChainAlignment,
f: &mut Formatter<RFormatContext>,
) -> FormatResult<()> {
// For the lead node in a binary chain, comments are handled by the standard
Expand Down Expand Up @@ -529,9 +579,14 @@ fn fmt_binary_chain(
Ok(())
});

let chain = match alignment {
ChainAlignment::Indented => Either::Left(indent(&chain)),
ChainAlignment::LeftAligned => Either::Right(chain),
};

write!(
f,
[group(&format_args![left.format(), indent(&chain)])
[group(&format_args![left.format(), &chain])
.should_expand(has_persistent_line_break(&tail, f.options()))]
)
}
Expand Down
23 changes: 23 additions & 0 deletions crates/air_r_formatter/tests/specs/r/binary_expression.R
Original file line number Diff line number Diff line change
Expand Up @@ -508,3 +508,26 @@ base_version <-
b_get(brand, "defaults", "shiny", "theme", "version") %||%
b_get(brand, "defaults", "bootstrap", "version") %||%
version_default()


# https://github.com/posit-dev/air/issues/220
data <-
starwars |>
filter(height > 172) |>
select(1:3)

plot <-
ggplot() +
geom_point()

foo <-
1 +
2

foo <-
TRUE ||
FALSE

# Persistent assign-newline and unbroken pipeline
data <-
ggplot() + geom_point()
51 changes: 48 additions & 3 deletions crates/air_r_formatter/tests/specs/r/binary_expression.R.snap
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,29 @@ base_version <-
b_get(brand, "defaults", "bootstrap", "version") %||%
version_default()
# https://github.com/posit-dev/air/issues/220
data <-
starwars |>
filter(height > 172) |>
select(1:3)
plot <-
ggplot() +
geom_point()
foo <-
1 +
2
foo <-
TRUE ||
FALSE
# Persistent assign-newline and unbroken pipeline
data <-
ggplot() + geom_point()
```
Expand Down Expand Up @@ -1161,9 +1184,31 @@ is_condition_true <-
# https://github.com/posit-dev/air/issues/91
base_version <-
version %||%
b_get(brand, "defaults", "shiny", "theme", "version") %||%
b_get(brand, "defaults", "bootstrap", "version") %||%
version_default()
b_get(brand, "defaults", "shiny", "theme", "version") %||%
b_get(brand, "defaults", "bootstrap", "version") %||%
version_default()
# https://github.com/posit-dev/air/issues/220
data <-
starwars |>
filter(height > 172) |>
select(1:3)
plot <-
ggplot() +
geom_point()
foo <-
1 +
2
foo <-
TRUE ||
FALSE
# Persistent assign-newline and unbroken pipeline
data <-
ggplot() + geom_point()
```
# Lines exceeding max width of 80 characters
Expand Down

0 comments on commit 2144caa

Please sign in to comment.