From ad04e49c43b3ddbc588924065158327af5213683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Pinz=C3=B3n=20Eslava?= Date: Thu, 19 Feb 2026 13:23:30 +1100 Subject: [PATCH] Fix ERB expression compilation when code contains a heredoc When an ERB output tag wraps code in parentheses and that code contains a heredoc (e.g. `method_call <<~GRAPHQL, variables`), the closing parenthesis must appear on its own line after the heredoc terminator. Without this, the compiled Ruby is syntactically invalid. Always insert a newline before the closing parenthesis unconditionally, since Ruby ignores trailing newlines in expressions. Includes a snapshot test for the compiled output. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-019c7333-8beb-715a-9183-8434cd5fb15a --- lib/reactionview/template/handlers/herb/herb.rb | 2 +- ...on_compiled_40e3b949384b06dd58f462a17aa0827b.txt | 3 ++- ...ut_compiled_ce838ee50a8780e359b0426fe5b6bfb4.txt | 4 +++- ...es_compiled_ca45577d00c738a24540dfeb3f7885ef.txt | 3 ++- ...ns_compiled_082a93002fba858b87a75785cd88a2b7.txt | 4 +++- ...on_compiled_8fc34c93947921e6cf438e5adfc691d7.txt | 3 ++- ...er_compiled_9d7872dac3cef10a2a63f61404a4b051.txt | 3 ++- ...to_compiled_351531c91827e79a1a9ab2f324c18ce9.txt | 3 ++- ...rs_compiled_3a9949064612857244b6b8987a2b6dc3.txt | 9 ++++++--- ...by_compiled_ffc55de9e7891578a81f0a2b0c1346a5.txt | 8 ++++++++ test/template/handlers/herb_test.rb | 13 +++++++++++++ 11 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 test/snapshots/herb/template_handler_test/test_0029_heredoc_with_trailing_arguments_compiles_to_valid_Ruby_compiled_ffc55de9e7891578a81f0a2b0c1346a5.txt diff --git a/lib/reactionview/template/handlers/herb/herb.rb b/lib/reactionview/template/handlers/herb/herb.rb index d117265..c8f8452 100644 --- a/lib/reactionview/template/handlers/herb/herb.rb +++ b/lib/reactionview/template/handlers/herb/herb.rb @@ -66,7 +66,7 @@ def add_rails_expression(indicator, code, wrap_parentheses:) end if wrap_parentheses - @src << "(" << code << ")" + @src << "(" << code << "\n)" else @src << " " << code end diff --git a/test/snapshots/herb/template_handler_test/test_0003_template_with_expression_compiled_40e3b949384b06dd58f462a17aa0827b.txt b/test/snapshots/herb/template_handler_test/test_0003_template_with_expression_compiled_40e3b949384b06dd58f462a17aa0827b.txt index 3835b27..af46782 100644 --- a/test/snapshots/herb/template_handler_test/test_0003_template_with_expression_compiled_40e3b949384b06dd58f462a17aa0827b.txt +++ b/test/snapshots/herb/template_handler_test/test_0003_template_with_expression_compiled_40e3b949384b06dd58f462a17aa0827b.txt @@ -1,2 +1,3 @@ - @output_buffer.safe_append='

Hello '.freeze; @output_buffer.append=(@name); @output_buffer.safe_append='

'.freeze; + @output_buffer.safe_append='

Hello '.freeze; @output_buffer.append=(@name +); @output_buffer.safe_append='

'.freeze; @output_buffer \ No newline at end of file diff --git a/test/snapshots/herb/template_handler_test/test_0005_raw_and_regular_output_compiled_ce838ee50a8780e359b0426fe5b6bfb4.txt b/test/snapshots/herb/template_handler_test/test_0005_raw_and_regular_output_compiled_ce838ee50a8780e359b0426fe5b6bfb4.txt index 6caf279..5c02636 100644 --- a/test/snapshots/herb/template_handler_test/test_0005_raw_and_regular_output_compiled_ce838ee50a8780e359b0426fe5b6bfb4.txt +++ b/test/snapshots/herb/template_handler_test/test_0005_raw_and_regular_output_compiled_ce838ee50a8780e359b0426fe5b6bfb4.txt @@ -1,2 +1,4 @@ - @output_buffer.append=(@html); @output_buffer.append=(raw @safe_html); + @output_buffer.append=(@html +); @output_buffer.append=(raw @safe_html +); @output_buffer \ No newline at end of file diff --git a/test/snapshots/herb/template_handler_test/test_0006_template_with_newlines_compiled_ca45577d00c738a24540dfeb3f7885ef.txt b/test/snapshots/herb/template_handler_test/test_0006_template_with_newlines_compiled_ca45577d00c738a24540dfeb3f7885ef.txt index 32fd006..dc3a160 100644 --- a/test/snapshots/herb/template_handler_test/test_0006_template_with_newlines_compiled_ca45577d00c738a24540dfeb3f7885ef.txt +++ b/test/snapshots/herb/template_handler_test/test_0006_template_with_newlines_compiled_ca45577d00c738a24540dfeb3f7885ef.txt @@ -1,4 +1,5 @@ @output_buffer.safe_append='
-'.freeze; @output_buffer.append=(@content); @output_buffer.safe_append=' +'.freeze; @output_buffer.append=(@content +); @output_buffer.safe_append='
'.freeze; @output_buffer \ No newline at end of file diff --git a/test/snapshots/herb/template_handler_test/test_0007_multiple_expressions_compiled_082a93002fba858b87a75785cd88a2b7.txt b/test/snapshots/herb/template_handler_test/test_0007_multiple_expressions_compiled_082a93002fba858b87a75785cd88a2b7.txt index f1afa87..5b2fc28 100644 --- a/test/snapshots/herb/template_handler_test/test_0007_multiple_expressions_compiled_082a93002fba858b87a75785cd88a2b7.txt +++ b/test/snapshots/herb/template_handler_test/test_0007_multiple_expressions_compiled_082a93002fba858b87a75785cd88a2b7.txt @@ -1,2 +1,4 @@ - @output_buffer.safe_append='

Hello '.freeze; @output_buffer.append=(@name); @output_buffer.safe_append='!

'.freeze; @output_buffer.append=(@message); @output_buffer.safe_append='

'.freeze; + @output_buffer.safe_append='

Hello '.freeze; @output_buffer.append=(@name +); @output_buffer.safe_append='!

'.freeze; @output_buffer.append=(@message +); @output_buffer.safe_append='

'.freeze; @output_buffer \ No newline at end of file diff --git a/test/snapshots/herb/template_handler_test/test_0008_xss_protection_compiled_8fc34c93947921e6cf438e5adfc691d7.txt b/test/snapshots/herb/template_handler_test/test_0008_xss_protection_compiled_8fc34c93947921e6cf438e5adfc691d7.txt index 0b73dcd..5c2e409 100644 --- a/test/snapshots/herb/template_handler_test/test_0008_xss_protection_compiled_8fc34c93947921e6cf438e5adfc691d7.txt +++ b/test/snapshots/herb/template_handler_test/test_0008_xss_protection_compiled_8fc34c93947921e6cf438e5adfc691d7.txt @@ -1,2 +1,3 @@ - @output_buffer.safe_append='
'.freeze; @output_buffer.append=(@unsafe_content); @output_buffer.safe_append='
'.freeze; + @output_buffer.safe_append='
'.freeze; @output_buffer.append=(@unsafe_content +); @output_buffer.safe_append='
'.freeze; @output_buffer \ No newline at end of file diff --git a/test/snapshots/herb/template_handler_test/test_0009_content_tag_helper_compiled_9d7872dac3cef10a2a63f61404a4b051.txt b/test/snapshots/herb/template_handler_test/test_0009_content_tag_helper_compiled_9d7872dac3cef10a2a63f61404a4b051.txt index e819f9c..1a0f57f 100644 --- a/test/snapshots/herb/template_handler_test/test_0009_content_tag_helper_compiled_9d7872dac3cef10a2a63f61404a4b051.txt +++ b/test/snapshots/herb/template_handler_test/test_0009_content_tag_helper_compiled_9d7872dac3cef10a2a63f61404a4b051.txt @@ -1,2 +1,3 @@ - @output_buffer.append=(content_tag :div, "Hello", class: "greeting"); + @output_buffer.append=(content_tag :div, "Hello", class: "greeting" +); @output_buffer \ No newline at end of file diff --git a/test/snapshots/herb/template_handler_test/test_0010_user_card_with_conditional_and_link_to_compiled_351531c91827e79a1a9ab2f324c18ce9.txt b/test/snapshots/herb/template_handler_test/test_0010_user_card_with_conditional_and_link_to_compiled_351531c91827e79a1a9ab2f324c18ce9.txt index d517164..7ef0940 100644 --- a/test/snapshots/herb/template_handler_test/test_0010_user_card_with_conditional_and_link_to_compiled_351531c91827e79a1a9ab2f324c18ce9.txt +++ b/test/snapshots/herb/template_handler_test/test_0010_user_card_with_conditional_and_link_to_compiled_351531c91827e79a1a9ab2f324c18ce9.txt @@ -1,5 +1,6 @@ @output_buffer.safe_append='
-

'.freeze; @output_buffer.append=(@user[:name]); @output_buffer.safe_append='

+

'.freeze; @output_buffer.append=(@user[:name] +); @output_buffer.safe_append='

'.freeze; if @user[:verified] @output_buffer.safe_append=' Verified '.freeze; end diff --git a/test/snapshots/herb/template_handler_test/test_0011_complex_layout_with_helpers_compiled_3a9949064612857244b6b8987a2b6dc3.txt b/test/snapshots/herb/template_handler_test/test_0011_complex_layout_with_helpers_compiled_3a9949064612857244b6b8987a2b6dc3.txt index 80b6784..0cae96f 100644 --- a/test/snapshots/herb/template_handler_test/test_0011_complex_layout_with_helpers_compiled_3a9949064612857244b6b8987a2b6dc3.txt +++ b/test/snapshots/herb/template_handler_test/test_0011_complex_layout_with_helpers_compiled_3a9949064612857244b6b8987a2b6dc3.txt @@ -1,13 +1,16 @@ @output_buffer.safe_append='
-

'.freeze; @output_buffer.append=(title "Events by Country"); @output_buffer.safe_append='

+

'.freeze; @output_buffer.append=(title "Events by Country" +); @output_buffer.safe_append='

- '.freeze; @output_buffer.append=(ui_button "View all cities", url: cities_path, kind: :secondary); @output_buffer.safe_append=' + '.freeze; @output_buffer.append=(ui_button "View all cities", url: cities_path, kind: :secondary +); @output_buffer.safe_append=' '.freeze; if @show_countries @output_buffer.safe_append='

Countries

'.freeze; @output_buffer.append= link_to country_path("switzerland"), id: "country-ch", class: "event-item" do; @output_buffer.safe_append=' 🇨🇭 Switzerland - '.freeze; @output_buffer.append=(ui_badge(5, kind: :secondary, class: "event-count")); @output_buffer.safe_append=' + '.freeze; @output_buffer.append=(ui_badge(5, kind: :secondary, class: "event-count") +); @output_buffer.safe_append=' '.freeze; end end @output_buffer.safe_append='
diff --git a/test/snapshots/herb/template_handler_test/test_0029_heredoc_with_trailing_arguments_compiles_to_valid_Ruby_compiled_ffc55de9e7891578a81f0a2b0c1346a5.txt b/test/snapshots/herb/template_handler_test/test_0029_heredoc_with_trailing_arguments_compiles_to_valid_Ruby_compiled_ffc55de9e7891578a81f0a2b0c1346a5.txt new file mode 100644 index 0000000..12e6535 --- /dev/null +++ b/test/snapshots/herb/template_handler_test/test_0029_heredoc_with_trailing_arguments_compiles_to_valid_Ruby_compiled_ffc55de9e7891578a81f0a2b0c1346a5.txt @@ -0,0 +1,8 @@ + @output_buffer.append=(method_call <<~GRAPHQL, variables + query { + field + } +GRAPHQL +); + @output_buffer.safe_append=' +'.freeze;@output_buffer \ No newline at end of file diff --git a/test/template/handlers/herb_test.rb b/test/template/handlers/herb_test.rb index 0b973f3..b6a3149 100644 --- a/test/template/handlers/herb_test.rb +++ b/test/template/handlers/herb_test.rb @@ -342,4 +342,17 @@ def @view_context.ui_badge(count, **_options) assert_compiled_snapshot(template) assert_evaluated_snapshot(template, ivars: { config: { key: "value" } }) end + + test "heredoc with trailing arguments compiles to valid Ruby" do + template = <<~ERB + <%= method_call <<~GRAPHQL, variables + query { + field + } + GRAPHQL + %> + ERB + + assert_compiled_snapshot(template) + end end