diff --git a/lib/herb/engine.rb b/lib/herb/engine.rb
index a6fd4b494..20ee95d27 100644
--- a/lib/herb/engine.rb
+++ b/lib/herb/engine.rb
@@ -175,6 +175,14 @@ def self.css(value)
end
end
+ def self.comment?(code)
+ code.include?("#")
+ end
+
+ def self.heredoc?(code)
+ code.match?(/<<[~-]?\s*['"`]?\w/)
+ end
+
protected
def add_text(text)
@@ -196,8 +204,8 @@ def add_code(code)
@src << " " << code
# TODO: rework and check for Prism::InlineComment as soon as we expose the Prism Nodes in the Herb AST
- if code.include?("#")
- @src << "\n"
+ if self.class.comment?(code) || self.class.heredoc?(code)
+ @src << "\n" unless code[-1] == "\n"
else
@src << ";" unless code[-1] == "\n"
end
@@ -267,20 +275,12 @@ def add_expression_block_end(code, escaped: false)
end
def trailing_newline(code)
- return "\n" if comment?(code)
- return "\n" if heredoc?(code)
+ return "\n" if self.class.comment?(code)
+ return "\n" if self.class.heredoc?(code)
""
end
- def comment?(code)
- code.include?("#")
- end
-
- def heredoc?(code)
- code.match?(/<<[~-]?\s*['"`]?\w/)
- end
-
def add_postamble(postamble)
terminate_expression
@src << postamble
diff --git a/lib/herb/engine/compiler.rb b/lib/herb/engine/compiler.rb
index 26c18fac0..818f96a3b 100644
--- a/lib/herb/engine/compiler.rb
+++ b/lib/herb/engine/compiler.rb
@@ -545,7 +545,7 @@ def apply_trim(node, code)
if at_line_start?
lspace = extract_and_remove_lspace!
- rspace = " \n"
+ rspace = Herb::Engine.heredoc?(code) ? "\n" : " \n"
@tokens << [:code, "#{lspace}#{code}#{rspace}", current_context]
@trim_next_whitespace = true
diff --git a/sig/herb/engine.rbs b/sig/herb/engine.rbs
index 2d8226d5c..3ac59a7ed 100644
--- a/sig/herb/engine.rbs
+++ b/sig/herb/engine.rbs
@@ -35,6 +35,10 @@ module Herb
def self.css: (untyped value) -> untyped
+ def self.comment?: (untyped code) -> untyped
+
+ def self.heredoc?: (untyped code) -> untyped
+
def add_text: (untyped text) -> untyped
def add_code: (untyped code) -> untyped
@@ -55,10 +59,6 @@ module Herb
def trailing_newline: (untyped code) -> untyped
- def comment?: (untyped code) -> untyped
-
- def heredoc?: (untyped code) -> untyped
-
def add_postamble: (untyped postamble) -> untyped
def with_buffer: () ?{ (?) -> untyped } -> untyped
diff --git a/test/engine/engine_test.rb b/test/engine/engine_test.rb
index 48af2239d..71978c3f4 100644
--- a/test/engine/engine_test.rb
+++ b/test/engine/engine_test.rb
@@ -177,5 +177,65 @@ class EngineTest < Minitest::Spec
assert_compiled_snapshot(template)
end
+
+ test "heredoc in code tag compiles to valid Ruby" do
+ template = <<~ERB
+ <%
+ text = <<~TEXT
+ Hello, world!
+ TEXT
+ %>
+ ERB
+
+ assert_compiled_snapshot(template)
+ end
+
+ test "heredoc in code tag inline compiles to valid Ruby" do
+ template = <<~ERB
+
<% text = <<~TEXT
+ Hello, world!
+ TEXT
+ %>
+ ERB
+
+ assert_compiled_snapshot(template)
+ end
+
+ test "heredoc with dash syntax in code tag compiles to valid Ruby" do
+ template = <<~ERB
+ <%
+ text = <<-TEXT
+ Hello, world!
+ TEXT
+ %>
+ ERB
+
+ assert_compiled_snapshot(template)
+ end
+
+ test "heredoc with quoted identifier in code tag compiles to valid Ruby" do
+ template = <<~ERB
+ <%
+ text = <<~'TEXT'
+ Hello, world!
+ TEXT
+ %>
+ ERB
+
+ assert_compiled_snapshot(template)
+ end
+
+ test "heredoc in escaped expression tag compiles to valid Ruby" do
+ template = <<~ERB
+ <%== method_call <<~GRAPHQL, variables
+ query {
+ field
+ }
+ GRAPHQL
+ %>
+ ERB
+
+ assert_compiled_snapshot(template)
+ end
end
end
diff --git a/test/engine/evaluation_test.rb b/test/engine/evaluation_test.rb
index d5a4c858b..ed75e1391 100644
--- a/test/engine/evaluation_test.rb
+++ b/test/engine/evaluation_test.rb
@@ -484,5 +484,18 @@ class EvaluationTest < Minitest::Spec
assert_evaluated_snapshot(template, { some_condition: false }, { escape: false })
end
+
+ test "heredoc in code tag" do
+ template = <<~ERB
+ <%
+ text = <<~TEXT
+ Hello, world!
+ TEXT
+ %>
+ <%= text %>
+ ERB
+
+ assert_evaluated_snapshot(template, {}, { escape: false })
+ end
end
end
diff --git a/test/parser/erb_test.rb b/test/parser/erb_test.rb
index 746ed5e8d..15c5833f5 100644
--- a/test/parser/erb_test.rb
+++ b/test/parser/erb_test.rb
@@ -380,5 +380,26 @@ class ERBTest < Minitest::Spec
<% end %>
HTML
end
+
+ test "heredoc in code tag" do
+ assert_parsed_snapshot(<<~HTML)
+ <%
+ text = <<~TEXT
+ Hello, world!
+ TEXT
+ %>
+ HTML
+ end
+
+ test "heredoc in output tag" do
+ assert_parsed_snapshot(<<~HTML)
+ <%= method_call <<~GRAPHQL, variables
+ query {
+ field
+ }
+ GRAPHQL
+ %>
+ HTML
+ end
end
end
diff --git a/test/snapshots/engine/engine_test/test_0019_heredoc_in_code_tag_compiles_to_valid_Ruby_4af6472f81c8026291897a7a34e6ae0b.txt b/test/snapshots/engine/engine_test/test_0019_heredoc_in_code_tag_compiles_to_valid_Ruby_4af6472f81c8026291897a7a34e6ae0b.txt
new file mode 100644
index 000000000..39be3c687
--- /dev/null
+++ b/test/snapshots/engine/engine_test/test_0019_heredoc_in_code_tag_compiles_to_valid_Ruby_4af6472f81c8026291897a7a34e6ae0b.txt
@@ -0,0 +1,8 @@
+---
+source: "Engine::EngineTest#test_0019_heredoc in code tag compiles to valid Ruby"
+input: "{source: \"<%\\n text = <<~TEXT\\n Hello, world!\\n TEXT\\n%>\\n\", options: {}}"
+---
+_buf = ::String.new; text = <<~TEXT
+ Hello, world!
+ TEXT
+_buf.to_s
diff --git a/test/snapshots/engine/engine_test/test_0020_heredoc_in_code_tag_inline_compiles_to_valid_Ruby_8767b3a12f6b010f26d6c31577e64603.txt b/test/snapshots/engine/engine_test/test_0020_heredoc_in_code_tag_inline_compiles_to_valid_Ruby_8767b3a12f6b010f26d6c31577e64603.txt
new file mode 100644
index 000000000..3bf98a3f8
--- /dev/null
+++ b/test/snapshots/engine/engine_test/test_0020_heredoc_in_code_tag_inline_compiles_to_valid_Ruby_8767b3a12f6b010f26d6c31577e64603.txt
@@ -0,0 +1,10 @@
+---
+source: "Engine::EngineTest#test_0020_heredoc in code tag inline compiles to valid Ruby"
+input: "{source: \"<% text = <<~TEXT\\n Hello, world!\\n TEXT\\n%>
\\n\", options: {}}"
+---
+_buf = ::String.new; _buf << ''.freeze; text = <<~TEXT
+ Hello, world!
+ TEXT
+ _buf << '
+'.freeze;
+_buf.to_s
diff --git a/test/snapshots/engine/engine_test/test_0021_heredoc_with_dash_syntax_in_code_tag_compiles_to_valid_Ruby_61fc267bae73b15dae254d0d5f9fbe0e.txt b/test/snapshots/engine/engine_test/test_0021_heredoc_with_dash_syntax_in_code_tag_compiles_to_valid_Ruby_61fc267bae73b15dae254d0d5f9fbe0e.txt
new file mode 100644
index 000000000..00793dd4b
--- /dev/null
+++ b/test/snapshots/engine/engine_test/test_0021_heredoc_with_dash_syntax_in_code_tag_compiles_to_valid_Ruby_61fc267bae73b15dae254d0d5f9fbe0e.txt
@@ -0,0 +1,8 @@
+---
+source: "Engine::EngineTest#test_0021_heredoc with dash syntax in code tag compiles to valid Ruby"
+input: "{source: \"<%\\n text = <<-TEXT\\n Hello, world!\\n TEXT\\n%>\\n\", options: {}}"
+---
+_buf = ::String.new; text = <<-TEXT
+ Hello, world!
+ TEXT
+_buf.to_s
diff --git a/test/snapshots/engine/engine_test/test_0022_heredoc_with_quoted_identifier_in_code_tag_compiles_to_valid_Ruby_c596f2621d8ae2223c3805e78ff1b3e3.txt b/test/snapshots/engine/engine_test/test_0022_heredoc_with_quoted_identifier_in_code_tag_compiles_to_valid_Ruby_c596f2621d8ae2223c3805e78ff1b3e3.txt
new file mode 100644
index 000000000..c6c198568
--- /dev/null
+++ b/test/snapshots/engine/engine_test/test_0022_heredoc_with_quoted_identifier_in_code_tag_compiles_to_valid_Ruby_c596f2621d8ae2223c3805e78ff1b3e3.txt
@@ -0,0 +1,8 @@
+---
+source: "Engine::EngineTest#test_0022_heredoc with quoted identifier in code tag compiles to valid Ruby"
+input: "{source: \"<%\\n text = <<~'TEXT'\\n Hello, world!\\n TEXT\\n%>\\n\", options: {}}"
+---
+_buf = ::String.new; text = <<~'TEXT'
+ Hello, world!
+ TEXT
+_buf.to_s
diff --git a/test/snapshots/engine/engine_test/test_0023_heredoc_in_escaped_expression_tag_compiles_to_valid_Ruby_09b879fd48c9c55790c1b75c4a20b8a8.txt b/test/snapshots/engine/engine_test/test_0023_heredoc_in_escaped_expression_tag_compiles_to_valid_Ruby_09b879fd48c9c55790c1b75c4a20b8a8.txt
new file mode 100644
index 000000000..787b3d044
--- /dev/null
+++ b/test/snapshots/engine/engine_test/test_0023_heredoc_in_escaped_expression_tag_compiles_to_valid_Ruby_09b879fd48c9c55790c1b75c4a20b8a8.txt
@@ -0,0 +1,12 @@
+---
+source: "Engine::EngineTest#test_0023_heredoc in escaped expression tag compiles to valid Ruby"
+input: "{source: \"<%== method_call <<~GRAPHQL, variables\\n query {\\n field\\n }\\nGRAPHQL\\n%>\\n\", options: {}}"
+---
+_buf = ::String.new; _buf << ::Herb::Engine.h((method_call <<~GRAPHQL, variables
+ query {
+ field
+ }
+GRAPHQL
+)); _buf << '
+'.freeze;
+_buf.to_s
diff --git a/test/snapshots/engine/erb_comments_test/test_0006_inline_ruby_comment_multiline_1dc1e842d2fb5a2484c2e2fa0eca3678.txt b/test/snapshots/engine/erb_comments_test/test_0006_inline_ruby_comment_multiline_1dc1e842d2fb5a2484c2e2fa0eca3678.txt
index fe71318c3..8dec465c2 100644
--- a/test/snapshots/engine/erb_comments_test/test_0006_inline_ruby_comment_multiline_1dc1e842d2fb5a2484c2e2fa0eca3678.txt
+++ b/test/snapshots/engine/erb_comments_test/test_0006_inline_ruby_comment_multiline_1dc1e842d2fb5a2484c2e2fa0eca3678.txt
@@ -4,7 +4,6 @@ input: "{source: \"<% # Comment\\nmore %> <% code = \\\"test\\\" %><%= code %>\"
---
_buf = ::String.new; # Comment
more
-
code = "test"
_buf << (code).to_s;
_buf.to_s
diff --git a/test/snapshots/engine/evaluation_test/test_0039_heredoc_in_code_tag_89436ef132cfca5077afe91beb8082b8.txt b/test/snapshots/engine/evaluation_test/test_0039_heredoc_in_code_tag_89436ef132cfca5077afe91beb8082b8.txt
new file mode 100644
index 000000000..fed1e294b
--- /dev/null
+++ b/test/snapshots/engine/evaluation_test/test_0039_heredoc_in_code_tag_89436ef132cfca5077afe91beb8082b8.txt
@@ -0,0 +1,6 @@
+---
+source: "Engine::EvaluationTest#test_0039_heredoc in code tag"
+input: "{source: \"<%\\n text = <<~TEXT\\n Hello, world!\\n TEXT\\n%>\\n<%= text %>\\n\", locals: {}, options: {escape: false}}"
+---
+Hello, world!
+
diff --git a/test/snapshots/parser/erb_test/test_0058_heredoc_in_code_tag_21fe6ee3694113c3bbf33a9aa83fa2e0.txt b/test/snapshots/parser/erb_test/test_0058_heredoc_in_code_tag_21fe6ee3694113c3bbf33a9aa83fa2e0.txt
new file mode 100644
index 000000000..b6c0579fd
--- /dev/null
+++ b/test/snapshots/parser/erb_test/test_0058_heredoc_in_code_tag_21fe6ee3694113c3bbf33a9aa83fa2e0.txt
@@ -0,0 +1,24 @@
+---
+source: "Parser::ERBTest#test_0058_heredoc in code tag"
+input: |2-
+<%
+ text = <<~TEXT
+ Hello, world!
+ TEXT
+%>
+---
+@ DocumentNode (location: (1:0)-(6:0))
+└── children: (2 items)
+ ├── @ ERBContentNode (location: (1:0)-(5:2))
+ │ ├── tag_opening: "<%" (location: (1:0)-(1:2))
+ │ ├── content: "
+ │ text = <<~TEXT
+ │ Hello, world!
+ │ TEXT
+ │ " (location: (1:2)-(5:0))
+ │ ├── tag_closing: "%>" (location: (5:0)-(5:2))
+ │ ├── parsed: true
+ │ └── valid: true
+ │
+ └── @ HTMLTextNode (location: (5:2)-(6:0))
+ └── content: "\n"
\ No newline at end of file
diff --git a/test/snapshots/parser/erb_test/test_0059_heredoc_in_output_tag_6f60ffbbed8f2a6641b04234d3e7b51d.txt b/test/snapshots/parser/erb_test/test_0059_heredoc_in_output_tag_6f60ffbbed8f2a6641b04234d3e7b51d.txt
new file mode 100644
index 000000000..fea4477c3
--- /dev/null
+++ b/test/snapshots/parser/erb_test/test_0059_heredoc_in_output_tag_6f60ffbbed8f2a6641b04234d3e7b51d.txt
@@ -0,0 +1,26 @@
+---
+source: "Parser::ERBTest#test_0059_heredoc in output tag"
+input: |2-
+<%= method_call <<~GRAPHQL, variables
+ query {
+ field
+ }
+GRAPHQL
+%>
+---
+@ DocumentNode (location: (1:0)-(7:0))
+└── children: (2 items)
+ ├── @ ERBContentNode (location: (1:0)-(6:2))
+ │ ├── tag_opening: "<%=" (location: (1:0)-(1:3))
+ │ ├── content: " method_call <<~GRAPHQL, variables
+ │ query {
+ │ field
+ │ }
+ │ GRAPHQL
+ │ " (location: (1:3)-(6:0))
+ │ ├── tag_closing: "%>" (location: (6:0)-(6:2))
+ │ ├── parsed: true
+ │ └── valid: true
+ │
+ └── @ HTMLTextNode (location: (6:2)-(7:0))
+ └── content: "\n"
\ No newline at end of file