From 9085aa537c56731d7fd1d25ca158f25e7adad4b6 Mon Sep 17 00:00:00 2001 From: Wleeaf <179125857+wleeaf@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:36:32 +0000 Subject: [PATCH] [fix] Keep parens on `raise` calls ending in an omitted hash value `raise(Error, cause:)` was formatted as `raise Error, cause:`. Without parens, Ruby takes whatever follows an omitted value as that value, so the next line became the `cause:` argument, and a trailing modifier (`raise Error, cause: if x`) became a syntax error. --- fixtures/small/raise_omitted_hash_value_actual.rb | 10 ++++++++++ fixtures/small/raise_omitted_hash_value_expected.rb | 10 ++++++++++ librubyfmt/src/format_prism.rs | 13 +++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 fixtures/small/raise_omitted_hash_value_actual.rb create mode 100644 fixtures/small/raise_omitted_hash_value_expected.rb diff --git a/fixtures/small/raise_omitted_hash_value_actual.rb b/fixtures/small/raise_omitted_hash_value_actual.rb new file mode 100644 index 00000000..39a9396f --- /dev/null +++ b/fixtures/small/raise_omitted_hash_value_actual.rb @@ -0,0 +1,10 @@ +def boom(cause) + raise(ArgumentError, "message", cause:) +end + +def retry_with(cause) + raise(RetryError, cause:) + cleanup +end + +raise(Error, cause:) if cause diff --git a/fixtures/small/raise_omitted_hash_value_expected.rb b/fixtures/small/raise_omitted_hash_value_expected.rb new file mode 100644 index 00000000..39a9396f --- /dev/null +++ b/fixtures/small/raise_omitted_hash_value_expected.rb @@ -0,0 +1,10 @@ +def boom(cause) + raise(ArgumentError, "message", cause:) +end + +def retry_with(cause) + raise(RetryError, cause:) + cleanup +end + +raise(Error, cause:) if cause diff --git a/librubyfmt/src/format_prism.rs b/librubyfmt/src/format_prism.rs index b4bf7ee7..d70aa43e 100644 --- a/librubyfmt/src/format_prism.rs +++ b/librubyfmt/src/format_prism.rs @@ -1766,6 +1766,19 @@ fn use_parens_for_call_node<'src>( return true; } + // Without parens, an omitted value at the end of the arguments + // (`raise Foo, cause:`) would take whatever comes next as its value + if call_node + .arguments() + .and_then(|arguments| arguments.arguments().last()) + .and_then(|arg| arg.as_keyword_hash_node()) + .and_then(|hash| hash.elements().last()) + .and_then(|element| element.as_assoc_node()) + .is_some_and(|assoc| assoc.value().as_implicit_node().is_some()) + { + return true; + } + if let Some(arguments) = call_node.arguments() && arguments.arguments().iter().any(|arg| { arg.as_splat_node().is_some() || arg.as_forwarding_arguments_node().is_some()