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/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
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:29)-(1:31))
+ │ │ ├── tag_name: "div" (location: (1:31)-(1:34))
+ │ │ ├── children: []
+ │ │ └── tag_closing: ">" (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:29)-(1:31))
+ │ │ ├── tag_name: "div" (location: (1:31)-(1:34))
+ │ │ ├── children: []
+ │ │ └── tag_closing: ">" (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:29)-(1:31))
+ │ │ ├── tag_name: "div" (location: (1:31)-(1:34))
+ │ │ ├── children: []
+ │ │ └── tag_closing: ">" (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:29)-(1:31))
+ │ │ ├── tag_name: "div" (location: (1:31)-(1:34))
+ │ │ ├── children: []
+ │ │ └── tag_closing: ">" (location: (1:34)-(1:35))
+ │ │
+ │ ├── is_void: false
+ │ └── source: "HTML"
+ │
+ └── @ HTMLTextNode (location: (1:35)-(2:0))
+ └── content: "\n"
\ No newline at end of file