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