From 5bd3758d1dd809a6c8567556ab3c835f5ced3efd Mon Sep 17 00:00:00 2001 From: Marco Roth Date: Sat, 28 Feb 2026 02:13:25 +0100 Subject: [PATCH 1/2] Parser: Introduce `StrayERBClosingTagError` --- config.yml | 9 +++ src/parser.c | 51 ++++++++++++ test/parser/stray_erb_closing_tag_test.rb | 81 +++++++++++++++++++ ...miter_15e84c87986bd8a2aba06dcee2be46b8.txt | 4 + ...mment_6056f70bdc685fbeb3e960f8bff735c2.txt | 4 + ...c68be-08996694f7ff1e6e34277dc32f646630.txt | 49 +++++++++++ ...c68be-2ffd01e26b8c99a6c7a6dad67c9489dc.txt | 49 +++++++++++ ...7b518-08996694f7ff1e6e34277dc32f646630.txt | 49 +++++++++++ ...7b518-2ffd01e26b8c99a6c7a6dad67c9489dc.txt | 49 +++++++++++ ...57719-08996694f7ff1e6e34277dc32f646630.txt | 25 ++++++ ...57719-2ffd01e26b8c99a6c7a6dad67c9489dc.txt | 25 ++++++ ...b3d28-08996694f7ff1e6e34277dc32f646630.txt | 14 ++++ ...b3d28-2ffd01e26b8c99a6c7a6dad67c9489dc.txt | 10 +++ ...b6604-08996694f7ff1e6e34277dc32f646630.txt | 21 +++++ ...b6604-2ffd01e26b8c99a6c7a6dad67c9489dc.txt | 17 ++++ ...80a8c-08996694f7ff1e6e34277dc32f646630.txt | 54 +++++++++++++ ...80a8c-2ffd01e26b8c99a6c7a6dad67c9489dc.txt | 54 +++++++++++++ ...77372-08996694f7ff1e6e34277dc32f646630.txt | 31 +++++++ ...77372-2ffd01e26b8c99a6c7a6dad67c9489dc.txt | 31 +++++++ ...d6df6-08996694f7ff1e6e34277dc32f646630.txt | 78 ++++++++++++++++++ ...d6df6-2ffd01e26b8c99a6c7a6dad67c9489dc.txt | 78 ++++++++++++++++++ 21 files changed, 783 insertions(+) create mode 100644 test/parser/stray_erb_closing_tag_test.rb create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0001_stray_%gt_in_open_tag_after_unquoted_ERB_attribute_value_c4a5f2fd004a3774105147a3180c68be-08996694f7ff1e6e34277dc32f646630.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0001_stray_%gt_in_open_tag_after_unquoted_ERB_attribute_value_c4a5f2fd004a3774105147a3180c68be-2ffd01e26b8c99a6c7a6dad67c9489dc.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0002_stray_%gt_in_open_tag_after_quoted_ERB_attribute_value_48f9d68f5fe0c3029c21c387e097b518-08996694f7ff1e6e34277dc32f646630.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0002_stray_%gt_in_open_tag_after_quoted_ERB_attribute_value_48f9d68f5fe0c3029c21c387e097b518-2ffd01e26b8c99a6c7a6dad67c9489dc.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0003_stray_%gt_in_open_tag_with_no_preceding_ERB_tag_1c94281d4272f5032b86a728f7757719-08996694f7ff1e6e34277dc32f646630.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0003_stray_%gt_in_open_tag_with_no_preceding_ERB_tag_1c94281d4272f5032b86a728f7757719-2ffd01e26b8c99a6c7a6dad67c9489dc.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0004_stray_%gt_at_document_top_level_cf8a5e0697cfd68db29b6798aa2b3d28-08996694f7ff1e6e34277dc32f646630.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0004_stray_%gt_at_document_top_level_cf8a5e0697cfd68db29b6798aa2b3d28-2ffd01e26b8c99a6c7a6dad67c9489dc.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0005_stray_%gt_at_document_top_level_after_ERB_tag_c0f67e970f7eec10b23cb091cccb6604-08996694f7ff1e6e34277dc32f646630.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0005_stray_%gt_at_document_top_level_after_ERB_tag_c0f67e970f7eec10b23cb091cccb6604-2ffd01e26b8c99a6c7a6dad67c9489dc.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0006_%gt_inside_quoted_attribute_value_is_not_stray_234130472b744d5f4da241526fe80a8c-08996694f7ff1e6e34277dc32f646630.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0006_%gt_inside_quoted_attribute_value_is_not_stray_234130472b744d5f4da241526fe80a8c-2ffd01e26b8c99a6c7a6dad67c9489dc.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0007_multiple_stray_%gt_in_open_tag_51d400041b3a5fc89d4c94c729e77372-08996694f7ff1e6e34277dc32f646630.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0007_multiple_stray_%gt_in_open_tag_51d400041b3a5fc89d4c94c729e77372-2ffd01e26b8c99a6c7a6dad67c9489dc.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0008_stray_%gt_between_attributes_in_open_tag_ff209340c28c63631300fd22c87d6df6-08996694f7ff1e6e34277dc32f646630.txt create mode 100644 test/snapshots/parser/stray_erb_closing_tag_test/test_0008_stray_%gt_between_attributes_in_open_tag_ff209340c28c63631300fd22c87d6df6-2ffd01e26b8c99a6c7a6dad67c9489dc.txt diff --git a/config.yml b/config.yml index d7a1ce619..38db0171f 100644 --- a/config.yml +++ b/config.yml @@ -307,6 +307,15 @@ errors: - name: opening_tag type: token + - name: StrayERBClosingTagError + message: + template: "Stray `%%>` found at (%u:%u). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%%>`, use the HTML entities `%>` instead." + arguments: + - start.line + - start.column + + fields: [] + - name: NestedERBTagError message: template: "ERB tag `%s` at (%u:%u) was terminated by nested `<%%` tag at (%zu:%zu). Nesting `<%%` tags is not supported." diff --git a/src/parser.c b/src/parser.c index 311bce04d..d65c2e93c 100644 --- a/src/parser.c +++ b/src/parser.c @@ -275,6 +275,31 @@ static AST_HTML_TEXT_NODE_T* parser_parse_text_content(parser_T* parser, hb_arra return NULL; } + if (parser->options.strict && parser->current_token->type == TOKEN_PERCENT) { + lexer_T lexer_copy = *parser->lexer; + token_T* peek_token = lexer_next_token(&lexer_copy); + + if (peek_token && peek_token->type == TOKEN_HTML_TAG_END) { + position_T stray_start = parser->current_token->location.start; + position_T stray_end = peek_token->location.end; + token_free(peek_token); + + append_strayerb_closing_tag_error(stray_start, stray_end, document_errors); + + token_T* percent = parser_advance(parser); + hb_buffer_append(&content, percent->value); + token_free(percent); + + token_T* gt = parser_advance(parser); + hb_buffer_append(&content, gt->value); + token_free(gt); + + continue; + } + + token_free(peek_token); + } + token_T* token = parser_advance(parser); hb_buffer_append(&content, token->value); token_free(token); @@ -1004,6 +1029,32 @@ static AST_HTML_OPEN_TAG_NODE_T* parser_parse_html_open_tag(parser_T* parser) { token_free(next_token); } + if (parser->current_token->type == TOKEN_PERCENT) { + lexer_T lexer_copy = *parser->lexer; + token_T* peek_token = lexer_next_token(&lexer_copy); + + if (peek_token && peek_token->type == TOKEN_HTML_TAG_END) { + position_T stray_start = parser->current_token->location.start; + position_T stray_end = peek_token->location.end; + token_free(peek_token); + + append_strayerb_closing_tag_error(stray_start, stray_end, errors); + + token_T* percent = parser_advance(parser); + token_T* gt = parser_advance(parser); + + AST_LITERAL_NODE_T* literal = ast_literal_node_init("%>", stray_start, stray_end, NULL); + hb_array_append(children, literal); + + token_free(percent); + token_free(gt); + + continue; + } + + token_free(peek_token); + } + parser_append_unexpected_error( parser, "Unexpected Token", diff --git a/test/parser/stray_erb_closing_tag_test.rb b/test/parser/stray_erb_closing_tag_test.rb new file mode 100644 index 000000000..af7180c93 --- /dev/null +++ b/test/parser/stray_erb_closing_tag_test.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module Parser + class StrayERBClosingTagTest < Minitest::Spec + include SnapshotUtils + + test "stray %> in open tag after unquoted ERB attribute value" do + template = <<~HTML +
%> + HTML + + assert_parsed_snapshot(template, strict: true) + assert_parsed_snapshot(template, strict: false) + end + + test "stray %> in open tag after quoted ERB attribute value" do + template = <<~HTML +
+ HTML + + assert_parsed_snapshot(template, strict: true) + assert_parsed_snapshot(template, strict: false) + end + + test "stray %> in open tag with no preceding ERB tag" do + template = <<~HTML +
+ HTML + + assert_parsed_snapshot(template, strict: true) + assert_parsed_snapshot(template, strict: false) + end + + test "stray %> at document top level" do + template = <<~HTML + hello %> world + HTML + + assert_parsed_snapshot(template, strict: true) + assert_parsed_snapshot(template, strict: false) + end + + test "stray %> at document top level after ERB tag" do + template = <<~HTML + <% content %> %> + HTML + + assert_parsed_snapshot(template, strict: true) + assert_parsed_snapshot(template, strict: false) + end + + test "%> inside quoted attribute value is not stray" do + template = <<~HTML +
hello
+ HTML + + assert_parsed_snapshot(template, strict: true) + assert_parsed_snapshot(template, strict: false) + end + + test "multiple stray %> in open tag" do + template = <<~HTML +
%> + HTML + + assert_parsed_snapshot(template, strict: true) + assert_parsed_snapshot(template, strict: false) + end + + test "stray %> between attributes in open tag" do + template = <<~HTML +
id="bar">
+ HTML + + assert_parsed_snapshot(template, strict: true) + assert_parsed_snapshot(template, strict: false) + end + end +end diff --git a/test/snapshots/parser/erb_test/test_0038_erb_tag_followed_by_literal_closing_delimiter_15e84c87986bd8a2aba06dcee2be46b8.txt b/test/snapshots/parser/erb_test/test_0038_erb_tag_followed_by_literal_closing_delimiter_15e84c87986bd8a2aba06dcee2be46b8.txt index 4ffdc2e97..f5dd92483 100644 --- a/test/snapshots/parser/erb_test/test_0038_erb_tag_followed_by_literal_closing_delimiter_15e84c87986bd8a2aba06dcee2be46b8.txt +++ b/test/snapshots/parser/erb_test/test_0038_erb_tag_followed_by_literal_closing_delimiter_15e84c87986bd8a2aba06dcee2be46b8.txt @@ -3,6 +3,10 @@ source: "Parser::ERBTest#test_0038_erb tag followed by literal closing delimiter input: "<% content %> %>" --- @ DocumentNode (location: (1:0)-(1:16)) +├── errors: (1 error) +│ └── @ StrayERBClosingTagError (location: (1:14)-(1:16)) +│ └── message: "Stray `%>` found at (1:14). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." +│ └── children: (2 items) ├── @ ERBContentNode (location: (1:0)-(1:13)) │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) diff --git a/test/snapshots/parser/error_recovery_test/test_0012_nested_ERB_tag_inside_ERB_comment_6056f70bdc685fbeb3e960f8bff735c2.txt b/test/snapshots/parser/error_recovery_test/test_0012_nested_ERB_tag_inside_ERB_comment_6056f70bdc685fbeb3e960f8bff735c2.txt index c05e3d445..95addf10c 100644 --- a/test/snapshots/parser/error_recovery_test/test_0012_nested_ERB_tag_inside_ERB_comment_6056f70bdc685fbeb3e960f8bff735c2.txt +++ b/test/snapshots/parser/error_recovery_test/test_0012_nested_ERB_tag_inside_ERB_comment_6056f70bdc685fbeb3e960f8bff735c2.txt @@ -3,6 +3,10 @@ source: "Parser::ErrorRecoveryTest#test_0012_nested ERB tag inside ERB comment" input: "<%# Another comment with <%= \"erb\" %> inside %>" --- @ DocumentNode (location: (1:0)-(1:47)) +├── errors: (1 error) +│ └── @ StrayERBClosingTagError (location: (1:45)-(1:47)) +│ └── message: "Stray `%>` found at (1:45). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." +│ └── children: (3 items) ├── @ ERBContentNode (location: (1:0)-(1:25)) │ ├── errors: (1 error) diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0001_stray_%gt_in_open_tag_after_unquoted_ERB_attribute_value_c4a5f2fd004a3774105147a3180c68be-08996694f7ff1e6e34277dc32f646630.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0001_stray_%gt_in_open_tag_after_unquoted_ERB_attribute_value_c4a5f2fd004a3774105147a3180c68be-08996694f7ff1e6e34277dc32f646630.txt new file mode 100644 index 000000000..6317e8882 --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0001_stray_%gt_in_open_tag_after_unquoted_ERB_attribute_value_c4a5f2fd004a3774105147a3180c68be-08996694f7ff1e6e34277dc32f646630.txt @@ -0,0 +1,49 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0001_stray %> in open tag after unquoted ERB attribute value" +input: |2- +
%> +options: {strict: true} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (1 item) + └── @ HTMLOpenTagNode (location: (1:0)-(2:0)) + ├── errors: (2 errors) + │ ├── @ StrayERBClosingTagError (location: (1:29)-(1:31)) + │ │ └── message: "Stray `%>` found at (1:29). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." + │ │ + │ └── @ UnclosedOpenTagError (location: (1:1)-(2:0)) + │ ├── message: "Opening tag `
` at (1:1) is missing closing `>`." + │ └── tag_name: "div" (location: (1:1)-(1:4)) + │ + ├── tag_opening: "<" (location: (1:0)-(1:1)) + ├── tag_name: "div" (location: (1:1)-(1:4)) + ├── tag_closing: ∅ + ├── children: (2 items) + │ ├── @ HTMLAttributeNode (location: (1:5)-(1:28)) + │ │ ├── name: + │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:7)) + │ │ │ └── children: (1 item) + │ │ │ └── @ LiteralNode (location: (1:5)-(1:7)) + │ │ │ └── content: "id" + │ │ │ + │ │ │ + │ │ ├── equals: "=" (location: (1:7)-(1:8)) + │ │ └── value: + │ │ └── @ HTMLAttributeValueNode (location: (1:8)-(1:28)) + │ │ ├── open_quote: ∅ + │ │ ├── children: (1 item) + │ │ │ └── @ ERBContentNode (location: (1:8)-(1:28)) + │ │ │ ├── tag_opening: "<%=" (location: (1:8)-(1:11)) + │ │ │ ├── content: " dom_id(@slot) " (location: (1:11)-(1:26)) + │ │ │ ├── tag_closing: "%>" (location: (1:26)-(1:28)) + │ │ │ ├── parsed: true + │ │ │ └── valid: true + │ │ │ + │ │ ├── close_quote: ∅ + │ │ └── quoted: false + │ │ + │ │ + │ └── @ LiteralNode (location: (1:29)-(1:31)) + │ └── content: "%>" + │ + └── is_void: false \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0001_stray_%gt_in_open_tag_after_unquoted_ERB_attribute_value_c4a5f2fd004a3774105147a3180c68be-2ffd01e26b8c99a6c7a6dad67c9489dc.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0001_stray_%gt_in_open_tag_after_unquoted_ERB_attribute_value_c4a5f2fd004a3774105147a3180c68be-2ffd01e26b8c99a6c7a6dad67c9489dc.txt new file mode 100644 index 000000000..be8725f03 --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0001_stray_%gt_in_open_tag_after_unquoted_ERB_attribute_value_c4a5f2fd004a3774105147a3180c68be-2ffd01e26b8c99a6c7a6dad67c9489dc.txt @@ -0,0 +1,49 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0001_stray %> in open tag after unquoted ERB attribute value" +input: |2- +
%> +options: {strict: false} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (1 item) + └── @ HTMLOpenTagNode (location: (1:0)-(2:0)) + ├── errors: (2 errors) + │ ├── @ StrayERBClosingTagError (location: (1:29)-(1:31)) + │ │ └── message: "Stray `%>` found at (1:29). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." + │ │ + │ └── @ UnclosedOpenTagError (location: (1:1)-(2:0)) + │ ├── message: "Opening tag `
` at (1:1) is missing closing `>`." + │ └── tag_name: "div" (location: (1:1)-(1:4)) + │ + ├── tag_opening: "<" (location: (1:0)-(1:1)) + ├── tag_name: "div" (location: (1:1)-(1:4)) + ├── tag_closing: ∅ + ├── children: (2 items) + │ ├── @ HTMLAttributeNode (location: (1:5)-(1:28)) + │ │ ├── name: + │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:7)) + │ │ │ └── children: (1 item) + │ │ │ └── @ LiteralNode (location: (1:5)-(1:7)) + │ │ │ └── content: "id" + │ │ │ + │ │ │ + │ │ ├── equals: "=" (location: (1:7)-(1:8)) + │ │ └── value: + │ │ └── @ HTMLAttributeValueNode (location: (1:8)-(1:28)) + │ │ ├── open_quote: ∅ + │ │ ├── children: (1 item) + │ │ │ └── @ ERBContentNode (location: (1:8)-(1:28)) + │ │ │ ├── tag_opening: "<%=" (location: (1:8)-(1:11)) + │ │ │ ├── content: " dom_id(@slot) " (location: (1:11)-(1:26)) + │ │ │ ├── tag_closing: "%>" (location: (1:26)-(1:28)) + │ │ │ ├── parsed: true + │ │ │ └── valid: true + │ │ │ + │ │ ├── close_quote: ∅ + │ │ └── quoted: false + │ │ + │ │ + │ └── @ LiteralNode (location: (1:29)-(1:31)) + │ └── content: "%>" + │ + └── is_void: false \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0002_stray_%gt_in_open_tag_after_quoted_ERB_attribute_value_48f9d68f5fe0c3029c21c387e097b518-08996694f7ff1e6e34277dc32f646630.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0002_stray_%gt_in_open_tag_after_quoted_ERB_attribute_value_48f9d68f5fe0c3029c21c387e097b518-08996694f7ff1e6e34277dc32f646630.txt new file mode 100644 index 000000000..b82318648 --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0002_stray_%gt_in_open_tag_after_quoted_ERB_attribute_value_48f9d68f5fe0c3029c21c387e097b518-08996694f7ff1e6e34277dc32f646630.txt @@ -0,0 +1,49 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0002_stray %> in open tag after quoted ERB attribute value" +input: |2- +
+options: {strict: true} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (1 item) + └── @ HTMLOpenTagNode (location: (1:0)-(2:0)) + ├── errors: (2 errors) + │ ├── @ StrayERBClosingTagError (location: (1:31)-(1:33)) + │ │ └── message: "Stray `%>` found at (1:31). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." + │ │ + │ └── @ UnclosedOpenTagError (location: (1:1)-(2:0)) + │ ├── message: "Opening tag `
` at (1:1) is missing closing `>`." + │ └── tag_name: "div" (location: (1:1)-(1:4)) + │ + ├── tag_opening: "<" (location: (1:0)-(1:1)) + ├── tag_name: "div" (location: (1:1)-(1:4)) + ├── tag_closing: ∅ + ├── children: (2 items) + │ ├── @ HTMLAttributeNode (location: (1:5)-(1:30)) + │ │ ├── name: + │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:7)) + │ │ │ └── children: (1 item) + │ │ │ └── @ LiteralNode (location: (1:5)-(1:7)) + │ │ │ └── content: "id" + │ │ │ + │ │ │ + │ │ ├── equals: "=" (location: (1:7)-(1:8)) + │ │ └── value: + │ │ └── @ HTMLAttributeValueNode (location: (1:8)-(1:30)) + │ │ ├── open_quote: """ (location: (1:8)-(1:9)) + │ │ ├── children: (1 item) + │ │ │ └── @ ERBContentNode (location: (1:9)-(1:29)) + │ │ │ ├── tag_opening: "<%=" (location: (1:9)-(1:12)) + │ │ │ ├── content: " dom_id(@slot) " (location: (1:12)-(1:27)) + │ │ │ ├── tag_closing: "%>" (location: (1:27)-(1:29)) + │ │ │ ├── parsed: true + │ │ │ └── valid: true + │ │ │ + │ │ ├── close_quote: """ (location: (1:29)-(1:30)) + │ │ └── quoted: true + │ │ + │ │ + │ └── @ LiteralNode (location: (1:31)-(1:33)) + │ └── content: "%>" + │ + └── is_void: false \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0002_stray_%gt_in_open_tag_after_quoted_ERB_attribute_value_48f9d68f5fe0c3029c21c387e097b518-2ffd01e26b8c99a6c7a6dad67c9489dc.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0002_stray_%gt_in_open_tag_after_quoted_ERB_attribute_value_48f9d68f5fe0c3029c21c387e097b518-2ffd01e26b8c99a6c7a6dad67c9489dc.txt new file mode 100644 index 000000000..743e7d016 --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0002_stray_%gt_in_open_tag_after_quoted_ERB_attribute_value_48f9d68f5fe0c3029c21c387e097b518-2ffd01e26b8c99a6c7a6dad67c9489dc.txt @@ -0,0 +1,49 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0002_stray %> in open tag after quoted ERB attribute value" +input: |2- +
+options: {strict: false} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (1 item) + └── @ HTMLOpenTagNode (location: (1:0)-(2:0)) + ├── errors: (2 errors) + │ ├── @ StrayERBClosingTagError (location: (1:31)-(1:33)) + │ │ └── message: "Stray `%>` found at (1:31). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." + │ │ + │ └── @ UnclosedOpenTagError (location: (1:1)-(2:0)) + │ ├── message: "Opening tag `
` at (1:1) is missing closing `>`." + │ └── tag_name: "div" (location: (1:1)-(1:4)) + │ + ├── tag_opening: "<" (location: (1:0)-(1:1)) + ├── tag_name: "div" (location: (1:1)-(1:4)) + ├── tag_closing: ∅ + ├── children: (2 items) + │ ├── @ HTMLAttributeNode (location: (1:5)-(1:30)) + │ │ ├── name: + │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:7)) + │ │ │ └── children: (1 item) + │ │ │ └── @ LiteralNode (location: (1:5)-(1:7)) + │ │ │ └── content: "id" + │ │ │ + │ │ │ + │ │ ├── equals: "=" (location: (1:7)-(1:8)) + │ │ └── value: + │ │ └── @ HTMLAttributeValueNode (location: (1:8)-(1:30)) + │ │ ├── open_quote: """ (location: (1:8)-(1:9)) + │ │ ├── children: (1 item) + │ │ │ └── @ ERBContentNode (location: (1:9)-(1:29)) + │ │ │ ├── tag_opening: "<%=" (location: (1:9)-(1:12)) + │ │ │ ├── content: " dom_id(@slot) " (location: (1:12)-(1:27)) + │ │ │ ├── tag_closing: "%>" (location: (1:27)-(1:29)) + │ │ │ ├── parsed: true + │ │ │ └── valid: true + │ │ │ + │ │ ├── close_quote: """ (location: (1:29)-(1:30)) + │ │ └── quoted: true + │ │ + │ │ + │ └── @ LiteralNode (location: (1:31)-(1:33)) + │ └── content: "%>" + │ + └── is_void: false \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0003_stray_%gt_in_open_tag_with_no_preceding_ERB_tag_1c94281d4272f5032b86a728f7757719-08996694f7ff1e6e34277dc32f646630.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0003_stray_%gt_in_open_tag_with_no_preceding_ERB_tag_1c94281d4272f5032b86a728f7757719-08996694f7ff1e6e34277dc32f646630.txt new file mode 100644 index 000000000..b820eef9e --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0003_stray_%gt_in_open_tag_with_no_preceding_ERB_tag_1c94281d4272f5032b86a728f7757719-08996694f7ff1e6e34277dc32f646630.txt @@ -0,0 +1,25 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0003_stray %> in open tag with no preceding ERB tag" +input: |2- +
+options: {strict: true} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (1 item) + └── @ HTMLOpenTagNode (location: (1:0)-(2:0)) + ├── errors: (2 errors) + │ ├── @ StrayERBClosingTagError (location: (1:5)-(1:7)) + │ │ └── message: "Stray `%>` found at (1:5). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." + │ │ + │ └── @ UnclosedOpenTagError (location: (1:1)-(2:0)) + │ ├── message: "Opening tag `
` at (1:1) is missing closing `>`." + │ └── tag_name: "div" (location: (1:1)-(1:4)) + │ + ├── tag_opening: "<" (location: (1:0)-(1:1)) + ├── tag_name: "div" (location: (1:1)-(1:4)) + ├── tag_closing: ∅ + ├── children: (1 item) + │ └── @ LiteralNode (location: (1:5)-(1:7)) + │ └── content: "%>" + │ + └── is_void: false \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0003_stray_%gt_in_open_tag_with_no_preceding_ERB_tag_1c94281d4272f5032b86a728f7757719-2ffd01e26b8c99a6c7a6dad67c9489dc.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0003_stray_%gt_in_open_tag_with_no_preceding_ERB_tag_1c94281d4272f5032b86a728f7757719-2ffd01e26b8c99a6c7a6dad67c9489dc.txt new file mode 100644 index 000000000..d45dcc91f --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0003_stray_%gt_in_open_tag_with_no_preceding_ERB_tag_1c94281d4272f5032b86a728f7757719-2ffd01e26b8c99a6c7a6dad67c9489dc.txt @@ -0,0 +1,25 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0003_stray %> in open tag with no preceding ERB tag" +input: |2- +
+options: {strict: false} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (1 item) + └── @ HTMLOpenTagNode (location: (1:0)-(2:0)) + ├── errors: (2 errors) + │ ├── @ StrayERBClosingTagError (location: (1:5)-(1:7)) + │ │ └── message: "Stray `%>` found at (1:5). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." + │ │ + │ └── @ UnclosedOpenTagError (location: (1:1)-(2:0)) + │ ├── message: "Opening tag `
` at (1:1) is missing closing `>`." + │ └── tag_name: "div" (location: (1:1)-(1:4)) + │ + ├── tag_opening: "<" (location: (1:0)-(1:1)) + ├── tag_name: "div" (location: (1:1)-(1:4)) + ├── tag_closing: ∅ + ├── children: (1 item) + │ └── @ LiteralNode (location: (1:5)-(1:7)) + │ └── content: "%>" + │ + └── is_void: false \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0004_stray_%gt_at_document_top_level_cf8a5e0697cfd68db29b6798aa2b3d28-08996694f7ff1e6e34277dc32f646630.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0004_stray_%gt_at_document_top_level_cf8a5e0697cfd68db29b6798aa2b3d28-08996694f7ff1e6e34277dc32f646630.txt new file mode 100644 index 000000000..c1acbb91a --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0004_stray_%gt_at_document_top_level_cf8a5e0697cfd68db29b6798aa2b3d28-08996694f7ff1e6e34277dc32f646630.txt @@ -0,0 +1,14 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0004_stray %> at document top level" +input: |2- +hello %> world +options: {strict: true} +--- +@ DocumentNode (location: (1:0)-(2:0)) +├── errors: (1 error) +│ └── @ StrayERBClosingTagError (location: (1:6)-(1:8)) +│ └── message: "Stray `%>` found at (1:6). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." +│ +└── children: (1 item) + └── @ HTMLTextNode (location: (1:0)-(2:0)) + └── content: "hello %> world\n" \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0004_stray_%gt_at_document_top_level_cf8a5e0697cfd68db29b6798aa2b3d28-2ffd01e26b8c99a6c7a6dad67c9489dc.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0004_stray_%gt_at_document_top_level_cf8a5e0697cfd68db29b6798aa2b3d28-2ffd01e26b8c99a6c7a6dad67c9489dc.txt new file mode 100644 index 000000000..33eb8f4e8 --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0004_stray_%gt_at_document_top_level_cf8a5e0697cfd68db29b6798aa2b3d28-2ffd01e26b8c99a6c7a6dad67c9489dc.txt @@ -0,0 +1,10 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0004_stray %> at document top level" +input: |2- +hello %> world +options: {strict: false} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (1 item) + └── @ HTMLTextNode (location: (1:0)-(2:0)) + └── content: "hello %> world\n" \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0005_stray_%gt_at_document_top_level_after_ERB_tag_c0f67e970f7eec10b23cb091cccb6604-08996694f7ff1e6e34277dc32f646630.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0005_stray_%gt_at_document_top_level_after_ERB_tag_c0f67e970f7eec10b23cb091cccb6604-08996694f7ff1e6e34277dc32f646630.txt new file mode 100644 index 000000000..65b744afa --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0005_stray_%gt_at_document_top_level_after_ERB_tag_c0f67e970f7eec10b23cb091cccb6604-08996694f7ff1e6e34277dc32f646630.txt @@ -0,0 +1,21 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0005_stray %> at document top level after ERB tag" +input: |2- +<% content %> %> +options: {strict: true} +--- +@ DocumentNode (location: (1:0)-(2:0)) +├── errors: (1 error) +│ └── @ StrayERBClosingTagError (location: (1:14)-(1:16)) +│ └── message: "Stray `%>` found at (1:14). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." +│ +└── children: (2 items) + ├── @ ERBContentNode (location: (1:0)-(1:13)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " content " (location: (1:2)-(1:11)) + │ ├── tag_closing: "%>" (location: (1:11)-(1:13)) + │ ├── parsed: true + │ └── valid: true + │ + └── @ HTMLTextNode (location: (1:13)-(2:0)) + └── content: " %>\n" \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0005_stray_%gt_at_document_top_level_after_ERB_tag_c0f67e970f7eec10b23cb091cccb6604-2ffd01e26b8c99a6c7a6dad67c9489dc.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0005_stray_%gt_at_document_top_level_after_ERB_tag_c0f67e970f7eec10b23cb091cccb6604-2ffd01e26b8c99a6c7a6dad67c9489dc.txt new file mode 100644 index 000000000..f9b0f7acb --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0005_stray_%gt_at_document_top_level_after_ERB_tag_c0f67e970f7eec10b23cb091cccb6604-2ffd01e26b8c99a6c7a6dad67c9489dc.txt @@ -0,0 +1,17 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0005_stray %> at document top level after ERB tag" +input: |2- +<% content %> %> +options: {strict: false} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (2 items) + ├── @ ERBContentNode (location: (1:0)-(1:13)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " content " (location: (1:2)-(1:11)) + │ ├── tag_closing: "%>" (location: (1:11)-(1:13)) + │ ├── parsed: true + │ └── valid: true + │ + └── @ HTMLTextNode (location: (1:13)-(2:0)) + └── content: " %>\n" \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0006_%gt_inside_quoted_attribute_value_is_not_stray_234130472b744d5f4da241526fe80a8c-08996694f7ff1e6e34277dc32f646630.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0006_%gt_inside_quoted_attribute_value_is_not_stray_234130472b744d5f4da241526fe80a8c-08996694f7ff1e6e34277dc32f646630.txt new file mode 100644 index 000000000..da6db2834 --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0006_%gt_inside_quoted_attribute_value_is_not_stray_234130472b744d5f4da241526fe80a8c-08996694f7ff1e6e34277dc32f646630.txt @@ -0,0 +1,54 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0006_%> inside quoted attribute value is not stray" +input: |2- +
hello
+options: {strict: true} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(1:35)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:24)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (1:23)-(1:24)) + │ │ ├── children: (1 item) + │ │ │ └── @ HTMLAttributeNode (location: (1:5)-(1:23)) + │ │ │ ├── name: + │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:10)) + │ │ │ │ └── children: (1 item) + │ │ │ │ └── @ LiteralNode (location: (1:5)-(1:10)) + │ │ │ │ └── content: "class" + │ │ │ │ + │ │ │ │ + │ │ │ ├── equals: "=" (location: (1:10)-(1:11)) + │ │ │ └── value: + │ │ │ └── @ HTMLAttributeValueNode (location: (1:11)-(1:23)) + │ │ │ ├── open_quote: """ (location: (1:11)-(1:12)) + │ │ │ ├── children: (1 item) + │ │ │ │ └── @ LiteralNode (location: (1:12)-(1:22)) + │ │ │ │ └── content: "foo %> bar" + │ │ │ │ + │ │ │ ├── close_quote: """ (location: (1:22)-(1:23)) + │ │ │ └── quoted: true + │ │ │ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (1 item) + │ │ └── @ HTMLTextNode (location: (1:24)-(1:29)) + │ │ └── content: "hello" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (1:29)-(1:35)) + │ │ ├── tag_opening: "" (location: (1:34)-(1:35)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (1:35)-(2:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0006_%gt_inside_quoted_attribute_value_is_not_stray_234130472b744d5f4da241526fe80a8c-2ffd01e26b8c99a6c7a6dad67c9489dc.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0006_%gt_inside_quoted_attribute_value_is_not_stray_234130472b744d5f4da241526fe80a8c-2ffd01e26b8c99a6c7a6dad67c9489dc.txt new file mode 100644 index 000000000..e05d126be --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0006_%gt_inside_quoted_attribute_value_is_not_stray_234130472b744d5f4da241526fe80a8c-2ffd01e26b8c99a6c7a6dad67c9489dc.txt @@ -0,0 +1,54 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0006_%> inside quoted attribute value is not stray" +input: |2- +
hello
+options: {strict: false} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(1:35)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:24)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (1:23)-(1:24)) + │ │ ├── children: (1 item) + │ │ │ └── @ HTMLAttributeNode (location: (1:5)-(1:23)) + │ │ │ ├── name: + │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:10)) + │ │ │ │ └── children: (1 item) + │ │ │ │ └── @ LiteralNode (location: (1:5)-(1:10)) + │ │ │ │ └── content: "class" + │ │ │ │ + │ │ │ │ + │ │ │ ├── equals: "=" (location: (1:10)-(1:11)) + │ │ │ └── value: + │ │ │ └── @ HTMLAttributeValueNode (location: (1:11)-(1:23)) + │ │ │ ├── open_quote: """ (location: (1:11)-(1:12)) + │ │ │ ├── children: (1 item) + │ │ │ │ └── @ LiteralNode (location: (1:12)-(1:22)) + │ │ │ │ └── content: "foo %> bar" + │ │ │ │ + │ │ │ ├── close_quote: """ (location: (1:22)-(1:23)) + │ │ │ └── quoted: true + │ │ │ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (1 item) + │ │ └── @ HTMLTextNode (location: (1:24)-(1:29)) + │ │ └── content: "hello" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (1:29)-(1:35)) + │ │ ├── tag_opening: "" (location: (1:34)-(1:35)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (1:35)-(2:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0007_multiple_stray_%gt_in_open_tag_51d400041b3a5fc89d4c94c729e77372-08996694f7ff1e6e34277dc32f646630.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0007_multiple_stray_%gt_in_open_tag_51d400041b3a5fc89d4c94c729e77372-08996694f7ff1e6e34277dc32f646630.txt new file mode 100644 index 000000000..d98da5d4c --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0007_multiple_stray_%gt_in_open_tag_51d400041b3a5fc89d4c94c729e77372-08996694f7ff1e6e34277dc32f646630.txt @@ -0,0 +1,31 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0007_multiple stray %> in open tag" +input: |2- +
%> +options: {strict: true} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (1 item) + └── @ HTMLOpenTagNode (location: (1:0)-(2:0)) + ├── errors: (3 errors) + │ ├── @ StrayERBClosingTagError (location: (1:5)-(1:7)) + │ │ └── message: "Stray `%>` found at (1:5). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." + │ │ + │ ├── @ StrayERBClosingTagError (location: (1:8)-(1:10)) + │ │ └── message: "Stray `%>` found at (1:8). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." + │ │ + │ └── @ UnclosedOpenTagError (location: (1:1)-(2:0)) + │ ├── message: "Opening tag `
` at (1:1) is missing closing `>`." + │ └── tag_name: "div" (location: (1:1)-(1:4)) + │ + ├── tag_opening: "<" (location: (1:0)-(1:1)) + ├── tag_name: "div" (location: (1:1)-(1:4)) + ├── tag_closing: ∅ + ├── children: (2 items) + │ ├── @ LiteralNode (location: (1:5)-(1:7)) + │ │ └── content: "%>" + │ │ + │ └── @ LiteralNode (location: (1:8)-(1:10)) + │ └── content: "%>" + │ + └── is_void: false \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0007_multiple_stray_%gt_in_open_tag_51d400041b3a5fc89d4c94c729e77372-2ffd01e26b8c99a6c7a6dad67c9489dc.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0007_multiple_stray_%gt_in_open_tag_51d400041b3a5fc89d4c94c729e77372-2ffd01e26b8c99a6c7a6dad67c9489dc.txt new file mode 100644 index 000000000..7e64fcbef --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0007_multiple_stray_%gt_in_open_tag_51d400041b3a5fc89d4c94c729e77372-2ffd01e26b8c99a6c7a6dad67c9489dc.txt @@ -0,0 +1,31 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0007_multiple stray %> in open tag" +input: |2- +
%> +options: {strict: false} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (1 item) + └── @ HTMLOpenTagNode (location: (1:0)-(2:0)) + ├── errors: (3 errors) + │ ├── @ StrayERBClosingTagError (location: (1:5)-(1:7)) + │ │ └── message: "Stray `%>` found at (1:5). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." + │ │ + │ ├── @ StrayERBClosingTagError (location: (1:8)-(1:10)) + │ │ └── message: "Stray `%>` found at (1:8). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." + │ │ + │ └── @ UnclosedOpenTagError (location: (1:1)-(2:0)) + │ ├── message: "Opening tag `
` at (1:1) is missing closing `>`." + │ └── tag_name: "div" (location: (1:1)-(1:4)) + │ + ├── tag_opening: "<" (location: (1:0)-(1:1)) + ├── tag_name: "div" (location: (1:1)-(1:4)) + ├── tag_closing: ∅ + ├── children: (2 items) + │ ├── @ LiteralNode (location: (1:5)-(1:7)) + │ │ └── content: "%>" + │ │ + │ └── @ LiteralNode (location: (1:8)-(1:10)) + │ └── content: "%>" + │ + └── is_void: false \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0008_stray_%gt_between_attributes_in_open_tag_ff209340c28c63631300fd22c87d6df6-08996694f7ff1e6e34277dc32f646630.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0008_stray_%gt_between_attributes_in_open_tag_ff209340c28c63631300fd22c87d6df6-08996694f7ff1e6e34277dc32f646630.txt new file mode 100644 index 000000000..50a36386c --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0008_stray_%gt_between_attributes_in_open_tag_ff209340c28c63631300fd22c87d6df6-08996694f7ff1e6e34277dc32f646630.txt @@ -0,0 +1,78 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0008_stray %> between attributes in open tag" +input: |2- +
id="bar">
+options: {strict: true} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(1:35)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:29)) + │ │ ├── errors: (1 error) + │ │ │ └── @ StrayERBClosingTagError (location: (1:17)-(1:19)) + │ │ │ └── message: "Stray `%>` found at (1:17). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." + │ │ │ + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (1:28)-(1:29)) + │ │ ├── children: (3 items) + │ │ │ ├── @ HTMLAttributeNode (location: (1:5)-(1:16)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:10)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (1:5)-(1:10)) + │ │ │ │ │ └── content: "class" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (1:10)-(1:11)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (1:11)-(1:16)) + │ │ │ │ ├── open_quote: """ (location: (1:11)-(1:12)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (1:12)-(1:15)) + │ │ │ │ │ └── content: "foo" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (1:15)-(1:16)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ ├── @ LiteralNode (location: (1:17)-(1:19)) + │ │ │ │ └── content: "%>" + │ │ │ │ + │ │ │ └── @ HTMLAttributeNode (location: (1:20)-(1:28)) + │ │ │ ├── name: + │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:20)-(1:22)) + │ │ │ │ └── children: (1 item) + │ │ │ │ └── @ LiteralNode (location: (1:20)-(1:22)) + │ │ │ │ └── content: "id" + │ │ │ │ + │ │ │ │ + │ │ │ ├── equals: "=" (location: (1:22)-(1:23)) + │ │ │ └── value: + │ │ │ └── @ HTMLAttributeValueNode (location: (1:23)-(1:28)) + │ │ │ ├── open_quote: """ (location: (1:23)-(1:24)) + │ │ │ ├── children: (1 item) + │ │ │ │ └── @ LiteralNode (location: (1:24)-(1:27)) + │ │ │ │ └── content: "bar" + │ │ │ │ + │ │ │ ├── close_quote: """ (location: (1:27)-(1:28)) + │ │ │ └── quoted: true + │ │ │ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: [] + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (1:29)-(1:35)) + │ │ ├── tag_opening: "" (location: (1:34)-(1:35)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (1:35)-(2:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/stray_erb_closing_tag_test/test_0008_stray_%gt_between_attributes_in_open_tag_ff209340c28c63631300fd22c87d6df6-2ffd01e26b8c99a6c7a6dad67c9489dc.txt b/test/snapshots/parser/stray_erb_closing_tag_test/test_0008_stray_%gt_between_attributes_in_open_tag_ff209340c28c63631300fd22c87d6df6-2ffd01e26b8c99a6c7a6dad67c9489dc.txt new file mode 100644 index 000000000..52c3f91a2 --- /dev/null +++ b/test/snapshots/parser/stray_erb_closing_tag_test/test_0008_stray_%gt_between_attributes_in_open_tag_ff209340c28c63631300fd22c87d6df6-2ffd01e26b8c99a6c7a6dad67c9489dc.txt @@ -0,0 +1,78 @@ +--- +source: "Parser::StrayERBClosingTagTest#test_0008_stray %> between attributes in open tag" +input: |2- +
id="bar">
+options: {strict: false} +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(1:35)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:29)) + │ │ ├── errors: (1 error) + │ │ │ └── @ StrayERBClosingTagError (location: (1:17)-(1:19)) + │ │ │ └── message: "Stray `%>` found at (1:17). This closing delimiter is not part of an ERB tag and will be treated as plain text. If you want a literal `%>`, use the HTML entities `%>` instead." + │ │ │ + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (1:28)-(1:29)) + │ │ ├── children: (3 items) + │ │ │ ├── @ HTMLAttributeNode (location: (1:5)-(1:16)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:10)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (1:5)-(1:10)) + │ │ │ │ │ └── content: "class" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (1:10)-(1:11)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (1:11)-(1:16)) + │ │ │ │ ├── open_quote: """ (location: (1:11)-(1:12)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (1:12)-(1:15)) + │ │ │ │ │ └── content: "foo" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (1:15)-(1:16)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ ├── @ LiteralNode (location: (1:17)-(1:19)) + │ │ │ │ └── content: "%>" + │ │ │ │ + │ │ │ └── @ HTMLAttributeNode (location: (1:20)-(1:28)) + │ │ │ ├── name: + │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:20)-(1:22)) + │ │ │ │ └── children: (1 item) + │ │ │ │ └── @ LiteralNode (location: (1:20)-(1:22)) + │ │ │ │ └── content: "id" + │ │ │ │ + │ │ │ │ + │ │ │ ├── equals: "=" (location: (1:22)-(1:23)) + │ │ │ └── value: + │ │ │ └── @ HTMLAttributeValueNode (location: (1:23)-(1:28)) + │ │ │ ├── open_quote: """ (location: (1:23)-(1:24)) + │ │ │ ├── children: (1 item) + │ │ │ │ └── @ LiteralNode (location: (1:24)-(1:27)) + │ │ │ │ └── content: "bar" + │ │ │ │ + │ │ │ ├── close_quote: """ (location: (1:27)-(1:28)) + │ │ │ └── quoted: true + │ │ │ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: [] + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (1:29)-(1:35)) + │ │ ├── tag_opening: "" (location: (1:34)-(1:35)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (1:35)-(2:0)) + └── content: "\n" \ No newline at end of file From 0c92ad7a40c20a8de59997d8a5448f82790b25a3 Mon Sep 17 00:00:00 2001 From: Marco Roth Date: Sat, 28 Feb 2026 02:24:39 +0100 Subject: [PATCH 2/2] RBS --- sig/herb/errors.rbs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sig/herb/errors.rbs b/sig/herb/errors.rbs index d57a9f873..d10cae911 100644 --- a/sig/herb/errors.rbs +++ b/sig/herb/errors.rbs @@ -422,6 +422,16 @@ module Herb def tree_inspect: (?indent: Integer, ?depth: Integer, ?depth_limit: Integer) -> String end + class StrayERBClosingTagError < Error + include Colors + + # : () -> String + def inspect: () -> String + + # : (?indent: Integer, ?depth: Integer, ?depth_limit: Integer) -> String + def tree_inspect: (?indent: Integer, ?depth: Integer, ?depth_limit: Integer) -> String + end + class NestedERBTagError < Error include Colors