Skip to content

Allow breaking after = in named arguments when name = value exceeds line-width - #528

Open
randy3k wants to merge 1 commit into
posit-dev:mainfrom
randy3k:named-arg-break-after-equal
Open

randy3k wants to merge 1 commit into
posit-dev:mainfrom
randy3k:named-arg-break-after-equal

Conversation

@randy3k

@randy3k randy3k commented Sep 25, 2026 •

Copy link
Copy Markdown

Closes #527.

Previously, fmt_argument_fields() always formatted named arguments with an unconditional space() between name = and value:

write!(f, [name_clause.format(), space(), value.format()])

When a named argument has a long name (e.g., backticked stopifnot(`...` = ...) messages) or an expression where name = value exceeds line-width (80 columns), keeping value glued to name = either forces the line past line-width or forces value itself to break across multiple lines even when value would fit flat on the next line.

This PR updates fmt_argument_fields() to:

  1. Preserve space() after = (no break) for:
    • Arguments inside test blocks (test_that() / describe()) to keep test descriptions on the opening line.
    • Braced expressions ({ ... }), function definitions (function(...), \(...)), and control flow statements (if, for, while, repeat).
    • Function calls whose callee name alone exceeds line-width.
  2. For other expressions, wrap the line break after = in a dedicated group (group(&indent(&soft_line_break_or_space()))) and conditionally indent the value (indent_if_group_breaks(&format_value, group_id)).
  3. To prevent expressions from prematurely breaking internally before breaking after =, flatten soft line breaks in format_value (or the <left> <op> prefix for multi-line binary expressions) when the span fits on a single indented line.

Examples

Before

# 1. String literal or atomic value where `name = value` exceeds 80 columns
foo(
  x,
  long_argument_name = "a_very_long_string_value_that_exceeds_eighty_columns_when_on_one_line",
  `allow_null is not supported; please provide a non-null input value` = !allow_null
)

# 2. Named arguments where `name = value` exceeds 80 columns on one line
stopifnot(
  `Provide equal-length x and y vectors, or a single y value` = lengths_match(
    x,
    y
  ) ||
    length(y) == 1L,
  "wait_done should be a logical scalar" = is_scalar_logical(wait_done) &&
    !is.na(wait_done),
  `file_path should be a single file name or an open connection` = is_character_or_connection(
    file_path
  )
)

mutate(
  df,
  branch_exit_fallback_calls = c(
    "warning",
    "warn",
    "message",
    "cat",
    "stopifnot"
  )
)

# 3. Pipe chains where `name = <lhs> |>` exceeds 80 columns
process_data(
  data = starwars |>
    filter(species == "Human") |>
    select(name, height, mass),
  filtered_human_character_subset_from_starwars_dataset = starwars_character_data |>
    filter(species == "Human") |>
    select(name, height, mass)
)

After

# 1. String literal or atomic value breaks cleanly after `=`
foo(
  x,
  long_argument_name =
    "a_very_long_string_value_that_exceeds_eighty_columns_when_on_one_line",
  `allow_null is not supported; please provide a non-null input value` =
    !allow_null
)

# 2. Breaking after `=` keeps `value` flat on a single indented line when it fits
stopifnot(
  `Provide equal-length x and y vectors, or a single y value` =
    lengths_match(x, y) || length(y) == 1L,
  "wait_done should be a logical scalar" =
    is_scalar_logical(wait_done) && !is.na(wait_done),
  `file_path should be a single file name or an open connection` =
    is_character_or_connection(file_path)
)

mutate(
  df,
  branch_exit_fallback_calls =
    c("warning", "warn", "message", "cat", "stopifnot")
)

# 3. Pipe chains break after `=` only when `name = <lhs> |>` exceeds 80 columns
process_data(
  data = starwars |>
    filter(species == "Human") |>
    select(name, height, mass),
  filtered_human_character_subset_from_starwars_dataset =
    starwars_character_data |>
      filter(species == "Human") |>
      select(name, height, mass)
)

Unchanged (preserved on the = line)

# Braced expressions, functions, and control flow stay on the `=` line
foo(
  long_argument_name_that_never_breaks_before_braces = {
    x + 1
  },
  long_argument_name_that_never_breaks_before_anon_fn = function(a, b) a + b,
  long_argument_name_that_never_breaks_before_lambda = \(a, b) a + b
)

# Short argument names with multi-line calls expand their own arguments without breaking after `=`
mutate(
  name = if_else(
    str_sub(name, 1, 3) %in% c("AL0", "AL1"),
    name,
    str_to_title(name)
  )
)

@randy3k
randy3k marked this pull request as ready for review September 25, 2026 03:48
@randy3k
randy3k force-pushed the named-arg-break-after-equal branch from bf16409 to c8b815c Compare September 25, 2026 04:17
@randy3k
randy3k marked this pull request as draft September 25, 2026 04:22
@randy3k
randy3k marked this pull request as ready for review September 25, 2026 04:24
@randy3k
randy3k marked this pull request as draft September 25, 2026 05:31
@randy3k
randy3k force-pushed the named-arg-break-after-equal branch 2 times, most recently from 58811b2 to 9fb6c79 Compare September 25, 2026 14:26
@randy3k
randy3k force-pushed the named-arg-break-after-equal branch from 9fb6c79 to 46a31b5 Compare September 25, 2026 19:15
@randy3k
randy3k marked this pull request as ready for review September 28, 2026 22:39

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow breaking (or preserving line breaks) after '=' in named arguments when 'name = value' exceeds line-width

1 participant