From 59c08c3c51cdcbbedf7681153fd86c633a1415cd Mon Sep 17 00:00:00 2001 From: Marco Roth Date: Tue, 4 Nov 2025 10:08:15 +0100 Subject: [PATCH] Parser: Introduce two-pass algorithm for matching HTML Elements This pull request introduces a two-pass algorithm for HTML tag matching to better handle ERB control flow boundaries. Previously, HTML tags were matched during parsing (single-pass), which caused issues when tags appeared across ERB control flow boundaries: ```erb
<% if true %>

Title <% end %>

``` This would incorrectly report errors or miss mismatched tags because the parser couldn't understand that the `<% if %>` creates a scope boundary. --- In the first pass during parsing, the parser collects `HTMLOpenTagNode` and `HTMLCloseTagNode` nodes separately without attempting to match them or perform any validation. In the second pass after the ERB structure has been fully analyzed, the new `herb_parser_match_html_tags_post_analyze()` function matches tags while respecting ERB control flow scope boundaries. This function recursively processes all ERB control structures including `if`/`elsif`/`else`, `case`/`when`, `begin`/`rescue`/`ensure`, and others, attaching any mismatch errors directly to the relevant stray open or close tag nodes. --- .clang-format-ignore | 3 + .gitignore | 3 + config.yml | 24 +- ext/herb/extension.c | 2 + .../packages/formatter/src/format-printer.ts | 4 +- .../test/rules/parser-no-errors.test.ts | 37 +- javascript/packages/node/binding.gyp | 3 + rust/tests/error_handling_test.rs | 22 +- rust/tests/parse_result_test.rs | 10 +- sig/herb/errors.rbs | 36 ++ src/analyze.c | 200 +++++++++-- src/analyze_helpers.c | 5 + src/analyzed_ruby.c | 20 ++ src/include/analyze.h | 8 + src/include/analyze_helpers.h | 3 + src/include/analyzed_ruby.h | 1 + src/include/parser.h | 8 +- src/include/token.h | 2 + src/parser.c | 185 +++++++--- src/token.c | 5 + templates/src/analyze_missing_end.c.erb | 36 ++ templates/src/analyze_transform.c.erb | 24 ++ templates/src/parser_match_tags.c.erb | 31 ++ test/analyze/case_in_test.rb | 2 - test/analyze/case_test.rb | 2 - test/analyze/if_test.rb | 24 ++ test/analyze/invalid_erb_structure_test.rb | 322 +++++++++++++++++ test/analyze/unless_test.rb | 2 + test/engine/error_handling_test.rb | 131 ++++++- test/parser/case_match_test.rb | 21 ++ test/parser/case_when_test.rb | 23 ++ test/parser/comments_test.rb | 10 + test/parser/erb_context_mismatch_test.rb | 115 ++++++ test/parser/erb_test.rb | 4 + test/parser/parser_test.rb | 4 +- test/parser/script_style_test.rb | 1 + test/parser/tags_test.rb | 25 +- test/parser/yield_test.rb | 50 +++ ...s_ins_e19b30c609f63840ec91a32bf81c84d3.txt | 72 ++++ ...cases_eac03f80fbde0f108657c6522c81f19f.txt | 72 ++++ ...st_if_cb6be8c9ca2ddbb438d62020648c6ed4.txt | 79 ++++ ...osing_8a42d39b7aee4fa9f2ca791cf1ef291b.txt | 102 ++++++ ...osing_8adc4e0a2a6b8baf0ab00bb12a016422.txt | 86 +++++ ...osing_424e3fda025275fa81a488fc87528e25.txt | 81 +++++ ...osing_12f297bfdeb3c060a0c220a445fcb51c.txt | 94 +++++ ...uotes_5cf54922a30f349212e5ec71c8d4d3b3.txt | 89 +++++ ...nches_00f04f842749802aede65921854fbd81.txt | 89 +++++ ...tside_d551e3630ae3b84c356b86f544fe1c74.txt | 98 +++++ ...butes_653d501c02f01c7eb2fa084da7d45f3c.txt | 95 +++++ ...scope_a609e4aae83bf7345c1ae12806881ecb.txt | 94 +++++ ...scope_33915d0eadb35429ec69cc5c382cfaf2.txt | 104 ++++++ ...scope_1340125c09cfc9ae2a3c763fb939f03f.txt | 104 ++++++ ...locks_ca324425f43bfaddc88ee581c44a5884.txt | 79 ++++ ...locks_c6c4f56cd642ee67863a77e686dadd6c.txt | 77 ++++ ...locks_458db68ec0fe89e877c5753e1d76367c.txt | 77 ++++ ...lause_7e1b1b64fe7f5060c0dbc6b94bf3cae8.txt | 149 ++++++++ ...scope_9e9e331db3c1e45c281ee0f0a4283d00.txt | 77 ++++ ...g_tag_447bae9e416269915e38be84519b2d28.txt | 135 +++++++ ...ntext_182bc23dd57db9ce2bba26309b131723.txt | 50 +++ ...ntext_ac5828658f5c90c9d487d6ef420b99a0.txt | 50 +++ ...ntext_26f3fe5cab89ff275796733f6967109e.txt | 50 +++ ...ntext_a9908e2810eb5ee58d0b4f4589e1ff36.txt | 57 +++ ..._loop_87141b29d2eca3e5d69b58f53982f8fc.txt | 42 +++ ..._loop_566add5cc403289f497833046d30cb83.txt | 53 +++ ..._loop_6ba6b13c23d9f008fe78926965fddb7c.txt | 43 +++ ...redo)_179ec23049a2db7f974635ed4ba2ecf7.txt | 339 ++++++++++++++++++ ...ifier_650bfc3134fa92038c335f96f9047860.txt | 66 ---- ...g_tag_f00fb4a0aa00f7c763be88670c0bc05d.txt | 63 ++-- ...alid)_542130e358dadabeb4a1629bc9bcf4f3.txt | 169 ++++----- ...se_in_69cf275355e6a10adc88e47d5090dd3a.txt | 131 +++++++ ..._when_71d06502a93ad345bf738ba8ace8bf9f.txt | 169 +++++++++ ...x_ERB_2c2d90415d057b7acbff8136b9e5e8bf.txt | 45 ++- ..._loop_e0b9e9e369a74b4d48962fb7540684ff.txt | 44 ++- ...th_if_04813d5ad89aa775065eb87d881ab931.txt | 50 +++ ...locks_186de8708f7329b37a560b950c6814bc.txt | 175 +++++++++ ...nally_6d7fbfb1a933a2f8aba9be55b52c0012.txt | 124 +++++++ ...locks_a6d5461489ddfa85081a5449445cef14.txt | 108 ++++++ ...ional_ff6b90e6b68b015927ad4554dc97fd8e.txt | 333 +++++++++++++++++ ...block_264d7b62538d8f8e318bce1a37dee984.txt | 57 +++ ...ntext_23760079615eb0a149447d79f51005bb.txt | 81 +++++ ...ening_7435ca180811536625a8df051573f55d.txt | 50 +++ ...osing_1ccadf7f0ed2141a178d6f7c5736dc6c.txt | 50 +++ ...g_%gt_1e22264334529582735f76812bfeec73.txt | 14 +- ...ng_gt_2707e9fbfa1307c14d5d6deaf40bd0af.txt | 14 +- ..._TODO_d7de40920545c14e5afcda3a2e9f0eb9.txt | 19 + ...tring_b1ffbf793264421bcbb1b9d6087f10b2.txt | 7 +- ...ntent_c3dcacbbac9dfaafc8298a1e8b6df38b.txt | 7 +- ...tring_4d120bcac86583501954aa7c7d1b54f8.txt | 2 +- ...t_end_1ccec9a4a170634bddb31f9c22d0fbc5.txt | 7 +- ..._tags_ffab60cb04f4e44e1ee7ed24d4ac9563.txt | 4 +- ..._tags_34e16086c329d845a1defbf80502a3a7.txt | 4 +- ..._tags_6bcbe820b5902dfe97a869593621bebf.txt | 2 +- ...rings_3217a5ed01ac8ce6c44f9e106f9b4017.txt | 2 +- ...ntent_e270b9ac006614d91dd6f0ac8fa94fa0.txt | 2 +- ..._tags_13c557e6f9f1cb452509b1e1750ede1d.txt | 4 +- ...mment_6cfabe5d024715f9a1a359ead2336bde.txt | 2 +- ...mment_f31fa6a4dd0f181740b8650c7c0eadba.txt | 2 +- ...mment_740adce7cfe9c265534de5ed0019592a.txt | 4 +- ...rings_dd4ddc45ba4f6eac82de8f8d0c4f8430.txt | 8 +- ...g_tag_baba657d4b37767ec99d327b6dec635a.txt | 2 +- ...line)_372da24666282e3c531de3c5a8de44ca.txt | 2 +- ...n_svg_1072d4c2f81d845d5c1f0fd86e40e23b.txt | 25 +- ...error_e086a53256a215470da47ef3ee745599.txt | 107 +++--- ...ement_0a3a0b592b9c285e050805307cee87c2.txt | 2 +- ..._tags_a371ac3aeb62b4174f84c9a365449896.txt | 6 +- ...g_tag_7165dc23abec2b6bf23cbd14f51aa23b.txt | 49 ++- ..._tags_b3702347c5b2426cdbc11f7c38dd944b.txt | 2 +- ...g_tag_55697eb571d997f32e6851b1abb5aecb.txt | 87 ++--- ..._tags_6ad485b119da439f33eef2025393e3ea.txt | 101 ++---- ..._tags_f273b8d48121740a82fdcc71150f3710.txt | 71 ++-- ..._tags_d55597438f3e5bee8cd21841b672b31a.txt | 109 ++++++ ...g_tag_7381d2541dc0e90b92193628ef31026f.txt | 153 ++++++++ ...tring_cf1f4c343891b3817c5470dca59c8a20.txt | 2 +- ...yield_811e738151285db6da8e3a493b8d6e2b.txt | 14 + ...ymbol_6cb3df93a89d085b4d803a26860118c0.txt | 14 + ...de_if_edde5ff3a664fd489f8aaefc285a9ae7.txt | 84 +++++ ...block_222b48dd8aa27af5e2f135cd33f583c8.txt | 34 ++ ..._when_cc5ad76c35f42b0e10034177ccaaff3e.txt | 65 ++++ ...th_if_04813d5ad89aa775065eb87d881ab931.txt | 50 +++ test/visitor_test.rb | 3 +- 120 files changed, 6039 insertions(+), 707 deletions(-) create mode 100644 templates/src/analyze_missing_end.c.erb create mode 100644 templates/src/analyze_transform.c.erb create mode 100644 templates/src/parser_match_tags.c.erb create mode 100644 test/analyze/invalid_erb_structure_test.rb create mode 100644 test/parser/case_match_test.rb create mode 100644 test/parser/case_when_test.rb create mode 100644 test/parser/erb_context_mismatch_test.rb create mode 100644 test/parser/yield_test.rb create mode 100644 test/snapshots/analyze/case_in_test/test_0006_nested_cases_ins_e19b30c609f63840ec91a32bf81c84d3.txt create mode 100644 test/snapshots/analyze/case_test/test_0006_nested_cases_eac03f80fbde0f108657c6522c81f19f.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0001_tags_spanning_erb_control_flow_boundaries_-_opening_tag_in_first_if_cb6be8c9ca2ddbb438d62020648c6ed4.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0002_invalid_erb_structure_-_else_outside_scope_after_tag_closing_8a42d39b7aee4fa9f2ca791cf1ef291b.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0003_invalid_erb_structure_-_end_outside_scope_after_tag_closing_8adc4e0a2a6b8baf0ab00bb12a016422.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0004_invalid_erb_structure_-_elsif_outside_scope_after_tag_closing_424e3fda025275fa81a488fc87528e25.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0005_invalid_erb_structure_-_else_outside_scope_before_tag_closing_12f297bfdeb3c060a0c220a445fcb51c.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0006_invalid_erb_structure_-_if_else_spans_across_attribute_value_quotes_5cf54922a30f349212e5ec71c8d4d3b3.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0007_invalid_erb_structure_-_multiline_attribute_value_spans_across_if_else_branches_00f04f842749802aede65921854fbd81.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0008_invalid_erb_structure_-_attribute_quote_closes_in_if_branch_with_else_end_outside_d551e3630ae3b84c356b86f544fe1c74.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0009_valid_erb_structure_-_if_else_end_inside_tag_attributes_653d501c02f01c7eb2fa084da7d45f3c.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0010_invalid_erb_structure_-_when_outside_case_scope_a609e4aae83bf7345c1ae12806881ecb.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0011_invalid_erb_structure_-_rescue_outside_begin_scope_33915d0eadb35429ec69cc5c382cfaf2.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0012_invalid_erb_structure_-_ensure_outside_begin_scope_1340125c09cfc9ae2a3c763fb939f03f.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0013_tags_in_different_erb_scopes_-_unless_blocks_ca324425f43bfaddc88ee581c44a5884.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0014_tags_in_different_erb_scopes_-_while_blocks_c6c4f56cd642ee67863a77e686dadd6c.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0015_tags_in_different_erb_scopes_-_for_blocks_458db68ec0fe89e877c5753e1d76367c.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0016_missing_close_HTML_tag_in_else_clause_7e1b1b64fe7f5060c0dbc6b94bf3cae8.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0017_elsif_in_class_attribute_value_scope_9e9e331db3c1e45c281ee0f0a4283d00.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0019_conditional_attributes_with_space_before_ERB_closing_tag_447bae9e416269915e38be84519b2d28.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0020_invalid_break_outside_of_loop_context_182bc23dd57db9ce2bba26309b131723.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0021_invalid_next_outside_of_loop_context_ac5828658f5c90c9d487d6ef420b99a0.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0022_invalid_redo_outside_of_loop_context_26f3fe5cab89ff275796733f6967109e.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0023_invalid_retry_outside_of_rescue_context_a9908e2810eb5ee58d0b4f4589e1ff36.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0024_invalid_break_in_if_statement_outside_of_loop_87141b29d2eca3e5d69b58f53982f8fc.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0025_invalid_next_in_case_statement_outside_of_loop_566add5cc403289f497833046d30cb83.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0026_invalid_break_at_document_root_after_while_loop_6ba6b13c23d9f008fe78926965fddb7c.txt create mode 100644 test/snapshots/analyze/invalid_erb_structure_test/test_0027_loop_control_keywords_in_various_contexts_(retry,_break,_next,_redo)_179ec23049a2db7f974635ed4ba2ecf7.txt delete mode 100644 test/snapshots/analyze/unless_test/test_0008_guard_clause_with_return_unless_modifier_650bfc3134fa92038c335f96f9047860.txt create mode 100644 test/snapshots/parser/case_match_test/test_0001_case_in_69cf275355e6a10adc88e47d5090dd3a.txt create mode 100644 test/snapshots/parser/case_when_test/test_0001_case_when_71d06502a93ad345bf738ba8ace8bf9f.txt create mode 100644 test/snapshots/parser/comments_test/test_0005_HTML_comment_with_if_04813d5ad89aa775065eb87d881ab931.txt create mode 100644 test/snapshots/parser/erb_context_mismatch_test/test_0001_out_of_order_closing_tags_across_ERB_blocks_186de8708f7329b37a560b950c6814bc.txt create mode 100644 test/snapshots/parser/erb_context_mismatch_test/test_0002_conditionally_opening_tag_then_closing_unconditionally_6d7fbfb1a933a2f8aba9be55b52c0012.txt create mode 100644 test/snapshots/parser/erb_context_mismatch_test/test_0003_opening_and_closing_tag_in_different_if_blocks_a6d5461489ddfa85081a5449445cef14.txt create mode 100644 test/snapshots/parser/erb_context_mismatch_test/test_0004_tag_opened_in_elsif_closed_outside_conditional_ff6b90e6b68b015927ad4554dc97fd8e.txt create mode 100644 test/snapshots/parser/erb_context_mismatch_test/test_0005_element_opened_in_block_must_be_closed_within_block_264d7b62538d8f8e318bce1a37dee984.txt create mode 100644 test/snapshots/parser/erb_context_mismatch_test/test_0006_opening_in_one_context_and_closing_in_another_context_23760079615eb0a149447d79f51005bb.txt create mode 100644 test/snapshots/parser/erb_context_mismatch_test/test_0007_stray_closing_tag_without_opening_7435ca180811536625a8df051573f55d.txt create mode 100644 test/snapshots/parser/erb_context_mismatch_test/test_0008_unclosed_tag_without_closing_1ccadf7f0ed2141a178d6f7c5736dc6c.txt create mode 100644 test/snapshots/parser/erb_test/test_0039_TODO_d7de40920545c14e5afcda3a2e9f0eb9.txt create mode 100644 test/snapshots/parser/tags_test/test_0024_should_recover_from_multiple_out_of_order_closing_tags_d55597438f3e5bee8cd21841b672b31a.txt create mode 100644 test/snapshots/parser/tags_test/test_0026_should_recover_from_multiple_void_elements_used_as_closing_tag_7381d2541dc0e90b92193628ef31026f.txt create mode 100644 test/snapshots/parser/yield_test/test_0001_yield_811e738151285db6da8e3a493b8d6e2b.txt create mode 100644 test/snapshots/parser/yield_test/test_0002_yield_with_symbol_6cb3df93a89d085b4d803a26860118c0.txt create mode 100644 test/snapshots/parser/yield_test/test_0003_yield_inside_if_edde5ff3a664fd489f8aaefc285a9ae7.txt create mode 100644 test/snapshots/parser/yield_test/test_0004_yield_inside_block_222b48dd8aa27af5e2f135cd33f583c8.txt create mode 100644 test/snapshots/parser/yield_test/test_0005_yield_inside_case_when_cc5ad76c35f42b0e10034177ccaaff3e.txt create mode 100644 test/test/snapshots/parser/comments_test/test_0005_HTML_comment_with_if_04813d5ad89aa775065eb87d881ab931.txt diff --git a/.clang-format-ignore b/.clang-format-ignore index 7052180e6..933313065 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -1,9 +1,12 @@ ext/herb/error_helpers.c ext/herb/nodes.c +src/analyze_missing_end.c +src/analyze_transform.c src/ast_nodes.c src/ast_pretty_print.c src/errors.c src/include/ast_nodes.h src/include/ast_pretty_print.h src/include/errors.h +src/parser_match_tags.c src/visitor.c diff --git a/.gitignore b/.gitignore index c2c1bde21..0f7accbf7 100644 --- a/.gitignore +++ b/.gitignore @@ -113,12 +113,15 @@ rust/src/errors.rs rust/src/nodes.rs sig/serialized_ast_errors.rbs sig/serialized_ast_nodes.rbs +src/analyze_missing_end.c +src/analyze_transform.c src/ast_nodes.c src/ast_pretty_print.c src/errors.c src/include/ast_nodes.h src/include/ast_pretty_print.h src/include/errors.h +src/parser_match_tags.c src/visitor.c wasm/error_helpers.cpp wasm/error_helpers.h diff --git a/config.yml b/config.yml index 83b8e1d04..14b676882 100644 --- a/config.yml +++ b/config.yml @@ -46,7 +46,7 @@ errors: - name: MissingOpeningTagError message: - template: "Found closing tag `` at (%u:%u) without a matching opening tag." + template: "Found closing tag `` at (%u:%u) without a matching opening tag in the same scope." arguments: - closing_tag->value - closing_tag->location.start.line @@ -58,7 +58,7 @@ errors: - name: MissingClosingTagError message: - template: "Opening tag `<%s>` at (%u:%u) doesn't have a matching closing tag ``." + template: "Opening tag `<%s>` at (%u:%u) doesn't have a matching closing tag `` in the same scope." arguments: - opening_tag->value - opening_tag->location.start.line @@ -151,6 +151,26 @@ errors: - name: level type: string + - name: ERBControlFlowScopeError + message: + template: "%s appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + arguments: + - keyword + + fields: + - name: keyword + type: string + + - name: MissingERBEndTagError + message: + template: "%s started here but never closed with an end tag. The end tag may be in a different scope." + arguments: + - keyword + + fields: + - name: keyword + type: string + warnings: fields: [] types: [] diff --git a/ext/herb/extension.c b/ext/herb/extension.c index 155908ac3..ea0ca7e63 100644 --- a/ext/herb/extension.c +++ b/ext/herb/extension.c @@ -78,6 +78,8 @@ static VALUE Herb_parse_file(VALUE self, VALUE path) { AST_DOCUMENT_NODE_T* root = herb_parse(string, NULL); + herb_analyze_parse_tree(root, string); + VALUE result = create_parse_result(root, source_value); ast_node_free((AST_NODE_T*) root); diff --git a/javascript/packages/formatter/src/format-printer.ts b/javascript/packages/formatter/src/format-printer.ts index 0fc37e41b..ceca30000 100644 --- a/javascript/packages/formatter/src/format-printer.ts +++ b/javascript/packages/formatter/src/format-printer.ts @@ -1056,8 +1056,8 @@ export class FormatPrinter extends Printer { inner = node.children.map(child => { if (isNode(child, HTMLTextNode) || isNode(child, LiteralNode)) { return child.content - } else if (isERBNode(child) || isNode(child, ERBContentNode)) { - return this.reconstructERBNode(child, false) + } else if (isERBNode(child)) { + return IdentityPrinter.print(child) } else { return "" } diff --git a/javascript/packages/linter/test/rules/parser-no-errors.test.ts b/javascript/packages/linter/test/rules/parser-no-errors.test.ts index 5196c72c9..b8d52b03a 100644 --- a/javascript/packages/linter/test/rules/parser-no-errors.test.ts +++ b/javascript/packages/linter/test/rules/parser-no-errors.test.ts @@ -17,10 +17,8 @@ describe("ParserNoErrorsRule", () => { }) test("should report errors for mismatched tag names", () => { - expectError("Tag `

` opened at (3:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)") - expectError("Tag `

` opened at (1:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)") - expectError("Opening tag `

` at (1:1) doesn't have a matching closing tag `

`. (`MISSING_CLOSING_TAG_ERROR`)") - expectError("Opening tag `

` at (3:1) doesn't have a matching closing tag `

`. (`MISSING_CLOSING_TAG_ERROR`)") + expectError("Opening tag `

` at (1:1) doesn't have a matching closing tag `

` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)") + expectError("Opening tag `

` at (3:1) doesn't have a matching closing tag `

` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)") assertOffenses(dedent`

Some content @@ -29,10 +27,7 @@ describe("ParserNoErrorsRule", () => { }) test("should report errors for unclosed elements", () => { - expectError("Tag `

` opened at (2:3) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)") - expectError("Tag `

` opened at (1:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)") - expectError("Opening tag `
` at (1:1) doesn't have a matching closing tag `
`. (`MISSING_CLOSING_TAG_ERROR`)") - expectError("Opening tag `

` at (2:3) closed with `

` at (3:2). (`TAG_NAMES_MISMATCH_ERROR`)") + expectError("Opening tag `

` at (2:3) doesn't have a matching closing tag `

` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)") assertOffenses(dedent`

Some content @@ -46,7 +41,7 @@ describe("ParserNoErrorsRule", () => { }) test("should report errors for missing opening tags", () => { - expectError("Found closing tag `

` at (2:2) without a matching opening tag. (`MISSING_OPENING_TAG_ERROR`)") + expectError("Found closing tag `` at (2:2) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`)") assertOffenses(dedent` Some content @@ -66,10 +61,10 @@ describe("ParserNoErrorsRule", () => { }) test("should report multiple parser errors", () => { - expectError("Tag `

` opened at (2:3) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)") - expectError("Tag `

` opened at (1:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)") - expectError("Opening tag `

` at (2:3) closed with `` at (4:2). (`TAG_NAMES_MISMATCH_ERROR`)") - expectError("Opening tag `

` at (2:3) closed with `

` at (3:2). (`TAG_NAMES_MISMATCH_ERROR`)") + expectError("Opening tag `

` at (1:1) doesn't have a matching closing tag `

` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)") + expectError("Opening tag `

` at (2:3) doesn't have a matching closing tag `

` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)") + expectError("Found closing tag `` at (3:2) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`)") + expectError("Found closing tag `` at (4:2) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`)") assertOffenses(dedent`

Unclosed paragraph @@ -79,8 +74,8 @@ describe("ParserNoErrorsRule", () => { }) test("should work alongside other linting rules", () => { - expectError("Tag `

` opened at (1:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)") - expectError("Opening tag `

` at (1:1) closed with `

` at (3:2). (`TAG_NAMES_MISMATCH_ERROR`)") + expectError("Opening tag `

` at (1:1) doesn't have a matching closing tag `

` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)") + expectError("Found closing tag `` at (3:2) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`)") assertOffenses(dedent`

<% %> @@ -89,10 +84,8 @@ describe("ParserNoErrorsRule", () => { }) test("should include error location information", () => { - expectError("Tag `

` opened at (2:3) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)") - expectError("Tag `
` opened at (1:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)") - expectError("Opening tag `

` at (2:3) closed with `

` at (3:2). (`TAG_NAMES_MISMATCH_ERROR`)") - expectError("Opening tag `

` at (2:3) closed with `

` at (2:15). (`TAG_NAMES_MISMATCH_ERROR`)") + expectError("Opening tag `

` at (2:3) doesn't have a matching closing tag `

` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)") + expectError("Found closing tag `` at (2:15) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`)") assertOffenses(dedent`

Content

@@ -101,10 +94,8 @@ describe("ParserNoErrorsRule", () => { }) test("should handle the specific case from issue #359", () => { - expectError("Tag `

` opened at (1:25) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)") - expectError("Tag `

` opened at (1:1) was never closed before the end of document. (`UNCLOSED_ELEMENT_ERROR`)") - expectError("Opening tag `

` at (1:1) doesn't have a matching closing tag `

`. (`MISSING_CLOSING_TAG_ERROR`)") - expectError("Opening tag `

` at (1:25) doesn't have a matching closing tag `

`. (`MISSING_CLOSING_TAG_ERROR`)") + expectError("Opening tag `

` at (1:1) doesn't have a matching closing tag `

` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)") + expectError("Opening tag `

` at (1:25) doesn't have a matching closing tag `

` in the same scope. (`MISSING_CLOSING_TAG_ERROR`)") assertOffenses(dedent`

Some heading content

`) diff --git a/javascript/packages/node/binding.gyp b/javascript/packages/node/binding.gyp index 70a26a7c1..4df3694f8 100644 --- a/javascript/packages/node/binding.gyp +++ b/javascript/packages/node/binding.gyp @@ -11,6 +11,8 @@ # Herb main source files "./extension/libherb/analyze_helpers.c", + "./extension/libherb/analyze_missing_end.c", + "./extension/libherb/analyze_transform.c", "./extension/libherb/analyze.c", "./extension/libherb/analyzed_ruby.c", "./extension/libherb/ast_node.c", @@ -26,6 +28,7 @@ "./extension/libherb/lexer.c", "./extension/libherb/location.c", "./extension/libherb/parser_helpers.c", + "./extension/libherb/parser_match_tags.c", "./extension/libherb/parser.c", "./extension/libherb/pretty_print.c", "./extension/libherb/prism_helpers.c", diff --git a/rust/tests/error_handling_test.rs b/rust/tests/error_handling_test.rs index 7617f230d..777f9c4c6 100644 --- a/rust/tests/error_handling_test.rs +++ b/rust/tests/error_handling_test.rs @@ -10,10 +10,9 @@ fn test_unclosed_element_error() { let result = parse(source).unwrap(); let tree_inspect = result.inspect(); - assert!(tree_inspect.contains("UnclosedElementError")); - assert!(tree_inspect.contains("Tag `
` opened at (1:1) was never closed")); + assert!(tree_inspect.contains("MissingClosingTagError")); - assert!(tree_inspect.contains("Opening tag `
` at (1:1) doesn't have a matching closing tag")); + assert!(tree_inspect.contains("Opening tag `
` at (1:1) doesn't have a matching closing")); } #[test] @@ -22,10 +21,19 @@ fn test_tag_names_mismatch_error() { let source = "
"; let result = parse(source).unwrap(); - let tree_inspect = result.inspect(); - assert!(tree_inspect.contains("TagNamesMismatchError")); - assert!(tree_inspect.contains("Opening tag `
` at (1:1) closed with ``")); + + print!("{}", tree_inspect); + + assert!(tree_inspect.contains("MissingClosingTagError (location: (1:0)-(1:5))")); + assert!(tree_inspect.contains( + "Opening tag `
` at (1:1) doesn't have a matching closing tag `
` in the same scope." + )); + + assert!(tree_inspect.contains("MissingOpeningTagError (location: (1:5)-(1:12))")); + assert!(tree_inspect.contains( + "Found closing tag `` at (1:7) without a matching opening tag in the same scope." + )); } #[test] @@ -34,8 +42,8 @@ fn test_no_errors_with_valid_html() { let source = "
Hello
"; let result = parse(source).unwrap(); - let tree_inspect = result.inspect(); + assert!(!tree_inspect.contains("error")); assert!(!tree_inspect.contains("ERROR")); } diff --git a/rust/tests/parse_result_test.rs b/rust/tests/parse_result_test.rs index 7f35d8c85..f87fd2869 100644 --- a/rust/tests/parse_result_test.rs +++ b/rust/tests/parse_result_test.rs @@ -49,10 +49,6 @@ fn test_parse_result_failed_with_unclosed_element() { let errors = result.recursive_errors(); - assert!(errors - .iter() - .any(|e| e.error_type() == "UNCLOSED_ELEMENT_ERROR")); - assert!(errors .iter() .any(|e| e.error_type() == "MISSING_CLOSING_TAG_ERROR")); @@ -81,7 +77,11 @@ fn test_parse_result_failed_with_tag_mismatch() { assert!(errors .iter() - .any(|e| e.error_type() == "TAG_NAMES_MISMATCH_ERROR")); + .any(|e| e.error_type() == "MISSING_OPENING_TAG_ERROR")); + + assert!(errors + .iter() + .any(|e| e.error_type() == "MISSING_CLOSING_TAG_ERROR")); } #[test] diff --git a/sig/herb/errors.rbs b/sig/herb/errors.rbs index 6ae9679f6..28ba337a5 100644 --- a/sig/herb/errors.rbs +++ b/sig/herb/errors.rbs @@ -207,5 +207,41 @@ module Herb # : (?Integer) -> String def tree_inspect: (?Integer) -> String end + + class ERBControlFlowScopeError < Error + include Colors + + attr_reader keyword: String + + # : (String, Location, String, String) -> void + def initialize: (String, Location, String, String) -> void + + # : () -> String + def inspect: () -> String + + # : () -> serialized_erb_control_flow_scope_error + def to_hash: () -> serialized_erb_control_flow_scope_error + + # : (?Integer) -> String + def tree_inspect: (?Integer) -> String + end + + class MissingERBEndTagError < Error + include Colors + + attr_reader keyword: String + + # : (String, Location, String, String) -> void + def initialize: (String, Location, String, String) -> void + + # : () -> String + def inspect: () -> String + + # : () -> serialized_missingerb_end_tag_error + def to_hash: () -> serialized_missingerb_end_tag_error + + # : (?Integer) -> String + def tree_inspect: (?Integer) -> String + end end end diff --git a/src/analyze.c b/src/analyze.c index 6e83f117e..348242453 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -6,6 +6,7 @@ #include "include/errors.h" #include "include/extract.h" #include "include/location.h" +#include "include/parser.h" #include "include/position.h" #include "include/pretty_print.h" #include "include/prism_helpers.h" @@ -1057,7 +1058,7 @@ static size_t process_block_children( return index; } -static hb_array_T* rewrite_node_array(AST_NODE_T* node, hb_array_T* array, analyze_ruby_context_T* context) { +hb_array_T* rewrite_node_array(AST_NODE_T* node, hb_array_T* array, analyze_ruby_context_T* context) { hb_array_T* new_array = hb_array_init(hb_array_size(array)); size_t index = 0; @@ -1112,41 +1113,178 @@ static hb_array_T* rewrite_node_array(AST_NODE_T* node, hb_array_T* array, analy return new_array; } -static bool transform_erb_nodes(const AST_NODE_T* node, void* data) { - analyze_ruby_context_T* context = (analyze_ruby_context_T*) data; - context->parent = (AST_NODE_T*) node; +static bool detect_invalid_erb_structures(const AST_NODE_T* node, void* data) { + invalid_erb_context_T* context = (invalid_erb_context_T*) data; - if (node->type == AST_DOCUMENT_NODE) { - AST_DOCUMENT_NODE_T* document_node = (AST_DOCUMENT_NODE_T*) node; - hb_array_T* old_array = document_node->children; - document_node->children = rewrite_node_array((AST_NODE_T*) node, document_node->children, context); - hb_array_free(&old_array); + if (node->type == AST_HTML_ATTRIBUTE_NAME_NODE) { return false; } + + bool is_loop_node = + (node->type == AST_ERB_WHILE_NODE || node->type == AST_ERB_UNTIL_NODE || node->type == AST_ERB_FOR_NODE + || node->type == AST_ERB_BLOCK_NODE); + + bool is_begin_node = (node->type == AST_ERB_BEGIN_NODE); + + if (is_loop_node) { context->loop_depth++; } + + if (is_begin_node) { context->rescue_depth++; } + + if (node->type == AST_ERB_CONTENT_NODE) { + const AST_ERB_CONTENT_NODE_T* content_node = (const AST_ERB_CONTENT_NODE_T*) node; + + if (content_node->parsed && !content_node->valid && content_node->analyzed_ruby != NULL) { + analyzed_ruby_T* analyzed = content_node->analyzed_ruby; + + // =begin + if (has_error_message(analyzed, "embedded document meets end of file")) { + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } + + return true; + } + + // =end + if (has_error_message(analyzed, "unexpected '=', ignoring it") + && has_error_message(analyzed, "unexpected 'end', ignoring it")) { + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } + + return true; + } + + const char* keyword = NULL; + + if (context->loop_depth == 0) { + if (has_error_message(analyzed, "Invalid break")) { + keyword = "`<% break %>`"; + } else if (has_error_message(analyzed, "Invalid next")) { + keyword = "`<% next %>`"; + } else if (has_error_message(analyzed, "Invalid redo")) { + keyword = "`<% redo %>`"; + } + } else { + if (has_error_message(analyzed, "Invalid redo") || has_error_message(analyzed, "Invalid break") + || has_error_message(analyzed, "Invalid next")) { + + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } + + return true; + } + } + + if (context->rescue_depth == 0) { + if (has_error_message(analyzed, "Invalid retry without rescue")) { keyword = "`<% retry %>`"; } + } else { + if (has_error_message(analyzed, "Invalid retry without rescue")) { + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } + + return true; + } + } + + if (keyword == NULL) { keyword = erb_keyword_from_analyzed_ruby(analyzed); } + + if (keyword != NULL && !token_value_empty(content_node->tag_closing)) { + append_erb_control_flow_scope_error(keyword, node->location.start, node->location.end, node->errors); + } + } } - if (node->type == AST_HTML_ELEMENT_NODE) { - AST_HTML_ELEMENT_NODE_T* element_node = (AST_HTML_ELEMENT_NODE_T*) node; - hb_array_T* old_array = element_node->body; - element_node->body = rewrite_node_array((AST_NODE_T*) node, element_node->body, context); - hb_array_free(&old_array); + if (node->type == AST_ERB_IF_NODE) { + const AST_ERB_IF_NODE_T* if_node = (const AST_ERB_IF_NODE_T*) node; + + if (if_node->end_node == NULL) { check_erb_node_for_missing_end(node); } + + if (if_node->statements != NULL) { + for (size_t i = 0; i < hb_array_size(if_node->statements); i++) { + AST_NODE_T* statement = (AST_NODE_T*) hb_array_get(if_node->statements, i); + + if (statement != NULL) { herb_visit_node(statement, detect_invalid_erb_structures, context); } + } + } + + AST_NODE_T* subsequent = if_node->subsequent; + + while (subsequent != NULL) { + if (subsequent->type == AST_ERB_CONTENT_NODE) { + const AST_ERB_CONTENT_NODE_T* content_node = (const AST_ERB_CONTENT_NODE_T*) subsequent; + + if (content_node->parsed && !content_node->valid && content_node->analyzed_ruby != NULL) { + analyzed_ruby_T* analyzed = content_node->analyzed_ruby; + const char* keyword = erb_keyword_from_analyzed_ruby(analyzed); + + if (!token_value_empty(content_node->tag_closing)) { + append_erb_control_flow_scope_error( + keyword, + subsequent->location.start, + subsequent->location.end, + subsequent->errors + ); + } + } + } + + if (subsequent->type == AST_ERB_IF_NODE) { + const AST_ERB_IF_NODE_T* elsif_node = (const AST_ERB_IF_NODE_T*) subsequent; + + if (elsif_node->statements != NULL) { + for (size_t i = 0; i < hb_array_size(elsif_node->statements); i++) { + AST_NODE_T* statement = (AST_NODE_T*) hb_array_get(elsif_node->statements, i); + + if (statement != NULL) { herb_visit_node(statement, detect_invalid_erb_structures, context); } + } + } + + subsequent = elsif_node->subsequent; + } else if (subsequent->type == AST_ERB_ELSE_NODE) { + const AST_ERB_ELSE_NODE_T* else_node = (const AST_ERB_ELSE_NODE_T*) subsequent; + + if (else_node->statements != NULL) { + for (size_t i = 0; i < hb_array_size(else_node->statements); i++) { + AST_NODE_T* statement = (AST_NODE_T*) hb_array_get(else_node->statements, i); + + if (statement != NULL) { herb_visit_node(statement, detect_invalid_erb_structures, context); } + } + } + + break; + } else { + break; + } + } } - if (node->type == AST_HTML_OPEN_TAG_NODE) { - AST_HTML_OPEN_TAG_NODE_T* open_tag = (AST_HTML_OPEN_TAG_NODE_T*) node; - hb_array_T* old_array = open_tag->children; - open_tag->children = rewrite_node_array((AST_NODE_T*) node, open_tag->children, context); - hb_array_free(&old_array); + if (node->type == AST_ERB_UNLESS_NODE || node->type == AST_ERB_WHILE_NODE || node->type == AST_ERB_UNTIL_NODE + || node->type == AST_ERB_FOR_NODE || node->type == AST_ERB_CASE_NODE || node->type == AST_ERB_CASE_MATCH_NODE + || node->type == AST_ERB_BEGIN_NODE || node->type == AST_ERB_BLOCK_NODE || node->type == AST_ERB_ELSE_NODE) { + herb_visit_child_nodes(node, detect_invalid_erb_structures, context); } - if (node->type == AST_HTML_ATTRIBUTE_VALUE_NODE) { - AST_HTML_ATTRIBUTE_VALUE_NODE_T* value_node = (AST_HTML_ATTRIBUTE_VALUE_NODE_T*) node; - hb_array_T* old_array = value_node->children; - value_node->children = rewrite_node_array((AST_NODE_T*) node, value_node->children, context); - hb_array_free(&old_array); + if (node->type == AST_ERB_UNLESS_NODE || node->type == AST_ERB_WHILE_NODE || node->type == AST_ERB_UNTIL_NODE + || node->type == AST_ERB_FOR_NODE || node->type == AST_ERB_CASE_NODE || node->type == AST_ERB_CASE_MATCH_NODE + || node->type == AST_ERB_BEGIN_NODE || node->type == AST_ERB_BLOCK_NODE || node->type == AST_ERB_ELSE_NODE) { + check_erb_node_for_missing_end(node); + + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } + + return false; } - herb_visit_child_nodes(node, transform_erb_nodes, data); + if (node->type == AST_ERB_IF_NODE) { + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } - return false; + return false; + } + + bool result = true; + + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } + + return result; } void herb_analyze_parse_tree(AST_DOCUMENT_NODE_T* document, const char* source) { @@ -1159,10 +1297,20 @@ void herb_analyze_parse_tree(AST_DOCUMENT_NODE_T* document, const char* source) herb_visit_node((AST_NODE_T*) document, transform_erb_nodes, context); + invalid_erb_context_T* invalid_context = malloc(sizeof(invalid_erb_context_T)); + invalid_context->loop_depth = 0; + invalid_context->rescue_depth = 0; + + herb_visit_node((AST_NODE_T*) document, detect_invalid_erb_structures, invalid_context); + herb_analyze_parse_errors(document, source); + herb_parser_match_html_tags_post_analyze(document); + hb_array_free(&context->ruby_context_stack); + free(context); + free(invalid_context); } void herb_analyze_parse_errors(AST_DOCUMENT_NODE_T* document, const char* source) { diff --git a/src/analyze_helpers.c b/src/analyze_helpers.c index 576a10b9d..36117367c 100644 --- a/src/analyze_helpers.c +++ b/src/analyze_helpers.c @@ -238,6 +238,11 @@ bool search_else_nodes(analyzed_ruby_T* analyzed) { bool search_end_nodes(analyzed_ruby_T* analyzed) { if (has_error_message(analyzed, "unexpected 'end', ignoring it")) { + if (has_error_message(analyzed, "unexpected '=', ignoring it")) { + // `=end` + return false; + } + analyzed->has_end = true; return true; } diff --git a/src/analyzed_ruby.c b/src/analyzed_ruby.c index 10e5f585f..6840b8a10 100644 --- a/src/analyzed_ruby.c +++ b/src/analyzed_ruby.c @@ -43,3 +43,23 @@ void free_analyzed_ruby(analyzed_ruby_T* analyzed) { free(analyzed); } + +const char* erb_keyword_from_analyzed_ruby(const analyzed_ruby_T* analyzed) { + if (analyzed->has_end) { + return "`<% end %>`"; + } else if (analyzed->has_else_node) { + return "`<% else %>`"; + } else if (analyzed->has_elsif_node) { + return "`<% elsif %>`"; + } else if (analyzed->has_when_node) { + return "`<% when %>`"; + } else if (analyzed->has_in_node) { + return "`<% in %>`"; + } else if (analyzed->has_rescue_node) { + return "`<% rescue %>`"; + } else if (analyzed->has_ensure_node) { + return "`<% ensure %>`"; + } + + return NULL; +} diff --git a/src/include/analyze.h b/src/include/analyze.h index d6f5a1049..154349f06 100644 --- a/src/include/analyze.h +++ b/src/include/analyze.h @@ -33,7 +33,15 @@ typedef enum { CONTROL_TYPE_UNKNOWN } control_type_t; +typedef struct { + int loop_depth; + int rescue_depth; +} invalid_erb_context_T; + void herb_analyze_parse_errors(AST_DOCUMENT_NODE_T* document, const char* source); void herb_analyze_parse_tree(AST_DOCUMENT_NODE_T* document, const char* source); +hb_array_T* rewrite_node_array(AST_NODE_T* node, hb_array_T* array, analyze_ruby_context_T* context); +bool transform_erb_nodes(const AST_NODE_T* node, void* data); + #endif diff --git a/src/include/analyze_helpers.h b/src/include/analyze_helpers.h index 95a433982..457e9d193 100644 --- a/src/include/analyze_helpers.h +++ b/src/include/analyze_helpers.h @@ -5,6 +5,7 @@ #include #include "analyzed_ruby.h" +#include "ast_node.h" bool has_if_node(analyzed_ruby_T* analyzed); bool has_elsif_node(analyzed_ruby_T* analyzed); @@ -46,4 +47,6 @@ bool search_rescue_nodes(analyzed_ruby_T* analyzed); bool search_ensure_nodes(analyzed_ruby_T* analyzed); bool search_yield_nodes(const pm_node_t* node, void* data); +void check_erb_node_for_missing_end(const AST_NODE_T* node); + #endif diff --git a/src/include/analyzed_ruby.h b/src/include/analyzed_ruby.h index fc31037af..e6b93cf10 100644 --- a/src/include/analyzed_ruby.h +++ b/src/include/analyzed_ruby.h @@ -33,5 +33,6 @@ typedef struct ANALYZED_RUBY_STRUCT { analyzed_ruby_T* init_analyzed_ruby(hb_string_T source); void free_analyzed_ruby(analyzed_ruby_T* analyzed); +const char* erb_keyword_from_analyzed_ruby(const analyzed_ruby_T* analyzed); #endif diff --git a/src/include/parser.h b/src/include/parser.h index 4eb92f3fb..1ce8b496d 100644 --- a/src/include/parser.h +++ b/src/include/parser.h @@ -30,12 +30,16 @@ typedef struct PARSER_STRUCT { parser_options_T options; } parser_T; +size_t parser_sizeof(void); + void herb_parser_init(parser_T* parser, lexer_T* lexer, parser_options_T options); AST_DOCUMENT_NODE_T* herb_parser_parse(parser_T* parser); -size_t parser_sizeof(void); - +void herb_parser_match_html_tags_post_analyze(AST_DOCUMENT_NODE_T* document); void herb_parser_deinit(parser_T* parser); +void match_tags_in_node_array(hb_array_T* nodes, hb_array_T* errors); +bool match_tags_visitor(const AST_NODE_T* node, void* data); + #endif diff --git a/src/include/token.h b/src/include/token.h index 0e7133afd..cba31e65f 100644 --- a/src/include/token.h +++ b/src/include/token.h @@ -14,4 +14,6 @@ token_T* token_copy(token_T* token); void token_free(token_T* token); +bool token_value_empty(const token_T* token); + #endif diff --git a/src/parser.c b/src/parser.c index 3cc25791a..252f6dfd1 100644 --- a/src/parser.c +++ b/src/parser.c @@ -12,6 +12,7 @@ #include "include/util/hb_array.h" #include "include/util/hb_buffer.h" #include "include/util/hb_string.h" +#include "include/visitor.h" #include #include @@ -959,35 +960,24 @@ static AST_HTML_ELEMENT_NODE_T* parser_parse_html_regular_element( ); } -static AST_HTML_ELEMENT_NODE_T* parser_parse_html_element(parser_T* parser) { +static AST_NODE_T* parser_parse_html_element(parser_T* parser) { AST_HTML_OPEN_TAG_NODE_T* open_tag = parser_parse_html_open_tag(parser); // - if (open_tag->is_void) { return parser_parse_html_self_closing_element(parser, open_tag); } + if (open_tag->is_void) { return (AST_NODE_T*) parser_parse_html_self_closing_element(parser, open_tag); } // , in void element list, and not in inside an element if (!open_tag->is_void && is_void_element(hb_string(open_tag->tag_name->value)) && !parser_in_svg_context(parser)) { - return parser_parse_html_self_closing_element(parser, open_tag); + return (AST_NODE_T*) parser_parse_html_self_closing_element(parser, open_tag); } - AST_HTML_ELEMENT_NODE_T* regular_element = parser_parse_html_regular_element(parser, open_tag); - if (regular_element != NULL) { return regular_element; } - - hb_array_T* errors = hb_array_init(8); + if (open_tag->tag_name->value && parser_is_foreign_content_tag(hb_string(open_tag->tag_name->value))) { + AST_HTML_ELEMENT_NODE_T* regular_element = parser_parse_html_regular_element(parser, open_tag); - parser_append_unexpected_error(parser, "Unknown HTML open tag type", "HTMLOpenTag or HTMLSelfCloseTag", errors); + if (regular_element != NULL) { return (AST_NODE_T*) regular_element; } + } - return ast_html_element_node_init( - open_tag, - open_tag->tag_name, - NULL, - NULL, - false, - ELEMENT_SOURCE_HTML, - open_tag->base.location.start, - open_tag->base.location.end, - errors - ); + return (AST_NODE_T*) open_tag; } static AST_ERB_CONTENT_NODE_T* parser_parse_erb_tag(parser_T* parser) { @@ -1077,7 +1067,8 @@ static void parser_parse_foreign_content(parser_T* parser, hb_array_T* children, } static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, hb_array_T* errors) { - while (token_is_none_of(parser, TOKEN_HTML_TAG_START_CLOSE, TOKEN_EOF)) { + while (token_is_not(parser, TOKEN_EOF)) { + if (token_is(parser, TOKEN_ERB_START)) { hb_array_append(children, parser_parse_erb_tag(parser)); continue; @@ -1108,6 +1099,11 @@ static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, h continue; } + if (token_is(parser, TOKEN_HTML_TAG_START_CLOSE)) { + hb_array_append(children, parser_parse_html_close_tag(parser)); + continue; + } + if (token_is_any_of( parser, TOKEN_AMPERSAND, @@ -1143,47 +1139,107 @@ static void parser_parse_in_data_state(parser_T* parser, hb_array_T* children, h } } -static void parser_parse_unclosed_html_tags(const parser_T* parser, hb_array_T* errors) { - while (hb_array_size(parser->open_tags_stack) > 0) { - token_T* unclosed_tag = parser_pop_open_tag(parser); +static size_t find_matching_close_tag(hb_array_T* nodes, size_t start_idx, hb_string_T tag_name) { + int depth = 0; - append_unclosed_element_error( - unclosed_tag, - parser->current_token->location.start, - parser->current_token->location.end, - errors - ); + for (size_t i = start_idx + 1; i < hb_array_size(nodes); i++) { + AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, i); + if (node == NULL) { continue; } + + if (node->type == AST_HTML_OPEN_TAG_NODE) { + AST_HTML_OPEN_TAG_NODE_T* open = (AST_HTML_OPEN_TAG_NODE_T*) node; - token_free(unclosed_tag); + if (hb_string_equals(hb_string(open->tag_name->value), tag_name)) { depth++; } + } else if (node->type == AST_HTML_CLOSE_TAG_NODE) { + AST_HTML_CLOSE_TAG_NODE_T* close = (AST_HTML_CLOSE_TAG_NODE_T*) node; + + if (hb_string_equals(hb_string(close->tag_name->value), tag_name)) { + if (depth == 0) { return i; } + depth--; + } + } } + + return (size_t) -1; } -static void parser_parse_stray_closing_tags(parser_T* parser, hb_array_T* children, hb_array_T* errors) { - while (token_is_not(parser, TOKEN_EOF)) { - if (token_is_not(parser, TOKEN_HTML_TAG_START_CLOSE)) { - parser_append_unexpected_token_error(parser, TOKEN_HTML_TAG_START_CLOSE, errors); +static hb_array_T* parser_build_elements_from_tags(hb_array_T* nodes, hb_array_T* errors); - token_T* unexpected = parser_advance(parser); - token_free(unexpected); +static hb_array_T* parser_build_elements_from_tags(hb_array_T* nodes, hb_array_T* errors) { + hb_array_T* result = hb_array_init(hb_array_size(nodes)); - continue; - } + for (size_t index = 0; index < hb_array_size(nodes); index++) { + AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, index); + if (node == NULL) { continue; } - AST_HTML_CLOSE_TAG_NODE_T* close_tag = parser_parse_html_close_tag(parser); + if (node->type == AST_HTML_OPEN_TAG_NODE) { + AST_HTML_OPEN_TAG_NODE_T* open_tag = (AST_HTML_OPEN_TAG_NODE_T*) node; + hb_string_T tag_name = hb_string(open_tag->tag_name->value); - if (!is_void_element(hb_string(close_tag->tag_name->value))) { - append_missing_opening_tag_error( - close_tag->tag_name, - close_tag->base.location.start, - close_tag->base.location.end, - close_tag->base.errors - ); - } + size_t close_index = find_matching_close_tag(nodes, index, tag_name); + + if (close_index == (size_t) -1) { + if (hb_array_size(open_tag->base.errors) == 0) { + append_missing_closing_tag_error( + open_tag->tag_name, + open_tag->base.location.start, + open_tag->base.location.end, + open_tag->base.errors + ); + } + + hb_array_append(result, node); + } else { + AST_HTML_CLOSE_TAG_NODE_T* close_tag = (AST_HTML_CLOSE_TAG_NODE_T*) hb_array_get(nodes, close_index); + + hb_array_T* body = hb_array_init(close_index - index - 1); + + for (size_t j = index + 1; j < close_index; j++) { + hb_array_append(body, hb_array_get(nodes, j)); + } - hb_array_append(children, close_tag); + hb_array_T* processed_body = parser_build_elements_from_tags(body, errors); + hb_array_free(&body); - parser_parse_in_data_state(parser, children, errors); + hb_array_T* element_errors = hb_array_init(8); + + AST_HTML_ELEMENT_NODE_T* element = ast_html_element_node_init( + open_tag, + open_tag->tag_name, + processed_body, + close_tag, + false, + ELEMENT_SOURCE_HTML, + open_tag->base.location.start, + close_tag->base.location.end, + element_errors + ); + + hb_array_append(result, element); + + index = close_index; + } + } else if (node->type == AST_HTML_CLOSE_TAG_NODE) { + AST_HTML_CLOSE_TAG_NODE_T* close_tag = (AST_HTML_CLOSE_TAG_NODE_T*) node; + + if (!is_void_element(hb_string(close_tag->tag_name->value))) { + if (hb_array_size(close_tag->base.errors) == 0) { + append_missing_opening_tag_error( + close_tag->tag_name, + close_tag->base.location.start, + close_tag->base.location.end, + close_tag->base.errors + ); + } + } + + hb_array_append(result, node); + } else { + hb_array_append(result, node); + } } + + return result; } static AST_DOCUMENT_NODE_T* parser_parse_document(parser_T* parser) { @@ -1192,8 +1248,6 @@ static AST_DOCUMENT_NODE_T* parser_parse_document(parser_T* parser) { position_T start = parser->current_token->location.start; parser_parse_in_data_state(parser, children, errors); - parser_parse_unclosed_html_tags(parser, errors); - parser_parse_stray_closing_tags(parser, children, errors); token_T* eof = parser_consume_expected(parser, TOKEN_EOF, errors); @@ -1241,3 +1295,32 @@ void herb_parser_deinit(parser_T* parser) { if (parser->current_token != NULL) { token_free(parser->current_token); } if (parser->open_tags_stack != NULL) { hb_array_free(&parser->open_tags_stack); } } + +void match_tags_in_node_array(hb_array_T* nodes, hb_array_T* errors) { + if (nodes == NULL || hb_array_size(nodes) == 0) { return; } + + hb_array_T* processed = parser_build_elements_from_tags(nodes, errors); + + while (hb_array_size(nodes) > 0) { + hb_array_remove(nodes, 0); + } + + for (size_t i = 0; i < hb_array_size(processed); i++) { + hb_array_append(nodes, hb_array_get(processed, i)); + } + + hb_array_free(&processed); + + for (size_t i = 0; i < hb_array_size(nodes); i++) { + AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, i); + if (node == NULL) { continue; } + + herb_visit_node(node, match_tags_visitor, errors); + } +} + +void herb_parser_match_html_tags_post_analyze(AST_DOCUMENT_NODE_T* document) { + if (document == NULL) { return; } + + match_tags_in_node_array(document->children, document->base.errors); +} diff --git a/src/token.c b/src/token.c index 157e64e04..42c62bcac 100644 --- a/src/token.c +++ b/src/token.c @@ -5,6 +5,7 @@ #include "include/token_struct.h" #include "include/util.h" +#include #include #include #include @@ -140,6 +141,10 @@ token_T* token_copy(token_T* token) { return new_token; } +bool token_value_empty(const token_T* token) { + return token == NULL || token->value == NULL || token->value[0] == '\0'; +} + void token_free(token_T* token) { if (!token) { return; } diff --git a/templates/src/analyze_missing_end.c.erb b/templates/src/analyze_missing_end.c.erb new file mode 100644 index 000000000..291b89e4d --- /dev/null +++ b/templates/src/analyze_missing_end.c.erb @@ -0,0 +1,36 @@ +#include "include/analyze_helpers.h" +#include "include/errors.h" + +<%- + nodes_with_end_node = nodes.select do |node| + node.fields.any? { |field| field.name == "end_node" && field.is_a?(Herb::Template::NodeField) } + end +-%> + +void check_erb_node_for_missing_end(const AST_NODE_T* node) { + switch (node->type) { + <%- nodes_with_end_node.each do |node| -%> + <%- keyword = node.name.gsub(/^ERB/, '').gsub(/Match|Node$/, '').downcase -%> + case <%= node.type %>: { + const <%= node.struct_type %>* <%= node.human %> = (const <%= node.struct_type %>*) node; + + if (<%= node.human %>->end_node == NULL) { + append_missingerb_end_tag_error( + <%- if keyword == "block" -%> + "ERB block", + <%- else -%> + "`<" "%" " <%= keyword %> " "%" ">`", + <%- end -%> + <%= node.human %>->tag_opening->location.start, + <%= node.human %>->tag_closing->location.end, + node->errors + ); + } + + break; + } + + <%- end -%> + default: break; + } +} diff --git a/templates/src/analyze_transform.c.erb b/templates/src/analyze_transform.c.erb new file mode 100644 index 000000000..f77eb2b96 --- /dev/null +++ b/templates/src/analyze_transform.c.erb @@ -0,0 +1,24 @@ +#include "include/analyze.h" +#include "include/visitor.h" + +bool transform_erb_nodes(const AST_NODE_T* node, void* data) { + analyze_ruby_context_T* context = (analyze_ruby_context_T*) data; + context->parent = (AST_NODE_T*) node; + + <%- nodes.each do |node| -%> + <%- node.fields.each do |field| -%> + <%- if field.is_a?(Herb::Template::ArrayField) && field.name != "errors" -%> + if (node->type == <%= node.type %>) { + <%= node.struct_type %>* <%= node.human %> = (<%= node.struct_type %>*) node; + hb_array_T* old_array = <%= node.human %>-><%= field.name %>; + <%= node.human %>-><%= field.name %> = rewrite_node_array((AST_NODE_T*) node, <%= node.human %>-><%= field.name %>, context); + hb_array_free(&old_array); + } + + <%- end -%> + <%- end -%> + <%- end -%> + herb_visit_child_nodes(node, transform_erb_nodes, data); + + return false; +} diff --git a/templates/src/parser_match_tags.c.erb b/templates/src/parser_match_tags.c.erb new file mode 100644 index 000000000..120706dc1 --- /dev/null +++ b/templates/src/parser_match_tags.c.erb @@ -0,0 +1,31 @@ +#include "include/parser.h" +#include "include/ast_nodes.h" +#include "include/util/hb_array.h" +#include "include/visitor.h" + +bool match_tags_visitor(const AST_NODE_T* node, void* data) { + hb_array_T* errors = (hb_array_T*) data; + + if (node == NULL) { return false; } + + switch (node->type) { + <%- nodes.each do |node| -%> + <%- array_fields = node.fields.select { |f| f.is_a?(Herb::Template::ArrayField) && f.name != "errors" } -%> + <%- if array_fields.any? -%> + case <%= node.type %>: { + const <%= node.struct_type %>* <%= node.human %> = (const <%= node.struct_type %>*) node; + + <%- array_fields.each do |field| -%> + if (<%= node.human %>-><%= field.name %> != NULL) { + match_tags_in_node_array(<%= node.human %>-><%= field.name %>, errors); + } + <%- end -%> + } break; + + <%- end -%> + <%- end -%> + default: break; + } + + return true; +} diff --git a/test/analyze/case_in_test.rb b/test/analyze/case_in_test.rb index 8fb1a25ac..5fe13a1d4 100644 --- a/test/analyze/case_in_test.rb +++ b/test/analyze/case_in_test.rb @@ -68,8 +68,6 @@ class CaseInTest < Minitest::Spec end test "nested cases ins" do - skip - assert_parsed_snapshot(<<~HTML) <% case variable %> <% in [String] %> diff --git a/test/analyze/case_test.rb b/test/analyze/case_test.rb index a4b7bf5d4..d76b6b7aa 100644 --- a/test/analyze/case_test.rb +++ b/test/analyze/case_test.rb @@ -66,8 +66,6 @@ class CaseTest < Minitest::Spec end test "nested cases" do - skip - assert_parsed_snapshot(<<~HTML) <% case variable %> <% when String %> diff --git a/test/analyze/if_test.rb b/test/analyze/if_test.rb index dc890e23e..7902ffc18 100644 --- a/test/analyze/if_test.rb +++ b/test/analyze/if_test.rb @@ -104,6 +104,8 @@ class IfTest < Minitest::Spec end test "guard clause with return if modifier" do + skip "we cannot detect MethodDefinitions yet" + assert_parsed_snapshot(<<~HTML) <% def some_method %> <% return if true %> @@ -120,5 +122,27 @@ class IfTest < Minitest::Spec <% end %> HTML end + + test "ERB if/end embedded in attribute name without space" do + skip "<% if valid? %> gets joined to the disabled attribute, but the if contains a space inside the if body" + + assert_parsed_snapshot(<<~HTML) + + HTML + end + + test "conditional attribute value" do + skip + + assert_parsed_snapshot(<<~HTML) + + HTML + end end end diff --git a/test/analyze/invalid_erb_structure_test.rb b/test/analyze/invalid_erb_structure_test.rb new file mode 100644 index 000000000..f7a993e28 --- /dev/null +++ b/test/analyze/invalid_erb_structure_test.rb @@ -0,0 +1,322 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module Analyze + class InvalidERBStructureTest < Minitest::Spec + include SnapshotUtils + + test "tags spanning erb control flow boundaries - opening tag in first if" do + assert_parsed_snapshot(<<~HTML) + <% if condition? %> +
+ <% end %> + + <% if condition? %> +
+ <% end %> + HTML + end + + test "invalid erb structure - else outside scope after tag closing" do + assert_parsed_snapshot(<<~HTML) +
+ class="a" + > + <% else %> + <% end %> +
+ HTML + end + + test "invalid erb structure - end outside scope after tag closing" do + assert_parsed_snapshot(<<~HTML) +
+ class="a" + > + <% end %> +
+ HTML + end + + test "invalid erb structure - elsif outside scope after tag closing" do + assert_parsed_snapshot(<<~HTML) +
+ > + <% elsif other_condition %> + <% end %> +
+ HTML + end + + test "invalid erb structure - else outside scope before tag closing" do + assert_parsed_snapshot(<<~HTML) +
+ class="a" + <% else %> + > + <% end %> + +
+ HTML + end + + test "invalid erb structure - if/else spans across attribute value quotes" do + assert_parsed_snapshot(<<~HTML) +
> + Content +
+ HTML + end + + test "invalid erb structure - multiline attribute value spans across if/else branches" do + assert_parsed_snapshot(<<~HTML) +
+ class="a + <% else %> + b" + <% end %> + > + Content +
+ HTML + end + + test "invalid erb structure - attribute quote closes in if branch with else/end outside" do + assert_parsed_snapshot(<<~HTML) +
+ > + <% end %> + +
+ HTML + end + + test "valid erb structure - if/else/end inside tag attributes" do + assert_parsed_snapshot(<<~HTML) +
+ class="a" + <% else %> + class="b" + <% end %> + >
+ HTML + end + + test "invalid erb structure - when outside case scope" do + assert_parsed_snapshot(<<~HTML) +
+ <% when 1 %> + class="a" + > + <% end %> +
+ HTML + end + + test "invalid erb structure - rescue outside begin scope" do + assert_parsed_snapshot(<<~HTML) +
+ data-value="test" + > + <% rescue %> + <% end %> +
+ HTML + end + + test "invalid erb structure - ensure outside begin scope" do + assert_parsed_snapshot(<<~HTML) +
+ data-value="test" + > + <% ensure %> + <% end %> +
+ HTML + end + + test "tags in different erb scopes - unless blocks" do + assert_parsed_snapshot(<<~HTML) + <% unless disabled? %> +
+ <% end %> + + <% unless enabled? %> +
+ <% end %> + HTML + end + + test "tags in different erb scopes - while blocks" do + assert_parsed_snapshot(<<~HTML) + <% while items.any? %> +
    + <% end %> + + <% while other_items.any? %> +
+ <% end %> + HTML + end + + test "tags in different erb scopes - for blocks" do + assert_parsed_snapshot(<<~HTML) + <% for item in items %> +
+ <% end %> + + <% for item in other_items %> +
+ <% end %> + HTML + end + + test "missing close HTML tag in else clause" do + assert_parsed_snapshot(<<~HTML) +

+ <% if true %> +
Text1
+ <% elsif false %> +
Text2
+ <% else %> +
Text3 + <% end %> +

+ HTML + end + + test "elsif in class attribute value scope" do + assert_parsed_snapshot(<<~HTML) +

class="<% elsif true %>" <% end %>> + Content +

+ HTML + end + + test "conditional attributes without space before ERB closing tag" do + skip "<% end %> shouldn't be treated as the value of disabled" + + assert_parsed_snapshot(<<~HTML) + + HTML + end + + test "conditional attributes with space before ERB closing tag" do + assert_parsed_snapshot(<<~HTML) + + HTML + end + + test "invalid break outside of loop context" do + assert_parsed_snapshot(<<~HTML) +
+ <% break %> +
+ HTML + end + + test "invalid next outside of loop context" do + assert_parsed_snapshot(<<~HTML) +
+ <% next %> +
+ HTML + end + + test "invalid redo outside of loop context" do + assert_parsed_snapshot(<<~HTML) +
+ <% redo %> +
+ HTML + end + + test "invalid retry outside of rescue context" do + assert_parsed_snapshot(<<~HTML) +
+ <% retry %> +
+ HTML + end + + test "invalid break in if statement outside of loop" do + assert_parsed_snapshot(<<~HTML) + <% if condition? %> + <% break %> + <% end %> + HTML + end + + test "invalid next in case statement outside of loop" do + assert_parsed_snapshot(<<~HTML) + <% case value %> + <% when 1 %> + <% next %> + <% end %> + HTML + end + + test "invalid break at document root after while loop" do + assert_parsed_snapshot(<<~HTML) + <% while true %> + content + <% end %> + + <% break %> + HTML + end + + test "loop control keywords in various contexts (retry, break, next, redo)" do + assert_parsed_snapshot(<<~HTML) + <% while true %> + <% retry %> + <% break %> + <% next %> + <% redo %> + <% end %> + + <% loop do %> + <% retry %> + <% break %> + <% next %> + <% redo %> + <% end %> + + <% begin %> + <% retry %> + <% rescue %> + <% retry %> + <% break %> + <% next %> + <% redo %> + <% end %> + + <% break %> + <% next %> + <% retry %> + <% redo %> + HTML + end + end +end diff --git a/test/analyze/unless_test.rb b/test/analyze/unless_test.rb index 8bc0628fb..076575d78 100644 --- a/test/analyze/unless_test.rb +++ b/test/analyze/unless_test.rb @@ -76,6 +76,8 @@ class UnlessTest < Minitest::Spec end test "guard clause with return unless modifier" do + skip "we cannot detect MethodDefinitions yet" + assert_parsed_snapshot(<<~HTML) <% def some_method %> <% return unless condition %> diff --git a/test/engine/error_handling_test.rb b/test/engine/error_handling_test.rb index 96398009c..e7685a323 100644 --- a/test/engine/error_handling_test.rb +++ b/test/engine/error_handling_test.rb @@ -18,9 +18,10 @@ class ErrorHandlingTest < Minitest::Spec end assert_includes error.message, "HTML+ERB Compilation Errors" - assert_includes error.message, "TagNamesMismatch" + assert_includes error.message, "MissingClosingTag" assert_includes error.message, "Opening tag `

`" - assert_includes error.message, "closed with ``" + assert_includes error.message, "MissingOpeningTag" + assert_includes error.message, "Found closing tag ``" end test "unclosed html element" do @@ -36,8 +37,8 @@ class ErrorHandlingTest < Minitest::Spec Herb::Engine.new(template) end - assert_includes error.message, "UnclosedElement" - assert_includes error.message, "never closed before the end of document" + assert_includes error.message, "MissingClosingTag" + assert_includes error.message, "doesn't have a matching closing tag" end test "missing opening tag" do @@ -146,7 +147,10 @@ class ErrorHandlingTest < Minitest::Spec Herb::Engine.new(template, filename: "test_template.erb") end - assert_includes error.message, "TagNamesMismatch: Opening tag `

` at (1:1) closed with `` at (3:2)." + assert_includes error.message, "MissingClosingTag" + assert_includes error.message, "Opening tag `
`" + assert_includes error.message, "MissingOpeningTag" + assert_includes error.message, "Found closing tag ``" end test "multiple errors reported" do @@ -324,5 +328,122 @@ class ErrorHandlingTest < Minitest::Spec assert_includes e.message, "unexpected_token_close_context: unexpected end-of-input, assuming it is closing the parent top level context" end end + + test "tags spanning erb control flow boundaries" do + template = <<~ERB + <% if condition? %> +
+ <% end %> + + <% if condition? %> +
+ <% end %> + ERB + + error = assert_raises(Herb::Engine::CompilationError) do + Herb::Engine.new(template) + end + + assert_includes error.message, "MissingClosingTag" + assert_includes error.message, "Opening tag `
`" + assert_includes error.message, "MissingOpeningTag" + assert_includes error.message, "Found closing tag `
`" + end + + test "invalid erb control flow structure - else outside scope" do + template = <<~ERB +
+ class="a" + > + <% else %> + <% end %> +
+ ERB + + error = assert_raises(Herb::Engine::CompilationError) do + Herb::Engine.new(template) + end + + assert_includes error.message, "ERBControlFlowScope" + assert_includes error.message, "`<% else %>`" + assert_includes error.message, "outside its control flow block" + assert_includes error.message, "Keep ERB control flow statements together" + end + + test "invalid erb control flow structure - end outside scope" do + template = <<~ERB +
+ class="a" + > + <% else %> + <% end %> +
+ ERB + + error = assert_raises(Herb::Engine::CompilationError) do + Herb::Engine.new(template) + end + + assert_includes error.message, "ERBControlFlowScope" + assert_includes error.message, "`<% end %>`" + assert_includes error.message, "outside its control flow block" + end + + test "invalid erb control flow structure - elsif outside scope" do + template = <<~ERB +
+ > + <% elsif other_condition %> + <% end %> +
+ ERB + + error = assert_raises(Herb::Engine::CompilationError) do + Herb::Engine.new(template) + end + + assert_includes error.message, "ERBControlFlowScope" + assert_includes error.message, "`<% elsif %>`" + end + + test "invalid erb structure - else outside scope before tag closing" do + template = <<~ERB +
+ class="a" + <% else %> + > + <% end %> + +
+ ERB + + error = assert_raises(Herb::Engine::CompilationError) do + Herb::Engine.new(template) + end + + assert_includes error.message, "ERBControlFlowScope" + assert_includes error.message, "`<% end %>`" + end + + test "valid erb structure - if/else/end inside tag attributes" do + template = <<~ERB +
+ class="a" + <% else %> + class="b" + <% end %> + >
+ ERB + + engine = Herb::Engine.new(template) + + assert_instance_of Herb::Engine, engine + assert_instance_of String, engine.src + end end end diff --git a/test/parser/case_match_test.rb b/test/parser/case_match_test.rb new file mode 100644 index 000000000..f074851c7 --- /dev/null +++ b/test/parser/case_match_test.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module Parser + class CaseMatchTest < Minitest::Spec + include SnapshotUtils + + test "case/in" do + assert_parsed_snapshot(<<~HTML) + <% case { hash: { nested: '4' } } %> + children + <% in { hash: { nested: } } %> + nested + <% else %> + else + <% end %> + HTML + end + end +end diff --git a/test/parser/case_when_test.rb b/test/parser/case_when_test.rb new file mode 100644 index 000000000..fe279ba8b --- /dev/null +++ b/test/parser/case_when_test.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module Parser + class CaseWhenTest < Minitest::Spec + include SnapshotUtils + + test "case/when" do + assert_parsed_snapshot(<<~HTML) + <% case variable %> +

Children

+ <% when Integer %> +

Integer

+ <% when String %> +

String

+ <% else %> +

else

+ <% end %> + HTML + end + end +end diff --git a/test/parser/comments_test.rb b/test/parser/comments_test.rb index 857f2344b..232db8e05 100644 --- a/test/parser/comments_test.rb +++ b/test/parser/comments_test.rb @@ -24,5 +24,15 @@ class CommentsTest < Minitest::Spec

)) end + + test "HTML comment with if" do + assert_parsed_snapshot(<<~HTML) + + HTML + end end end diff --git a/test/parser/erb_context_mismatch_test.rb b/test/parser/erb_context_mismatch_test.rb new file mode 100644 index 000000000..98033d72f --- /dev/null +++ b/test/parser/erb_context_mismatch_test.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module Parser + class ERBContextMismatchTest < Minitest::Spec + include SnapshotUtils + + # https://github.com/marcoroth/herb/issues/701 + test "out of order closing tags across ERB blocks" do + assert_parsed_snapshot(<<~ERB) +
+
+
+ <%= render CardComponent.new do |card| %> +
+ + <% end %> +
+
+ ERB + end + + # https://github.com/marcoroth/herb/issues/398 + test "conditionally opening tag then closing unconditionally" do + assert_parsed_snapshot(<<~ERB) + <% if some_condition %> +
+ <% else %> +
+ <% end %> + Content +
+ ERB + end + + # https://github.com/marcoroth/herb/issues/399 + test "opening and closing tag in different if blocks" do + assert_parsed_snapshot(<<~ERB) + <% if wrap_in_dialog? %> + + <% end %> + +
Stuff
+ + <% if wrap_in_dialog? %> +
+ <% end %> + ERB + end + + # https://github.com/marcoroth/herb/issues/490 + test "tag opened in elsif closed outside conditional" do + assert_parsed_snapshot(<<~ERB) + + ERB + end + + # https://github.com/marcoroth/herb/issues/83 + test "element opened in block must be closed within block" do + assert_parsed_snapshot(<<~ERB) + <% if true %> +

123 + <% end %> +

+ ERB + end + + # https://github.com/marcoroth/herb/issues/84 + test "opening in one context and closing in another context" do + assert_parsed_snapshot(<<~ERB) + <% if true %> +

+ <% end %> + + Content + + <% if true %> +

+ <% end %> + ERB + end + + test "stray closing tag without opening" do + assert_parsed_snapshot(<<~ERB) +
+ content +
+ + ERB + end + + test "unclosed tag without closing" do + assert_parsed_snapshot(<<~ERB) +
+ content +
+ ERB + end + end +end diff --git a/test/parser/erb_test.rb b/test/parser/erb_test.rb index d8815228c..3dabb4c1d 100644 --- a/test/parser/erb_test.rb +++ b/test/parser/erb_test.rb @@ -217,5 +217,9 @@ class ERBTest < Minitest::Spec test "erb tag followed by literal closing delimiter" do assert_parsed_snapshot(%(<% content %> %>)) end + + test "TODO" do + assert_parsed_snapshot(%(<%= 1 + %>)) + end end end diff --git a/test/parser/parser_test.rb b/test/parser/parser_test.rb index 6e066b85e..66a0849f2 100644 --- a/test/parser/parser_test.rb +++ b/test/parser/parser_test.rb @@ -39,8 +39,8 @@ class ParserTest < Minitest::Spec │ ├── tag_opening: "<%=" (location: (1:4)-(1:7)) │ ├── content: " RUBY_VERSION " (location: (1:7)-(1:21)) │ ├── tag_closing: "%>" (location: (1:21)-(1:23)) - │ ├── parsed: false - │ └── valid: false + │ ├── parsed: true + │ └── valid: true │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:23)-(1:28)) diff --git a/test/parser/script_style_test.rb b/test/parser/script_style_test.rb index ab24bd91d..9365a125d 100644 --- a/test/parser/script_style_test.rb +++ b/test/parser/script_style_test.rb @@ -35,6 +35,7 @@ class ScriptStyleTest < Minitest::Spec end test "script tag with ERB-like content in string" do + # TODO: this is not right yet assert_parsed_snapshot(%()) end diff --git a/test/parser/tags_test.rb b/test/parser/tags_test.rb index 86e6ecc66..1e1e1c9c8 100644 --- a/test/parser/tags_test.rb +++ b/test/parser/tags_test.rb @@ -104,17 +104,8 @@ class TagsTest < Minitest::Spec )) end - # TODO: ideal parse result - # - # - match up the
and
tags - # -

should have a missing closing tag - # - should have a missing opening tag - # -

should have a missing opening tag - # - no other errors - # test "should recover from multiple out of order closing tags" do - skip - assert_parsed_snapshot(%( + assert_parsed_snapshot(<<~HTML)

@@ -122,7 +113,7 @@ class TagsTest < Minitest::Spec

- )) + HTML end test "should recover from void elements used as closing tag" do @@ -137,16 +128,8 @@ class TagsTest < Minitest::Spec )) end - # TODO: ideal parse result - # - # - matched up
and
tags - # -
VoidElementClosingTagError - # -
VoidElementClosingTagError - # - no other errors - # test "should recover from multiple void elements used as closing tag" do - skip - assert_parsed_snapshot(%( + assert_parsed_snapshot(<<~HTML)

@@ -155,7 +138,7 @@ class TagsTest < Minitest::Spec

World

- )) + HTML end test "stray closing tag with whitespace" do diff --git a/test/parser/yield_test.rb b/test/parser/yield_test.rb new file mode 100644 index 000000000..2351b3278 --- /dev/null +++ b/test/parser/yield_test.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module Parser + class YieldTest < Minitest::Spec + include SnapshotUtils + + test "yield" do + assert_parsed_snapshot(<<~HTML) + <%= yield %> + HTML + end + + test "yield with symbol" do + assert_parsed_snapshot(<<~HTML) + <%= yield :head %> + HTML + end + + test "yield inside if" do + assert_parsed_snapshot(<<~HTML) + <% if content_for?(:header) %> + + <% end %> + HTML + end + + test "yield inside block" do + assert_parsed_snapshot(<<~HTML) + <% container do %> + <%= yield :content %> + <% end %> + HTML + end + + test "yield inside case/when" do + assert_parsed_snapshot(<<~HTML) + <% case a %> + <% when String %> + <%= yield :string %> + <% when Integer %> + <%= yield :integer %> + <% end %> + HTML + end + end +end diff --git a/test/snapshots/analyze/case_in_test/test_0006_nested_cases_ins_e19b30c609f63840ec91a32bf81c84d3.txt b/test/snapshots/analyze/case_in_test/test_0006_nested_cases_ins_e19b30c609f63840ec91a32bf81c84d3.txt new file mode 100644 index 000000000..c6bac0a75 --- /dev/null +++ b/test/snapshots/analyze/case_in_test/test_0006_nested_cases_ins_e19b30c609f63840ec91a32bf81c84d3.txt @@ -0,0 +1,72 @@ +--- +source: "Analyze::CaseInTest#test_0006_nested cases ins" +input: |2- +<% case variable %> +<% in [String] %> + String + + <% case variable %> + <% in [Integer] %> + Integer + <% end %> +<% end %> +--- +@ DocumentNode (location: (1:0)-(10:0)) +└── children: (2 items) + ├── @ ERBCaseMatchNode (location: (1:0)-(9:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " case variable " (location: (1:2)-(1:17)) + │ ├── tag_closing: "%>" (location: (1:17)-(1:19)) + │ ├── children: (1 item) + │ │ └── @ HTMLTextNode (location: (1:19)-(2:0)) + │ │ └── content: "\n" + │ │ + │ ├── conditions: (1 item) + │ │ └── @ ERBInNode (location: (2:0)-(2:17)) + │ │ ├── tag_opening: "<%" (location: (2:0)-(2:2)) + │ │ ├── content: " in [String] " (location: (2:2)-(2:15)) + │ │ ├── tag_closing: "%>" (location: (2:15)-(2:17)) + │ │ └── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (2:17)-(5:2)) + │ │ │ └── content: "\n String\n\n " + │ │ │ + │ │ ├── @ ERBCaseMatchNode (location: (5:2)-(8:11)) + │ │ │ ├── tag_opening: "<%" (location: (5:2)-(5:4)) + │ │ │ ├── content: " case variable " (location: (5:4)-(5:19)) + │ │ │ ├── tag_closing: "%>" (location: (5:19)-(5:21)) + │ │ │ ├── children: (1 item) + │ │ │ │ └── @ HTMLTextNode (location: (5:21)-(6:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── conditions: (1 item) + │ │ │ │ └── @ ERBInNode (location: (6:2)-(6:20)) + │ │ │ │ ├── tag_opening: "<%" (location: (6:2)-(6:4)) + │ │ │ │ ├── content: " in [Integer] " (location: (6:4)-(6:18)) + │ │ │ │ ├── tag_closing: "%>" (location: (6:18)-(6:20)) + │ │ │ │ └── statements: (1 item) + │ │ │ │ └── @ HTMLTextNode (location: (6:20)-(8:2)) + │ │ │ │ └── content: "\n Integer\n " + │ │ │ │ + │ │ │ │ + │ │ │ ├── else_clause: ∅ + │ │ │ └── end_node: + │ │ │ └── @ ERBEndNode (location: (8:2)-(8:11)) + │ │ │ ├── tag_opening: "<%" (location: (8:2)-(8:4)) + │ │ │ ├── content: " end " (location: (8:4)-(8:9)) + │ │ │ └── tag_closing: "%>" (location: (8:9)-(8:11)) + │ │ │ + │ │ │ + │ │ └── @ HTMLTextNode (location: (8:11)-(9:0)) + │ │ └── content: "\n" + │ │ + │ │ + │ ├── else_clause: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (9:0)-(9:9)) + │ ├── tag_opening: "<%" (location: (9:0)-(9:2)) + │ ├── content: " end " (location: (9:2)-(9:7)) + │ └── tag_closing: "%>" (location: (9:7)-(9:9)) + │ + │ + └── @ HTMLTextNode (location: (9:9)-(10:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/case_test/test_0006_nested_cases_eac03f80fbde0f108657c6522c81f19f.txt b/test/snapshots/analyze/case_test/test_0006_nested_cases_eac03f80fbde0f108657c6522c81f19f.txt new file mode 100644 index 000000000..59e7bf78b --- /dev/null +++ b/test/snapshots/analyze/case_test/test_0006_nested_cases_eac03f80fbde0f108657c6522c81f19f.txt @@ -0,0 +1,72 @@ +--- +source: "Analyze::CaseTest#test_0006_nested cases" +input: |2- +<% case variable %> +<% when String %> + String + + <% case variable %> + <% when Integer %> + Integer + <% end %> +<% end %> +--- +@ DocumentNode (location: (1:0)-(10:0)) +└── children: (2 items) + ├── @ ERBCaseNode (location: (1:0)-(9:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " case variable " (location: (1:2)-(1:17)) + │ ├── tag_closing: "%>" (location: (1:17)-(1:19)) + │ ├── children: (1 item) + │ │ └── @ HTMLTextNode (location: (1:19)-(2:0)) + │ │ └── content: "\n" + │ │ + │ ├── conditions: (1 item) + │ │ └── @ ERBWhenNode (location: (2:0)-(2:17)) + │ │ ├── tag_opening: "<%" (location: (2:0)-(2:2)) + │ │ ├── content: " when String " (location: (2:2)-(2:15)) + │ │ ├── tag_closing: "%>" (location: (2:15)-(2:17)) + │ │ └── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (2:17)-(5:2)) + │ │ │ └── content: "\n String\n\n " + │ │ │ + │ │ ├── @ ERBCaseNode (location: (5:2)-(8:11)) + │ │ │ ├── tag_opening: "<%" (location: (5:2)-(5:4)) + │ │ │ ├── content: " case variable " (location: (5:4)-(5:19)) + │ │ │ ├── tag_closing: "%>" (location: (5:19)-(5:21)) + │ │ │ ├── children: (1 item) + │ │ │ │ └── @ HTMLTextNode (location: (5:21)-(6:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── conditions: (1 item) + │ │ │ │ └── @ ERBWhenNode (location: (6:2)-(6:20)) + │ │ │ │ ├── tag_opening: "<%" (location: (6:2)-(6:4)) + │ │ │ │ ├── content: " when Integer " (location: (6:4)-(6:18)) + │ │ │ │ ├── tag_closing: "%>" (location: (6:18)-(6:20)) + │ │ │ │ └── statements: (1 item) + │ │ │ │ └── @ HTMLTextNode (location: (6:20)-(8:2)) + │ │ │ │ └── content: "\n Integer\n " + │ │ │ │ + │ │ │ │ + │ │ │ ├── else_clause: ∅ + │ │ │ └── end_node: + │ │ │ └── @ ERBEndNode (location: (8:2)-(8:11)) + │ │ │ ├── tag_opening: "<%" (location: (8:2)-(8:4)) + │ │ │ ├── content: " end " (location: (8:4)-(8:9)) + │ │ │ └── tag_closing: "%>" (location: (8:9)-(8:11)) + │ │ │ + │ │ │ + │ │ └── @ HTMLTextNode (location: (8:11)-(9:0)) + │ │ └── content: "\n" + │ │ + │ │ + │ ├── else_clause: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (9:0)-(9:9)) + │ ├── tag_opening: "<%" (location: (9:0)-(9:2)) + │ ├── content: " end " (location: (9:2)-(9:7)) + │ └── tag_closing: "%>" (location: (9:7)-(9:9)) + │ + │ + └── @ HTMLTextNode (location: (9:9)-(10:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0001_tags_spanning_erb_control_flow_boundaries_-_opening_tag_in_first_if_cb6be8c9ca2ddbb438d62020648c6ed4.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0001_tags_spanning_erb_control_flow_boundaries_-_opening_tag_in_first_if_cb6be8c9ca2ddbb438d62020648c6ed4.txt new file mode 100644 index 000000000..1513f96cf --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0001_tags_spanning_erb_control_flow_boundaries_-_opening_tag_in_first_if_cb6be8c9ca2ddbb438d62020648c6ed4.txt @@ -0,0 +1,79 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0001_tags spanning erb control flow boundaries - opening tag in first if" +input: |2- +<% if condition? %> +
+<% end %> + +<% if condition? %> +
+<% end %> +--- +@ DocumentNode (location: (1:0)-(8:0)) +└── children: (4 items) + ├── @ ERBIfNode (location: (1:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " if condition? " (location: (1:2)-(1:17)) + │ ├── tag_closing: "%>" (location: (1:17)-(1:19)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:19)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLOpenTagNode (location: (2:2)-(2:7)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingClosingTagError (location: (2:2)-(2:7)) + │ │ │ │ ├── message: "Opening tag `
` at (2:3) doesn't have a matching closing tag `
` in the same scope." + │ │ │ │ └── opening_tag: "div" (location: (2:3)-(2:6)) + │ │ │ │ + │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ ├── tag_name: "div" (location: (2:3)-(2:6)) + │ │ │ ├── tag_closing: ">" (location: (2:6)-(2:7)) + │ │ │ ├── children: [] + │ │ │ └── is_void: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:7)-(3:0)) + │ │ └── content: "\n" + │ │ + │ ├── subsequent: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (3:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) + │ ├── content: " end " (location: (3:2)-(3:7)) + │ └── tag_closing: "%>" (location: (3:7)-(3:9)) + │ + │ + ├── @ HTMLTextNode (location: (3:9)-(5:0)) + │ └── content: "\n\n" + │ + ├── @ ERBIfNode (location: (5:0)-(7:9)) + │ ├── tag_opening: "<%" (location: (5:0)-(5:2)) + │ ├── content: " if condition? " (location: (5:2)-(5:17)) + │ ├── tag_closing: "%>" (location: (5:17)-(5:19)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (5:19)-(6:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLCloseTagNode (location: (6:2)-(6:8)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingOpeningTagError (location: (6:2)-(6:8)) + │ │ │ │ ├── message: "Found closing tag `
` at (6:4) without a matching opening tag in the same scope." + │ │ │ │ └── closing_tag: "div" (location: (6:4)-(6:7)) + │ │ │ │ + │ │ │ ├── tag_opening: "" (location: (6:7)-(6:8)) + │ │ │ + │ │ └── @ HTMLTextNode (location: (6:8)-(7:0)) + │ │ └── content: "\n" + │ │ + │ ├── subsequent: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (7:0)-(7:9)) + │ ├── tag_opening: "<%" (location: (7:0)-(7:2)) + │ ├── content: " end " (location: (7:2)-(7:7)) + │ └── tag_closing: "%>" (location: (7:7)-(7:9)) + │ + │ + └── @ HTMLTextNode (location: (7:9)-(8:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0002_invalid_erb_structure_-_else_outside_scope_after_tag_closing_8a42d39b7aee4fa9f2ca791cf1ef291b.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0002_invalid_erb_structure_-_else_outside_scope_after_tag_closing_8a42d39b7aee4fa9f2ca791cf1ef291b.txt new file mode 100644 index 000000000..0adb4ccfc --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0002_invalid_erb_structure_-_else_outside_scope_after_tag_closing_8a42d39b7aee4fa9f2ca791cf1ef291b.txt @@ -0,0 +1,102 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0002_invalid erb structure - else outside scope after tag closing" +input: |2- +
+ class="a" +> + <% else %> + <% end %> +
+--- +@ DocumentNode (location: (1:0)-(8:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(7:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(4:1)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (4:0)-(4:1)) + │ │ ├── children: (1 item) + │ │ │ └── @ ERBIfNode (location: (2:2)-(3:13)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingERBEndTagError (location: (2:2)-(2:25)) + │ │ │ │ ├── message: "`<% if %>` started here but never closed with an end tag. The end tag may be in a different scope." + │ │ │ │ └── keyword: "`<% if %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " if some_condition " (location: (2:4)-(2:23)) + │ │ │ ├── tag_closing: "%>" (location: (2:23)-(2:25)) + │ │ │ ├── statements: (1 item) + │ │ │ │ └── @ HTMLAttributeNode (location: (3:4)-(3:13)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (3:4)-(3:9)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (3:4)-(3:9)) + │ │ │ │ │ └── content: "class" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (3:9)-(3:10)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (3:10)-(3:13)) + │ │ │ │ ├── open_quote: """ (location: (3:10)-(3:11)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (3:11)-(3:12)) + │ │ │ │ │ └── content: "a" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (3:12)-(3:13)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ ├── subsequent: ∅ + │ │ │ └── end_node: ∅ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (5 items) + │ │ ├── @ HTMLTextNode (location: (4:1)-(5:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (5:2)-(5:12)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (5:2)-(5:12)) + │ │ │ │ ├── message: "`<% else %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% else %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (5:2)-(5:4)) + │ │ │ ├── content: " else " (location: (5:4)-(5:10)) + │ │ │ ├── tag_closing: "%>" (location: (5:10)-(5:12)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ ├── @ HTMLTextNode (location: (5:12)-(6:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (6:2)-(6:11)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (6:2)-(6:11)) + │ │ │ │ ├── message: "`<% end %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% end %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (6:2)-(6:4)) + │ │ │ ├── content: " end " (location: (6:4)-(6:9)) + │ │ │ ├── tag_closing: "%>" (location: (6:9)-(6:11)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (6:11)-(7:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (7:0)-(7:6)) + │ │ ├── tag_opening: "" (location: (7:5)-(7:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (7:6)-(8:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0003_invalid_erb_structure_-_end_outside_scope_after_tag_closing_8adc4e0a2a6b8baf0ab00bb12a016422.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0003_invalid_erb_structure_-_end_outside_scope_after_tag_closing_8adc4e0a2a6b8baf0ab00bb12a016422.txt new file mode 100644 index 000000000..116d1da27 --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0003_invalid_erb_structure_-_end_outside_scope_after_tag_closing_8adc4e0a2a6b8baf0ab00bb12a016422.txt @@ -0,0 +1,86 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0003_invalid erb structure - end outside scope after tag closing" +input: |2- +
+ class="a" +> + <% end %> +
+--- +@ DocumentNode (location: (1:0)-(7:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(6:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(4:1)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (4:0)-(4:1)) + │ │ ├── children: (1 item) + │ │ │ └── @ ERBIfNode (location: (2:2)-(3:13)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingERBEndTagError (location: (2:2)-(2:25)) + │ │ │ │ ├── message: "`<% if %>` started here but never closed with an end tag. The end tag may be in a different scope." + │ │ │ │ └── keyword: "`<% if %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " if some_condition " (location: (2:4)-(2:23)) + │ │ │ ├── tag_closing: "%>" (location: (2:23)-(2:25)) + │ │ │ ├── statements: (1 item) + │ │ │ │ └── @ HTMLAttributeNode (location: (3:4)-(3:13)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (3:4)-(3:9)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (3:4)-(3:9)) + │ │ │ │ │ └── content: "class" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (3:9)-(3:10)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (3:10)-(3:13)) + │ │ │ │ ├── open_quote: """ (location: (3:10)-(3:11)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (3:11)-(3:12)) + │ │ │ │ │ └── content: "a" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (3:12)-(3:13)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ ├── subsequent: ∅ + │ │ │ └── end_node: ∅ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (4:1)-(5:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (5:2)-(5:11)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (5:2)-(5:11)) + │ │ │ │ ├── message: "`<% end %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% end %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (5:2)-(5:4)) + │ │ │ ├── content: " end " (location: (5:4)-(5:9)) + │ │ │ ├── tag_closing: "%>" (location: (5:9)-(5:11)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (5:11)-(6:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (6:0)-(6:6)) + │ │ ├── tag_opening: "" (location: (6:5)-(6:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (6:6)-(7:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0004_invalid_erb_structure_-_elsif_outside_scope_after_tag_closing_424e3fda025275fa81a488fc87528e25.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0004_invalid_erb_structure_-_elsif_outside_scope_after_tag_closing_424e3fda025275fa81a488fc87528e25.txt new file mode 100644 index 000000000..d839d2598 --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0004_invalid_erb_structure_-_elsif_outside_scope_after_tag_closing_424e3fda025275fa81a488fc87528e25.txt @@ -0,0 +1,81 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0004_invalid erb structure - elsif outside scope after tag closing" +input: |2- +
+> + <% elsif other_condition %> + <% end %> +
+--- +@ DocumentNode (location: (1:0)-(7:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(6:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(3:1)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (3:0)-(3:1)) + │ │ ├── children: (1 item) + │ │ │ └── @ ERBIfNode (location: (2:2)-(2:25)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingERBEndTagError (location: (2:2)-(2:25)) + │ │ │ │ ├── message: "`<% if %>` started here but never closed with an end tag. The end tag may be in a different scope." + │ │ │ │ └── keyword: "`<% if %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " if some_condition " (location: (2:4)-(2:23)) + │ │ │ ├── tag_closing: "%>" (location: (2:23)-(2:25)) + │ │ │ ├── statements: [] + │ │ │ ├── subsequent: ∅ + │ │ │ └── end_node: ∅ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (5 items) + │ │ ├── @ HTMLTextNode (location: (3:1)-(4:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (4:2)-(4:29)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (4:2)-(4:29)) + │ │ │ │ ├── message: "`<% elsif %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% elsif %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (4:2)-(4:4)) + │ │ │ ├── content: " elsif other_condition " (location: (4:4)-(4:27)) + │ │ │ ├── tag_closing: "%>" (location: (4:27)-(4:29)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ ├── @ HTMLTextNode (location: (4:29)-(5:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (5:2)-(5:11)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (5:2)-(5:11)) + │ │ │ │ ├── message: "`<% end %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% end %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (5:2)-(5:4)) + │ │ │ ├── content: " end " (location: (5:4)-(5:9)) + │ │ │ ├── tag_closing: "%>" (location: (5:9)-(5:11)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (5:11)-(6:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (6:0)-(6:6)) + │ │ ├── tag_opening: "" (location: (6:5)-(6:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (6:6)-(7:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0005_invalid_erb_structure_-_else_outside_scope_before_tag_closing_12f297bfdeb3c060a0c220a445fcb51c.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0005_invalid_erb_structure_-_else_outside_scope_before_tag_closing_12f297bfdeb3c060a0c220a445fcb51c.txt new file mode 100644 index 000000000..c4cec83c9 --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0005_invalid_erb_structure_-_else_outside_scope_before_tag_closing_12f297bfdeb3c060a0c220a445fcb51c.txt @@ -0,0 +1,94 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0005_invalid erb structure - else outside scope before tag closing" +input: |2- +
+ class="a" + <% else %> +> +<% end %> + +
+--- +@ DocumentNode (location: (1:0)-(9:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(8:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(5:1)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (5:0)-(5:1)) + │ │ ├── children: (1 item) + │ │ │ └── @ ERBIfNode (location: (2:2)-(3:13)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingERBEndTagError (location: (2:2)-(2:25)) + │ │ │ │ ├── message: "`<% if %>` started here but never closed with an end tag. The end tag may be in a different scope." + │ │ │ │ └── keyword: "`<% if %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " if some_condition " (location: (2:4)-(2:23)) + │ │ │ ├── tag_closing: "%>" (location: (2:23)-(2:25)) + │ │ │ ├── statements: (1 item) + │ │ │ │ └── @ HTMLAttributeNode (location: (3:4)-(3:13)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (3:4)-(3:9)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (3:4)-(3:9)) + │ │ │ │ │ └── content: "class" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (3:9)-(3:10)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (3:10)-(3:13)) + │ │ │ │ ├── open_quote: """ (location: (3:10)-(3:11)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (3:11)-(3:12)) + │ │ │ │ │ └── content: "a" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (3:12)-(3:13)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ ├── subsequent: + │ │ │ │ └── @ ERBElseNode (location: (4:2)-(4:12)) + │ │ │ │ ├── tag_opening: "<%" (location: (4:2)-(4:4)) + │ │ │ │ ├── content: " else " (location: (4:4)-(4:10)) + │ │ │ │ ├── tag_closing: "%>" (location: (4:10)-(4:12)) + │ │ │ │ └── statements: [] + │ │ │ │ + │ │ │ └── end_node: ∅ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (5:1)-(6:0)) + │ │ │ └── content: "\n" + │ │ │ + │ │ ├── @ ERBContentNode (location: (6:0)-(6:9)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (6:0)-(6:9)) + │ │ │ │ ├── message: "`<% end %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% end %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (6:0)-(6:2)) + │ │ │ ├── content: " end " (location: (6:2)-(6:7)) + │ │ │ ├── tag_closing: "%>" (location: (6:7)-(6:9)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (6:9)-(8:0)) + │ │ └── content: "\n\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (8:0)-(8:6)) + │ │ ├── tag_opening: "" (location: (8:5)-(8:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (8:6)-(9:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0006_invalid_erb_structure_-_if_else_spans_across_attribute_value_quotes_5cf54922a30f349212e5ec71c8d4d3b3.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0006_invalid_erb_structure_-_if_else_spans_across_attribute_value_quotes_5cf54922a30f349212e5ec71c8d4d3b3.txt new file mode 100644 index 000000000..c3192eba6 --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0006_invalid_erb_structure_-_if_else_spans_across_attribute_value_quotes_5cf54922a30f349212e5ec71c8d4d3b3.txt @@ -0,0 +1,89 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0006_invalid erb structure - if/else spans across attribute value quotes" +input: |2- +
> + Content +
+--- +@ DocumentNode (location: (1:0)-(4:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(3:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:58)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (1:57)-(1:58)) + │ │ ├── children: (2 items) + │ │ │ ├── @ HTMLAttributeNode (location: (1:5)-(1:48)) + │ │ │ │ ├── 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:48)) + │ │ │ │ ├── open_quote: """ (location: (1:11)-(1:12)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ ERBIfNode (location: (1:12)-(1:36)) + │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ └── @ MissingERBEndTagError (location: (1:12)-(1:35)) + │ │ │ │ │ │ ├── message: "`<% if %>` started here but never closed with an end tag. The end tag may be in a different scope." + │ │ │ │ │ │ └── keyword: "`<% if %>`" + │ │ │ │ │ │ + │ │ │ │ │ ├── tag_opening: "<%" (location: (1:12)-(1:14)) + │ │ │ │ │ ├── content: " if some_condition " (location: (1:14)-(1:33)) + │ │ │ │ │ ├── tag_closing: "%>" (location: (1:33)-(1:35)) + │ │ │ │ │ ├── statements: (1 item) + │ │ │ │ │ │ └── @ LiteralNode (location: (1:35)-(1:36)) + │ │ │ │ │ │ └── content: "a" + │ │ │ │ │ │ + │ │ │ │ │ ├── subsequent: + │ │ │ │ │ │ └── @ ERBElseNode (location: (1:36)-(1:47)) + │ │ │ │ │ │ ├── tag_opening: "<%" (location: (1:36)-(1:38)) + │ │ │ │ │ │ ├── content: " else " (location: (1:38)-(1:44)) + │ │ │ │ │ │ ├── tag_closing: "%>" (location: (1:44)-(1:46)) + │ │ │ │ │ │ └── statements: (1 item) + │ │ │ │ │ │ └── @ LiteralNode (location: (1:46)-(1:47)) + │ │ │ │ │ │ └── content: "b" + │ │ │ │ │ │ + │ │ │ │ │ │ + │ │ │ │ │ └── end_node: ∅ + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (1:47)-(1:48)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ └── @ ERBContentNode (location: (1:48)-(1:57)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (1:48)-(1:57)) + │ │ │ │ ├── message: "`<% end %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% end %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (1:48)-(1:50)) + │ │ │ ├── content: " end " (location: (1:50)-(1:55)) + │ │ │ ├── tag_closing: "%>" (location: (1:55)-(1:57)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (1 item) + │ │ └── @ HTMLTextNode (location: (1:58)-(3:0)) + │ │ └── content: "\n Content\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (3:0)-(3:6)) + │ │ ├── tag_opening: "" (location: (3:5)-(3:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (3:6)-(4:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0007_invalid_erb_structure_-_multiline_attribute_value_spans_across_if_else_branches_00f04f842749802aede65921854fbd81.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0007_invalid_erb_structure_-_multiline_attribute_value_spans_across_if_else_branches_00f04f842749802aede65921854fbd81.txt new file mode 100644 index 000000000..81dec1aeb --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0007_invalid_erb_structure_-_multiline_attribute_value_spans_across_if_else_branches_00f04f842749802aede65921854fbd81.txt @@ -0,0 +1,89 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0007_invalid erb structure - multiline attribute value spans across if/else branches" +input: |2- +
+ class="a + <% else %> + b" + <% end %> +> + Content +
+--- +@ DocumentNode (location: (1:0)-(10:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(9:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(7:1)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (7:0)-(7:1)) + │ │ ├── children: (1 item) + │ │ │ └── @ ERBIfNode (location: (2:2)-(6:11)) + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " if some_condition " (location: (2:4)-(2:23)) + │ │ │ ├── tag_closing: "%>" (location: (2:23)-(2:25)) + │ │ │ ├── statements: (1 item) + │ │ │ │ └── @ HTMLAttributeNode (location: (3:4)-(5:6)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (3:4)-(3:9)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (3:4)-(3:9)) + │ │ │ │ │ └── content: "class" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (3:9)-(3:10)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (3:10)-(5:6)) + │ │ │ │ ├── open_quote: """ (location: (3:10)-(3:11)) + │ │ │ │ ├── children: (3 items) + │ │ │ │ │ ├── @ LiteralNode (location: (3:11)-(4:2)) + │ │ │ │ │ │ └── content: "a\n " + │ │ │ │ │ │ + │ │ │ │ │ ├── @ ERBContentNode (location: (4:2)-(4:12)) + │ │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ │ └── @ ERBControlFlowScopeError (location: (4:2)-(4:12)) + │ │ │ │ │ │ │ ├── message: "`<% else %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ │ │ │ └── keyword: "`<% else %>`" + │ │ │ │ │ │ │ + │ │ │ │ │ │ ├── tag_opening: "<%" (location: (4:2)-(4:4)) + │ │ │ │ │ │ ├── content: " else " (location: (4:4)-(4:10)) + │ │ │ │ │ │ ├── tag_closing: "%>" (location: (4:10)-(4:12)) + │ │ │ │ │ │ ├── parsed: true + │ │ │ │ │ │ └── valid: false + │ │ │ │ │ │ + │ │ │ │ │ └── @ LiteralNode (location: (4:12)-(5:5)) + │ │ │ │ │ └── content: "\n b" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (5:5)-(5:6)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ ├── subsequent: ∅ + │ │ │ └── end_node: + │ │ │ └── @ ERBEndNode (location: (6:2)-(6:11)) + │ │ │ ├── tag_opening: "<%" (location: (6:2)-(6:4)) + │ │ │ ├── content: " end " (location: (6:4)-(6:9)) + │ │ │ └── tag_closing: "%>" (location: (6:9)-(6:11)) + │ │ │ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (1 item) + │ │ └── @ HTMLTextNode (location: (7:1)-(9:0)) + │ │ └── content: "\n Content\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (9:0)-(9:6)) + │ │ ├── tag_opening: "" (location: (9:5)-(9:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (9:6)-(10:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0008_invalid_erb_structure_-_attribute_quote_closes_in_if_branch_with_else_end_outside_d551e3630ae3b84c356b86f544fe1c74.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0008_invalid_erb_structure_-_attribute_quote_closes_in_if_branch_with_else_end_outside_d551e3630ae3b84c356b86f544fe1c74.txt new file mode 100644 index 000000000..779965694 --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0008_invalid_erb_structure_-_attribute_quote_closes_in_if_branch_with_else_end_outside_d551e3630ae3b84c356b86f544fe1c74.txt @@ -0,0 +1,98 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0008_invalid erb structure - attribute quote closes in if branch with else/end outside" +input: |2- +
+> +<% end %> + +
+--- +@ DocumentNode (location: (1:0)-(7:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(6:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(3:1)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (3:0)-(3:1)) + │ │ ├── children: (2 items) + │ │ │ ├── @ HTMLAttributeNode (location: (1:5)-(1:38)) + │ │ │ │ ├── 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:38)) + │ │ │ │ ├── open_quote: """ (location: (1:11)-(1:12)) + │ │ │ │ ├── children: (2 items) + │ │ │ │ │ ├── @ LiteralNode (location: (1:12)-(1:14)) + │ │ │ │ │ │ └── content: "a " + │ │ │ │ │ │ + │ │ │ │ │ └── @ ERBIfNode (location: (1:14)-(1:37)) + │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ └── @ MissingERBEndTagError (location: (1:14)-(1:37)) + │ │ │ │ │ │ ├── message: "`<% if %>` started here but never closed with an end tag. The end tag may be in a different scope." + │ │ │ │ │ │ └── keyword: "`<% if %>`" + │ │ │ │ │ │ + │ │ │ │ │ ├── tag_opening: "<%" (location: (1:14)-(1:16)) + │ │ │ │ │ ├── content: " if some_condition " (location: (1:16)-(1:35)) + │ │ │ │ │ ├── tag_closing: "%>" (location: (1:35)-(1:37)) + │ │ │ │ │ ├── statements: [] + │ │ │ │ │ ├── subsequent: ∅ + │ │ │ │ │ └── end_node: ∅ + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (1:37)-(1:38)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ └── @ ERBContentNode (location: (2:2)-(2:12)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (2:2)-(2:12)) + │ │ │ │ ├── message: "`<% else %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% else %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " else " (location: (2:4)-(2:10)) + │ │ │ ├── tag_closing: "%>" (location: (2:10)-(2:12)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (3:1)-(4:0)) + │ │ │ └── content: "\n" + │ │ │ + │ │ ├── @ ERBContentNode (location: (4:0)-(4:9)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (4:0)-(4:9)) + │ │ │ │ ├── message: "`<% end %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% end %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (4:0)-(4:2)) + │ │ │ ├── content: " end " (location: (4:2)-(4:7)) + │ │ │ ├── tag_closing: "%>" (location: (4:7)-(4:9)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (4:9)-(6:0)) + │ │ └── content: "\n\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (6:0)-(6:6)) + │ │ ├── tag_opening: "" (location: (6:5)-(6:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (6:6)-(7:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0009_valid_erb_structure_-_if_else_end_inside_tag_attributes_653d501c02f01c7eb2fa084da7d45f3c.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0009_valid_erb_structure_-_if_else_end_inside_tag_attributes_653d501c02f01c7eb2fa084da7d45f3c.txt new file mode 100644 index 000000000..3d7acffd1 --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0009_valid_erb_structure_-_if_else_end_inside_tag_attributes_653d501c02f01c7eb2fa084da7d45f3c.txt @@ -0,0 +1,95 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0009_valid erb structure - if/else/end inside tag attributes" +input: |2- +
+ class="a" + <% else %> + class="b" + <% end %> +>
+--- +@ DocumentNode (location: (1:0)-(8:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(7:7)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(7:1)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (7:0)-(7:1)) + │ │ ├── children: (1 item) + │ │ │ └── @ ERBIfNode (location: (2:2)-(6:11)) + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " if some_condition " (location: (2:4)-(2:23)) + │ │ │ ├── tag_closing: "%>" (location: (2:23)-(2:25)) + │ │ │ ├── statements: (1 item) + │ │ │ │ └── @ HTMLAttributeNode (location: (3:4)-(3:13)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (3:4)-(3:9)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (3:4)-(3:9)) + │ │ │ │ │ └── content: "class" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (3:9)-(3:10)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (3:10)-(3:13)) + │ │ │ │ ├── open_quote: """ (location: (3:10)-(3:11)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (3:11)-(3:12)) + │ │ │ │ │ └── content: "a" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (3:12)-(3:13)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ ├── subsequent: + │ │ │ │ └── @ ERBElseNode (location: (4:2)-(5:13)) + │ │ │ │ ├── tag_opening: "<%" (location: (4:2)-(4:4)) + │ │ │ │ ├── content: " else " (location: (4:4)-(4:10)) + │ │ │ │ ├── tag_closing: "%>" (location: (4:10)-(4:12)) + │ │ │ │ └── statements: (1 item) + │ │ │ │ └── @ HTMLAttributeNode (location: (5:4)-(5:13)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (5:4)-(5:9)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (5:4)-(5:9)) + │ │ │ │ │ └── content: "class" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (5:9)-(5:10)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (5:10)-(5:13)) + │ │ │ │ ├── open_quote: """ (location: (5:10)-(5:11)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (5:11)-(5:12)) + │ │ │ │ │ └── content: "b" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (5:12)-(5:13)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ └── end_node: + │ │ │ └── @ ERBEndNode (location: (6:2)-(6:11)) + │ │ │ ├── tag_opening: "<%" (location: (6:2)-(6:4)) + │ │ │ ├── content: " end " (location: (6:4)-(6:9)) + │ │ │ └── tag_closing: "%>" (location: (6:9)-(6:11)) + │ │ │ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: [] + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (7:1)-(7:7)) + │ │ ├── tag_opening: "" (location: (7:6)-(7:7)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (7:7)-(8:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0010_invalid_erb_structure_-_when_outside_case_scope_a609e4aae83bf7345c1ae12806881ecb.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0010_invalid_erb_structure_-_when_outside_case_scope_a609e4aae83bf7345c1ae12806881ecb.txt new file mode 100644 index 000000000..084196413 --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0010_invalid_erb_structure_-_when_outside_case_scope_a609e4aae83bf7345c1ae12806881ecb.txt @@ -0,0 +1,94 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0010_invalid erb structure - when outside case scope" +input: |2- +
+ <% when 1 %> + class="a" +> +<% end %> +
+--- +@ DocumentNode (location: (1:0)-(8:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(7:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(5:1)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (5:0)-(5:1)) + │ │ ├── children: (1 item) + │ │ │ └── @ ERBCaseNode (location: (2:2)-(3:14)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingERBEndTagError (location: (2:2)-(2:18)) + │ │ │ │ ├── message: "`<% case %>` started here but never closed with an end tag. The end tag may be in a different scope." + │ │ │ │ └── keyword: "`<% case %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " case value " (location: (2:4)-(2:16)) + │ │ │ ├── tag_closing: "%>" (location: (2:16)-(2:18)) + │ │ │ ├── children: [] + │ │ │ ├── conditions: (1 item) + │ │ │ │ └── @ ERBWhenNode (location: (3:2)-(3:14)) + │ │ │ │ ├── tag_opening: "<%" (location: (3:2)-(3:4)) + │ │ │ │ ├── content: " when 1 " (location: (3:4)-(3:12)) + │ │ │ │ ├── tag_closing: "%>" (location: (3:12)-(3:14)) + │ │ │ │ └── statements: (1 item) + │ │ │ │ └── @ HTMLAttributeNode (location: (4:4)-(4:13)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (4:4)-(4:9)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (4:4)-(4:9)) + │ │ │ │ │ └── content: "class" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (4:9)-(4:10)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (4:10)-(4:13)) + │ │ │ │ ├── open_quote: """ (location: (4:10)-(4:11)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (4:11)-(4:12)) + │ │ │ │ │ └── content: "a" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (4:12)-(4:13)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ ├── else_clause: ∅ + │ │ │ └── end_node: ∅ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (5:1)-(6:0)) + │ │ │ └── content: "\n" + │ │ │ + │ │ ├── @ ERBContentNode (location: (6:0)-(6:9)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (6:0)-(6:9)) + │ │ │ │ ├── message: "`<% end %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% end %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (6:0)-(6:2)) + │ │ │ ├── content: " end " (location: (6:2)-(6:7)) + │ │ │ ├── tag_closing: "%>" (location: (6:7)-(6:9)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (6:9)-(7:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (7:0)-(7:6)) + │ │ ├── tag_opening: "" (location: (7:5)-(7:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (7:6)-(8:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0011_invalid_erb_structure_-_rescue_outside_begin_scope_33915d0eadb35429ec69cc5c382cfaf2.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0011_invalid_erb_structure_-_rescue_outside_begin_scope_33915d0eadb35429ec69cc5c382cfaf2.txt new file mode 100644 index 000000000..932d80ddc --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0011_invalid_erb_structure_-_rescue_outside_begin_scope_33915d0eadb35429ec69cc5c382cfaf2.txt @@ -0,0 +1,104 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0011_invalid erb structure - rescue outside begin scope" +input: |2- +
+ data-value="test" +> + <% rescue %> + <% end %> +
+--- +@ DocumentNode (location: (1:0)-(8:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(7:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(4:1)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (4:0)-(4:1)) + │ │ ├── children: (1 item) + │ │ │ └── @ ERBBeginNode (location: (2:2)-(2:13)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingERBEndTagError (location: (2:2)-(2:13)) + │ │ │ │ ├── message: "`<% begin %>` started here but never closed with an end tag. The end tag may be in a different scope." + │ │ │ │ └── keyword: "`<% begin %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " begin " (location: (2:4)-(2:11)) + │ │ │ ├── tag_closing: "%>" (location: (2:11)-(2:13)) + │ │ │ ├── statements: (1 item) + │ │ │ │ └── @ HTMLAttributeNode (location: (3:4)-(3:21)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (3:4)-(3:14)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (3:4)-(3:14)) + │ │ │ │ │ └── content: "data-value" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (3:14)-(3:15)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (3:15)-(3:21)) + │ │ │ │ ├── open_quote: """ (location: (3:15)-(3:16)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (3:16)-(3:20)) + │ │ │ │ │ └── content: "test" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (3:20)-(3:21)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ ├── rescue_clause: ∅ + │ │ │ ├── else_clause: ∅ + │ │ │ ├── ensure_clause: ∅ + │ │ │ └── end_node: ∅ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (5 items) + │ │ ├── @ HTMLTextNode (location: (4:1)-(5:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (5:2)-(5:14)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (5:2)-(5:14)) + │ │ │ │ ├── message: "`<% rescue %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% rescue %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (5:2)-(5:4)) + │ │ │ ├── content: " rescue " (location: (5:4)-(5:12)) + │ │ │ ├── tag_closing: "%>" (location: (5:12)-(5:14)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ ├── @ HTMLTextNode (location: (5:14)-(6:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (6:2)-(6:11)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (6:2)-(6:11)) + │ │ │ │ ├── message: "`<% end %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% end %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (6:2)-(6:4)) + │ │ │ ├── content: " end " (location: (6:4)-(6:9)) + │ │ │ ├── tag_closing: "%>" (location: (6:9)-(6:11)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (6:11)-(7:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (7:0)-(7:6)) + │ │ ├── tag_opening: "" (location: (7:5)-(7:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (7:6)-(8:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0012_invalid_erb_structure_-_ensure_outside_begin_scope_1340125c09cfc9ae2a3c763fb939f03f.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0012_invalid_erb_structure_-_ensure_outside_begin_scope_1340125c09cfc9ae2a3c763fb939f03f.txt new file mode 100644 index 000000000..1b696768b --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0012_invalid_erb_structure_-_ensure_outside_begin_scope_1340125c09cfc9ae2a3c763fb939f03f.txt @@ -0,0 +1,104 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0012_invalid erb structure - ensure outside begin scope" +input: |2- +
+ data-value="test" +> + <% ensure %> + <% end %> +
+--- +@ DocumentNode (location: (1:0)-(8:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(7:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(4:1)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (4:0)-(4:1)) + │ │ ├── children: (1 item) + │ │ │ └── @ ERBBeginNode (location: (2:2)-(2:13)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingERBEndTagError (location: (2:2)-(2:13)) + │ │ │ │ ├── message: "`<% begin %>` started here but never closed with an end tag. The end tag may be in a different scope." + │ │ │ │ └── keyword: "`<% begin %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " begin " (location: (2:4)-(2:11)) + │ │ │ ├── tag_closing: "%>" (location: (2:11)-(2:13)) + │ │ │ ├── statements: (1 item) + │ │ │ │ └── @ HTMLAttributeNode (location: (3:4)-(3:21)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (3:4)-(3:14)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (3:4)-(3:14)) + │ │ │ │ │ └── content: "data-value" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (3:14)-(3:15)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (3:15)-(3:21)) + │ │ │ │ ├── open_quote: """ (location: (3:15)-(3:16)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (3:16)-(3:20)) + │ │ │ │ │ └── content: "test" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (3:20)-(3:21)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ ├── rescue_clause: ∅ + │ │ │ ├── else_clause: ∅ + │ │ │ ├── ensure_clause: ∅ + │ │ │ └── end_node: ∅ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (5 items) + │ │ ├── @ HTMLTextNode (location: (4:1)-(5:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (5:2)-(5:14)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (5:2)-(5:14)) + │ │ │ │ ├── message: "`<% ensure %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% ensure %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (5:2)-(5:4)) + │ │ │ ├── content: " ensure " (location: (5:4)-(5:12)) + │ │ │ ├── tag_closing: "%>" (location: (5:12)-(5:14)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ ├── @ HTMLTextNode (location: (5:14)-(6:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (6:2)-(6:11)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (6:2)-(6:11)) + │ │ │ │ ├── message: "`<% end %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% end %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (6:2)-(6:4)) + │ │ │ ├── content: " end " (location: (6:4)-(6:9)) + │ │ │ ├── tag_closing: "%>" (location: (6:9)-(6:11)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (6:11)-(7:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (7:0)-(7:6)) + │ │ ├── tag_opening: "" (location: (7:5)-(7:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (7:6)-(8:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0013_tags_in_different_erb_scopes_-_unless_blocks_ca324425f43bfaddc88ee581c44a5884.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0013_tags_in_different_erb_scopes_-_unless_blocks_ca324425f43bfaddc88ee581c44a5884.txt new file mode 100644 index 000000000..01d55b735 --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0013_tags_in_different_erb_scopes_-_unless_blocks_ca324425f43bfaddc88ee581c44a5884.txt @@ -0,0 +1,79 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0013_tags in different erb scopes - unless blocks" +input: |2- +<% unless disabled? %> +
+<% end %> + +<% unless enabled? %> +
+<% end %> +--- +@ DocumentNode (location: (1:0)-(8:0)) +└── children: (4 items) + ├── @ ERBUnlessNode (location: (1:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " unless disabled? " (location: (1:2)-(1:20)) + │ ├── tag_closing: "%>" (location: (1:20)-(1:22)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:22)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLOpenTagNode (location: (2:2)-(2:11)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingClosingTagError (location: (2:2)-(2:11)) + │ │ │ │ ├── message: "Opening tag `
` at (2:3) doesn't have a matching closing tag `
` in the same scope." + │ │ │ │ └── opening_tag: "section" (location: (2:3)-(2:10)) + │ │ │ │ + │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ ├── tag_name: "section" (location: (2:3)-(2:10)) + │ │ │ ├── tag_closing: ">" (location: (2:10)-(2:11)) + │ │ │ ├── children: [] + │ │ │ └── is_void: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:11)-(3:0)) + │ │ └── content: "\n" + │ │ + │ ├── else_clause: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (3:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) + │ ├── content: " end " (location: (3:2)-(3:7)) + │ └── tag_closing: "%>" (location: (3:7)-(3:9)) + │ + │ + ├── @ HTMLTextNode (location: (3:9)-(5:0)) + │ └── content: "\n\n" + │ + ├── @ ERBUnlessNode (location: (5:0)-(7:9)) + │ ├── tag_opening: "<%" (location: (5:0)-(5:2)) + │ ├── content: " unless enabled? " (location: (5:2)-(5:19)) + │ ├── tag_closing: "%>" (location: (5:19)-(5:21)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (5:21)-(6:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLCloseTagNode (location: (6:2)-(6:12)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingOpeningTagError (location: (6:2)-(6:12)) + │ │ │ │ ├── message: "Found closing tag `` at (6:4) without a matching opening tag in the same scope." + │ │ │ │ └── closing_tag: "section" (location: (6:4)-(6:11)) + │ │ │ │ + │ │ │ ├── tag_opening: "" (location: (6:11)-(6:12)) + │ │ │ + │ │ └── @ HTMLTextNode (location: (6:12)-(7:0)) + │ │ └── content: "\n" + │ │ + │ ├── else_clause: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (7:0)-(7:9)) + │ ├── tag_opening: "<%" (location: (7:0)-(7:2)) + │ ├── content: " end " (location: (7:2)-(7:7)) + │ └── tag_closing: "%>" (location: (7:7)-(7:9)) + │ + │ + └── @ HTMLTextNode (location: (7:9)-(8:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0014_tags_in_different_erb_scopes_-_while_blocks_c6c4f56cd642ee67863a77e686dadd6c.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0014_tags_in_different_erb_scopes_-_while_blocks_c6c4f56cd642ee67863a77e686dadd6c.txt new file mode 100644 index 000000000..f0ec69316 --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0014_tags_in_different_erb_scopes_-_while_blocks_c6c4f56cd642ee67863a77e686dadd6c.txt @@ -0,0 +1,77 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0014_tags in different erb scopes - while blocks" +input: |2- +<% while items.any? %> +
    +<% end %> + +<% while other_items.any? %> +
+<% end %> +--- +@ DocumentNode (location: (1:0)-(8:0)) +└── children: (4 items) + ├── @ ERBWhileNode (location: (1:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " while items.any? " (location: (1:2)-(1:20)) + │ ├── tag_closing: "%>" (location: (1:20)-(1:22)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:22)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLOpenTagNode (location: (2:2)-(2:6)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingClosingTagError (location: (2:2)-(2:6)) + │ │ │ │ ├── message: "Opening tag `
    ` at (2:3) doesn't have a matching closing tag `
` in the same scope." + │ │ │ │ └── opening_tag: "ul" (location: (2:3)-(2:5)) + │ │ │ │ + │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ ├── tag_name: "ul" (location: (2:3)-(2:5)) + │ │ │ ├── tag_closing: ">" (location: (2:5)-(2:6)) + │ │ │ ├── children: [] + │ │ │ └── is_void: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:6)-(3:0)) + │ │ └── content: "\n" + │ │ + │ └── end_node: + │ └── @ ERBEndNode (location: (3:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) + │ ├── content: " end " (location: (3:2)-(3:7)) + │ └── tag_closing: "%>" (location: (3:7)-(3:9)) + │ + │ + ├── @ HTMLTextNode (location: (3:9)-(5:0)) + │ └── content: "\n\n" + │ + ├── @ ERBWhileNode (location: (5:0)-(7:9)) + │ ├── tag_opening: "<%" (location: (5:0)-(5:2)) + │ ├── content: " while other_items.any? " (location: (5:2)-(5:26)) + │ ├── tag_closing: "%>" (location: (5:26)-(5:28)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (5:28)-(6:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLCloseTagNode (location: (6:2)-(6:7)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingOpeningTagError (location: (6:2)-(6:7)) + │ │ │ │ ├── message: "Found closing tag `` at (6:4) without a matching opening tag in the same scope." + │ │ │ │ └── closing_tag: "ul" (location: (6:4)-(6:6)) + │ │ │ │ + │ │ │ ├── tag_opening: "" (location: (6:6)-(6:7)) + │ │ │ + │ │ └── @ HTMLTextNode (location: (6:7)-(7:0)) + │ │ └── content: "\n" + │ │ + │ └── end_node: + │ └── @ ERBEndNode (location: (7:0)-(7:9)) + │ ├── tag_opening: "<%" (location: (7:0)-(7:2)) + │ ├── content: " end " (location: (7:2)-(7:7)) + │ └── tag_closing: "%>" (location: (7:7)-(7:9)) + │ + │ + └── @ HTMLTextNode (location: (7:9)-(8:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0015_tags_in_different_erb_scopes_-_for_blocks_458db68ec0fe89e877c5753e1d76367c.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0015_tags_in_different_erb_scopes_-_for_blocks_458db68ec0fe89e877c5753e1d76367c.txt new file mode 100644 index 000000000..f5e86b5f0 --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0015_tags_in_different_erb_scopes_-_for_blocks_458db68ec0fe89e877c5753e1d76367c.txt @@ -0,0 +1,77 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0015_tags in different erb scopes - for blocks" +input: |2- +<% for item in items %> +
+<% end %> + +<% for item in other_items %> +
+<% end %> +--- +@ DocumentNode (location: (1:0)-(8:0)) +└── children: (4 items) + ├── @ ERBForNode (location: (1:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " for item in items " (location: (1:2)-(1:21)) + │ ├── tag_closing: "%>" (location: (1:21)-(1:23)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:23)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLOpenTagNode (location: (2:2)-(2:7)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingClosingTagError (location: (2:2)-(2:7)) + │ │ │ │ ├── message: "Opening tag `
` at (2:3) doesn't have a matching closing tag `
` in the same scope." + │ │ │ │ └── opening_tag: "div" (location: (2:3)-(2:6)) + │ │ │ │ + │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ ├── tag_name: "div" (location: (2:3)-(2:6)) + │ │ │ ├── tag_closing: ">" (location: (2:6)-(2:7)) + │ │ │ ├── children: [] + │ │ │ └── is_void: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:7)-(3:0)) + │ │ └── content: "\n" + │ │ + │ └── end_node: + │ └── @ ERBEndNode (location: (3:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) + │ ├── content: " end " (location: (3:2)-(3:7)) + │ └── tag_closing: "%>" (location: (3:7)-(3:9)) + │ + │ + ├── @ HTMLTextNode (location: (3:9)-(5:0)) + │ └── content: "\n\n" + │ + ├── @ ERBForNode (location: (5:0)-(7:9)) + │ ├── tag_opening: "<%" (location: (5:0)-(5:2)) + │ ├── content: " for item in other_items " (location: (5:2)-(5:27)) + │ ├── tag_closing: "%>" (location: (5:27)-(5:29)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (5:29)-(6:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLCloseTagNode (location: (6:2)-(6:8)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingOpeningTagError (location: (6:2)-(6:8)) + │ │ │ │ ├── message: "Found closing tag `
` at (6:4) without a matching opening tag in the same scope." + │ │ │ │ └── closing_tag: "div" (location: (6:4)-(6:7)) + │ │ │ │ + │ │ │ ├── tag_opening: "" (location: (6:7)-(6:8)) + │ │ │ + │ │ └── @ HTMLTextNode (location: (6:8)-(7:0)) + │ │ └── content: "\n" + │ │ + │ └── end_node: + │ └── @ ERBEndNode (location: (7:0)-(7:9)) + │ ├── tag_opening: "<%" (location: (7:0)-(7:2)) + │ ├── content: " end " (location: (7:2)-(7:7)) + │ └── tag_closing: "%>" (location: (7:7)-(7:9)) + │ + │ + └── @ HTMLTextNode (location: (7:9)-(8:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0016_missing_close_HTML_tag_in_else_clause_7e1b1b64fe7f5060c0dbc6b94bf3cae8.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0016_missing_close_HTML_tag_in_else_clause_7e1b1b64fe7f5060c0dbc6b94bf3cae8.txt new file mode 100644 index 000000000..3a0a6729f --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0016_missing_close_HTML_tag_in_else_clause_7e1b1b64fe7f5060c0dbc6b94bf3cae8.txt @@ -0,0 +1,149 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0016_missing close HTML tag in else clause" +input: |2- +

+ <% if true %> +
Text1
+ <% elsif false %> +
Text2
+ <% else %> +
Text3 + <% end %> +

+--- +@ DocumentNode (location: (1:0)-(10:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(9:5)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:4)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "h1" (location: (1:1)-(1:3)) + │ │ ├── tag_closing: ">" (location: (1:3)-(1:4)) + │ │ ├── children: [] + │ │ └── is_void: false + │ │ + │ ├── tag_name: "h1" (location: (1:1)-(1:3)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:4)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBIfNode (location: (2:2)-(8:11)) + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " if true " (location: (2:4)-(2:13)) + │ │ │ ├── tag_closing: "%>" (location: (2:13)-(2:15)) + │ │ │ ├── statements: (3 items) + │ │ │ │ ├── @ HTMLTextNode (location: (2:15)-(3:4)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ HTMLElementNode (location: (3:4)-(3:20)) + │ │ │ │ │ ├── open_tag: + │ │ │ │ │ │ └── @ HTMLOpenTagNode (location: (3:4)-(3:9)) + │ │ │ │ │ │ ├── tag_opening: "<" (location: (3:4)-(3:5)) + │ │ │ │ │ │ ├── tag_name: "div" (location: (3:5)-(3:8)) + │ │ │ │ │ │ ├── tag_closing: ">" (location: (3:8)-(3:9)) + │ │ │ │ │ │ ├── children: [] + │ │ │ │ │ │ └── is_void: false + │ │ │ │ │ │ + │ │ │ │ │ ├── tag_name: "div" (location: (3:5)-(3:8)) + │ │ │ │ │ ├── body: (1 item) + │ │ │ │ │ │ └── @ HTMLTextNode (location: (3:9)-(3:14)) + │ │ │ │ │ │ └── content: "Text1" + │ │ │ │ │ │ + │ │ │ │ │ ├── close_tag: + │ │ │ │ │ │ └── @ HTMLCloseTagNode (location: (3:14)-(3:20)) + │ │ │ │ │ │ ├── tag_opening: "" (location: (3:19)-(3:20)) + │ │ │ │ │ │ + │ │ │ │ │ ├── is_void: false + │ │ │ │ │ └── source: "HTML" + │ │ │ │ │ + │ │ │ │ └── @ HTMLTextNode (location: (3:20)-(4:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── subsequent: + │ │ │ │ └── @ ERBIfNode (location: (4:2)-(6:2)) + │ │ │ │ ├── tag_opening: "<%" (location: (4:2)-(4:4)) + │ │ │ │ ├── content: " elsif false " (location: (4:4)-(4:17)) + │ │ │ │ ├── tag_closing: "%>" (location: (4:17)-(4:19)) + │ │ │ │ ├── statements: (3 items) + │ │ │ │ │ ├── @ HTMLTextNode (location: (4:19)-(5:4)) + │ │ │ │ │ │ └── content: "\n " + │ │ │ │ │ │ + │ │ │ │ │ ├── @ HTMLElementNode (location: (5:4)-(5:20)) + │ │ │ │ │ │ ├── open_tag: + │ │ │ │ │ │ │ └── @ HTMLOpenTagNode (location: (5:4)-(5:9)) + │ │ │ │ │ │ │ ├── tag_opening: "<" (location: (5:4)-(5:5)) + │ │ │ │ │ │ │ ├── tag_name: "div" (location: (5:5)-(5:8)) + │ │ │ │ │ │ │ ├── tag_closing: ">" (location: (5:8)-(5:9)) + │ │ │ │ │ │ │ ├── children: [] + │ │ │ │ │ │ │ └── is_void: false + │ │ │ │ │ │ │ + │ │ │ │ │ │ ├── tag_name: "div" (location: (5:5)-(5:8)) + │ │ │ │ │ │ ├── body: (1 item) + │ │ │ │ │ │ │ └── @ HTMLTextNode (location: (5:9)-(5:14)) + │ │ │ │ │ │ │ └── content: "Text2" + │ │ │ │ │ │ │ + │ │ │ │ │ │ ├── close_tag: + │ │ │ │ │ │ │ └── @ HTMLCloseTagNode (location: (5:14)-(5:20)) + │ │ │ │ │ │ │ ├── tag_opening: "" (location: (5:19)-(5:20)) + │ │ │ │ │ │ │ + │ │ │ │ │ │ ├── is_void: false + │ │ │ │ │ │ └── source: "HTML" + │ │ │ │ │ │ + │ │ │ │ │ └── @ HTMLTextNode (location: (5:20)-(6:2)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── subsequent: + │ │ │ │ │ └── @ ERBElseNode (location: (6:2)-(8:2)) + │ │ │ │ │ ├── tag_opening: "<%" (location: (6:2)-(6:4)) + │ │ │ │ │ ├── content: " else " (location: (6:4)-(6:10)) + │ │ │ │ │ ├── tag_closing: "%>" (location: (6:10)-(6:12)) + │ │ │ │ │ └── statements: (3 items) + │ │ │ │ │ ├── @ HTMLTextNode (location: (6:12)-(7:4)) + │ │ │ │ │ │ └── content: "\n " + │ │ │ │ │ │ + │ │ │ │ │ ├── @ HTMLOpenTagNode (location: (7:4)-(7:9)) + │ │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ │ └── @ MissingClosingTagError (location: (7:4)-(7:9)) + │ │ │ │ │ │ │ ├── message: "Opening tag `
` at (7:5) doesn't have a matching closing tag `
` in the same scope." + │ │ │ │ │ │ │ └── opening_tag: "div" (location: (7:5)-(7:8)) + │ │ │ │ │ │ │ + │ │ │ │ │ │ ├── tag_opening: "<" (location: (7:4)-(7:5)) + │ │ │ │ │ │ ├── tag_name: "div" (location: (7:5)-(7:8)) + │ │ │ │ │ │ ├── tag_closing: ">" (location: (7:8)-(7:9)) + │ │ │ │ │ │ ├── children: [] + │ │ │ │ │ │ └── is_void: false + │ │ │ │ │ │ + │ │ │ │ │ └── @ HTMLTextNode (location: (7:9)-(8:2)) + │ │ │ │ │ └── content: "Text3\n " + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ └── end_node: ∅ + │ │ │ │ + │ │ │ └── end_node: + │ │ │ └── @ ERBEndNode (location: (8:2)-(8:11)) + │ │ │ ├── tag_opening: "<%" (location: (8:2)-(8:4)) + │ │ │ ├── content: " end " (location: (8:4)-(8:9)) + │ │ │ └── tag_closing: "%>" (location: (8:9)-(8:11)) + │ │ │ + │ │ │ + │ │ └── @ HTMLTextNode (location: (8:11)-(9:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (9:0)-(9:5)) + │ │ ├── tag_opening: "" (location: (9:4)-(9:5)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (9:5)-(10:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0017_elsif_in_class_attribute_value_scope_9e9e331db3c1e45c281ee0f0a4283d00.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0017_elsif_in_class_attribute_value_scope_9e9e331db3c1e45c281ee0f0a4283d00.txt new file mode 100644 index 000000000..a9def8f0f --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0017_elsif_in_class_attribute_value_scope_9e9e331db3c1e45c281ee0f0a4283d00.txt @@ -0,0 +1,77 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0017_elsif in class attribute value scope" +input: |2- +

class="<% elsif true %>" <% end %>> + Content +

+--- +@ DocumentNode (location: (1:0)-(4:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(3:5)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:53)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "h1" (location: (1:1)-(1:3)) + │ │ ├── tag_closing: ">" (location: (1:52)-(1:53)) + │ │ ├── children: (1 item) + │ │ │ └── @ ERBIfNode (location: (1:4)-(1:52)) + │ │ │ ├── tag_opening: "<%" (location: (1:4)-(1:6)) + │ │ │ ├── content: " if true " (location: (1:6)-(1:15)) + │ │ │ ├── tag_closing: "%>" (location: (1:15)-(1:17)) + │ │ │ ├── statements: (1 item) + │ │ │ │ └── @ HTMLAttributeNode (location: (1:18)-(1:42)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:18)-(1:23)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (1:18)-(1:23)) + │ │ │ │ │ └── content: "class" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (1:23)-(1:24)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (1:24)-(1:42)) + │ │ │ │ ├── open_quote: """ (location: (1:24)-(1:25)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ ERBContentNode (location: (1:25)-(1:41)) + │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ └── @ ERBControlFlowScopeError (location: (1:25)-(1:41)) + │ │ │ │ │ │ ├── message: "`<% elsif %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ │ │ └── keyword: "`<% elsif %>`" + │ │ │ │ │ │ + │ │ │ │ │ ├── tag_opening: "<%" (location: (1:25)-(1:27)) + │ │ │ │ │ ├── content: " elsif true " (location: (1:27)-(1:39)) + │ │ │ │ │ ├── tag_closing: "%>" (location: (1:39)-(1:41)) + │ │ │ │ │ ├── parsed: true + │ │ │ │ │ └── valid: false + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (1:41)-(1:42)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ ├── subsequent: ∅ + │ │ │ └── end_node: + │ │ │ └── @ ERBEndNode (location: (1:43)-(1:52)) + │ │ │ ├── tag_opening: "<%" (location: (1:43)-(1:45)) + │ │ │ ├── content: " end " (location: (1:45)-(1:50)) + │ │ │ └── tag_closing: "%>" (location: (1:50)-(1:52)) + │ │ │ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "h1" (location: (1:1)-(1:3)) + │ ├── body: (1 item) + │ │ └── @ HTMLTextNode (location: (1:53)-(3:0)) + │ │ └── content: "\n Content\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (3:0)-(3:5)) + │ │ ├── tag_opening: "" (location: (3:4)-(3:5)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (3:5)-(4:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0019_conditional_attributes_with_space_before_ERB_closing_tag_447bae9e416269915e38be84519b2d28.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0019_conditional_attributes_with_space_before_ERB_closing_tag_447bae9e416269915e38be84519b2d28.txt new file mode 100644 index 000000000..4d3fafe35 --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0019_conditional_attributes_with_space_before_ERB_closing_tag_447bae9e416269915e38be84519b2d28.txt @@ -0,0 +1,135 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0019_conditional attributes with space before ERB closing tag" +input: |2- + +--- +@ DocumentNode (location: (1:0)-(7:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(6:16)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(6:1)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "button" (location: (1:1)-(1:7)) + │ │ ├── tag_closing: ">" (location: (6:0)-(6:1)) + │ │ ├── children: (4 items) + │ │ │ ├── @ HTMLAttributeNode (location: (2:2)-(2:15)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (2:2)-(2:6)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (2:2)-(2:6)) + │ │ │ │ │ └── content: "type" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (2:6)-(2:7)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (2:7)-(2:15)) + │ │ │ │ ├── open_quote: """ (location: (2:7)-(2:8)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (2:8)-(2:14)) + │ │ │ │ │ └── content: "submit" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (2:14)-(2:15)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ ├── @ ERBIfNode (location: (3:2)-(3:35)) + │ │ │ │ ├── tag_opening: "<%" (location: (3:2)-(3:4)) + │ │ │ │ ├── content: " if false " (location: (3:4)-(3:14)) + │ │ │ │ ├── tag_closing: "%>" (location: (3:14)-(3:16)) + │ │ │ │ ├── statements: (1 item) + │ │ │ │ │ └── @ HTMLAttributeNode (location: (3:17)-(3:25)) + │ │ │ │ │ ├── name: + │ │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (3:17)-(3:25)) + │ │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ │ └── @ LiteralNode (location: (3:17)-(3:25)) + │ │ │ │ │ │ └── content: "disabled" + │ │ │ │ │ │ + │ │ │ │ │ │ + │ │ │ │ │ ├── equals: ∅ + │ │ │ │ │ └── value: ∅ + │ │ │ │ │ + │ │ │ │ ├── subsequent: ∅ + │ │ │ │ └── end_node: + │ │ │ │ └── @ ERBEndNode (location: (3:26)-(3:35)) + │ │ │ │ ├── tag_opening: "<%" (location: (3:26)-(3:28)) + │ │ │ │ ├── content: " end " (location: (3:28)-(3:33)) + │ │ │ │ └── tag_closing: "%>" (location: (3:33)-(3:35)) + │ │ │ │ + │ │ │ │ + │ │ │ ├── @ ERBIfNode (location: (4:2)-(4:43)) + │ │ │ │ ├── tag_opening: "<%" (location: (4:2)-(4:4)) + │ │ │ │ ├── content: " if false " (location: (4:4)-(4:14)) + │ │ │ │ ├── tag_closing: "%>" (location: (4:14)-(4:16)) + │ │ │ │ ├── statements: (1 item) + │ │ │ │ │ └── @ HTMLAttributeNode (location: (4:17)-(4:33)) + │ │ │ │ │ ├── name: + │ │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (4:17)-(4:26)) + │ │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ │ └── @ LiteralNode (location: (4:17)-(4:26)) + │ │ │ │ │ │ └── content: "aria-busy" + │ │ │ │ │ │ + │ │ │ │ │ │ + │ │ │ │ │ ├── equals: "=" (location: (4:26)-(4:27)) + │ │ │ │ │ └── value: + │ │ │ │ │ └── @ HTMLAttributeValueNode (location: (4:27)-(4:33)) + │ │ │ │ │ ├── open_quote: """ (location: (4:27)-(4:28)) + │ │ │ │ │ ├── children: (1 item) + │ │ │ │ │ │ └── @ LiteralNode (location: (4:28)-(4:32)) + │ │ │ │ │ │ └── content: "true" + │ │ │ │ │ │ + │ │ │ │ │ ├── close_quote: """ (location: (4:32)-(4:33)) + │ │ │ │ │ └── quoted: true + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── subsequent: ∅ + │ │ │ │ └── end_node: + │ │ │ │ └── @ ERBEndNode (location: (4:34)-(4:43)) + │ │ │ │ ├── tag_opening: "<%" (location: (4:34)-(4:36)) + │ │ │ │ ├── content: " end " (location: (4:36)-(4:41)) + │ │ │ │ └── tag_closing: "%>" (location: (4:41)-(4:43)) + │ │ │ │ + │ │ │ │ + │ │ │ └── @ HTMLAttributeNode (location: (5:2)-(5:13)) + │ │ │ ├── name: + │ │ │ │ └── @ HTMLAttributeNameNode (location: (5:2)-(5:7)) + │ │ │ │ └── children: (1 item) + │ │ │ │ └── @ LiteralNode (location: (5:2)-(5:7)) + │ │ │ │ └── content: "class" + │ │ │ │ + │ │ │ │ + │ │ │ ├── equals: "=" (location: (5:7)-(5:8)) + │ │ │ └── value: + │ │ │ └── @ HTMLAttributeValueNode (location: (5:8)-(5:13)) + │ │ │ ├── open_quote: """ (location: (5:8)-(5:9)) + │ │ │ ├── children: (1 item) + │ │ │ │ └── @ LiteralNode (location: (5:9)-(5:12)) + │ │ │ │ └── content: "btn" + │ │ │ │ + │ │ │ ├── close_quote: """ (location: (5:12)-(5:13)) + │ │ │ └── quoted: true + │ │ │ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "button" (location: (1:1)-(1:7)) + │ ├── body: (1 item) + │ │ └── @ HTMLTextNode (location: (6:1)-(6:7)) + │ │ └── content: "Submit" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (6:7)-(6:16)) + │ │ ├── tag_opening: "" (location: (6:15)-(6:16)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (6:16)-(7:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0020_invalid_break_outside_of_loop_context_182bc23dd57db9ce2bba26309b131723.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0020_invalid_break_outside_of_loop_context_182bc23dd57db9ce2bba26309b131723.txt new file mode 100644 index 000000000..bdaaa0773 --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0020_invalid_break_outside_of_loop_context_182bc23dd57db9ce2bba26309b131723.txt @@ -0,0 +1,50 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0020_invalid break outside of loop context" +input: |2- +
+ <% break %> +
+--- +@ DocumentNode (location: (1:0)-(4:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(3:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (1:4)-(1:5)) + │ │ ├── children: [] + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:5)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (2:2)-(2:13)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (2:2)-(2:13)) + │ │ │ │ ├── message: "`<% break %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% break %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " break " (location: (2:4)-(2:11)) + │ │ │ ├── tag_closing: "%>" (location: (2:11)-(2:13)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:13)-(3:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (3:0)-(3:6)) + │ │ ├── tag_opening: "" (location: (3:5)-(3:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (3:6)-(4:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0021_invalid_next_outside_of_loop_context_ac5828658f5c90c9d487d6ef420b99a0.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0021_invalid_next_outside_of_loop_context_ac5828658f5c90c9d487d6ef420b99a0.txt new file mode 100644 index 000000000..c9310893d --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0021_invalid_next_outside_of_loop_context_ac5828658f5c90c9d487d6ef420b99a0.txt @@ -0,0 +1,50 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0021_invalid next outside of loop context" +input: |2- +
+ <% next %> +
+--- +@ DocumentNode (location: (1:0)-(4:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(3:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (1:4)-(1:5)) + │ │ ├── children: [] + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:5)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (2:2)-(2:12)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (2:2)-(2:12)) + │ │ │ │ ├── message: "`<% next %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% next %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " next " (location: (2:4)-(2:10)) + │ │ │ ├── tag_closing: "%>" (location: (2:10)-(2:12)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:12)-(3:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (3:0)-(3:6)) + │ │ ├── tag_opening: "" (location: (3:5)-(3:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (3:6)-(4:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0022_invalid_redo_outside_of_loop_context_26f3fe5cab89ff275796733f6967109e.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0022_invalid_redo_outside_of_loop_context_26f3fe5cab89ff275796733f6967109e.txt new file mode 100644 index 000000000..0c6385d1b --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0022_invalid_redo_outside_of_loop_context_26f3fe5cab89ff275796733f6967109e.txt @@ -0,0 +1,50 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0022_invalid redo outside of loop context" +input: |2- +
+ <% redo %> +
+--- +@ DocumentNode (location: (1:0)-(4:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(3:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (1:4)-(1:5)) + │ │ ├── children: [] + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:5)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (2:2)-(2:12)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (2:2)-(2:12)) + │ │ │ │ ├── message: "`<% redo %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% redo %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " redo " (location: (2:4)-(2:10)) + │ │ │ ├── tag_closing: "%>" (location: (2:10)-(2:12)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:12)-(3:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (3:0)-(3:6)) + │ │ ├── tag_opening: "" (location: (3:5)-(3:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (3:6)-(4:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0023_invalid_retry_outside_of_rescue_context_a9908e2810eb5ee58d0b4f4589e1ff36.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0023_invalid_retry_outside_of_rescue_context_a9908e2810eb5ee58d0b4f4589e1ff36.txt new file mode 100644 index 000000000..b5de6e04d --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0023_invalid_retry_outside_of_rescue_context_a9908e2810eb5ee58d0b4f4589e1ff36.txt @@ -0,0 +1,57 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0023_invalid retry outside of rescue context" +input: |2- +
+ <% retry %> +
+--- +@ DocumentNode (location: (1:0)-(4:0)) +├── errors: (1 error) +│ └── @ RubyParseError (location: (2:5)-(2:10)) +│ ├── message: "invalid_retry_without_rescue: Invalid retry without rescue" +│ ├── error_message: "Invalid retry without rescue" +│ ├── diagnostic_id: "invalid_retry_without_rescue" +│ └── level: "syntax" +│ +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(3:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (1:4)-(1:5)) + │ │ ├── children: [] + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:5)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (2:2)-(2:13)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (2:2)-(2:13)) + │ │ │ │ ├── message: "`<% retry %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% retry %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " retry " (location: (2:4)-(2:11)) + │ │ │ ├── tag_closing: "%>" (location: (2:11)-(2:13)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:13)-(3:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (3:0)-(3:6)) + │ │ ├── tag_opening: "" (location: (3:5)-(3:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (3:6)-(4:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0024_invalid_break_in_if_statement_outside_of_loop_87141b29d2eca3e5d69b58f53982f8fc.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0024_invalid_break_in_if_statement_outside_of_loop_87141b29d2eca3e5d69b58f53982f8fc.txt new file mode 100644 index 000000000..254c47fef --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0024_invalid_break_in_if_statement_outside_of_loop_87141b29d2eca3e5d69b58f53982f8fc.txt @@ -0,0 +1,42 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0024_invalid break in if statement outside of loop" +input: |2- +<% if condition? %> + <% break %> +<% end %> +--- +@ DocumentNode (location: (1:0)-(4:0)) +└── children: (2 items) + ├── @ ERBIfNode (location: (1:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " if condition? " (location: (1:2)-(1:17)) + │ ├── tag_closing: "%>" (location: (1:17)-(1:19)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:19)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (2:2)-(2:13)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (2:2)-(2:13)) + │ │ │ │ ├── message: "`<% break %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% break %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " break " (location: (2:4)-(2:11)) + │ │ │ ├── tag_closing: "%>" (location: (2:11)-(2:13)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:13)-(3:0)) + │ │ └── content: "\n" + │ │ + │ ├── subsequent: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (3:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) + │ ├── content: " end " (location: (3:2)-(3:7)) + │ └── tag_closing: "%>" (location: (3:7)-(3:9)) + │ + │ + └── @ HTMLTextNode (location: (3:9)-(4:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0025_invalid_next_in_case_statement_outside_of_loop_566add5cc403289f497833046d30cb83.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0025_invalid_next_in_case_statement_outside_of_loop_566add5cc403289f497833046d30cb83.txt new file mode 100644 index 000000000..7cc31066c --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0025_invalid_next_in_case_statement_outside_of_loop_566add5cc403289f497833046d30cb83.txt @@ -0,0 +1,53 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0025_invalid next in case statement outside of loop" +input: |2- +<% case value %> +<% when 1 %> + <% next %> +<% end %> +--- +@ DocumentNode (location: (1:0)-(5:0)) +└── children: (2 items) + ├── @ ERBCaseNode (location: (1:0)-(4:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " case value " (location: (1:2)-(1:14)) + │ ├── tag_closing: "%>" (location: (1:14)-(1:16)) + │ ├── children: (1 item) + │ │ └── @ HTMLTextNode (location: (1:16)-(2:0)) + │ │ └── content: "\n" + │ │ + │ ├── conditions: (1 item) + │ │ └── @ ERBWhenNode (location: (2:0)-(2:12)) + │ │ ├── tag_opening: "<%" (location: (2:0)-(2:2)) + │ │ ├── content: " when 1 " (location: (2:2)-(2:10)) + │ │ ├── tag_closing: "%>" (location: (2:10)-(2:12)) + │ │ └── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (2:12)-(3:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (3:2)-(3:12)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (3:2)-(3:12)) + │ │ │ │ ├── message: "`<% next %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% next %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (3:2)-(3:4)) + │ │ │ ├── content: " next " (location: (3:4)-(3:10)) + │ │ │ ├── tag_closing: "%>" (location: (3:10)-(3:12)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (3:12)-(4:0)) + │ │ └── content: "\n" + │ │ + │ │ + │ ├── else_clause: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (4:0)-(4:9)) + │ ├── tag_opening: "<%" (location: (4:0)-(4:2)) + │ ├── content: " end " (location: (4:2)-(4:7)) + │ └── tag_closing: "%>" (location: (4:7)-(4:9)) + │ + │ + └── @ HTMLTextNode (location: (4:9)-(5:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0026_invalid_break_at_document_root_after_while_loop_6ba6b13c23d9f008fe78926965fddb7c.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0026_invalid_break_at_document_root_after_while_loop_6ba6b13c23d9f008fe78926965fddb7c.txt new file mode 100644 index 000000000..ed1423515 --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0026_invalid_break_at_document_root_after_while_loop_6ba6b13c23d9f008fe78926965fddb7c.txt @@ -0,0 +1,43 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0026_invalid break at document root after while loop" +input: |2- +<% while true %> + content +<% end %> + +<% break %> +--- +@ DocumentNode (location: (1:0)-(6:0)) +└── children: (4 items) + ├── @ ERBWhileNode (location: (1:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " while true " (location: (1:2)-(1:14)) + │ ├── tag_closing: "%>" (location: (1:14)-(1:16)) + │ ├── statements: (1 item) + │ │ └── @ HTMLTextNode (location: (1:16)-(3:0)) + │ │ └── content: "\n content\n" + │ │ + │ └── end_node: + │ └── @ ERBEndNode (location: (3:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) + │ ├── content: " end " (location: (3:2)-(3:7)) + │ └── tag_closing: "%>" (location: (3:7)-(3:9)) + │ + │ + ├── @ HTMLTextNode (location: (3:9)-(5:0)) + │ └── content: "\n\n" + │ + ├── @ ERBContentNode (location: (5:0)-(5:11)) + │ ├── errors: (1 error) + │ │ └── @ ERBControlFlowScopeError (location: (5:0)-(5:11)) + │ │ ├── message: "`<% break %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ └── keyword: "`<% break %>`" + │ │ + │ ├── tag_opening: "<%" (location: (5:0)-(5:2)) + │ ├── content: " break " (location: (5:2)-(5:9)) + │ ├── tag_closing: "%>" (location: (5:9)-(5:11)) + │ ├── parsed: true + │ └── valid: false + │ + └── @ HTMLTextNode (location: (5:11)-(6:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/invalid_erb_structure_test/test_0027_loop_control_keywords_in_various_contexts_(retry,_break,_next,_redo)_179ec23049a2db7f974635ed4ba2ecf7.txt b/test/snapshots/analyze/invalid_erb_structure_test/test_0027_loop_control_keywords_in_various_contexts_(retry,_break,_next,_redo)_179ec23049a2db7f974635ed4ba2ecf7.txt new file mode 100644 index 000000000..f72e7355a --- /dev/null +++ b/test/snapshots/analyze/invalid_erb_structure_test/test_0027_loop_control_keywords_in_various_contexts_(retry,_break,_next,_redo)_179ec23049a2db7f974635ed4ba2ecf7.txt @@ -0,0 +1,339 @@ +--- +source: "Analyze::InvalidERBStructureTest#test_0027_loop control keywords in various contexts (retry, break, next, redo)" +input: |2- +<% while true %> + <% retry %> + <% break %> + <% next %> + <% redo %> +<% end %> + +<% loop do %> + <% retry %> + <% break %> + <% next %> + <% redo %> +<% end %> + +<% begin %> + <% retry %> +<% rescue %> + <% retry %> + <% break %> + <% next %> + <% redo %> +<% end %> + +<% break %> +<% next %> +<% retry %> +<% redo %> +--- +@ DocumentNode (location: (1:0)-(28:0)) +├── errors: (4 errors) +│ ├── @ RubyParseError (location: (2:5)-(2:10)) +│ │ ├── message: "invalid_retry_without_rescue: Invalid retry without rescue" +│ │ ├── error_message: "Invalid retry without rescue" +│ │ ├── diagnostic_id: "invalid_retry_without_rescue" +│ │ └── level: "syntax" +│ │ +│ ├── @ RubyParseError (location: (9:5)-(9:10)) +│ │ ├── message: "invalid_retry_without_rescue: Invalid retry without rescue" +│ │ ├── error_message: "Invalid retry without rescue" +│ │ ├── diagnostic_id: "invalid_retry_without_rescue" +│ │ └── level: "syntax" +│ │ +│ ├── @ RubyParseError (location: (16:5)-(16:10)) +│ │ ├── message: "invalid_retry_without_rescue: Invalid retry without rescue" +│ │ ├── error_message: "Invalid retry without rescue" +│ │ ├── diagnostic_id: "invalid_retry_without_rescue" +│ │ └── level: "syntax" +│ │ +│ └── @ RubyParseError (location: (26:3)-(26:8)) +│ ├── message: "invalid_retry_without_rescue: Invalid retry without rescue" +│ ├── error_message: "Invalid retry without rescue" +│ ├── diagnostic_id: "invalid_retry_without_rescue" +│ └── level: "syntax" +│ +└── children: (14 items) + ├── @ ERBWhileNode (location: (1:0)-(6:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " while true " (location: (1:2)-(1:14)) + │ ├── tag_closing: "%>" (location: (1:14)-(1:16)) + │ ├── statements: (9 items) + │ │ ├── @ HTMLTextNode (location: (1:16)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (2:2)-(2:13)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (2:2)-(2:13)) + │ │ │ │ ├── message: "`<% retry %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% retry %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) + │ │ │ ├── content: " retry " (location: (2:4)-(2:11)) + │ │ │ ├── tag_closing: "%>" (location: (2:11)-(2:13)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ ├── @ HTMLTextNode (location: (2:13)-(3:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (3:2)-(3:13)) + │ │ │ ├── tag_opening: "<%" (location: (3:2)-(3:4)) + │ │ │ ├── content: " break " (location: (3:4)-(3:11)) + │ │ │ ├── tag_closing: "%>" (location: (3:11)-(3:13)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ ├── @ HTMLTextNode (location: (3:13)-(4:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (4:2)-(4:12)) + │ │ │ ├── tag_opening: "<%" (location: (4:2)-(4:4)) + │ │ │ ├── content: " next " (location: (4:4)-(4:10)) + │ │ │ ├── tag_closing: "%>" (location: (4:10)-(4:12)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ ├── @ HTMLTextNode (location: (4:12)-(5:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (5:2)-(5:12)) + │ │ │ ├── tag_opening: "<%" (location: (5:2)-(5:4)) + │ │ │ ├── content: " redo " (location: (5:4)-(5:10)) + │ │ │ ├── tag_closing: "%>" (location: (5:10)-(5:12)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (5:12)-(6:0)) + │ │ └── content: "\n" + │ │ + │ └── end_node: + │ └── @ ERBEndNode (location: (6:0)-(6:9)) + │ ├── tag_opening: "<%" (location: (6:0)-(6:2)) + │ ├── content: " end " (location: (6:2)-(6:7)) + │ └── tag_closing: "%>" (location: (6:7)-(6:9)) + │ + │ + ├── @ HTMLTextNode (location: (6:9)-(8:0)) + │ └── content: "\n\n" + │ + ├── @ ERBBlockNode (location: (8:0)-(13:9)) + │ ├── tag_opening: "<%" (location: (8:0)-(8:2)) + │ ├── content: " loop do " (location: (8:2)-(8:11)) + │ ├── tag_closing: "%>" (location: (8:11)-(8:13)) + │ ├── body: (9 items) + │ │ ├── @ HTMLTextNode (location: (8:13)-(9:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (9:2)-(9:13)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ ERBControlFlowScopeError (location: (9:2)-(9:13)) + │ │ │ │ ├── message: "`<% retry %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ └── keyword: "`<% retry %>`" + │ │ │ │ + │ │ │ ├── tag_opening: "<%" (location: (9:2)-(9:4)) + │ │ │ ├── content: " retry " (location: (9:4)-(9:11)) + │ │ │ ├── tag_closing: "%>" (location: (9:11)-(9:13)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ ├── @ HTMLTextNode (location: (9:13)-(10:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (10:2)-(10:13)) + │ │ │ ├── tag_opening: "<%" (location: (10:2)-(10:4)) + │ │ │ ├── content: " break " (location: (10:4)-(10:11)) + │ │ │ ├── tag_closing: "%>" (location: (10:11)-(10:13)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ ├── @ HTMLTextNode (location: (10:13)-(11:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (11:2)-(11:12)) + │ │ │ ├── tag_opening: "<%" (location: (11:2)-(11:4)) + │ │ │ ├── content: " next " (location: (11:4)-(11:10)) + │ │ │ ├── tag_closing: "%>" (location: (11:10)-(11:12)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ ├── @ HTMLTextNode (location: (11:12)-(12:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (12:2)-(12:12)) + │ │ │ ├── tag_opening: "<%" (location: (12:2)-(12:4)) + │ │ │ ├── content: " redo " (location: (12:4)-(12:10)) + │ │ │ ├── tag_closing: "%>" (location: (12:10)-(12:12)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (12:12)-(13:0)) + │ │ └── content: "\n" + │ │ + │ └── end_node: + │ └── @ ERBEndNode (location: (13:0)-(13:9)) + │ ├── tag_opening: "<%" (location: (13:0)-(13:2)) + │ ├── content: " end " (location: (13:2)-(13:7)) + │ └── tag_closing: "%>" (location: (13:7)-(13:9)) + │ + │ + ├── @ HTMLTextNode (location: (13:9)-(15:0)) + │ └── content: "\n\n" + │ + ├── @ ERBBeginNode (location: (15:0)-(22:9)) + │ ├── tag_opening: "<%" (location: (15:0)-(15:2)) + │ ├── content: " begin " (location: (15:2)-(15:9)) + │ ├── tag_closing: "%>" (location: (15:9)-(15:11)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (15:11)-(16:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBContentNode (location: (16:2)-(16:13)) + │ │ │ ├── tag_opening: "<%" (location: (16:2)-(16:4)) + │ │ │ ├── content: " retry " (location: (16:4)-(16:11)) + │ │ │ ├── tag_closing: "%>" (location: (16:11)-(16:13)) + │ │ │ ├── parsed: true + │ │ │ └── valid: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (16:13)-(17:0)) + │ │ └── content: "\n" + │ │ + │ ├── rescue_clause: + │ │ └── @ ERBRescueNode (location: (17:0)-(22:0)) + │ │ ├── tag_opening: "<%" (location: (17:0)-(17:2)) + │ │ ├── content: " rescue " (location: (17:2)-(17:10)) + │ │ ├── tag_closing: "%>" (location: (17:10)-(17:12)) + │ │ ├── statements: (9 items) + │ │ │ ├── @ HTMLTextNode (location: (17:12)-(18:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── @ ERBContentNode (location: (18:2)-(18:13)) + │ │ │ │ ├── tag_opening: "<%" (location: (18:2)-(18:4)) + │ │ │ │ ├── content: " retry " (location: (18:4)-(18:11)) + │ │ │ │ ├── tag_closing: "%>" (location: (18:11)-(18:13)) + │ │ │ │ ├── parsed: true + │ │ │ │ └── valid: false + │ │ │ │ + │ │ │ ├── @ HTMLTextNode (location: (18:13)-(19:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── @ ERBContentNode (location: (19:2)-(19:13)) + │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ └── @ ERBControlFlowScopeError (location: (19:2)-(19:13)) + │ │ │ │ │ ├── message: "`<% break %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ │ └── keyword: "`<% break %>`" + │ │ │ │ │ + │ │ │ │ ├── tag_opening: "<%" (location: (19:2)-(19:4)) + │ │ │ │ ├── content: " break " (location: (19:4)-(19:11)) + │ │ │ │ ├── tag_closing: "%>" (location: (19:11)-(19:13)) + │ │ │ │ ├── parsed: true + │ │ │ │ └── valid: false + │ │ │ │ + │ │ │ ├── @ HTMLTextNode (location: (19:13)-(20:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── @ ERBContentNode (location: (20:2)-(20:12)) + │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ └── @ ERBControlFlowScopeError (location: (20:2)-(20:12)) + │ │ │ │ │ ├── message: "`<% next %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ │ └── keyword: "`<% next %>`" + │ │ │ │ │ + │ │ │ │ ├── tag_opening: "<%" (location: (20:2)-(20:4)) + │ │ │ │ ├── content: " next " (location: (20:4)-(20:10)) + │ │ │ │ ├── tag_closing: "%>" (location: (20:10)-(20:12)) + │ │ │ │ ├── parsed: true + │ │ │ │ └── valid: false + │ │ │ │ + │ │ │ ├── @ HTMLTextNode (location: (20:12)-(21:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── @ ERBContentNode (location: (21:2)-(21:12)) + │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ └── @ ERBControlFlowScopeError (location: (21:2)-(21:12)) + │ │ │ │ │ ├── message: "`<% redo %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ │ │ │ └── keyword: "`<% redo %>`" + │ │ │ │ │ + │ │ │ │ ├── tag_opening: "<%" (location: (21:2)-(21:4)) + │ │ │ │ ├── content: " redo " (location: (21:4)-(21:10)) + │ │ │ │ ├── tag_closing: "%>" (location: (21:10)-(21:12)) + │ │ │ │ ├── parsed: true + │ │ │ │ └── valid: false + │ │ │ │ + │ │ │ └── @ HTMLTextNode (location: (21:12)-(22:0)) + │ │ │ └── content: "\n" + │ │ │ + │ │ └── subsequent: ∅ + │ │ + │ ├── else_clause: ∅ + │ ├── ensure_clause: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (22:0)-(22:9)) + │ ├── tag_opening: "<%" (location: (22:0)-(22:2)) + │ ├── content: " end " (location: (22:2)-(22:7)) + │ └── tag_closing: "%>" (location: (22:7)-(22:9)) + │ + │ + ├── @ HTMLTextNode (location: (22:9)-(24:0)) + │ └── content: "\n\n" + │ + ├── @ ERBContentNode (location: (24:0)-(24:11)) + │ ├── errors: (1 error) + │ │ └── @ ERBControlFlowScopeError (location: (24:0)-(24:11)) + │ │ ├── message: "`<% break %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ └── keyword: "`<% break %>`" + │ │ + │ ├── tag_opening: "<%" (location: (24:0)-(24:2)) + │ ├── content: " break " (location: (24:2)-(24:9)) + │ ├── tag_closing: "%>" (location: (24:9)-(24:11)) + │ ├── parsed: true + │ └── valid: false + │ + ├── @ HTMLTextNode (location: (24:11)-(25:0)) + │ └── content: "\n" + │ + ├── @ ERBContentNode (location: (25:0)-(25:10)) + │ ├── errors: (1 error) + │ │ └── @ ERBControlFlowScopeError (location: (25:0)-(25:10)) + │ │ ├── message: "`<% next %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ └── keyword: "`<% next %>`" + │ │ + │ ├── tag_opening: "<%" (location: (25:0)-(25:2)) + │ ├── content: " next " (location: (25:2)-(25:8)) + │ ├── tag_closing: "%>" (location: (25:8)-(25:10)) + │ ├── parsed: true + │ └── valid: false + │ + ├── @ HTMLTextNode (location: (25:10)-(26:0)) + │ └── content: "\n" + │ + ├── @ ERBContentNode (location: (26:0)-(26:11)) + │ ├── errors: (1 error) + │ │ └── @ ERBControlFlowScopeError (location: (26:0)-(26:11)) + │ │ ├── message: "`<% retry %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ └── keyword: "`<% retry %>`" + │ │ + │ ├── tag_opening: "<%" (location: (26:0)-(26:2)) + │ ├── content: " retry " (location: (26:2)-(26:9)) + │ ├── tag_closing: "%>" (location: (26:9)-(26:11)) + │ ├── parsed: true + │ └── valid: false + │ + ├── @ HTMLTextNode (location: (26:11)-(27:0)) + │ └── content: "\n" + │ + ├── @ ERBContentNode (location: (27:0)-(27:10)) + │ ├── errors: (1 error) + │ │ └── @ ERBControlFlowScopeError (location: (27:0)-(27:10)) + │ │ ├── message: "`<% redo %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)." + │ │ └── keyword: "`<% redo %>`" + │ │ + │ ├── tag_opening: "<%" (location: (27:0)-(27:2)) + │ ├── content: " redo " (location: (27:2)-(27:8)) + │ ├── tag_closing: "%>" (location: (27:8)-(27:10)) + │ ├── parsed: true + │ └── valid: false + │ + └── @ HTMLTextNode (location: (27:10)-(28:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/analyze/unless_test/test_0008_guard_clause_with_return_unless_modifier_650bfc3134fa92038c335f96f9047860.txt b/test/snapshots/analyze/unless_test/test_0008_guard_clause_with_return_unless_modifier_650bfc3134fa92038c335f96f9047860.txt deleted file mode 100644 index 2788b4d02..000000000 --- a/test/snapshots/analyze/unless_test/test_0008_guard_clause_with_return_unless_modifier_650bfc3134fa92038c335f96f9047860.txt +++ /dev/null @@ -1,66 +0,0 @@ ---- -source: "Analyze::UnlessTest#test_0008_guard clause with return unless modifier" -input: |2- -<% def some_method %> - <% return unless condition %> -
This will render
-<% end %> ---- -@ DocumentNode (location: (1:0)-(5:0)) -└── children: (8 items) - ├── @ ERBContentNode (location: (1:0)-(1:21)) - │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) - │ ├── content: " def some_method " (location: (1:2)-(1:19)) - │ ├── tag_closing: "%>" (location: (1:19)-(1:21)) - │ ├── parsed: true - │ └── valid: false - │ - ├── @ HTMLTextNode (location: (1:21)-(2:2)) - │ └── content: "\n " - │ - ├── @ ERBContentNode (location: (2:2)-(2:31)) - │ ├── tag_opening: "<%" (location: (2:2)-(2:4)) - │ ├── content: " return unless condition " (location: (2:4)-(2:29)) - │ ├── tag_closing: "%>" (location: (2:29)-(2:31)) - │ ├── parsed: true - │ └── valid: true - │ - ├── @ HTMLTextNode (location: (2:31)-(3:2)) - │ └── content: "\n " - │ - ├── @ HTMLElementNode (location: (3:2)-(3:29)) - │ ├── open_tag: - │ │ └── @ HTMLOpenTagNode (location: (3:2)-(3:7)) - │ │ ├── tag_opening: "<" (location: (3:2)-(3:3)) - │ │ ├── tag_name: "div" (location: (3:3)-(3:6)) - │ │ ├── tag_closing: ">" (location: (3:6)-(3:7)) - │ │ ├── children: [] - │ │ └── is_void: false - │ │ - │ ├── tag_name: "div" (location: (3:3)-(3:6)) - │ ├── body: (1 item) - │ │ └── @ HTMLTextNode (location: (3:7)-(3:23)) - │ │ └── content: "This will render" - │ │ - │ ├── close_tag: - │ │ └── @ HTMLCloseTagNode (location: (3:23)-(3:29)) - │ │ ├── tag_opening: "" (location: (3:28)-(3:29)) - │ │ - │ ├── is_void: false - │ └── source: "HTML" - │ - ├── @ HTMLTextNode (location: (3:29)-(4:0)) - │ └── content: "\n" - │ - ├── @ ERBContentNode (location: (4:0)-(4:9)) - │ ├── tag_opening: "<%" (location: (4:0)-(4:2)) - │ ├── content: " end " (location: (4:2)-(4:7)) - │ ├── tag_closing: "%>" (location: (4:7)-(4:9)) - │ ├── parsed: true - │ └── valid: false - │ - └── @ HTMLTextNode (location: (4:9)-(5:0)) - └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/attributes_test/test_0006_attribute_with_no_quotes_value,_no_whitespace,_and_non_self-closing_tag_f00fb4a0aa00f7c763be88670c0bc05d.txt b/test/snapshots/parser/attributes_test/test_0006_attribute_with_no_quotes_value,_no_whitespace,_and_non_self-closing_tag_f00fb4a0aa00f7c763be88670c0bc05d.txt index d5b8072dc..bd7bd58a5 100644 --- a/test/snapshots/parser/attributes_test/test_0006_attribute_with_no_quotes_value,_no_whitespace,_and_non_self-closing_tag_f00fb4a0aa00f7c763be88670c0bc05d.txt +++ b/test/snapshots/parser/attributes_test/test_0006_attribute_with_no_quotes_value,_no_whitespace,_and_non_self-closing_tag_f00fb4a0aa00f7c763be88670c0bc05d.txt @@ -3,48 +3,35 @@ source: "Parser::AttributesTest#test_0006_attribute with no quotes value, no whi input: "
" --- @ DocumentNode (location: (1:0)-(1:17)) -├── errors: (1 error) -│ └── @ UnclosedElementError (location: (1:17)-(1:17)) -│ ├── message: "Tag `
` opened at (1:1) was never closed before the end of document." -│ └── opening_tag: "div" (location: (1:1)-(1:4)) -│ └── children: (1 item) - └── @ HTMLElementNode (location: (1:0)-(1:17)) + └── @ HTMLOpenTagNode (location: (1:0)-(1:17)) ├── errors: (1 error) - │ └── @ MissingClosingTagError (location: (1:1)-(1:4)) - │ ├── message: "Opening tag `
` at (1:1) doesn't have a matching closing tag `
`." + │ └── @ MissingClosingTagError (location: (1:0)-(1:17)) + │ ├── message: "Opening tag `
` at (1:1) doesn't have a matching closing tag `
` in the same scope." │ └── opening_tag: "div" (location: (1:1)-(1:4)) │ - ├── open_tag: - │ └── @ HTMLOpenTagNode (location: (1:0)-(1:17)) - │ ├── tag_opening: "<" (location: (1:0)-(1:1)) - │ ├── tag_name: "div" (location: (1:1)-(1:4)) - │ ├── tag_closing: ">" (location: (1:16)-(1:17)) - │ ├── children: (1 item) - │ │ └── @ HTMLAttributeNode (location: (1:5)-(1:16)) - │ │ ├── name: - │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:10)) - │ │ │ └── children: (1 item) - │ │ │ └── @ LiteralNode (location: (1:5)-(1:10)) - │ │ │ └── content: "value" - │ │ │ - │ │ │ - │ │ ├── equals: "=" (location: (1:10)-(1:11)) - │ │ └── value: - │ │ └── @ HTMLAttributeValueNode (location: (1:11)-(1:16)) - │ │ ├── open_quote: ∅ - │ │ ├── children: (1 item) - │ │ │ └── @ LiteralNode (location: (1:11)-(1:16)) - │ │ │ └── content: "hello" - │ │ │ - │ │ ├── close_quote: ∅ - │ │ └── quoted: false + ├── tag_opening: "<" (location: (1:0)-(1:1)) + ├── tag_name: "div" (location: (1:1)-(1:4)) + ├── tag_closing: ">" (location: (1:16)-(1:17)) + ├── children: (1 item) + │ └── @ HTMLAttributeNode (location: (1:5)-(1:16)) + │ ├── name: + │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:10)) + │ │ └── children: (1 item) + │ │ └── @ LiteralNode (location: (1:5)-(1:10)) + │ │ └── content: "value" │ │ │ │ - │ └── is_void: false + │ ├── equals: "=" (location: (1:10)-(1:11)) + │ └── value: + │ └── @ HTMLAttributeValueNode (location: (1:11)-(1:16)) + │ ├── open_quote: ∅ + │ ├── children: (1 item) + │ │ └── @ LiteralNode (location: (1:11)-(1:16)) + │ │ └── content: "hello" + │ │ + │ ├── close_quote: ∅ + │ └── quoted: false │ - ├── tag_name: "div" (location: (1:1)-(1:4)) - ├── body: [] - ├── close_tag: ∅ - ├── is_void: false - └── source: "HTML" \ No newline at end of file + │ + └── is_void: false \ No newline at end of file diff --git a/test/snapshots/parser/attributes_test/test_0037_attribute_with_backtick_containing_HTML_(invalid)_542130e358dadabeb4a1629bc9bcf4f3.txt b/test/snapshots/parser/attributes_test/test_0037_attribute_with_backtick_containing_HTML_(invalid)_542130e358dadabeb4a1629bc9bcf4f3.txt index 085ee3be6..1fc311d3e 100644 --- a/test/snapshots/parser/attributes_test/test_0037_attribute_with_backtick_containing_HTML_(invalid)_542130e358dadabeb4a1629bc9bcf4f3.txt +++ b/test/snapshots/parser/attributes_test/test_0037_attribute_with_backtick_containing_HTML_(invalid)_542130e358dadabeb4a1629bc9bcf4f3.txt @@ -3,94 +3,83 @@ source: "Parser::AttributesTest#test_0037_attribute with backtick containing HTM input: "
Hello`>
" --- @ DocumentNode (location: (1:0)-(1:46)) -├── errors: (1 error) -│ └── @ UnclosedElementError (location: (1:40)-(1:42)) -│ ├── message: "Tag `
` opened at (1:1) was never closed before the end of document." -│ └── opening_tag: "div" (location: (1:1)-(1:4)) -│ -└── children: (3 items) - ├── @ HTMLElementNode (location: (1:0)-(1:38)) - │ ├── errors: (1 error) - │ │ └── @ TagNamesMismatchError (location: (1:33)-(1:37)) - │ │ ├── message: "Opening tag `
` at (1:1) closed with `` at (1:33)." - │ │ ├── opening_tag: "div" (location: (1:1)-(1:4)) - │ │ └── closing_tag: "span" (location: (1:33)-(1:37)) - │ │ - │ ├── open_tag: - │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:26)) - │ │ ├── errors: (1 error) - │ │ │ └── @ UnexpectedError (location: (1:20)-(1:21)) - │ │ │ ├── message: "Unexpected Token. Expected: `TOKEN_IDENTIFIER, TOKEN_AT, TOKEN_ERB_START,TOKEN_WHITESPACE, or TOKEN_NEWLINE`, found: `TOKEN_HTML_TAG_START`." - │ │ │ ├── description: "Unexpected Token" - │ │ │ ├── expected: "TOKEN_IDENTIFIER, TOKEN_AT, TOKEN_ERB_START,TOKEN_WHITESPACE, or TOKEN_NEWLINE" - │ │ │ └── found: "TOKEN_HTML_TAG_START" - │ │ │ - │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) - │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) - │ │ ├── tag_closing: ">" (location: (1:25)-(1:26)) - │ │ ├── children: (2 items) - │ │ │ ├── @ HTMLAttributeNode (location: (1:5)-(1:20)) - │ │ │ │ ├── name: - │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:18)) - │ │ │ │ │ └── children: (1 item) - │ │ │ │ │ └── @ LiteralNode (location: (1:5)-(1:18)) - │ │ │ │ │ └── content: "data-template" - │ │ │ │ │ - │ │ │ │ │ - │ │ │ │ ├── equals: "=" (location: (1:18)-(1:19)) - │ │ │ │ └── value: - │ │ │ │ └── @ HTMLAttributeValueNode (location: (1:19)-(1:20)) - │ │ │ │ ├── errors: (1 error) - │ │ │ │ │ └── @ UnexpectedError (location: (1:19)-(1:20)) - │ │ │ │ │ ├── message: "Invalid quote character for HTML attribute. Expected: `single quote (') or double quote (\")`, found: `backtick (`)`." - │ │ │ │ │ ├── description: "Invalid quote character for HTML attribute" - │ │ │ │ │ ├── expected: "single quote (') or double quote (\")" - │ │ │ │ │ └── found: "backtick (`)" - │ │ │ │ │ - │ │ │ │ ├── open_quote: ∅ - │ │ │ │ ├── children: [] - │ │ │ │ ├── close_quote: ∅ - │ │ │ │ └── quoted: false - │ │ │ │ - │ │ │ │ - │ │ │ └── @ HTMLAttributeNode (location: (1:21)-(1:25)) - │ │ │ ├── name: - │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:21)-(1:25)) - │ │ │ │ └── children: (1 item) - │ │ │ │ └── @ LiteralNode (location: (1:21)-(1:25)) - │ │ │ │ └── content: "span" - │ │ │ │ - │ │ │ │ - │ │ │ ├── equals: ∅ - │ │ │ └── value: ∅ - │ │ │ - │ │ └── is_void: false - │ │ - │ ├── tag_name: "div" (location: (1:1)-(1:4)) - │ ├── body: (1 item) - │ │ └── @ HTMLTextNode (location: (1:26)-(1:31)) - │ │ └── content: "Hello" - │ │ - │ ├── close_tag: - │ │ └── @ HTMLCloseTagNode (location: (1:31)-(1:38)) - │ │ ├── tag_opening: "" (location: (1:37)-(1:38)) - │ │ - │ ├── is_void: false - │ └── source: "HTML" - │ - ├── @ HTMLTextNode (location: (1:38)-(1:40)) - │ └── content: "`>" - │ - └── @ HTMLCloseTagNode (location: (1:40)-(1:46)) - ├── errors: (1 error) - │ └── @ MissingOpeningTagError (location: (1:40)-(1:46)) - │ ├── message: "Found closing tag `
` at (1:42) without a matching opening tag." - │ └── closing_tag: "div" (location: (1:42)-(1:45)) +└── children: (1 item) + └── @ HTMLElementNode (location: (1:0)-(1:46)) + ├── open_tag: + │ └── @ HTMLOpenTagNode (location: (1:0)-(1:26)) + │ ├── errors: (1 error) + │ │ └── @ UnexpectedError (location: (1:20)-(1:21)) + │ │ ├── message: "Unexpected Token. Expected: `TOKEN_IDENTIFIER, TOKEN_AT, TOKEN_ERB_START,TOKEN_WHITESPACE, or TOKEN_NEWLINE`, found: `TOKEN_HTML_TAG_START`." + │ │ ├── description: "Unexpected Token" + │ │ ├── expected: "TOKEN_IDENTIFIER, TOKEN_AT, TOKEN_ERB_START,TOKEN_WHITESPACE, or TOKEN_NEWLINE" + │ │ └── found: "TOKEN_HTML_TAG_START" + │ │ + │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── tag_closing: ">" (location: (1:25)-(1:26)) + │ ├── children: (2 items) + │ │ ├── @ HTMLAttributeNode (location: (1:5)-(1:20)) + │ │ │ ├── name: + │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:18)) + │ │ │ │ └── children: (1 item) + │ │ │ │ └── @ LiteralNode (location: (1:5)-(1:18)) + │ │ │ │ └── content: "data-template" + │ │ │ │ + │ │ │ │ + │ │ │ ├── equals: "=" (location: (1:18)-(1:19)) + │ │ │ └── value: + │ │ │ └── @ HTMLAttributeValueNode (location: (1:19)-(1:20)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ UnexpectedError (location: (1:19)-(1:20)) + │ │ │ │ ├── message: "Invalid quote character for HTML attribute. Expected: `single quote (') or double quote (\")`, found: `backtick (`)`." + │ │ │ │ ├── description: "Invalid quote character for HTML attribute" + │ │ │ │ ├── expected: "single quote (') or double quote (\")" + │ │ │ │ └── found: "backtick (`)" + │ │ │ │ + │ │ │ ├── open_quote: ∅ + │ │ │ ├── children: [] + │ │ │ ├── close_quote: ∅ + │ │ │ └── quoted: false + │ │ │ + │ │ │ + │ │ └── @ HTMLAttributeNode (location: (1:21)-(1:25)) + │ │ ├── name: + │ │ │ └── @ HTMLAttributeNameNode (location: (1:21)-(1:25)) + │ │ │ └── children: (1 item) + │ │ │ └── @ LiteralNode (location: (1:21)-(1:25)) + │ │ │ └── content: "span" + │ │ │ + │ │ │ + │ │ ├── equals: ∅ + │ │ └── value: ∅ + │ │ + │ └── is_void: false │ - ├── tag_opening: "" (location: (1:45)-(1:46)) \ No newline at end of file + ├── tag_name: "div" (location: (1:1)-(1:4)) + ├── body: (3 items) + │ ├── @ HTMLTextNode (location: (1:26)-(1:31)) + │ │ └── content: "Hello" + │ │ + │ ├── @ HTMLCloseTagNode (location: (1:31)-(1:38)) + │ │ ├── errors: (1 error) + │ │ │ └── @ MissingOpeningTagError (location: (1:31)-(1:38)) + │ │ │ ├── message: "Found closing tag `` at (1:33) without a matching opening tag in the same scope." + │ │ │ └── closing_tag: "span" (location: (1:33)-(1:37)) + │ │ │ + │ │ ├── tag_opening: "" (location: (1:37)-(1:38)) + │ │ + │ └── @ HTMLTextNode (location: (1:38)-(1:40)) + │ └── content: "`>" + │ + ├── close_tag: + │ └── @ HTMLCloseTagNode (location: (1:40)-(1:46)) + │ ├── tag_opening: "" (location: (1:45)-(1:46)) + │ + ├── is_void: false + └── source: "HTML" \ No newline at end of file diff --git a/test/snapshots/parser/case_match_test/test_0001_case_in_69cf275355e6a10adc88e47d5090dd3a.txt b/test/snapshots/parser/case_match_test/test_0001_case_in_69cf275355e6a10adc88e47d5090dd3a.txt new file mode 100644 index 000000000..2e841c76d --- /dev/null +++ b/test/snapshots/parser/case_match_test/test_0001_case_in_69cf275355e6a10adc88e47d5090dd3a.txt @@ -0,0 +1,131 @@ +--- +source: "Parser::CaseMatchTest#test_0001_case/in" +input: |2- +<% case { hash: { nested: '4' } } %> + children +<% in { hash: { nested: } } %> + nested +<% else %> + else +<% end %> +--- +@ DocumentNode (location: (1:0)-(8:0)) +└── children: (2 items) + ├── @ ERBCaseMatchNode (location: (1:0)-(7:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " case { hash: { nested: '4' } } " (location: (1:2)-(1:34)) + │ ├── tag_closing: "%>" (location: (1:34)-(1:36)) + │ ├── children: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:36)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLElementNode (location: (2:2)-(2:23)) + │ │ │ ├── open_tag: + │ │ │ │ └── @ HTMLOpenTagNode (location: (2:2)-(2:8)) + │ │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ │ ├── tag_name: "span" (location: (2:3)-(2:7)) + │ │ │ │ ├── tag_closing: ">" (location: (2:7)-(2:8)) + │ │ │ │ ├── children: [] + │ │ │ │ └── is_void: false + │ │ │ │ + │ │ │ ├── tag_name: "span" (location: (2:3)-(2:7)) + │ │ │ ├── body: (1 item) + │ │ │ │ └── @ HTMLTextNode (location: (2:8)-(2:16)) + │ │ │ │ └── content: "children" + │ │ │ │ + │ │ │ ├── close_tag: + │ │ │ │ └── @ HTMLCloseTagNode (location: (2:16)-(2:23)) + │ │ │ │ ├── tag_opening: "" (location: (2:22)-(2:23)) + │ │ │ │ + │ │ │ ├── is_void: false + │ │ │ └── source: "HTML" + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:23)-(3:0)) + │ │ └── content: "\n" + │ │ + │ ├── conditions: (1 item) + │ │ └── @ ERBInNode (location: (3:0)-(3:30)) + │ │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) + │ │ ├── content: " in { hash: { nested: } } " (location: (3:2)-(3:28)) + │ │ ├── tag_closing: "%>" (location: (3:28)-(3:30)) + │ │ └── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (3:30)-(4:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLElementNode (location: (4:2)-(4:21)) + │ │ │ ├── open_tag: + │ │ │ │ └── @ HTMLOpenTagNode (location: (4:2)-(4:8)) + │ │ │ │ ├── tag_opening: "<" (location: (4:2)-(4:3)) + │ │ │ │ ├── tag_name: "span" (location: (4:3)-(4:7)) + │ │ │ │ ├── tag_closing: ">" (location: (4:7)-(4:8)) + │ │ │ │ ├── children: [] + │ │ │ │ └── is_void: false + │ │ │ │ + │ │ │ ├── tag_name: "span" (location: (4:3)-(4:7)) + │ │ │ ├── body: (1 item) + │ │ │ │ └── @ HTMLTextNode (location: (4:8)-(4:14)) + │ │ │ │ └── content: "nested" + │ │ │ │ + │ │ │ ├── close_tag: + │ │ │ │ └── @ HTMLCloseTagNode (location: (4:14)-(4:21)) + │ │ │ │ ├── tag_opening: "" (location: (4:20)-(4:21)) + │ │ │ │ + │ │ │ ├── is_void: false + │ │ │ └── source: "HTML" + │ │ │ + │ │ └── @ HTMLTextNode (location: (4:21)-(5:0)) + │ │ └── content: "\n" + │ │ + │ │ + │ ├── else_clause: + │ │ └── @ ERBElseNode (location: (5:0)-(5:10)) + │ │ ├── tag_opening: "<%" (location: (5:0)-(5:2)) + │ │ ├── content: " else " (location: (5:2)-(5:8)) + │ │ ├── tag_closing: "%>" (location: (5:8)-(5:10)) + │ │ └── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (5:10)-(6:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLElementNode (location: (6:2)-(6:19)) + │ │ │ ├── open_tag: + │ │ │ │ └── @ HTMLOpenTagNode (location: (6:2)-(6:8)) + │ │ │ │ ├── tag_opening: "<" (location: (6:2)-(6:3)) + │ │ │ │ ├── tag_name: "span" (location: (6:3)-(6:7)) + │ │ │ │ ├── tag_closing: ">" (location: (6:7)-(6:8)) + │ │ │ │ ├── children: [] + │ │ │ │ └── is_void: false + │ │ │ │ + │ │ │ ├── tag_name: "span" (location: (6:3)-(6:7)) + │ │ │ ├── body: (1 item) + │ │ │ │ └── @ HTMLTextNode (location: (6:8)-(6:12)) + │ │ │ │ └── content: "else" + │ │ │ │ + │ │ │ ├── close_tag: + │ │ │ │ └── @ HTMLCloseTagNode (location: (6:12)-(6:19)) + │ │ │ │ ├── tag_opening: "" (location: (6:18)-(6:19)) + │ │ │ │ + │ │ │ ├── is_void: false + │ │ │ └── source: "HTML" + │ │ │ + │ │ └── @ HTMLTextNode (location: (6:19)-(7:0)) + │ │ └── content: "\n" + │ │ + │ │ + │ └── end_node: + │ └── @ ERBEndNode (location: (7:0)-(7:9)) + │ ├── tag_opening: "<%" (location: (7:0)-(7:2)) + │ ├── content: " end " (location: (7:2)-(7:7)) + │ └── tag_closing: "%>" (location: (7:7)-(7:9)) + │ + │ + └── @ HTMLTextNode (location: (7:9)-(8:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/case_when_test/test_0001_case_when_71d06502a93ad345bf738ba8ace8bf9f.txt b/test/snapshots/parser/case_when_test/test_0001_case_when_71d06502a93ad345bf738ba8ace8bf9f.txt new file mode 100644 index 000000000..0a855e066 --- /dev/null +++ b/test/snapshots/parser/case_when_test/test_0001_case_when_71d06502a93ad345bf738ba8ace8bf9f.txt @@ -0,0 +1,169 @@ +--- +source: "Parser::CaseWhenTest#test_0001_case/when" +input: |2- +<% case variable %> +

Children

+<% when Integer %> +

Integer

+<% when String %> +

String

+<% else %> +

else

+<% end %> +--- +@ DocumentNode (location: (1:0)-(10:0)) +└── children: (2 items) + ├── @ ERBCaseNode (location: (1:0)-(9:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " case variable " (location: (1:2)-(1:17)) + │ ├── tag_closing: "%>" (location: (1:17)-(1:19)) + │ ├── children: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:19)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLElementNode (location: (2:2)-(2:19)) + │ │ │ ├── open_tag: + │ │ │ │ └── @ HTMLOpenTagNode (location: (2:2)-(2:6)) + │ │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ │ ├── tag_name: "h1" (location: (2:3)-(2:5)) + │ │ │ │ ├── tag_closing: ">" (location: (2:5)-(2:6)) + │ │ │ │ ├── children: [] + │ │ │ │ └── is_void: false + │ │ │ │ + │ │ │ ├── tag_name: "h1" (location: (2:3)-(2:5)) + │ │ │ ├── body: (1 item) + │ │ │ │ └── @ HTMLTextNode (location: (2:6)-(2:14)) + │ │ │ │ └── content: "Children" + │ │ │ │ + │ │ │ ├── close_tag: + │ │ │ │ └── @ HTMLCloseTagNode (location: (2:14)-(2:19)) + │ │ │ │ ├── tag_opening: "" (location: (2:18)-(2:19)) + │ │ │ │ + │ │ │ ├── is_void: false + │ │ │ └── source: "HTML" + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:19)-(3:0)) + │ │ └── content: "\n" + │ │ + │ ├── conditions: (2 items) + │ │ ├── @ ERBWhenNode (location: (3:0)-(3:18)) + │ │ │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) + │ │ │ ├── content: " when Integer " (location: (3:2)-(3:16)) + │ │ │ ├── tag_closing: "%>" (location: (3:16)-(3:18)) + │ │ │ └── statements: (3 items) + │ │ │ ├── @ HTMLTextNode (location: (3:18)-(4:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── @ HTMLElementNode (location: (4:2)-(4:18)) + │ │ │ │ ├── open_tag: + │ │ │ │ │ └── @ HTMLOpenTagNode (location: (4:2)-(4:6)) + │ │ │ │ │ ├── tag_opening: "<" (location: (4:2)-(4:3)) + │ │ │ │ │ ├── tag_name: "h1" (location: (4:3)-(4:5)) + │ │ │ │ │ ├── tag_closing: ">" (location: (4:5)-(4:6)) + │ │ │ │ │ ├── children: [] + │ │ │ │ │ └── is_void: false + │ │ │ │ │ + │ │ │ │ ├── tag_name: "h1" (location: (4:3)-(4:5)) + │ │ │ │ ├── body: (1 item) + │ │ │ │ │ └── @ HTMLTextNode (location: (4:6)-(4:13)) + │ │ │ │ │ └── content: "Integer" + │ │ │ │ │ + │ │ │ │ ├── close_tag: + │ │ │ │ │ └── @ HTMLCloseTagNode (location: (4:13)-(4:18)) + │ │ │ │ │ ├── tag_opening: "" (location: (4:17)-(4:18)) + │ │ │ │ │ + │ │ │ │ ├── is_void: false + │ │ │ │ └── source: "HTML" + │ │ │ │ + │ │ │ └── @ HTMLTextNode (location: (4:18)-(5:0)) + │ │ │ └── content: "\n" + │ │ │ + │ │ │ + │ │ └── @ ERBWhenNode (location: (5:0)-(5:17)) + │ │ ├── tag_opening: "<%" (location: (5:0)-(5:2)) + │ │ ├── content: " when String " (location: (5:2)-(5:15)) + │ │ ├── tag_closing: "%>" (location: (5:15)-(5:17)) + │ │ └── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (5:17)-(6:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLElementNode (location: (6:2)-(6:17)) + │ │ │ ├── open_tag: + │ │ │ │ └── @ HTMLOpenTagNode (location: (6:2)-(6:6)) + │ │ │ │ ├── tag_opening: "<" (location: (6:2)-(6:3)) + │ │ │ │ ├── tag_name: "h1" (location: (6:3)-(6:5)) + │ │ │ │ ├── tag_closing: ">" (location: (6:5)-(6:6)) + │ │ │ │ ├── children: [] + │ │ │ │ └── is_void: false + │ │ │ │ + │ │ │ ├── tag_name: "h1" (location: (6:3)-(6:5)) + │ │ │ ├── body: (1 item) + │ │ │ │ └── @ HTMLTextNode (location: (6:6)-(6:12)) + │ │ │ │ └── content: "String" + │ │ │ │ + │ │ │ ├── close_tag: + │ │ │ │ └── @ HTMLCloseTagNode (location: (6:12)-(6:17)) + │ │ │ │ ├── tag_opening: "" (location: (6:16)-(6:17)) + │ │ │ │ + │ │ │ ├── is_void: false + │ │ │ └── source: "HTML" + │ │ │ + │ │ └── @ HTMLTextNode (location: (6:17)-(7:0)) + │ │ └── content: "\n" + │ │ + │ │ + │ ├── else_clause: + │ │ └── @ ERBElseNode (location: (7:0)-(7:10)) + │ │ ├── tag_opening: "<%" (location: (7:0)-(7:2)) + │ │ ├── content: " else " (location: (7:2)-(7:8)) + │ │ ├── tag_closing: "%>" (location: (7:8)-(7:10)) + │ │ └── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (7:10)-(8:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLElementNode (location: (8:2)-(8:15)) + │ │ │ ├── open_tag: + │ │ │ │ └── @ HTMLOpenTagNode (location: (8:2)-(8:6)) + │ │ │ │ ├── tag_opening: "<" (location: (8:2)-(8:3)) + │ │ │ │ ├── tag_name: "h1" (location: (8:3)-(8:5)) + │ │ │ │ ├── tag_closing: ">" (location: (8:5)-(8:6)) + │ │ │ │ ├── children: [] + │ │ │ │ └── is_void: false + │ │ │ │ + │ │ │ ├── tag_name: "h1" (location: (8:3)-(8:5)) + │ │ │ ├── body: (1 item) + │ │ │ │ └── @ HTMLTextNode (location: (8:6)-(8:10)) + │ │ │ │ └── content: "else" + │ │ │ │ + │ │ │ ├── close_tag: + │ │ │ │ └── @ HTMLCloseTagNode (location: (8:10)-(8:15)) + │ │ │ │ ├── tag_opening: "" (location: (8:14)-(8:15)) + │ │ │ │ + │ │ │ ├── is_void: false + │ │ │ └── source: "HTML" + │ │ │ + │ │ └── @ HTMLTextNode (location: (8:15)-(9:0)) + │ │ └── content: "\n" + │ │ + │ │ + │ └── end_node: + │ └── @ ERBEndNode (location: (9:0)-(9:9)) + │ ├── tag_opening: "<%" (location: (9:0)-(9:2)) + │ ├── content: " end " (location: (9:2)-(9:7)) + │ └── tag_closing: "%>" (location: (9:7)-(9:9)) + │ + │ + └── @ HTMLTextNode (location: (9:9)-(10:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/cdata_test/test_0009_CDATA_with_complex_ERB_2c2d90415d057b7acbff8136b9e5e8bf.txt b/test/snapshots/parser/cdata_test/test_0009_CDATA_with_complex_ERB_2c2d90415d057b7acbff8136b9e5e8bf.txt index fde3a42e2..f58882376 100644 --- a/test/snapshots/parser/cdata_test/test_0009_CDATA_with_complex_ERB_2c2d90415d057b7acbff8136b9e5e8bf.txt +++ b/test/snapshots/parser/cdata_test/test_0009_CDATA_with_complex_ERB_2c2d90415d057b7acbff8136b9e5e8bf.txt @@ -11,36 +11,35 @@ input: |2- └── children: (2 items) ├── @ CDATANode (location: (1:0)-(5:3)) │ ├── tag_opening: "" (location: (2:19)-(2:21)) - │ │ │ ├── parsed: true - │ │ │ └── valid: false + │ │ │ ├── statements: (3 items) + │ │ │ │ ├── @ LiteralNode (location: (2:21)-(3:4)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ ERBContentNode (location: (3:4)-(3:19)) + │ │ │ │ │ ├── tag_opening: "<%=" (location: (3:4)-(3:7)) + │ │ │ │ │ ├── content: " @content " (location: (3:7)-(3:17)) + │ │ │ │ │ ├── tag_closing: "%>" (location: (3:17)-(3:19)) + │ │ │ │ │ ├── parsed: true + │ │ │ │ │ └── valid: true + │ │ │ │ │ + │ │ │ │ └── @ LiteralNode (location: (3:19)-(4:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── subsequent: ∅ + │ │ │ └── end_node: + │ │ │ └── @ ERBEndNode (location: (4:2)-(4:11)) + │ │ │ ├── tag_opening: "<%" (location: (4:2)-(4:4)) + │ │ │ ├── content: " end " (location: (4:4)-(4:9)) + │ │ │ └── tag_closing: "%>" (location: (4:9)-(4:11)) │ │ │ - │ │ ├── @ LiteralNode (location: (2:21)-(3:4)) - │ │ │ └── content: "\n " - │ │ │ - │ │ ├── @ ERBContentNode (location: (3:4)-(3:19)) - │ │ │ ├── tag_opening: "<%=" (location: (3:4)-(3:7)) - │ │ │ ├── content: " @content " (location: (3:7)-(3:17)) - │ │ │ ├── tag_closing: "%>" (location: (3:17)-(3:19)) - │ │ │ ├── parsed: true - │ │ │ └── valid: true - │ │ │ - │ │ ├── @ LiteralNode (location: (3:19)-(4:2)) - │ │ │ └── content: "\n " - │ │ │ - │ │ ├── @ ERBContentNode (location: (4:2)-(4:11)) - │ │ │ ├── tag_opening: "<%" (location: (4:2)-(4:4)) - │ │ │ ├── content: " end " (location: (4:4)-(4:9)) - │ │ │ ├── tag_closing: "%>" (location: (4:9)-(4:11)) - │ │ │ ├── parsed: true - │ │ │ └── valid: false │ │ │ │ │ └── @ LiteralNode (location: (4:11)-(5:0)) │ │ └── content: "\n" diff --git a/test/snapshots/parser/cdata_test/test_0017_CDATA_with_ERB_loop_e0b9e9e369a74b4d48962fb7540684ff.txt b/test/snapshots/parser/cdata_test/test_0017_CDATA_with_ERB_loop_e0b9e9e369a74b4d48962fb7540684ff.txt index dacf5ef2d..a45a217e0 100644 --- a/test/snapshots/parser/cdata_test/test_0017_CDATA_with_ERB_loop_e0b9e9e369a74b4d48962fb7540684ff.txt +++ b/test/snapshots/parser/cdata_test/test_0017_CDATA_with_ERB_loop_e0b9e9e369a74b4d48962fb7540684ff.txt @@ -11,36 +11,34 @@ input: |2- └── children: (2 items) ├── @ CDATANode (location: (1:0)-(5:3)) │ ├── tag_opening: "" (location: (2:27)-(2:29)) - │ │ │ ├── parsed: true - │ │ │ └── valid: false + │ │ │ ├── body: (3 items) + │ │ │ │ ├── @ LiteralNode (location: (2:29)-(3:10)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ ERBContentNode (location: (3:10)-(3:26)) + │ │ │ │ │ ├── tag_opening: "<%=" (location: (3:10)-(3:13)) + │ │ │ │ │ ├── content: " item.name " (location: (3:13)-(3:24)) + │ │ │ │ │ ├── tag_closing: "%>" (location: (3:24)-(3:26)) + │ │ │ │ │ ├── parsed: true + │ │ │ │ │ └── valid: true + │ │ │ │ │ + │ │ │ │ └── @ LiteralNode (location: (3:26)-(4:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ └── end_node: + │ │ │ └── @ ERBEndNode (location: (4:2)-(4:11)) + │ │ │ ├── tag_opening: "<%" (location: (4:2)-(4:4)) + │ │ │ ├── content: " end " (location: (4:4)-(4:9)) + │ │ │ └── tag_closing: "%>" (location: (4:9)-(4:11)) │ │ │ - │ │ ├── @ LiteralNode (location: (2:29)-(3:10)) - │ │ │ └── content: "\n " - │ │ │ - │ │ ├── @ ERBContentNode (location: (3:10)-(3:26)) - │ │ │ ├── tag_opening: "<%=" (location: (3:10)-(3:13)) - │ │ │ ├── content: " item.name " (location: (3:13)-(3:24)) - │ │ │ ├── tag_closing: "%>" (location: (3:24)-(3:26)) - │ │ │ ├── parsed: true - │ │ │ └── valid: true - │ │ │ - │ │ ├── @ LiteralNode (location: (3:26)-(4:2)) - │ │ │ └── content: "\n " - │ │ │ - │ │ ├── @ ERBContentNode (location: (4:2)-(4:11)) - │ │ │ ├── tag_opening: "<%" (location: (4:2)-(4:4)) - │ │ │ ├── content: " end " (location: (4:4)-(4:9)) - │ │ │ ├── tag_closing: "%>" (location: (4:9)-(4:11)) - │ │ │ ├── parsed: true - │ │ │ └── valid: false │ │ │ │ │ └── @ LiteralNode (location: (4:11)-(5:0)) │ │ └── content: "\n" diff --git a/test/snapshots/parser/comments_test/test_0005_HTML_comment_with_if_04813d5ad89aa775065eb87d881ab931.txt b/test/snapshots/parser/comments_test/test_0005_HTML_comment_with_if_04813d5ad89aa775065eb87d881ab931.txt new file mode 100644 index 000000000..7e6a98fb5 --- /dev/null +++ b/test/snapshots/parser/comments_test/test_0005_HTML_comment_with_if_04813d5ad89aa775065eb87d881ab931.txt @@ -0,0 +1,50 @@ +--- +source: "Parser::CommentsTest#test_0005_HTML comment with if" +input: |2- + +--- +@ DocumentNode (location: (1:0)-(6:0)) +└── children: (2 items) + ├── @ HTMLCommentNode (location: (1:0)-(5:3)) + │ ├── comment_start: "" (location: (5:0)-(5:3)) + │ + └── @ HTMLTextNode (location: (5:3)-(6:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/erb_context_mismatch_test/test_0001_out_of_order_closing_tags_across_ERB_blocks_186de8708f7329b37a560b950c6814bc.txt b/test/snapshots/parser/erb_context_mismatch_test/test_0001_out_of_order_closing_tags_across_ERB_blocks_186de8708f7329b37a560b950c6814bc.txt new file mode 100644 index 000000000..ffcf2d0d0 --- /dev/null +++ b/test/snapshots/parser/erb_context_mismatch_test/test_0001_out_of_order_closing_tags_across_ERB_blocks_186de8708f7329b37a560b950c6814bc.txt @@ -0,0 +1,175 @@ +--- +source: "Parser::ERBContextMismatchTest#test_0001_out of order closing tags across ERB blocks" +input: |2- +
+
+
+ <%= render CardComponent.new do |card| %> +
+ + <% end %> +
+
+--- +@ DocumentNode (location: (1:0)-(10:0)) +└── children: (4 items) + ├── @ HTMLOpenTagNode (location: (1:0)-(1:22)) + │ ├── errors: (1 error) + │ │ └── @ MissingClosingTagError (location: (1:0)-(1:22)) + │ │ ├── message: "Opening tag `
` at (1:1) doesn't have a matching closing tag `
` in the same scope." + │ │ └── opening_tag: "div" (location: (1:1)-(1:4)) + │ │ + │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── tag_closing: ">" (location: (1:21)-(1:22)) + │ ├── children: (1 item) + │ │ └── @ HTMLAttributeNode (location: (1:5)-(1:21)) + │ │ ├── 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:21)) + │ │ ├── open_quote: """ (location: (1:11)-(1:12)) + │ │ ├── children: (1 item) + │ │ │ └── @ LiteralNode (location: (1:12)-(1:20)) + │ │ │ └── content: "h-screen" + │ │ │ + │ │ ├── close_quote: """ (location: (1:20)-(1:21)) + │ │ └── quoted: true + │ │ + │ │ + │ └── is_void: false + │ + ├── @ HTMLTextNode (location: (1:22)-(2:2)) + │ └── content: "\n " + │ + ├── @ HTMLElementNode (location: (2:2)-(9:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (2:2)-(2:30)) + │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ ├── tag_name: "div" (location: (2:3)-(2:6)) + │ │ ├── tag_closing: ">" (location: (2:29)-(2:30)) + │ │ ├── children: (1 item) + │ │ │ └── @ HTMLAttributeNode (location: (2:7)-(2:29)) + │ │ │ ├── name: + │ │ │ │ └── @ HTMLAttributeNameNode (location: (2:7)-(2:12)) + │ │ │ │ └── children: (1 item) + │ │ │ │ └── @ LiteralNode (location: (2:7)-(2:12)) + │ │ │ │ └── content: "class" + │ │ │ │ + │ │ │ │ + │ │ │ ├── equals: "=" (location: (2:12)-(2:13)) + │ │ │ └── value: + │ │ │ └── @ HTMLAttributeValueNode (location: (2:13)-(2:29)) + │ │ │ ├── open_quote: """ (location: (2:13)-(2:14)) + │ │ │ ├── children: (1 item) + │ │ │ │ └── @ LiteralNode (location: (2:14)-(2:28)) + │ │ │ │ └── content: "page-container" + │ │ │ │ + │ │ │ ├── close_quote: """ (location: (2:28)-(2:29)) + │ │ │ └── quoted: true + │ │ │ + │ │ │ + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (2:3)-(2:6)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (2:30)-(3:4)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLElementNode (location: (3:4)-(8:8)) + │ │ │ ├── open_tag: + │ │ │ │ └── @ HTMLOpenTagNode (location: (3:4)-(3:34)) + │ │ │ │ ├── tag_opening: "<" (location: (3:4)-(3:5)) + │ │ │ │ ├── tag_name: "div" (location: (3:5)-(3:8)) + │ │ │ │ ├── tag_closing: ">" (location: (3:33)-(3:34)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ HTMLAttributeNode (location: (3:9)-(3:33)) + │ │ │ │ │ ├── name: + │ │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (3:9)-(3:14)) + │ │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ │ └── @ LiteralNode (location: (3:9)-(3:14)) + │ │ │ │ │ │ └── content: "class" + │ │ │ │ │ │ + │ │ │ │ │ │ + │ │ │ │ │ ├── equals: "=" (location: (3:14)-(3:15)) + │ │ │ │ │ └── value: + │ │ │ │ │ └── @ HTMLAttributeValueNode (location: (3:15)-(3:33)) + │ │ │ │ │ ├── open_quote: """ (location: (3:15)-(3:16)) + │ │ │ │ │ ├── children: (1 item) + │ │ │ │ │ │ └── @ LiteralNode (location: (3:16)-(3:32)) + │ │ │ │ │ │ └── content: "max-w-lg mx-auto" + │ │ │ │ │ │ + │ │ │ │ │ ├── close_quote: """ (location: (3:32)-(3:33)) + │ │ │ │ │ └── quoted: true + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ └── is_void: false + │ │ │ │ + │ │ │ ├── tag_name: "div" (location: (3:5)-(3:8)) + │ │ │ ├── body: (3 items) + │ │ │ │ ├── @ HTMLTextNode (location: (3:34)-(4:6)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ ERBBlockNode (location: (4:6)-(7:13)) + │ │ │ │ │ ├── tag_opening: "<%=" (location: (4:6)-(4:9)) + │ │ │ │ │ ├── content: " render CardComponent.new do |card| " (location: (4:9)-(4:45)) + │ │ │ │ │ ├── tag_closing: "%>" (location: (4:45)-(4:47)) + │ │ │ │ │ ├── body: (3 items) + │ │ │ │ │ │ ├── @ HTMLTextNode (location: (4:47)-(5:4)) + │ │ │ │ │ │ │ └── content: "\n " + │ │ │ │ │ │ │ + │ │ │ │ │ │ ├── @ HTMLCloseTagNode (location: (5:4)-(5:10)) + │ │ │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ │ │ └── @ MissingOpeningTagError (location: (5:4)-(5:10)) + │ │ │ │ │ │ │ │ ├── message: "Found closing tag `
` at (5:6) without a matching opening tag in the same scope." + │ │ │ │ │ │ │ │ └── closing_tag: "div" (location: (5:6)-(5:9)) + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ ├── tag_opening: "" (location: (5:9)-(5:10)) + │ │ │ │ │ │ │ + │ │ │ │ │ │ └── @ HTMLTextNode (location: (5:10)-(7:4)) + │ │ │ │ │ │ └── content: "\n\n " + │ │ │ │ │ │ + │ │ │ │ │ └── end_node: + │ │ │ │ │ └── @ ERBEndNode (location: (7:4)-(7:13)) + │ │ │ │ │ ├── tag_opening: "<%" (location: (7:4)-(7:6)) + │ │ │ │ │ ├── content: " end " (location: (7:6)-(7:11)) + │ │ │ │ │ └── tag_closing: "%>" (location: (7:11)-(7:13)) + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ └── @ HTMLTextNode (location: (7:13)-(8:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── close_tag: + │ │ │ │ └── @ HTMLCloseTagNode (location: (8:2)-(8:8)) + │ │ │ │ ├── tag_opening: "" (location: (8:7)-(8:8)) + │ │ │ │ + │ │ │ ├── is_void: false + │ │ │ └── source: "HTML" + │ │ │ + │ │ └── @ HTMLTextNode (location: (8:8)-(9:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (9:0)-(9:6)) + │ │ ├── tag_opening: "" (location: (9:5)-(9:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (9:6)-(10:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/erb_context_mismatch_test/test_0002_conditionally_opening_tag_then_closing_unconditionally_6d7fbfb1a933a2f8aba9be55b52c0012.txt b/test/snapshots/parser/erb_context_mismatch_test/test_0002_conditionally_opening_tag_then_closing_unconditionally_6d7fbfb1a933a2f8aba9be55b52c0012.txt new file mode 100644 index 000000000..4c4dc4597 --- /dev/null +++ b/test/snapshots/parser/erb_context_mismatch_test/test_0002_conditionally_opening_tag_then_closing_unconditionally_6d7fbfb1a933a2f8aba9be55b52c0012.txt @@ -0,0 +1,124 @@ +--- +source: "Parser::ERBContextMismatchTest#test_0002_conditionally opening tag then closing unconditionally" +input: |2- +<% if some_condition %> +
+<% else %> +
+<% end %> + Content +
+--- +@ DocumentNode (location: (1:0)-(8:0)) +└── children: (4 items) + ├── @ ERBIfNode (location: (1:0)-(5:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " if some_condition " (location: (1:2)-(1:21)) + │ ├── tag_closing: "%>" (location: (1:21)-(1:23)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:23)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLOpenTagNode (location: (2:2)-(2:17)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingClosingTagError (location: (2:2)-(2:17)) + │ │ │ │ ├── message: "Opening tag `
` at (2:3) doesn't have a matching closing tag `
` in the same scope." + │ │ │ │ └── opening_tag: "div" (location: (2:3)-(2:6)) + │ │ │ │ + │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ ├── tag_name: "div" (location: (2:3)-(2:6)) + │ │ │ ├── tag_closing: ">" (location: (2:16)-(2:17)) + │ │ │ ├── children: (1 item) + │ │ │ │ └── @ HTMLAttributeNode (location: (2:7)-(2:16)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (2:7)-(2:12)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (2:7)-(2:12)) + │ │ │ │ │ └── content: "class" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (2:12)-(2:13)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (2:13)-(2:16)) + │ │ │ │ ├── open_quote: """ (location: (2:13)-(2:14)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (2:14)-(2:15)) + │ │ │ │ │ └── content: "a" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (2:15)-(2:16)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ └── is_void: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:17)-(3:0)) + │ │ └── content: "\n" + │ │ + │ ├── subsequent: + │ │ └── @ ERBElseNode (location: (3:0)-(5:0)) + │ │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) + │ │ ├── content: " else " (location: (3:2)-(3:8)) + │ │ ├── tag_closing: "%>" (location: (3:8)-(3:10)) + │ │ └── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (3:10)-(4:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLOpenTagNode (location: (4:2)-(4:17)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingClosingTagError (location: (4:2)-(4:17)) + │ │ │ │ ├── message: "Opening tag `
` at (4:3) doesn't have a matching closing tag `
` in the same scope." + │ │ │ │ └── opening_tag: "div" (location: (4:3)-(4:6)) + │ │ │ │ + │ │ │ ├── tag_opening: "<" (location: (4:2)-(4:3)) + │ │ │ ├── tag_name: "div" (location: (4:3)-(4:6)) + │ │ │ ├── tag_closing: ">" (location: (4:16)-(4:17)) + │ │ │ ├── children: (1 item) + │ │ │ │ └── @ HTMLAttributeNode (location: (4:7)-(4:16)) + │ │ │ │ ├── name: + │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (4:7)-(4:12)) + │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (4:7)-(4:12)) + │ │ │ │ │ └── content: "class" + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── equals: "=" (location: (4:12)-(4:13)) + │ │ │ │ └── value: + │ │ │ │ └── @ HTMLAttributeValueNode (location: (4:13)-(4:16)) + │ │ │ │ ├── open_quote: """ (location: (4:13)-(4:14)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ LiteralNode (location: (4:14)-(4:15)) + │ │ │ │ │ └── content: "b" + │ │ │ │ │ + │ │ │ │ ├── close_quote: """ (location: (4:15)-(4:16)) + │ │ │ │ └── quoted: true + │ │ │ │ + │ │ │ │ + │ │ │ └── is_void: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (4:17)-(5:0)) + │ │ └── content: "\n" + │ │ + │ │ + │ └── end_node: + │ └── @ ERBEndNode (location: (5:0)-(5:9)) + │ ├── tag_opening: "<%" (location: (5:0)-(5:2)) + │ ├── content: " end " (location: (5:2)-(5:7)) + │ └── tag_closing: "%>" (location: (5:7)-(5:9)) + │ + │ + ├── @ HTMLTextNode (location: (5:9)-(7:0)) + │ └── content: "\n Content\n" + │ + ├── @ HTMLCloseTagNode (location: (7:0)-(7:6)) + │ ├── errors: (1 error) + │ │ └── @ MissingOpeningTagError (location: (7:0)-(7:6)) + │ │ ├── message: "Found closing tag `
` at (7:2) without a matching opening tag in the same scope." + │ │ └── closing_tag: "div" (location: (7:2)-(7:5)) + │ │ + │ ├── tag_opening: "" (location: (7:5)-(7:6)) + │ + └── @ HTMLTextNode (location: (7:6)-(8:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/erb_context_mismatch_test/test_0003_opening_and_closing_tag_in_different_if_blocks_a6d5461489ddfa85081a5449445cef14.txt b/test/snapshots/parser/erb_context_mismatch_test/test_0003_opening_and_closing_tag_in_different_if_blocks_a6d5461489ddfa85081a5449445cef14.txt new file mode 100644 index 000000000..1fa5826a8 --- /dev/null +++ b/test/snapshots/parser/erb_context_mismatch_test/test_0003_opening_and_closing_tag_in_different_if_blocks_a6d5461489ddfa85081a5449445cef14.txt @@ -0,0 +1,108 @@ +--- +source: "Parser::ERBContextMismatchTest#test_0003_opening and closing tag in different if blocks" +input: |2- +<% if wrap_in_dialog? %> + +<% end %> + +
Stuff
+ +<% if wrap_in_dialog? %> +
+<% end %> +--- +@ DocumentNode (location: (1:0)-(10:0)) +└── children: (6 items) + ├── @ ERBIfNode (location: (1:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " if wrap_in_dialog? " (location: (1:2)-(1:22)) + │ ├── tag_closing: "%>" (location: (1:22)-(1:24)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:24)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLOpenTagNode (location: (2:2)-(2:10)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingClosingTagError (location: (2:2)-(2:10)) + │ │ │ │ ├── message: "Opening tag `` at (2:3) doesn't have a matching closing tag `` in the same scope." + │ │ │ │ └── opening_tag: "dialog" (location: (2:3)-(2:9)) + │ │ │ │ + │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ ├── tag_name: "dialog" (location: (2:3)-(2:9)) + │ │ │ ├── tag_closing: ">" (location: (2:9)-(2:10)) + │ │ │ ├── children: [] + │ │ │ └── is_void: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:10)-(3:0)) + │ │ └── content: "\n" + │ │ + │ ├── subsequent: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (3:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) + │ ├── content: " end " (location: (3:2)-(3:7)) + │ └── tag_closing: "%>" (location: (3:7)-(3:9)) + │ + │ + ├── @ HTMLTextNode (location: (3:9)-(5:0)) + │ └── content: "\n\n" + │ + ├── @ HTMLElementNode (location: (5:0)-(5:16)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (5:0)-(5:5)) + │ │ ├── tag_opening: "<" (location: (5:0)-(5:1)) + │ │ ├── tag_name: "div" (location: (5:1)-(5:4)) + │ │ ├── tag_closing: ">" (location: (5:4)-(5:5)) + │ │ ├── children: [] + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (5:1)-(5:4)) + │ ├── body: (1 item) + │ │ └── @ HTMLTextNode (location: (5:5)-(5:10)) + │ │ └── content: "Stuff" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (5:10)-(5:16)) + │ │ ├── tag_opening: "" (location: (5:15)-(5:16)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + ├── @ HTMLTextNode (location: (5:16)-(7:0)) + │ └── content: "\n\n" + │ + ├── @ ERBIfNode (location: (7:0)-(9:9)) + │ ├── tag_opening: "<%" (location: (7:0)-(7:2)) + │ ├── content: " if wrap_in_dialog? " (location: (7:2)-(7:22)) + │ ├── tag_closing: "%>" (location: (7:22)-(7:24)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (7:24)-(8:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLCloseTagNode (location: (8:2)-(8:11)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingOpeningTagError (location: (8:2)-(8:11)) + │ │ │ │ ├── message: "Found closing tag `` at (8:4) without a matching opening tag in the same scope." + │ │ │ │ └── closing_tag: "dialog" (location: (8:4)-(8:10)) + │ │ │ │ + │ │ │ ├── tag_opening: "" (location: (8:10)-(8:11)) + │ │ │ + │ │ └── @ HTMLTextNode (location: (8:11)-(9:0)) + │ │ └── content: "\n" + │ │ + │ ├── subsequent: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (9:0)-(9:9)) + │ ├── tag_opening: "<%" (location: (9:0)-(9:2)) + │ ├── content: " end " (location: (9:2)-(9:7)) + │ └── tag_closing: "%>" (location: (9:7)-(9:9)) + │ + │ + └── @ HTMLTextNode (location: (9:9)-(10:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/erb_context_mismatch_test/test_0004_tag_opened_in_elsif_closed_outside_conditional_ff6b90e6b68b015927ad4554dc97fd8e.txt b/test/snapshots/parser/erb_context_mismatch_test/test_0004_tag_opened_in_elsif_closed_outside_conditional_ff6b90e6b68b015927ad4554dc97fd8e.txt new file mode 100644 index 000000000..9d5d3703d --- /dev/null +++ b/test/snapshots/parser/erb_context_mismatch_test/test_0004_tag_opened_in_elsif_closed_outside_conditional_ff6b90e6b68b015927ad4554dc97fd8e.txt @@ -0,0 +1,333 @@ +--- +source: "Parser::ERBContextMismatchTest#test_0004_tag opened in elsif closed outside conditional" +input: |2- + +--- +@ DocumentNode (location: (1:0)-(16:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(15:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "nav" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (1:4)-(1:5)) + │ │ ├── children: [] + │ │ └── is_void: false + │ │ + │ ├── tag_name: "nav" (location: (1:1)-(1:4)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:5)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLElementNode (location: (2:2)-(14:7)) + │ │ │ ├── open_tag: + │ │ │ │ └── @ HTMLOpenTagNode (location: (2:2)-(2:6)) + │ │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ │ ├── tag_name: "ul" (location: (2:3)-(2:5)) + │ │ │ │ ├── tag_closing: ">" (location: (2:5)-(2:6)) + │ │ │ │ ├── children: [] + │ │ │ │ └── is_void: false + │ │ │ │ + │ │ │ ├── tag_name: "ul" (location: (2:3)-(2:5)) + │ │ │ ├── body: (5 items) + │ │ │ │ ├── @ HTMLTextNode (location: (2:6)-(3:4)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ ERBIfNode (location: (3:4)-(12:13)) + │ │ │ │ │ ├── tag_opening: "<%" (location: (3:4)-(3:6)) + │ │ │ │ │ ├── content: " if magic == :foo " (location: (3:6)-(3:24)) + │ │ │ │ │ ├── tag_closing: "%>" (location: (3:24)-(3:26)) + │ │ │ │ │ ├── statements: (5 items) + │ │ │ │ │ │ ├── @ HTMLTextNode (location: (3:26)-(4:6)) + │ │ │ │ │ │ │ └── content: "\n " + │ │ │ │ │ │ │ + │ │ │ │ │ │ ├── @ HTMLOpenTagNode (location: (4:6)-(4:22)) + │ │ │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ │ │ └── @ MissingClosingTagError (location: (4:6)-(4:22)) + │ │ │ │ │ │ │ │ ├── message: "Opening tag `
  • ` at (4:7) doesn't have a matching closing tag `
  • ` in the same scope." + │ │ │ │ │ │ │ │ └── opening_tag: "li" (location: (4:7)-(4:9)) + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ ├── tag_opening: "<" (location: (4:6)-(4:7)) + │ │ │ │ │ │ │ ├── tag_name: "li" (location: (4:7)-(4:9)) + │ │ │ │ │ │ │ ├── tag_closing: ">" (location: (4:21)-(4:22)) + │ │ │ │ │ │ │ ├── children: (1 item) + │ │ │ │ │ │ │ │ └── @ HTMLAttributeNode (location: (4:10)-(4:21)) + │ │ │ │ │ │ │ │ ├── name: + │ │ │ │ │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (4:10)-(4:15)) + │ │ │ │ │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ │ │ │ │ └── @ LiteralNode (location: (4:10)-(4:15)) + │ │ │ │ │ │ │ │ │ └── content: "class" + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ ├── equals: "=" (location: (4:15)-(4:16)) + │ │ │ │ │ │ │ │ └── value: + │ │ │ │ │ │ │ │ └── @ HTMLAttributeValueNode (location: (4:16)-(4:21)) + │ │ │ │ │ │ │ │ ├── open_quote: """ (location: (4:16)-(4:17)) + │ │ │ │ │ │ │ │ ├── children: (1 item) + │ │ │ │ │ │ │ │ │ └── @ LiteralNode (location: (4:17)-(4:20)) + │ │ │ │ │ │ │ │ │ └── content: "foo" + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ ├── close_quote: """ (location: (4:20)-(4:21)) + │ │ │ │ │ │ │ │ └── quoted: true + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ └── is_void: false + │ │ │ │ │ │ │ + │ │ │ │ │ │ ├── @ HTMLTextNode (location: (4:22)-(5:8)) + │ │ │ │ │ │ │ └── content: "\n " + │ │ │ │ │ │ │ + │ │ │ │ │ │ ├── @ HTMLElementNode (location: (5:8)-(5:29)) + │ │ │ │ │ │ │ ├── open_tag: + │ │ │ │ │ │ │ │ └── @ HTMLOpenTagNode (location: (5:8)-(5:22)) + │ │ │ │ │ │ │ │ ├── tag_opening: "<" (location: (5:8)-(5:9)) + │ │ │ │ │ │ │ │ ├── tag_name: "a" (location: (5:9)-(5:10)) + │ │ │ │ │ │ │ │ ├── tag_closing: ">" (location: (5:21)-(5:22)) + │ │ │ │ │ │ │ │ ├── children: (1 item) + │ │ │ │ │ │ │ │ │ └── @ HTMLAttributeNode (location: (5:11)-(5:21)) + │ │ │ │ │ │ │ │ │ ├── name: + │ │ │ │ │ │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (5:11)-(5:15)) + │ │ │ │ │ │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ │ │ │ │ │ └── @ LiteralNode (location: (5:11)-(5:15)) + │ │ │ │ │ │ │ │ │ │ └── content: "href" + │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ ├── equals: "=" (location: (5:15)-(5:16)) + │ │ │ │ │ │ │ │ │ └── value: + │ │ │ │ │ │ │ │ │ └── @ HTMLAttributeValueNode (location: (5:16)-(5:21)) + │ │ │ │ │ │ │ │ │ ├── open_quote: """ (location: (5:16)-(5:17)) + │ │ │ │ │ │ │ │ │ ├── children: (1 item) + │ │ │ │ │ │ │ │ │ │ └── @ LiteralNode (location: (5:17)-(5:20)) + │ │ │ │ │ │ │ │ │ │ └── content: "foo" + │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ ├── close_quote: """ (location: (5:20)-(5:21)) + │ │ │ │ │ │ │ │ │ └── quoted: true + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ └── is_void: false + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ ├── tag_name: "a" (location: (5:9)-(5:10)) + │ │ │ │ │ │ │ ├── body: (1 item) + │ │ │ │ │ │ │ │ └── @ HTMLTextNode (location: (5:22)-(5:25)) + │ │ │ │ │ │ │ │ └── content: "foo" + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ ├── close_tag: + │ │ │ │ │ │ │ │ └── @ HTMLCloseTagNode (location: (5:25)-(5:29)) + │ │ │ │ │ │ │ │ ├── tag_opening: "" (location: (5:28)-(5:29)) + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ ├── is_void: false + │ │ │ │ │ │ │ └── source: "HTML" + │ │ │ │ │ │ │ + │ │ │ │ │ │ └── @ HTMLTextNode (location: (5:29)-(6:4)) + │ │ │ │ │ │ └── content: "\n " + │ │ │ │ │ │ + │ │ │ │ │ ├── subsequent: + │ │ │ │ │ │ └── @ ERBIfNode (location: (6:4)-(9:4)) + │ │ │ │ │ │ ├── tag_opening: "<%" (location: (6:4)-(6:6)) + │ │ │ │ │ │ ├── content: " elsif magic == :bar " (location: (6:6)-(6:27)) + │ │ │ │ │ │ ├── tag_closing: "%>" (location: (6:27)-(6:29)) + │ │ │ │ │ │ ├── statements: (5 items) + │ │ │ │ │ │ │ ├── @ HTMLTextNode (location: (6:29)-(7:6)) + │ │ │ │ │ │ │ │ └── content: "\n " + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ ├── @ HTMLOpenTagNode (location: (7:6)-(7:22)) + │ │ │ │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ │ │ │ └── @ MissingClosingTagError (location: (7:6)-(7:22)) + │ │ │ │ │ │ │ │ │ ├── message: "Opening tag `
  • ` at (7:7) doesn't have a matching closing tag `
  • ` in the same scope." + │ │ │ │ │ │ │ │ │ └── opening_tag: "li" (location: (7:7)-(7:9)) + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ ├── tag_opening: "<" (location: (7:6)-(7:7)) + │ │ │ │ │ │ │ │ ├── tag_name: "li" (location: (7:7)-(7:9)) + │ │ │ │ │ │ │ │ ├── tag_closing: ">" (location: (7:21)-(7:22)) + │ │ │ │ │ │ │ │ ├── children: (1 item) + │ │ │ │ │ │ │ │ │ └── @ HTMLAttributeNode (location: (7:10)-(7:21)) + │ │ │ │ │ │ │ │ │ ├── name: + │ │ │ │ │ │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (7:10)-(7:15)) + │ │ │ │ │ │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ │ │ │ │ │ └── @ LiteralNode (location: (7:10)-(7:15)) + │ │ │ │ │ │ │ │ │ │ └── content: "class" + │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ ├── equals: "=" (location: (7:15)-(7:16)) + │ │ │ │ │ │ │ │ │ └── value: + │ │ │ │ │ │ │ │ │ └── @ HTMLAttributeValueNode (location: (7:16)-(7:21)) + │ │ │ │ │ │ │ │ │ ├── open_quote: """ (location: (7:16)-(7:17)) + │ │ │ │ │ │ │ │ │ ├── children: (1 item) + │ │ │ │ │ │ │ │ │ │ └── @ LiteralNode (location: (7:17)-(7:20)) + │ │ │ │ │ │ │ │ │ │ └── content: "bar" + │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ ├── close_quote: """ (location: (7:20)-(7:21)) + │ │ │ │ │ │ │ │ │ └── quoted: true + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ └── is_void: false + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ ├── @ HTMLTextNode (location: (7:22)-(8:8)) + │ │ │ │ │ │ │ │ └── content: "\n " + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ ├── @ HTMLElementNode (location: (8:8)-(8:29)) + │ │ │ │ │ │ │ │ ├── open_tag: + │ │ │ │ │ │ │ │ │ └── @ HTMLOpenTagNode (location: (8:8)-(8:22)) + │ │ │ │ │ │ │ │ │ ├── tag_opening: "<" (location: (8:8)-(8:9)) + │ │ │ │ │ │ │ │ │ ├── tag_name: "a" (location: (8:9)-(8:10)) + │ │ │ │ │ │ │ │ │ ├── tag_closing: ">" (location: (8:21)-(8:22)) + │ │ │ │ │ │ │ │ │ ├── children: (1 item) + │ │ │ │ │ │ │ │ │ │ └── @ HTMLAttributeNode (location: (8:11)-(8:21)) + │ │ │ │ │ │ │ │ │ │ ├── name: + │ │ │ │ │ │ │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (8:11)-(8:15)) + │ │ │ │ │ │ │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ │ │ │ │ │ │ └── @ LiteralNode (location: (8:11)-(8:15)) + │ │ │ │ │ │ │ │ │ │ │ └── content: "href" + │ │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ ├── equals: "=" (location: (8:15)-(8:16)) + │ │ │ │ │ │ │ │ │ │ └── value: + │ │ │ │ │ │ │ │ │ │ └── @ HTMLAttributeValueNode (location: (8:16)-(8:21)) + │ │ │ │ │ │ │ │ │ │ ├── open_quote: """ (location: (8:16)-(8:17)) + │ │ │ │ │ │ │ │ │ │ ├── children: (1 item) + │ │ │ │ │ │ │ │ │ │ │ └── @ LiteralNode (location: (8:17)-(8:20)) + │ │ │ │ │ │ │ │ │ │ │ └── content: "bar" + │ │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ ├── close_quote: """ (location: (8:20)-(8:21)) + │ │ │ │ │ │ │ │ │ │ └── quoted: true + │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ └── is_void: false + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ ├── tag_name: "a" (location: (8:9)-(8:10)) + │ │ │ │ │ │ │ │ ├── body: (1 item) + │ │ │ │ │ │ │ │ │ └── @ HTMLTextNode (location: (8:22)-(8:25)) + │ │ │ │ │ │ │ │ │ └── content: "bar" + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ ├── close_tag: + │ │ │ │ │ │ │ │ │ └── @ HTMLCloseTagNode (location: (8:25)-(8:29)) + │ │ │ │ │ │ │ │ │ ├── tag_opening: "" (location: (8:28)-(8:29)) + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ ├── is_void: false + │ │ │ │ │ │ │ │ └── source: "HTML" + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ └── @ HTMLTextNode (location: (8:29)-(9:4)) + │ │ │ │ │ │ │ └── content: "\n " + │ │ │ │ │ │ │ + │ │ │ │ │ │ ├── subsequent: + │ │ │ │ │ │ │ └── @ ERBElseNode (location: (9:4)-(12:4)) + │ │ │ │ │ │ │ ├── tag_opening: "<%" (location: (9:4)-(9:6)) + │ │ │ │ │ │ │ ├── content: " else " (location: (9:6)-(9:12)) + │ │ │ │ │ │ │ ├── tag_closing: "%>" (location: (9:12)-(9:14)) + │ │ │ │ │ │ │ └── statements: (5 items) + │ │ │ │ │ │ │ ├── @ HTMLTextNode (location: (9:14)-(10:6)) + │ │ │ │ │ │ │ │ └── content: "\n " + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ ├── @ HTMLOpenTagNode (location: (10:6)-(10:10)) + │ │ │ │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ │ │ │ └── @ MissingClosingTagError (location: (10:6)-(10:10)) + │ │ │ │ │ │ │ │ │ ├── message: "Opening tag `
  • ` at (10:7) doesn't have a matching closing tag `
  • ` in the same scope." + │ │ │ │ │ │ │ │ │ └── opening_tag: "li" (location: (10:7)-(10:9)) + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ ├── tag_opening: "<" (location: (10:6)-(10:7)) + │ │ │ │ │ │ │ │ ├── tag_name: "li" (location: (10:7)-(10:9)) + │ │ │ │ │ │ │ │ ├── tag_closing: ">" (location: (10:9)-(10:10)) + │ │ │ │ │ │ │ │ ├── children: [] + │ │ │ │ │ │ │ │ └── is_void: false + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ ├── @ HTMLTextNode (location: (10:10)-(11:8)) + │ │ │ │ │ │ │ │ └── content: "\n " + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ ├── @ HTMLElementNode (location: (11:8)-(11:28)) + │ │ │ │ │ │ │ │ ├── open_tag: + │ │ │ │ │ │ │ │ │ └── @ HTMLOpenTagNode (location: (11:8)-(11:14)) + │ │ │ │ │ │ │ │ │ ├── tag_opening: "<" (location: (11:8)-(11:9)) + │ │ │ │ │ │ │ │ │ ├── tag_name: "span" (location: (11:9)-(11:13)) + │ │ │ │ │ │ │ │ │ ├── tag_closing: ">" (location: (11:13)-(11:14)) + │ │ │ │ │ │ │ │ │ ├── children: [] + │ │ │ │ │ │ │ │ │ └── is_void: false + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ ├── tag_name: "span" (location: (11:9)-(11:13)) + │ │ │ │ │ │ │ │ ├── body: (1 item) + │ │ │ │ │ │ │ │ │ └── @ HTMLTextNode (location: (11:14)-(11:21)) + │ │ │ │ │ │ │ │ │ └── content: "DEFAULT" + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ ├── close_tag: + │ │ │ │ │ │ │ │ │ └── @ HTMLCloseTagNode (location: (11:21)-(11:28)) + │ │ │ │ │ │ │ │ │ ├── tag_opening: "" (location: (11:27)-(11:28)) + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ ├── is_void: false + │ │ │ │ │ │ │ │ └── source: "HTML" + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ └── @ HTMLTextNode (location: (11:28)-(12:4)) + │ │ │ │ │ │ │ └── content: "\n " + │ │ │ │ │ │ │ + │ │ │ │ │ │ │ + │ │ │ │ │ │ └── end_node: ∅ + │ │ │ │ │ │ + │ │ │ │ │ └── end_node: + │ │ │ │ │ └── @ ERBEndNode (location: (12:4)-(12:13)) + │ │ │ │ │ ├── tag_opening: "<%" (location: (12:4)-(12:6)) + │ │ │ │ │ ├── content: " end " (location: (12:6)-(12:11)) + │ │ │ │ │ └── tag_closing: "%>" (location: (12:11)-(12:13)) + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ ├── @ HTMLTextNode (location: (12:13)-(13:4)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ HTMLCloseTagNode (location: (13:4)-(13:9)) + │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ └── @ MissingOpeningTagError (location: (13:4)-(13:9)) + │ │ │ │ │ │ ├── message: "Found closing tag `` at (13:6) without a matching opening tag in the same scope." + │ │ │ │ │ │ └── closing_tag: "li" (location: (13:6)-(13:8)) + │ │ │ │ │ │ + │ │ │ │ │ ├── tag_opening: "" (location: (13:8)-(13:9)) + │ │ │ │ │ + │ │ │ │ └── @ HTMLTextNode (location: (13:9)-(14:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── close_tag: + │ │ │ │ └── @ HTMLCloseTagNode (location: (14:2)-(14:7)) + │ │ │ │ ├── tag_opening: "" (location: (14:6)-(14:7)) + │ │ │ │ + │ │ │ ├── is_void: false + │ │ │ └── source: "HTML" + │ │ │ + │ │ └── @ HTMLTextNode (location: (14:7)-(15:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (15:0)-(15:6)) + │ │ ├── tag_opening: "" (location: (15:5)-(15:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (15:6)-(16:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/erb_context_mismatch_test/test_0005_element_opened_in_block_must_be_closed_within_block_264d7b62538d8f8e318bce1a37dee984.txt b/test/snapshots/parser/erb_context_mismatch_test/test_0005_element_opened_in_block_must_be_closed_within_block_264d7b62538d8f8e318bce1a37dee984.txt new file mode 100644 index 000000000..87b21de63 --- /dev/null +++ b/test/snapshots/parser/erb_context_mismatch_test/test_0005_element_opened_in_block_must_be_closed_within_block_264d7b62538d8f8e318bce1a37dee984.txt @@ -0,0 +1,57 @@ +--- +source: "Parser::ERBContextMismatchTest#test_0005_element opened in block must be closed within block" +input: |2- +<% if true %> +

    123 +<% end %> +

    +--- +@ DocumentNode (location: (1:0)-(5:0)) +└── children: (4 items) + ├── @ ERBIfNode (location: (1:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " if true " (location: (1:2)-(1:11)) + │ ├── tag_closing: "%>" (location: (1:11)-(1:13)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:13)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLOpenTagNode (location: (2:2)-(2:6)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingClosingTagError (location: (2:2)-(2:6)) + │ │ │ │ ├── message: "Opening tag `

    ` at (2:3) doesn't have a matching closing tag `

    ` in the same scope." + │ │ │ │ └── opening_tag: "h1" (location: (2:3)-(2:5)) + │ │ │ │ + │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ ├── tag_name: "h1" (location: (2:3)-(2:5)) + │ │ │ ├── tag_closing: ">" (location: (2:5)-(2:6)) + │ │ │ ├── children: [] + │ │ │ └── is_void: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:6)-(3:0)) + │ │ └── content: "123\n" + │ │ + │ ├── subsequent: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (3:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) + │ ├── content: " end " (location: (3:2)-(3:7)) + │ └── tag_closing: "%>" (location: (3:7)-(3:9)) + │ + │ + ├── @ HTMLTextNode (location: (3:9)-(4:0)) + │ └── content: "\n" + │ + ├── @ HTMLCloseTagNode (location: (4:0)-(4:5)) + │ ├── errors: (1 error) + │ │ └── @ MissingOpeningTagError (location: (4:0)-(4:5)) + │ │ ├── message: "Found closing tag `

    ` at (4:2) without a matching opening tag in the same scope." + │ │ └── closing_tag: "h1" (location: (4:2)-(4:4)) + │ │ + │ ├── tag_opening: "" (location: (4:4)-(4:5)) + │ + └── @ HTMLTextNode (location: (4:5)-(5:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/erb_context_mismatch_test/test_0006_opening_in_one_context_and_closing_in_another_context_23760079615eb0a149447d79f51005bb.txt b/test/snapshots/parser/erb_context_mismatch_test/test_0006_opening_in_one_context_and_closing_in_another_context_23760079615eb0a149447d79f51005bb.txt new file mode 100644 index 000000000..931b2adb7 --- /dev/null +++ b/test/snapshots/parser/erb_context_mismatch_test/test_0006_opening_in_one_context_and_closing_in_another_context_23760079615eb0a149447d79f51005bb.txt @@ -0,0 +1,81 @@ +--- +source: "Parser::ERBContextMismatchTest#test_0006_opening in one context and closing in another context" +input: |2- +<% if true %> +

    +<% end %> + + Content + +<% if true %> +

    +<% end %> +--- +@ DocumentNode (location: (1:0)-(10:0)) +└── children: (4 items) + ├── @ ERBIfNode (location: (1:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " if true " (location: (1:2)-(1:11)) + │ ├── tag_closing: "%>" (location: (1:11)-(1:13)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:13)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLOpenTagNode (location: (2:2)-(2:6)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingClosingTagError (location: (2:2)-(2:6)) + │ │ │ │ ├── message: "Opening tag `

    ` at (2:3) doesn't have a matching closing tag `

    ` in the same scope." + │ │ │ │ └── opening_tag: "h1" (location: (2:3)-(2:5)) + │ │ │ │ + │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ ├── tag_name: "h1" (location: (2:3)-(2:5)) + │ │ │ ├── tag_closing: ">" (location: (2:5)-(2:6)) + │ │ │ ├── children: [] + │ │ │ └── is_void: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:6)-(3:0)) + │ │ └── content: "\n" + │ │ + │ ├── subsequent: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (3:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) + │ ├── content: " end " (location: (3:2)-(3:7)) + │ └── tag_closing: "%>" (location: (3:7)-(3:9)) + │ + │ + ├── @ HTMLTextNode (location: (3:9)-(7:0)) + │ └── content: "\n\n Content\n\n" + │ + ├── @ ERBIfNode (location: (7:0)-(9:9)) + │ ├── tag_opening: "<%" (location: (7:0)-(7:2)) + │ ├── content: " if true " (location: (7:2)-(7:11)) + │ ├── tag_closing: "%>" (location: (7:11)-(7:13)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (7:13)-(8:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLCloseTagNode (location: (8:2)-(8:7)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingOpeningTagError (location: (8:2)-(8:7)) + │ │ │ │ ├── message: "Found closing tag `` at (8:4) without a matching opening tag in the same scope." + │ │ │ │ └── closing_tag: "h1" (location: (8:4)-(8:6)) + │ │ │ │ + │ │ │ ├── tag_opening: "" (location: (8:6)-(8:7)) + │ │ │ + │ │ └── @ HTMLTextNode (location: (8:7)-(9:0)) + │ │ └── content: "\n" + │ │ + │ ├── subsequent: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (9:0)-(9:9)) + │ ├── tag_opening: "<%" (location: (9:0)-(9:2)) + │ ├── content: " end " (location: (9:2)-(9:7)) + │ └── tag_closing: "%>" (location: (9:7)-(9:9)) + │ + │ + └── @ HTMLTextNode (location: (9:9)-(10:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/erb_context_mismatch_test/test_0007_stray_closing_tag_without_opening_7435ca180811536625a8df051573f55d.txt b/test/snapshots/parser/erb_context_mismatch_test/test_0007_stray_closing_tag_without_opening_7435ca180811536625a8df051573f55d.txt new file mode 100644 index 000000000..4407f1233 --- /dev/null +++ b/test/snapshots/parser/erb_context_mismatch_test/test_0007_stray_closing_tag_without_opening_7435ca180811536625a8df051573f55d.txt @@ -0,0 +1,50 @@ +--- +source: "Parser::ERBContextMismatchTest#test_0007_stray closing tag without opening" +input: |2- +
    + content +
    + +--- +@ DocumentNode (location: (1:0)-(5:0)) +└── children: (4 items) + ├── @ HTMLElementNode (location: (1:0)-(3:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (1:4)-(1:5)) + │ │ ├── children: [] + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (1 item) + │ │ └── @ HTMLTextNode (location: (1:5)-(3:0)) + │ │ └── content: "\n content\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (3:0)-(3:6)) + │ │ ├── tag_opening: "" (location: (3:5)-(3:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + ├── @ HTMLTextNode (location: (3:6)-(4:0)) + │ └── content: "\n" + │ + ├── @ HTMLCloseTagNode (location: (4:0)-(4:7)) + │ ├── errors: (1 error) + │ │ └── @ MissingOpeningTagError (location: (4:0)-(4:7)) + │ │ ├── message: "Found closing tag `` at (4:2) without a matching opening tag in the same scope." + │ │ └── closing_tag: "span" (location: (4:2)-(4:6)) + │ │ + │ ├── tag_opening: "" (location: (4:6)-(4:7)) + │ + └── @ HTMLTextNode (location: (4:7)-(5:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/erb_context_mismatch_test/test_0008_unclosed_tag_without_closing_1ccadf7f0ed2141a178d6f7c5736dc6c.txt b/test/snapshots/parser/erb_context_mismatch_test/test_0008_unclosed_tag_without_closing_1ccadf7f0ed2141a178d6f7c5736dc6c.txt new file mode 100644 index 000000000..949914dc2 --- /dev/null +++ b/test/snapshots/parser/erb_context_mismatch_test/test_0008_unclosed_tag_without_closing_1ccadf7f0ed2141a178d6f7c5736dc6c.txt @@ -0,0 +1,50 @@ +--- +source: "Parser::ERBContextMismatchTest#test_0008_unclosed tag without closing" +input: |2- +
    + content +
    +--- +@ DocumentNode (location: (1:0)-(4:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(3:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (1:4)-(1:5)) + │ │ ├── children: [] + │ │ └── is_void: false + │ │ + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:5)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLOpenTagNode (location: (2:2)-(2:8)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingClosingTagError (location: (2:2)-(2:8)) + │ │ │ │ ├── message: "Opening tag `` at (2:3) doesn't have a matching closing tag `` in the same scope." + │ │ │ │ └── opening_tag: "span" (location: (2:3)-(2:7)) + │ │ │ │ + │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ ├── tag_name: "span" (location: (2:3)-(2:7)) + │ │ │ ├── tag_closing: ">" (location: (2:7)-(2:8)) + │ │ │ ├── children: [] + │ │ │ └── is_void: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:8)-(3:0)) + │ │ └── content: "content\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (3:0)-(3:6)) + │ │ ├── tag_opening: "" (location: (3:5)-(3:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (3:6)-(4:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/erb_test/test_0036_unterminated_erb_missing_closing_%gt_1e22264334529582735f76812bfeec73.txt b/test/snapshots/parser/erb_test/test_0036_unterminated_erb_missing_closing_%gt_1e22264334529582735f76812bfeec73.txt index 0fd8613a5..84868c148 100644 --- a/test/snapshots/parser/erb_test/test_0036_unterminated_erb_missing_closing_%gt_1e22264334529582735f76812bfeec73.txt +++ b/test/snapshots/parser/erb_test/test_0036_unterminated_erb_missing_closing_%gt_1e22264334529582735f76812bfeec73.txt @@ -5,16 +5,20 @@ input: "<% if true" @ DocumentNode (location: (1:0)-(1:10)) └── children: (1 item) └── @ ERBIfNode (location: (1:0)-(1:10)) - ├── errors: (2 errors) + ├── errors: (3 errors) │ ├── @ UnexpectedTokenError (location: (1:2)-(1:10)) │ │ ├── message: "Found `TOKEN_ERROR` when expecting `TOKEN_ERB_CONTENT` at (1:2)." │ │ ├── expected_type: "TOKEN_ERB_CONTENT" │ │ └── found: " if true" (location: (1:2)-(1:10)) │ │ - │ └── @ UnexpectedTokenError (location: (1:10)-(1:10)) - │ ├── message: "Found `TOKEN_EOF` when expecting `TOKEN_ERB_END` at (1:10)." - │ ├── expected_type: "TOKEN_ERB_END" - │ └── found: "" (location: (1:10)-(1:10)) + │ ├── @ UnexpectedTokenError (location: (1:10)-(1:10)) + │ │ ├── message: "Found `TOKEN_EOF` when expecting `TOKEN_ERB_END` at (1:10)." + │ │ ├── expected_type: "TOKEN_ERB_END" + │ │ └── found: "" (location: (1:10)-(1:10)) + │ │ + │ └── @ MissingERBEndTagError (location: (1:0)-(1:10)) + │ ├── message: "`<% if %>` started here but never closed with an end tag. The end tag may be in a different scope." + │ └── keyword: "`<% if %>`" │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) ├── content: " if true" (location: (1:2)-(1:10)) diff --git a/test/snapshots/parser/erb_test/test_0037_unterminated_erb_missing_closing_gt_2707e9fbfa1307c14d5d6deaf40bd0af.txt b/test/snapshots/parser/erb_test/test_0037_unterminated_erb_missing_closing_gt_2707e9fbfa1307c14d5d6deaf40bd0af.txt index 650c387f4..743f8d77b 100644 --- a/test/snapshots/parser/erb_test/test_0037_unterminated_erb_missing_closing_gt_2707e9fbfa1307c14d5d6deaf40bd0af.txt +++ b/test/snapshots/parser/erb_test/test_0037_unterminated_erb_missing_closing_gt_2707e9fbfa1307c14d5d6deaf40bd0af.txt @@ -5,16 +5,20 @@ input: "<% if true %" @ DocumentNode (location: (1:0)-(1:12)) └── children: (1 item) └── @ ERBIfNode (location: (1:0)-(1:12)) - ├── errors: (2 errors) + ├── errors: (3 errors) │ ├── @ UnexpectedTokenError (location: (1:2)-(1:12)) │ │ ├── message: "Found `TOKEN_ERROR` when expecting `TOKEN_ERB_CONTENT` at (1:2)." │ │ ├── expected_type: "TOKEN_ERB_CONTENT" │ │ └── found: " if true %" (location: (1:2)-(1:12)) │ │ - │ └── @ UnexpectedTokenError (location: (1:12)-(1:12)) - │ ├── message: "Found `TOKEN_EOF` when expecting `TOKEN_ERB_END` at (1:12)." - │ ├── expected_type: "TOKEN_ERB_END" - │ └── found: "" (location: (1:12)-(1:12)) + │ ├── @ UnexpectedTokenError (location: (1:12)-(1:12)) + │ │ ├── message: "Found `TOKEN_EOF` when expecting `TOKEN_ERB_END` at (1:12)." + │ │ ├── expected_type: "TOKEN_ERB_END" + │ │ └── found: "" (location: (1:12)-(1:12)) + │ │ + │ └── @ MissingERBEndTagError (location: (1:0)-(1:12)) + │ ├── message: "`<% if %>` started here but never closed with an end tag. The end tag may be in a different scope." + │ └── keyword: "`<% if %>`" │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) ├── content: " if true %" (location: (1:2)-(1:12)) diff --git a/test/snapshots/parser/erb_test/test_0039_TODO_d7de40920545c14e5afcda3a2e9f0eb9.txt b/test/snapshots/parser/erb_test/test_0039_TODO_d7de40920545c14e5afcda3a2e9f0eb9.txt new file mode 100644 index 000000000..59a382d5f --- /dev/null +++ b/test/snapshots/parser/erb_test/test_0039_TODO_d7de40920545c14e5afcda3a2e9f0eb9.txt @@ -0,0 +1,19 @@ +--- +source: "Parser::ERBTest#test_0039_TODO" +input: "<%= 1 + %>" +--- +@ DocumentNode (location: (1:0)-(1:10)) +├── errors: (1 error) +│ └── @ RubyParseError (location: (1:9)-(1:10)) +│ ├── message: "expect_expression_after_operator: unexpected ';'; expected an expression after the operator" +│ ├── error_message: "unexpected ';'; expected an expression after the operator" +│ ├── diagnostic_id: "expect_expression_after_operator" +│ └── level: "syntax" +│ +└── children: (1 item) + └── @ ERBContentNode (location: (1:0)-(1:10)) + ├── tag_opening: "<%=" (location: (1:0)-(1:3)) + ├── content: " 1 + " (location: (1:3)-(1:8)) + ├── tag_closing: "%>" (location: (1:8)-(1:10)) + ├── parsed: true + └── valid: false \ No newline at end of file diff --git a/test/snapshots/parser/script_style_test/test_0006_script_tag_with_ERB-like_content_in_string_b1ffbf793264421bcbb1b9d6087f10b2.txt b/test/snapshots/parser/script_style_test/test_0006_script_tag_with_ERB-like_content_in_string_b1ffbf793264421bcbb1b9d6087f10b2.txt index c797d575c..a52aabb72 100644 --- a/test/snapshots/parser/script_style_test/test_0006_script_tag_with_ERB-like_content_in_string_b1ffbf793264421bcbb1b9d6087f10b2.txt +++ b/test/snapshots/parser/script_style_test/test_0006_script_tag_with_ERB-like_content_in_string_b1ffbf793264421bcbb1b9d6087f10b2.txt @@ -3,16 +3,11 @@ source: "Parser::ScriptStyleTest#test_0006_script tag with ERB-like content in s input: "" --- @ DocumentNode (location: (1:0)-(1:35)) -├── errors: (1 error) -│ └── @ UnclosedElementError (location: (1:35)-(1:35)) -│ ├── message: "Tag ``." + │ ├── message: "Opening tag `` in the same scope." │ └── opening_tag: "script" (location: (1:1)-(1:7)) │ ├── open_tag: diff --git a/test/snapshots/parser/script_style_test/test_0007_script_tag_with_incomplete_ERB-like_content_c3dcacbbac9dfaafc8298a1e8b6df38b.txt b/test/snapshots/parser/script_style_test/test_0007_script_tag_with_incomplete_ERB-like_content_c3dcacbbac9dfaafc8298a1e8b6df38b.txt index 2aafc5857..9704b89a3 100644 --- a/test/snapshots/parser/script_style_test/test_0007_script_tag_with_incomplete_ERB-like_content_c3dcacbbac9dfaafc8298a1e8b6df38b.txt +++ b/test/snapshots/parser/script_style_test/test_0007_script_tag_with_incomplete_ERB-like_content_c3dcacbbac9dfaafc8298a1e8b6df38b.txt @@ -3,16 +3,11 @@ source: "Parser::ScriptStyleTest#test_0007_script tag with incomplete ERB-like c input: "" --- @ DocumentNode (location: (1:0)-(1:47)) -├── errors: (1 error) -│ └── @ UnclosedElementError (location: (1:47)-(1:47)) -│ ├── message: "Tag ``." + │ ├── message: "Opening tag `` in the same scope." │ └── opening_tag: "script" (location: (1:1)-(1:7)) │ ├── open_tag: diff --git a/test/snapshots/parser/script_style_test/test_0008_script_tag_with_closing_script_tag_in_string_4d120bcac86583501954aa7c7d1b54f8.txt b/test/snapshots/parser/script_style_test/test_0008_script_tag_with_closing_script_tag_in_string_4d120bcac86583501954aa7c7d1b54f8.txt index 5c992cc1d..35f2abc64 100644 --- a/test/snapshots/parser/script_style_test/test_0008_script_tag_with_closing_script_tag_in_string_4d120bcac86583501954aa7c7d1b54f8.txt +++ b/test/snapshots/parser/script_style_test/test_0008_script_tag_with_closing_script_tag_in_string_4d120bcac86583501954aa7c7d1b54f8.txt @@ -34,7 +34,7 @@ input: "\";" └── @ HTMLCloseTagNode (location: (1:28)-(1:37)) ├── errors: (1 error) │ └── @ MissingOpeningTagError (location: (1:28)-(1:37)) - │ ├── message: "Found closing tag `` at (1:30) without a matching opening tag." + │ ├── message: "Found closing tag `` at (1:30) without a matching opening tag in the same scope." │ └── closing_tag: "script" (location: (1:30)-(1:36)) │ ├── tag_opening: "console.log(\"test\"); <%" --- @ DocumentNode (location: (1:0)-(1:40)) -├── errors: (1 error) -│ └── @ UnclosedElementError (location: (1:40)-(1:40)) -│ ├── message: "Tag ``." + │ ├── message: "Opening tag `` in the same scope." │ └── opening_tag: "script" (location: (1:1)-(1:7)) │ ├── open_tag: diff --git a/test/snapshots/parser/script_style_test/test_0032_script_tag_with_single_quote_strings_containing_closing_tags_ffab60cb04f4e44e1ee7ed24d4ac9563.txt b/test/snapshots/parser/script_style_test/test_0032_script_tag_with_single_quote_strings_containing_closing_tags_ffab60cb04f4e44e1ee7ed24d4ac9563.txt index 77f819478..4626409df 100644 --- a/test/snapshots/parser/script_style_test/test_0032_script_tag_with_single_quote_strings_containing_closing_tags_ffab60cb04f4e44e1ee7ed24d4ac9563.txt +++ b/test/snapshots/parser/script_style_test/test_0032_script_tag_with_single_quote_strings_containing_closing_tags_ffab60cb04f4e44e1ee7ed24d4ac9563.txt @@ -34,7 +34,7 @@ input: "'; var s2 = '';" ├── @ HTMLCloseTagNode (location: (1:40)-(1:48)) │ ├── errors: (1 error) │ │ └── @ MissingOpeningTagError (location: (1:40)-(1:48)) - │ │ ├── message: "Found closing tag `` at (1:42) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (1:42) without a matching opening tag in the same scope." │ │ └── closing_tag: "style" (location: (1:42)-(1:47)) │ │ │ ├── tag_opening: "var s1 = ''; var s2 = '';" └── @ HTMLCloseTagNode (location: (1:50)-(1:59)) ├── errors: (1 error) │ └── @ MissingOpeningTagError (location: (1:50)-(1:59)) - │ ├── message: "Found closing tag `` at (1:52) without a matching opening tag." + │ ├── message: "Found closing tag `` at (1:52) without a matching opening tag in the same scope." │ └── closing_tag: "script" (location: (1:52)-(1:58)) │ ├── tag_opening: "var s1 = \"\"; var s2 = \"\";" ├── @ HTMLCloseTagNode (location: (1:40)-(1:48)) │ ├── errors: (1 error) │ │ └── @ MissingOpeningTagError (location: (1:40)-(1:48)) - │ │ ├── message: "Found closing tag `` at (1:42) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (1:42) without a matching opening tag in the same scope." │ │ └── closing_tag: "style" (location: (1:42)-(1:47)) │ │ │ ├── tag_opening: "var s1 = \"\"; var s2 = \"\";" └── @ HTMLCloseTagNode (location: (1:50)-(1:59)) ├── errors: (1 error) │ └── @ MissingOpeningTagError (location: (1:50)-(1:59)) - │ ├── message: "Found closing tag `` at (1:52) without a matching opening tag." + │ ├── message: "Found closing tag `` at (1:52) without a matching opening tag in the same scope." │ └── closing_tag: "script" (location: (1:52)-(1:58)) │ ├── tag_opening: "var template = ``;" └── @ HTMLCloseTagNode (location: (1:35)-(1:44)) ├── errors: (1 error) │ └── @ MissingOpeningTagError (location: (1:35)-(1:44)) - │ ├── message: "Found closing tag `` at (1:37) without a matching opening tag." + │ ├── message: "Found closing tag `` at (1:37) without a matching opening tag in the same scope." │ └── closing_tag: "script" (location: (1:37)-(1:43)) │ ├── tag_opening: "var s = \"He said \\\"\\\" yesterday\";" └── @ HTMLCloseTagNode (location: (1:50)-(1:59)) ├── errors: (1 error) │ └── @ MissingOpeningTagError (location: (1:50)-(1:59)) - │ ├── message: "Found closing tag `` at (1:52) without a matching opening tag." + │ ├── message: "Found closing tag `` at (1:52) without a matching opening tag in the same scope." │ └── closing_tag: "script" (location: (1:52)-(1:58)) │ ├── tag_opening: ".icon::before { content: \"\"; }" └── @ HTMLCloseTagNode (location: (1:45)-(1:53)) ├── errors: (1 error) │ └── @ MissingOpeningTagError (location: (1:45)-(1:53)) - │ ├── message: "Found closing tag `` at (1:47) without a matching opening tag." + │ ├── message: "Found closing tag `` at (1:47) without a matching opening tag in the same scope." │ └── closing_tag: "style" (location: (1:47)-(1:52)) │ ├── tag_opening: "` at (3:18) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (3:18) without a matching opening tag in the same scope." │ │ └── closing_tag: "script" (location: (3:18)-(3:24)) │ │ │ ├── tag_opening: "` at (5:2) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (5:2) without a matching opening tag in the same scope." │ │ └── closing_tag: "script" (location: (5:2)-(5:8)) │ │ │ ├── tag_opening: "` at (3:2) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (3:2) without a matching opening tag in the same scope." │ │ └── closing_tag: "script" (location: (3:2)-(3:8)) │ │ │ ├── tag_opening: "` at (3:2) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (3:2) without a matching opening tag in the same scope." │ │ └── closing_tag: "script" (location: (3:2)-(3:8)) │ │ │ ├── tag_opening: "` at (4:29) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (4:29) without a matching opening tag in the same scope." │ │ └── closing_tag: "style" (location: (4:29)-(4:34)) │ │ │ ├── tag_opening: "` at (7:2) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (7:2) without a matching opening tag in the same scope." │ │ └── closing_tag: "script" (location: (7:2)-(7:8)) │ │ │ ├── tag_opening: "` at (3:27) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (3:27) without a matching opening tag in the same scope." │ │ └── closing_tag: "script" (location: (3:27)-(3:33)) │ │ │ ├── tag_opening: "` at (5:12) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (5:12) without a matching opening tag in the same scope." │ │ └── closing_tag: "script" (location: (5:12)-(5:18)) │ │ │ ├── tag_opening: "` at (6:32) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (6:32) without a matching opening tag in the same scope." │ │ └── closing_tag: "script" (location: (6:32)-(6:38)) │ │ │ ├── tag_opening: "` at (7:2) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (7:2) without a matching opening tag in the same scope." │ │ └── closing_tag: "script" (location: (7:2)-(7:8)) │ │ │ ├── tag_opening: "` at (5:2) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (5:2) without a matching opening tag in the same scope." │ │ └── closing_tag: "script" (location: (5:2)-(5:8)) │ │ │ ├── tag_opening: "` at (3:2) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (3:2) without a matching opening tag in the same scope." │ │ └── closing_tag: "script" (location: (3:2)-(3:8)) │ │ │ ├── tag_opening: "` or `
    ` instead of `
    `." │ │ │ │ ├── tag_name: "br" (location: (2:8)-(2:10)) - │ │ │ │ ├── children: [] - │ │ │ │ └── tag_closing: ">" (location: (2:10)-(2:11)) + │ │ │ │ ├── expected: "
    " + │ │ │ │ └── found: "
    " │ │ │ │ - │ │ │ ├── is_void: false - │ │ │ └── source: "HTML" + │ │ │ ├── tag_opening: "" (location: (2:10)-(2:11)) │ │ │ │ │ └── @ HTMLTextNode (location: (2:11)-(3:0)) │ │ └── content: "\n" diff --git a/test/snapshots/parser/svg_test/test_0006_svg_unclosed_element_reports_an_error_e086a53256a215470da47ef3ee745599.txt b/test/snapshots/parser/svg_test/test_0006_svg_unclosed_element_reports_an_error_e086a53256a215470da47ef3ee745599.txt index 1e729020c..319e4883b 100644 --- a/test/snapshots/parser/svg_test/test_0006_svg_unclosed_element_reports_an_error_e086a53256a215470da47ef3ee745599.txt +++ b/test/snapshots/parser/svg_test/test_0006_svg_unclosed_element_reports_an_error_e086a53256a215470da47ef3ee745599.txt @@ -6,68 +6,45 @@ input: |2- --- @ DocumentNode (location: (1:0)-(4:0)) -├── errors: (2 errors) -│ ├── @ UnclosedElementError (location: (4:0)-(4:0)) -│ │ ├── message: "Tag `` opened at (2:3) was never closed before the end of document." -│ │ └── opening_tag: "g" (location: (2:3)-(2:4)) -│ │ -│ └── @ UnclosedElementError (location: (4:0)-(4:0)) -│ ├── message: "Tag `` opened at (1:1) was never closed before the end of document." -│ └── opening_tag: "svg" (location: (1:1)-(1:4)) -│ -└── children: (1 item) - └── @ HTMLElementNode (location: (1:0)-(1:5)) - ├── errors: (1 error) - │ └── @ MissingClosingTagError (location: (1:1)-(1:4)) - │ ├── message: "Opening tag `` at (1:1) doesn't have a matching closing tag ``." - │ └── opening_tag: "svg" (location: (1:1)-(1:4)) - │ - ├── open_tag: - │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) - │ ├── tag_opening: "<" (location: (1:0)-(1:1)) - │ ├── tag_name: "svg" (location: (1:1)-(1:4)) - │ ├── tag_closing: ">" (location: (1:4)-(1:5)) - │ ├── children: [] - │ └── is_void: false - │ - ├── tag_name: "svg" (location: (1:1)-(1:4)) - ├── body: (3 items) - │ ├── @ HTMLTextNode (location: (1:5)-(2:2)) - │ │ └── content: "\n " - │ │ - │ ├── @ HTMLElementNode (location: (2:2)-(3:6)) - │ │ ├── errors: (1 error) - │ │ │ └── @ TagNamesMismatchError (location: (3:2)-(3:5)) - │ │ │ ├── message: "Opening tag `` at (2:3) closed with `` at (3:2)." - │ │ │ ├── opening_tag: "g" (location: (2:3)-(2:4)) - │ │ │ └── closing_tag: "svg" (location: (3:2)-(3:5)) - │ │ │ - │ │ ├── open_tag: - │ │ │ └── @ HTMLOpenTagNode (location: (2:2)-(2:5)) - │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) - │ │ │ ├── tag_name: "g" (location: (2:3)-(2:4)) - │ │ │ ├── tag_closing: ">" (location: (2:4)-(2:5)) - │ │ │ ├── children: [] - │ │ │ └── is_void: false - │ │ │ - │ │ ├── tag_name: "g" (location: (2:3)-(2:4)) - │ │ ├── body: (1 item) - │ │ │ └── @ HTMLTextNode (location: (2:5)-(3:0)) - │ │ │ └── content: "\n" - │ │ │ - │ │ ├── close_tag: - │ │ │ └── @ HTMLCloseTagNode (location: (3:0)-(3:6)) - │ │ │ ├── tag_opening: "" (location: (3:5)-(3:6)) - │ │ │ - │ │ ├── is_void: false - │ │ └── source: "HTML" - │ │ - │ └── @ HTMLTextNode (location: (3:6)-(4:0)) - │ └── content: "\n" - │ - ├── close_tag: ∅ - ├── is_void: false - └── source: "HTML" \ No newline at end of file +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(3:6)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "svg" (location: (1:1)-(1:4)) + │ │ ├── tag_closing: ">" (location: (1:4)-(1:5)) + │ │ ├── children: [] + │ │ └── is_void: false + │ │ + │ ├── tag_name: "svg" (location: (1:1)-(1:4)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:5)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLOpenTagNode (location: (2:2)-(2:5)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingClosingTagError (location: (2:2)-(2:5)) + │ │ │ │ ├── message: "Opening tag `` at (2:3) doesn't have a matching closing tag `` in the same scope." + │ │ │ │ └── opening_tag: "g" (location: (2:3)-(2:4)) + │ │ │ │ + │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ ├── tag_name: "g" (location: (2:3)-(2:4)) + │ │ │ ├── tag_closing: ">" (location: (2:4)-(2:5)) + │ │ │ ├── children: [] + │ │ │ └── is_void: false + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:5)-(3:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (3:0)-(3:6)) + │ │ ├── tag_opening: "" (location: (3:5)-(3:6)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (3:6)-(4:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/tags_test/test_0008_closing_tag_without_an_opening_tag_for_a_non-void_element_0a3a0b592b9c285e050805307cee87c2.txt b/test/snapshots/parser/tags_test/test_0008_closing_tag_without_an_opening_tag_for_a_non-void_element_0a3a0b592b9c285e050805307cee87c2.txt index b48bb0f9f..3128430ff 100644 --- a/test/snapshots/parser/tags_test/test_0008_closing_tag_without_an_opening_tag_for_a_non-void_element_0a3a0b592b9c285e050805307cee87c2.txt +++ b/test/snapshots/parser/tags_test/test_0008_closing_tag_without_an_opening_tag_for_a_non-void_element_0a3a0b592b9c285e050805307cee87c2.txt @@ -7,7 +7,7 @@ input: "
    " └── @ HTMLCloseTagNode (location: (1:0)-(1:6)) ├── errors: (1 error) │ └── @ MissingOpeningTagError (location: (1:0)-(1:6)) - │ ├── message: "Found closing tag `` at (1:2) without a matching opening tag." + │ ├── message: "Found closing tag `` at (1:2) without a matching opening tag in the same scope." │ └── closing_tag: "div" (location: (1:2)-(1:5)) │ ├── tag_opening: "" ├── @ HTMLCloseTagNode (location: (1:0)-(1:6)) │ ├── errors: (1 error) │ │ └── @ MissingOpeningTagError (location: (1:0)-(1:6)) - │ │ ├── message: "Found closing tag `` at (1:2) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (1:2) without a matching opening tag in the same scope." │ │ └── closing_tag: "div" (location: (1:2)-(1:5)) │ │ │ ├── tag_opening: "" ├── @ HTMLCloseTagNode (location: (1:6)-(1:12)) │ ├── errors: (1 error) │ │ └── @ MissingOpeningTagError (location: (1:6)-(1:12)) - │ │ ├── message: "Found closing tag `` at (1:8) without a matching opening tag." + │ │ ├── message: "Found closing tag `` at (1:8) without a matching opening tag in the same scope." │ │ └── closing_tag: "div" (location: (1:8)-(1:11)) │ │ │ ├── tag_opening: "" └── @ HTMLCloseTagNode (location: (1:12)-(1:19)) ├── errors: (1 error) │ └── @ MissingOpeningTagError (location: (1:12)-(1:19)) - │ ├── message: "Found closing tag `` at (1:14) without a matching opening tag." + │ ├── message: "Found closing tag `` at (1:14) without a matching opening tag in the same scope." │ └── closing_tag: "span" (location: (1:14)-(1:18)) │ ├── tag_opening: "" --- @ DocumentNode (location: (1:0)-(1:12)) -├── errors: (1 error) -│ └── @ UnclosedElementError (location: (1:12)-(1:12)) -│ ├── message: "Tag `` opened at (1:1) was never closed before the end of document." -│ └── opening_tag: "html" (location: (1:1)-(1:5)) -│ -└── children: (1 item) - └── @ HTMLElementNode (location: (1:0)-(1:12)) +└── children: (2 items) + ├── @ HTMLOpenTagNode (location: (1:0)-(1:6)) + │ ├── errors: (1 error) + │ │ └── @ MissingClosingTagError (location: (1:0)-(1:6)) + │ │ ├── message: "Opening tag `` at (1:1) doesn't have a matching closing tag `` in the same scope." + │ │ └── opening_tag: "html" (location: (1:1)-(1:5)) + │ │ + │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ ├── tag_name: "html" (location: (1:1)-(1:5)) + │ ├── tag_closing: ">" (location: (1:5)-(1:6)) + │ ├── children: [] + │ └── is_void: false + │ + └── @ HTMLCloseTagNode (location: (1:6)-(1:12)) ├── errors: (1 error) - │ └── @ TagNamesMismatchError (location: (1:8)-(1:11)) - │ ├── message: "Opening tag `` at (1:1) closed with `` at (1:8)." - │ ├── opening_tag: "html" (location: (1:1)-(1:5)) + │ └── @ MissingOpeningTagError (location: (1:6)-(1:12)) + │ ├── message: "Found closing tag `` at (1:8) without a matching opening tag in the same scope." │ └── closing_tag: "div" (location: (1:8)-(1:11)) │ - ├── open_tag: - │ └── @ HTMLOpenTagNode (location: (1:0)-(1:6)) - │ ├── tag_opening: "<" (location: (1:0)-(1:1)) - │ ├── tag_name: "html" (location: (1:1)-(1:5)) - │ ├── tag_closing: ">" (location: (1:5)-(1:6)) - │ ├── children: [] - │ └── is_void: false - │ - ├── tag_name: "html" (location: (1:1)-(1:5)) - ├── body: [] - ├── close_tag: - │ └── @ HTMLCloseTagNode (location: (1:6)-(1:12)) - │ ├── tag_opening: "" (location: (1:11)-(1:12)) - │ - ├── is_void: false - └── source: "HTML" \ No newline at end of file + ├── tag_opening: "" (location: (1:11)-(1:12)) \ No newline at end of file diff --git a/test/snapshots/parser/tags_test/test_0020_too_many_closing_tags_b3702347c5b2426cdbc11f7c38dd944b.txt b/test/snapshots/parser/tags_test/test_0020_too_many_closing_tags_b3702347c5b2426cdbc11f7c38dd944b.txt index 52f847b1f..777e5a069 100644 --- a/test/snapshots/parser/tags_test/test_0020_too_many_closing_tags_b3702347c5b2426cdbc11f7c38dd944b.txt +++ b/test/snapshots/parser/tags_test/test_0020_too_many_closing_tags_b3702347c5b2426cdbc11f7c38dd944b.txt @@ -28,7 +28,7 @@ input: "
    " └── @ HTMLCloseTagNode (location: (1:11)-(1:17)) ├── errors: (1 error) │ └── @ MissingOpeningTagError (location: (1:11)-(1:17)) - │ ├── message: "Found closing tag `` at (1:13) without a matching opening tag." + │ ├── message: "Found closing tag `` at (1:13) without a matching opening tag in the same scope." │ └── closing_tag: "div" (location: (1:13)-(1:16)) │ ├── tag_opening: "

    " --- @ DocumentNode (location: (1:0)-(1:24)) -├── errors: (2 errors) -│ ├── @ UnclosedElementError (location: (1:24)-(1:24)) -│ │ ├── message: "Tag `` opened at (1:6) was never closed before the end of document." -│ │ └── opening_tag: "span" (location: (1:6)-(1:10)) -│ │ -│ └── @ UnclosedElementError (location: (1:24)-(1:24)) -│ ├── message: "Tag `
    ` opened at (1:1) was never closed before the end of document." -│ └── opening_tag: "div" (location: (1:1)-(1:4)) -│ └── children: (1 item) - └── @ HTMLElementNode (location: (1:0)-(1:5)) - ├── errors: (1 error) - │ └── @ MissingClosingTagError (location: (1:1)-(1:4)) - │ ├── message: "Opening tag `
    ` at (1:1) doesn't have a matching closing tag `
    `." - │ └── opening_tag: "div" (location: (1:1)-(1:4)) - │ + └── @ HTMLElementNode (location: (1:0)-(1:24)) ├── open_tag: │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) │ ├── tag_opening: "<" (location: (1:0)-(1:1)) @@ -28,55 +14,46 @@ input: "

    " │ └── is_void: false │ ├── tag_name: "div" (location: (1:1)-(1:4)) - ├── body: (1 item) - │ └── @ HTMLElementNode (location: (1:5)-(1:24)) - │ ├── errors: (1 error) - │ │ └── @ TagNamesMismatchError (location: (1:20)-(1:23)) - │ │ ├── message: "Opening tag `` at (1:6) closed with `
    ` at (1:20)." - │ │ ├── opening_tag: "span" (location: (1:6)-(1:10)) - │ │ └── closing_tag: "div" (location: (1:20)-(1:23)) - │ │ + ├── body: (2 items) + │ ├── @ HTMLOpenTagNode (location: (1:5)-(1:11)) + │ │ ├── errors: (1 error) + │ │ │ └── @ MissingClosingTagError (location: (1:5)-(1:11)) + │ │ │ ├── message: "Opening tag `` at (1:6) doesn't have a matching closing tag `` in the same scope." + │ │ │ └── opening_tag: "span" (location: (1:6)-(1:10)) + │ │ │ + │ │ ├── tag_opening: "<" (location: (1:5)-(1:6)) + │ │ ├── tag_name: "span" (location: (1:6)-(1:10)) + │ │ ├── tag_closing: ">" (location: (1:10)-(1:11)) + │ │ ├── children: [] + │ │ └── is_void: false + │ │ + │ └── @ HTMLElementNode (location: (1:11)-(1:18)) │ ├── open_tag: - │ │ └── @ HTMLOpenTagNode (location: (1:5)-(1:11)) - │ │ ├── tag_opening: "<" (location: (1:5)-(1:6)) - │ │ ├── tag_name: "span" (location: (1:6)-(1:10)) - │ │ ├── tag_closing: ">" (location: (1:10)-(1:11)) + │ │ └── @ HTMLOpenTagNode (location: (1:11)-(1:14)) + │ │ ├── tag_opening: "<" (location: (1:11)-(1:12)) + │ │ ├── tag_name: "p" (location: (1:12)-(1:13)) + │ │ ├── tag_closing: ">" (location: (1:13)-(1:14)) │ │ ├── children: [] │ │ └── is_void: false │ │ - │ ├── tag_name: "span" (location: (1:6)-(1:10)) - │ ├── body: (1 item) - │ │ └── @ HTMLElementNode (location: (1:11)-(1:18)) - │ │ ├── open_tag: - │ │ │ └── @ HTMLOpenTagNode (location: (1:11)-(1:14)) - │ │ │ ├── tag_opening: "<" (location: (1:11)-(1:12)) - │ │ │ ├── tag_name: "p" (location: (1:12)-(1:13)) - │ │ │ ├── tag_closing: ">" (location: (1:13)-(1:14)) - │ │ │ ├── children: [] - │ │ │ └── is_void: false - │ │ │ - │ │ ├── tag_name: "p" (location: (1:12)-(1:13)) - │ │ ├── body: [] - │ │ ├── close_tag: - │ │ │ └── @ HTMLCloseTagNode (location: (1:14)-(1:18)) - │ │ │ ├── tag_opening: "" (location: (1:17)-(1:18)) - │ │ │ - │ │ ├── is_void: false - │ │ └── source: "HTML" - │ │ + │ ├── tag_name: "p" (location: (1:12)-(1:13)) + │ ├── body: [] │ ├── close_tag: - │ │ └── @ HTMLCloseTagNode (location: (1:18)-(1:24)) - │ │ ├── tag_opening: "" (location: (1:23)-(1:24)) + │ │ └── tag_closing: ">" (location: (1:17)-(1:18)) │ │ │ ├── is_void: false │ └── source: "HTML" │ - ├── close_tag: ∅ + ├── close_tag: + │ └── @ HTMLCloseTagNode (location: (1:18)-(1:24)) + │ ├── tag_opening: "" (location: (1:23)-(1:24)) + │ ├── is_void: false └── source: "HTML" \ No newline at end of file diff --git a/test/snapshots/parser/tags_test/test_0022_missing_multiple_closing_tags_6ad485b119da439f33eef2025393e3ea.txt b/test/snapshots/parser/tags_test/test_0022_missing_multiple_closing_tags_6ad485b119da439f33eef2025393e3ea.txt index 6d2a43682..f2625bd63 100644 --- a/test/snapshots/parser/tags_test/test_0022_missing_multiple_closing_tags_6ad485b119da439f33eef2025393e3ea.txt +++ b/test/snapshots/parser/tags_test/test_0022_missing_multiple_closing_tags_6ad485b119da439f33eef2025393e3ea.txt @@ -3,73 +3,48 @@ source: "Parser::TagsTest#test_0022_missing multiple closing tags" input: "

    " --- @ DocumentNode (location: (1:0)-(1:18)) -├── errors: (2 errors) -│ ├── @ UnclosedElementError (location: (1:18)-(1:18)) -│ │ ├── message: "Tag `` opened at (1:6) was never closed before the end of document." -│ │ └── opening_tag: "span" (location: (1:6)-(1:10)) -│ │ -│ └── @ UnclosedElementError (location: (1:18)-(1:18)) -│ ├── message: "Tag `
    ` opened at (1:1) was never closed before the end of document." -│ └── opening_tag: "div" (location: (1:1)-(1:4)) -│ -└── children: (1 item) - └── @ HTMLElementNode (location: (1:0)-(1:5)) - ├── errors: (1 error) - │ └── @ MissingClosingTagError (location: (1:1)-(1:4)) - │ ├── message: "Opening tag `
    ` at (1:1) doesn't have a matching closing tag `
    `." - │ └── opening_tag: "div" (location: (1:1)-(1:4)) - │ +└── children: (3 items) + ├── @ HTMLOpenTagNode (location: (1:0)-(1:5)) + │ ├── errors: (1 error) + │ │ └── @ MissingClosingTagError (location: (1:0)-(1:5)) + │ │ ├── message: "Opening tag `
    ` at (1:1) doesn't have a matching closing tag `
    ` in the same scope." + │ │ └── opening_tag: "div" (location: (1:1)-(1:4)) + │ │ + │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── tag_closing: ">" (location: (1:4)-(1:5)) + │ ├── children: [] + │ └── is_void: false + │ + ├── @ HTMLOpenTagNode (location: (1:5)-(1:11)) + │ ├── errors: (1 error) + │ │ └── @ MissingClosingTagError (location: (1:5)-(1:11)) + │ │ ├── message: "Opening tag `` at (1:6) doesn't have a matching closing tag `` in the same scope." + │ │ └── opening_tag: "span" (location: (1:6)-(1:10)) + │ │ + │ ├── tag_opening: "<" (location: (1:5)-(1:6)) + │ ├── tag_name: "span" (location: (1:6)-(1:10)) + │ ├── tag_closing: ">" (location: (1:10)-(1:11)) + │ ├── children: [] + │ └── is_void: false + │ + └── @ HTMLElementNode (location: (1:11)-(1:18)) ├── open_tag: - │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) - │ ├── tag_opening: "<" (location: (1:0)-(1:1)) - │ ├── tag_name: "div" (location: (1:1)-(1:4)) - │ ├── tag_closing: ">" (location: (1:4)-(1:5)) + │ └── @ HTMLOpenTagNode (location: (1:11)-(1:14)) + │ ├── tag_opening: "<" (location: (1:11)-(1:12)) + │ ├── tag_name: "p" (location: (1:12)-(1:13)) + │ ├── tag_closing: ">" (location: (1:13)-(1:14)) │ ├── children: [] │ └── is_void: false │ - ├── tag_name: "div" (location: (1:1)-(1:4)) - ├── body: (1 item) - │ └── @ HTMLElementNode (location: (1:5)-(1:11)) - │ ├── errors: (1 error) - │ │ └── @ MissingClosingTagError (location: (1:6)-(1:10)) - │ │ ├── message: "Opening tag `` at (1:6) doesn't have a matching closing tag ``." - │ │ └── opening_tag: "span" (location: (1:6)-(1:10)) - │ │ - │ ├── open_tag: - │ │ └── @ HTMLOpenTagNode (location: (1:5)-(1:11)) - │ │ ├── tag_opening: "<" (location: (1:5)-(1:6)) - │ │ ├── tag_name: "span" (location: (1:6)-(1:10)) - │ │ ├── tag_closing: ">" (location: (1:10)-(1:11)) - │ │ ├── children: [] - │ │ └── is_void: false - │ │ - │ ├── tag_name: "span" (location: (1:6)-(1:10)) - │ ├── body: (1 item) - │ │ └── @ HTMLElementNode (location: (1:11)-(1:18)) - │ │ ├── open_tag: - │ │ │ └── @ HTMLOpenTagNode (location: (1:11)-(1:14)) - │ │ │ ├── tag_opening: "<" (location: (1:11)-(1:12)) - │ │ │ ├── tag_name: "p" (location: (1:12)-(1:13)) - │ │ │ ├── tag_closing: ">" (location: (1:13)-(1:14)) - │ │ │ ├── children: [] - │ │ │ └── is_void: false - │ │ │ - │ │ ├── tag_name: "p" (location: (1:12)-(1:13)) - │ │ ├── body: [] - │ │ ├── close_tag: - │ │ │ └── @ HTMLCloseTagNode (location: (1:14)-(1:18)) - │ │ │ ├── tag_opening: "" (location: (1:17)-(1:18)) - │ │ │ - │ │ ├── is_void: false - │ │ └── source: "HTML" - │ │ - │ ├── close_tag: ∅ - │ ├── is_void: false - │ └── source: "HTML" + ├── tag_name: "p" (location: (1:12)-(1:13)) + ├── body: [] + ├── close_tag: + │ └── @ HTMLCloseTagNode (location: (1:14)-(1:18)) + │ ├── tag_opening: "" (location: (1:17)-(1:18)) │ - ├── close_tag: ∅ ├── is_void: false └── source: "HTML" \ No newline at end of file diff --git a/test/snapshots/parser/tags_test/test_0023_should_recover_from_out_of_order_closing_tags_f273b8d48121740a82fdcc71150f3710.txt b/test/snapshots/parser/tags_test/test_0023_should_recover_from_out_of_order_closing_tags_f273b8d48121740a82fdcc71150f3710.txt index 83869b29b..a0cf402b8 100644 --- a/test/snapshots/parser/tags_test/test_0023_should_recover_from_out_of_order_closing_tags_f273b8d48121740a82fdcc71150f3710.txt +++ b/test/snapshots/parser/tags_test/test_0023_should_recover_from_out_of_order_closing_tags_f273b8d48121740a82fdcc71150f3710.txt @@ -10,16 +10,11 @@ input: |2- --- @ DocumentNode (location: (1:0)-(7:6)) -├── errors: (1 error) -│ └── @ UnclosedElementError (location: (6:8)-(6:10)) -│ ├── message: "Tag `
    ` opened at (2:9) was never closed before the end of document." -│ └── opening_tag: "main" (location: (2:9)-(2:13)) -│ -└── children: (5 items) +└── children: (3 items) ├── @ HTMLTextNode (location: (1:0)-(2:8)) │ └── content: "\n " │ - ├── @ HTMLElementNode (location: (2:8)-(5:16)) + ├── @ HTMLElementNode (location: (2:8)-(6:15)) │ ├── open_tag: │ │ └── @ HTMLOpenTagNode (location: (2:8)-(2:14)) │ │ ├── tag_opening: "<" (location: (2:8)-(2:9)) @@ -33,13 +28,7 @@ input: |2- │ │ ├── @ HTMLTextNode (location: (2:14)-(3:10)) │ │ │ └── content: "\n " │ │ │ - │ │ ├── @ HTMLElementNode (location: (3:10)-(4:19)) - │ │ │ ├── errors: (1 error) - │ │ │ │ └── @ TagNamesMismatchError (location: (4:14)-(4:18)) - │ │ │ │ ├── message: "Opening tag `
    ` at (3:11) closed with `` at (4:14)." - │ │ │ │ ├── opening_tag: "div" (location: (3:11)-(3:14)) - │ │ │ │ └── closing_tag: "span" (location: (4:14)-(4:18)) - │ │ │ │ + │ │ ├── @ HTMLElementNode (location: (3:10)-(5:16)) │ │ │ ├── open_tag: │ │ │ │ └── @ HTMLOpenTagNode (location: (3:10)-(3:15)) │ │ │ │ ├── tag_opening: "<" (location: (3:10)-(3:11)) @@ -49,46 +38,46 @@ input: |2- │ │ │ │ └── is_void: false │ │ │ │ │ │ │ ├── tag_name: "div" (location: (3:11)-(3:14)) - │ │ │ ├── body: (1 item) - │ │ │ │ └── @ HTMLTextNode (location: (3:15)-(4:12)) - │ │ │ │ └── content: "\n " + │ │ │ ├── body: (3 items) + │ │ │ │ ├── @ HTMLTextNode (location: (3:15)-(4:12)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ HTMLCloseTagNode (location: (4:12)-(4:19)) + │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ └── @ MissingOpeningTagError (location: (4:12)-(4:19)) + │ │ │ │ │ │ ├── message: "Found closing tag `` at (4:14) without a matching opening tag in the same scope." + │ │ │ │ │ │ └── closing_tag: "span" (location: (4:14)-(4:18)) + │ │ │ │ │ │ + │ │ │ │ │ ├── tag_opening: "" (location: (4:18)-(4:19)) + │ │ │ │ │ + │ │ │ │ └── @ HTMLTextNode (location: (4:19)-(5:10)) + │ │ │ │ └── content: "\n " │ │ │ │ │ │ │ ├── close_tag: - │ │ │ │ └── @ HTMLCloseTagNode (location: (4:12)-(4:19)) - │ │ │ │ ├── tag_opening: "" (location: (4:18)-(4:19)) + │ │ │ │ └── tag_closing: ">" (location: (5:15)-(5:16)) │ │ │ │ │ │ │ ├── is_void: false │ │ │ └── source: "HTML" │ │ │ - │ │ └── @ HTMLTextNode (location: (4:19)-(5:10)) - │ │ └── content: "\n " + │ │ └── @ HTMLTextNode (location: (5:16)-(6:8)) + │ │ └── content: "\n " │ │ │ ├── close_tag: - │ │ └── @ HTMLCloseTagNode (location: (5:10)-(5:16)) - │ │ ├── tag_opening: "" (location: (5:15)-(5:16)) + │ │ └── tag_closing: ">" (location: (6:14)-(6:15)) │ │ │ ├── is_void: false │ └── source: "HTML" │ - ├── @ HTMLTextNode (location: (5:16)-(6:8)) - │ └── content: "\n " - │ - ├── @ HTMLCloseTagNode (location: (6:8)-(6:15)) - │ ├── errors: (1 error) - │ │ └── @ MissingOpeningTagError (location: (6:8)-(6:15)) - │ │ ├── message: "Found closing tag `
    ` at (6:10) without a matching opening tag." - │ │ └── closing_tag: "main" (location: (6:10)-(6:14)) - │ │ - │ ├── tag_opening: "" (location: (6:14)-(6:15)) - │ └── @ HTMLTextNode (location: (6:15)-(7:6)) └── content: "\n " \ No newline at end of file diff --git a/test/snapshots/parser/tags_test/test_0024_should_recover_from_multiple_out_of_order_closing_tags_d55597438f3e5bee8cd21841b672b31a.txt b/test/snapshots/parser/tags_test/test_0024_should_recover_from_multiple_out_of_order_closing_tags_d55597438f3e5bee8cd21841b672b31a.txt new file mode 100644 index 000000000..2207a9757 --- /dev/null +++ b/test/snapshots/parser/tags_test/test_0024_should_recover_from_multiple_out_of_order_closing_tags_d55597438f3e5bee8cd21841b672b31a.txt @@ -0,0 +1,109 @@ +--- +source: "Parser::TagsTest#test_0024_should recover from multiple out of order closing tags" +input: |2- +
    +
    +

    + +

    +

    +
    +--- +@ DocumentNode (location: (1:0)-(8:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(7:7)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:6)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "main" (location: (1:1)-(1:5)) + │ │ ├── tag_closing: ">" (location: (1:5)-(1:6)) + │ │ ├── children: [] + │ │ └── is_void: false + │ │ + │ ├── tag_name: "main" (location: (1:1)-(1:5)) + │ ├── body: (5 items) + │ │ ├── @ HTMLTextNode (location: (1:6)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLElementNode (location: (2:2)-(5:10)) + │ │ │ ├── open_tag: + │ │ │ │ └── @ HTMLOpenTagNode (location: (2:2)-(2:7)) + │ │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ │ ├── tag_name: "div" (location: (2:3)-(2:6)) + │ │ │ │ ├── tag_closing: ">" (location: (2:6)-(2:7)) + │ │ │ │ ├── children: [] + │ │ │ │ └── is_void: false + │ │ │ │ + │ │ │ ├── tag_name: "div" (location: (2:3)-(2:6)) + │ │ │ ├── body: (5 items) + │ │ │ │ ├── @ HTMLTextNode (location: (2:7)-(3:4)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ HTMLOpenTagNode (location: (3:4)-(3:7)) + │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ └── @ MissingClosingTagError (location: (3:4)-(3:7)) + │ │ │ │ │ │ ├── message: "Opening tag `

    ` at (3:5) doesn't have a matching closing tag `

    ` in the same scope." + │ │ │ │ │ │ └── opening_tag: "p" (location: (3:5)-(3:6)) + │ │ │ │ │ │ + │ │ │ │ │ ├── tag_opening: "<" (location: (3:4)-(3:5)) + │ │ │ │ │ ├── tag_name: "p" (location: (3:5)-(3:6)) + │ │ │ │ │ ├── tag_closing: ">" (location: (3:6)-(3:7)) + │ │ │ │ │ ├── children: [] + │ │ │ │ │ └── is_void: false + │ │ │ │ │ + │ │ │ │ ├── @ HTMLTextNode (location: (3:7)-(4:6)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ HTMLCloseTagNode (location: (4:6)-(4:13)) + │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ └── @ MissingOpeningTagError (location: (4:6)-(4:13)) + │ │ │ │ │ │ ├── message: "Found closing tag `` at (4:8) without a matching opening tag in the same scope." + │ │ │ │ │ │ └── closing_tag: "span" (location: (4:8)-(4:12)) + │ │ │ │ │ │ + │ │ │ │ │ ├── tag_opening: "" (location: (4:12)-(4:13)) + │ │ │ │ │ + │ │ │ │ └── @ HTMLTextNode (location: (4:13)-(5:4)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── close_tag: + │ │ │ │ └── @ HTMLCloseTagNode (location: (5:4)-(5:10)) + │ │ │ │ ├── tag_opening: "" (location: (5:9)-(5:10)) + │ │ │ │ + │ │ │ ├── is_void: false + │ │ │ └── source: "HTML" + │ │ │ + │ │ ├── @ HTMLTextNode (location: (5:10)-(6:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLCloseTagNode (location: (6:2)-(6:6)) + │ │ │ ├── errors: (1 error) + │ │ │ │ └── @ MissingOpeningTagError (location: (6:2)-(6:6)) + │ │ │ │ ├── message: "Found closing tag `

    ` at (6:4) without a matching opening tag in the same scope." + │ │ │ │ └── closing_tag: "p" (location: (6:4)-(6:5)) + │ │ │ │ + │ │ │ ├── tag_opening: "" (location: (6:5)-(6:6)) + │ │ │ + │ │ └── @ HTMLTextNode (location: (6:6)-(7:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (7:0)-(7:7)) + │ │ ├── tag_opening: "" (location: (7:6)-(7:7)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (7:7)-(8:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/tags_test/test_0026_should_recover_from_multiple_void_elements_used_as_closing_tag_7381d2541dc0e90b92193628ef31026f.txt b/test/snapshots/parser/tags_test/test_0026_should_recover_from_multiple_void_elements_used_as_closing_tag_7381d2541dc0e90b92193628ef31026f.txt new file mode 100644 index 000000000..24501cf2e --- /dev/null +++ b/test/snapshots/parser/tags_test/test_0026_should_recover_from_multiple_void_elements_used_as_closing_tag_7381d2541dc0e90b92193628ef31026f.txt @@ -0,0 +1,153 @@ +--- +source: "Parser::TagsTest#test_0026_should recover from multiple void elements used as closing tag" +input: |2- +
    +
    +
    + Hello +
    +

    World

    +
    +
    +--- +@ DocumentNode (location: (1:0)-(9:0)) +└── children: (2 items) + ├── @ HTMLElementNode (location: (1:0)-(8:7)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:6)) + │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ │ ├── tag_name: "main" (location: (1:1)-(1:5)) + │ │ ├── tag_closing: ">" (location: (1:5)-(1:6)) + │ │ ├── children: [] + │ │ └── is_void: false + │ │ + │ ├── tag_name: "main" (location: (1:1)-(1:5)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:6)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLElementNode (location: (2:2)-(7:8)) + │ │ │ ├── open_tag: + │ │ │ │ └── @ HTMLOpenTagNode (location: (2:2)-(2:7)) + │ │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ │ ├── tag_name: "div" (location: (2:3)-(2:6)) + │ │ │ │ ├── tag_closing: ">" (location: (2:6)-(2:7)) + │ │ │ │ ├── children: [] + │ │ │ │ └── is_void: false + │ │ │ │ + │ │ │ ├── tag_name: "div" (location: (2:3)-(2:6)) + │ │ │ ├── body: (9 items) + │ │ │ │ ├── @ HTMLTextNode (location: (2:7)-(3:4)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ HTMLCloseTagNode (location: (3:4)-(3:9)) + │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ └── @ VoidElementClosingTagError (location: (3:4)-(3:9)) + │ │ │ │ │ │ ├── message: "`br` is a void element and should not be used as a closing tag. Use `
    ` or `
    ` instead of `
    `." + │ │ │ │ │ │ ├── tag_name: "br" (location: (3:6)-(3:8)) + │ │ │ │ │ │ ├── expected: "
    " + │ │ │ │ │ │ └── found: "
    " + │ │ │ │ │ │ + │ │ │ │ │ ├── tag_opening: "" (location: (3:8)-(3:9)) + │ │ │ │ │ + │ │ │ │ ├── @ HTMLTextNode (location: (3:9)-(4:4)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ HTMLElementNode (location: (4:4)-(4:22)) + │ │ │ │ │ ├── open_tag: + │ │ │ │ │ │ └── @ HTMLOpenTagNode (location: (4:4)-(4:10)) + │ │ │ │ │ │ ├── tag_opening: "<" (location: (4:4)-(4:5)) + │ │ │ │ │ │ ├── tag_name: "span" (location: (4:5)-(4:9)) + │ │ │ │ │ │ ├── tag_closing: ">" (location: (4:9)-(4:10)) + │ │ │ │ │ │ ├── children: [] + │ │ │ │ │ │ └── is_void: false + │ │ │ │ │ │ + │ │ │ │ │ ├── tag_name: "span" (location: (4:5)-(4:9)) + │ │ │ │ │ ├── body: (1 item) + │ │ │ │ │ │ └── @ HTMLTextNode (location: (4:10)-(4:15)) + │ │ │ │ │ │ └── content: "Hello" + │ │ │ │ │ │ + │ │ │ │ │ ├── close_tag: + │ │ │ │ │ │ └── @ HTMLCloseTagNode (location: (4:15)-(4:22)) + │ │ │ │ │ │ ├── tag_opening: "" (location: (4:21)-(4:22)) + │ │ │ │ │ │ + │ │ │ │ │ ├── is_void: false + │ │ │ │ │ └── source: "HTML" + │ │ │ │ │ + │ │ │ │ ├── @ HTMLTextNode (location: (4:22)-(5:4)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ HTMLCloseTagNode (location: (5:4)-(5:9)) + │ │ │ │ │ ├── errors: (1 error) + │ │ │ │ │ │ └── @ VoidElementClosingTagError (location: (5:4)-(5:9)) + │ │ │ │ │ │ ├── message: "`br` is a void element and should not be used as a closing tag. Use `
    ` or `
    ` instead of `
    `." + │ │ │ │ │ │ ├── tag_name: "br" (location: (5:6)-(5:8)) + │ │ │ │ │ │ ├── expected: "
    " + │ │ │ │ │ │ └── found: "
    " + │ │ │ │ │ │ + │ │ │ │ │ ├── tag_opening: "" (location: (5:8)-(5:9)) + │ │ │ │ │ + │ │ │ │ ├── @ HTMLTextNode (location: (5:9)-(6:4)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ HTMLElementNode (location: (6:4)-(6:16)) + │ │ │ │ │ ├── open_tag: + │ │ │ │ │ │ └── @ HTMLOpenTagNode (location: (6:4)-(6:7)) + │ │ │ │ │ │ ├── tag_opening: "<" (location: (6:4)-(6:5)) + │ │ │ │ │ │ ├── tag_name: "p" (location: (6:5)-(6:6)) + │ │ │ │ │ │ ├── tag_closing: ">" (location: (6:6)-(6:7)) + │ │ │ │ │ │ ├── children: [] + │ │ │ │ │ │ └── is_void: false + │ │ │ │ │ │ + │ │ │ │ │ ├── tag_name: "p" (location: (6:5)-(6:6)) + │ │ │ │ │ ├── body: (1 item) + │ │ │ │ │ │ └── @ HTMLTextNode (location: (6:7)-(6:12)) + │ │ │ │ │ │ └── content: "World" + │ │ │ │ │ │ + │ │ │ │ │ ├── close_tag: + │ │ │ │ │ │ └── @ HTMLCloseTagNode (location: (6:12)-(6:16)) + │ │ │ │ │ │ ├── tag_opening: "" (location: (6:15)-(6:16)) + │ │ │ │ │ │ + │ │ │ │ │ ├── is_void: false + │ │ │ │ │ └── source: "HTML" + │ │ │ │ │ + │ │ │ │ └── @ HTMLTextNode (location: (6:16)-(7:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── close_tag: + │ │ │ │ └── @ HTMLCloseTagNode (location: (7:2)-(7:8)) + │ │ │ │ ├── tag_opening: "" (location: (7:7)-(7:8)) + │ │ │ │ + │ │ │ ├── is_void: false + │ │ │ └── source: "HTML" + │ │ │ + │ │ └── @ HTMLTextNode (location: (7:8)-(8:0)) + │ │ └── content: "\n" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (8:0)-(8:7)) + │ │ ├── tag_opening: "" (location: (8:6)-(8:7)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" + │ + └── @ HTMLTextNode (location: (8:7)-(9:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/tags_test/test_0032_script_tag_with_nested_script_tags_in_string_cf1f4c343891b3817c5470dca59c8a20.txt b/test/snapshots/parser/tags_test/test_0032_script_tag_with_nested_script_tags_in_string_cf1f4c343891b3817c5470dca59c8a20.txt index 9a38c1c6a..f742d1925 100644 --- a/test/snapshots/parser/tags_test/test_0032_script_tag_with_nested_script_tags_in_string_cf1f4c343891b3817c5470dca59c8a20.txt +++ b/test/snapshots/parser/tags_test/test_0032_script_tag_with_nested_script_tags_in_string_cf1f4c343891b3817c5470dca59c8a20.txt @@ -34,7 +34,7 @@ input: "');" └── @ HTMLCloseTagNode (location: (1:59)-(1:68)) ├── errors: (1 error) │ └── @ MissingOpeningTagError (location: (1:59)-(1:68)) - │ ├── message: "Found closing tag `` at (1:61) without a matching opening tag." + │ ├── message: "Found closing tag `` at (1:61) without a matching opening tag in the same scope." │ └── closing_tag: "script" (location: (1:61)-(1:67)) │ ├── tag_opening: " +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (2 items) + ├── @ ERBYieldNode (location: (1:0)-(1:12)) + │ ├── tag_opening: "<%=" (location: (1:0)-(1:3)) + │ ├── content: " yield " (location: (1:3)-(1:10)) + │ └── tag_closing: "%>" (location: (1:10)-(1:12)) + │ + └── @ HTMLTextNode (location: (1:12)-(2:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/yield_test/test_0002_yield_with_symbol_6cb3df93a89d085b4d803a26860118c0.txt b/test/snapshots/parser/yield_test/test_0002_yield_with_symbol_6cb3df93a89d085b4d803a26860118c0.txt new file mode 100644 index 000000000..4386a06f3 --- /dev/null +++ b/test/snapshots/parser/yield_test/test_0002_yield_with_symbol_6cb3df93a89d085b4d803a26860118c0.txt @@ -0,0 +1,14 @@ +--- +source: "Parser::YieldTest#test_0002_yield with symbol" +input: |2- +<%= yield :head %> +--- +@ DocumentNode (location: (1:0)-(2:0)) +└── children: (2 items) + ├── @ ERBYieldNode (location: (1:0)-(1:18)) + │ ├── tag_opening: "<%=" (location: (1:0)-(1:3)) + │ ├── content: " yield :head " (location: (1:3)-(1:16)) + │ └── tag_closing: "%>" (location: (1:16)-(1:18)) + │ + └── @ HTMLTextNode (location: (1:18)-(2:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/yield_test/test_0003_yield_inside_if_edde5ff3a664fd489f8aaefc285a9ae7.txt b/test/snapshots/parser/yield_test/test_0003_yield_inside_if_edde5ff3a664fd489f8aaefc285a9ae7.txt new file mode 100644 index 000000000..171357d54 --- /dev/null +++ b/test/snapshots/parser/yield_test/test_0003_yield_inside_if_edde5ff3a664fd489f8aaefc285a9ae7.txt @@ -0,0 +1,84 @@ +--- +source: "Parser::YieldTest#test_0003_yield inside if" +input: |2- +<% if content_for?(:header) %> + +<% end %> +--- +@ DocumentNode (location: (1:0)-(6:0)) +└── children: (2 items) + ├── @ ERBIfNode (location: (1:0)-(5:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " if content_for?(:header) " (location: (1:2)-(1:28)) + │ ├── tag_closing: "%>" (location: (1:28)-(1:30)) + │ ├── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:30)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ HTMLElementNode (location: (2:2)-(4:8)) + │ │ │ ├── open_tag: + │ │ │ │ └── @ HTMLOpenTagNode (location: (2:2)-(2:22)) + │ │ │ │ ├── tag_opening: "<" (location: (2:2)-(2:3)) + │ │ │ │ ├── tag_name: "div" (location: (2:3)-(2:6)) + │ │ │ │ ├── tag_closing: ">" (location: (2:21)-(2:22)) + │ │ │ │ ├── children: (1 item) + │ │ │ │ │ └── @ HTMLAttributeNode (location: (2:7)-(2:21)) + │ │ │ │ │ ├── name: + │ │ │ │ │ │ └── @ HTMLAttributeNameNode (location: (2:7)-(2:12)) + │ │ │ │ │ │ └── children: (1 item) + │ │ │ │ │ │ └── @ LiteralNode (location: (2:7)-(2:12)) + │ │ │ │ │ │ └── content: "class" + │ │ │ │ │ │ + │ │ │ │ │ │ + │ │ │ │ │ ├── equals: "=" (location: (2:12)-(2:13)) + │ │ │ │ │ └── value: + │ │ │ │ │ └── @ HTMLAttributeValueNode (location: (2:13)-(2:21)) + │ │ │ │ │ ├── open_quote: """ (location: (2:13)-(2:14)) + │ │ │ │ │ ├── children: (1 item) + │ │ │ │ │ │ └── @ LiteralNode (location: (2:14)-(2:20)) + │ │ │ │ │ │ └── content: "hidden" + │ │ │ │ │ │ + │ │ │ │ │ ├── close_quote: """ (location: (2:20)-(2:21)) + │ │ │ │ │ └── quoted: true + │ │ │ │ │ + │ │ │ │ │ + │ │ │ │ └── is_void: false + │ │ │ │ + │ │ │ ├── tag_name: "div" (location: (2:3)-(2:6)) + │ │ │ ├── body: (3 items) + │ │ │ │ ├── @ HTMLTextNode (location: (2:22)-(3:4)) + │ │ │ │ │ └── content: "\n " + │ │ │ │ │ + │ │ │ │ ├── @ ERBYieldNode (location: (3:4)-(3:24)) + │ │ │ │ │ ├── tag_opening: "<%=" (location: (3:4)-(3:7)) + │ │ │ │ │ ├── content: " yield :header " (location: (3:7)-(3:22)) + │ │ │ │ │ └── tag_closing: "%>" (location: (3:22)-(3:24)) + │ │ │ │ │ + │ │ │ │ └── @ HTMLTextNode (location: (3:24)-(4:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── close_tag: + │ │ │ │ └── @ HTMLCloseTagNode (location: (4:2)-(4:8)) + │ │ │ │ ├── tag_opening: "" (location: (4:7)-(4:8)) + │ │ │ │ + │ │ │ ├── is_void: false + │ │ │ └── source: "HTML" + │ │ │ + │ │ └── @ HTMLTextNode (location: (4:8)-(5:0)) + │ │ └── content: "\n" + │ │ + │ ├── subsequent: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (5:0)-(5:9)) + │ ├── tag_opening: "<%" (location: (5:0)-(5:2)) + │ ├── content: " end " (location: (5:2)-(5:7)) + │ └── tag_closing: "%>" (location: (5:7)-(5:9)) + │ + │ + └── @ HTMLTextNode (location: (5:9)-(6:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/yield_test/test_0004_yield_inside_block_222b48dd8aa27af5e2f135cd33f583c8.txt b/test/snapshots/parser/yield_test/test_0004_yield_inside_block_222b48dd8aa27af5e2f135cd33f583c8.txt new file mode 100644 index 000000000..663654dff --- /dev/null +++ b/test/snapshots/parser/yield_test/test_0004_yield_inside_block_222b48dd8aa27af5e2f135cd33f583c8.txt @@ -0,0 +1,34 @@ +--- +source: "Parser::YieldTest#test_0004_yield inside block" +input: |2- +<% container do %> + <%= yield :content %> +<% end %> +--- +@ DocumentNode (location: (1:0)-(4:0)) +└── children: (2 items) + ├── @ ERBBlockNode (location: (1:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " container do " (location: (1:2)-(1:16)) + │ ├── tag_closing: "%>" (location: (1:16)-(1:18)) + │ ├── body: (3 items) + │ │ ├── @ HTMLTextNode (location: (1:18)-(2:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBYieldNode (location: (2:2)-(2:23)) + │ │ │ ├── tag_opening: "<%=" (location: (2:2)-(2:5)) + │ │ │ ├── content: " yield :content " (location: (2:5)-(2:21)) + │ │ │ └── tag_closing: "%>" (location: (2:21)-(2:23)) + │ │ │ + │ │ └── @ HTMLTextNode (location: (2:23)-(3:0)) + │ │ └── content: "\n" + │ │ + │ └── end_node: + │ └── @ ERBEndNode (location: (3:0)-(3:9)) + │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) + │ ├── content: " end " (location: (3:2)-(3:7)) + │ └── tag_closing: "%>" (location: (3:7)-(3:9)) + │ + │ + └── @ HTMLTextNode (location: (3:9)-(4:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/snapshots/parser/yield_test/test_0005_yield_inside_case_when_cc5ad76c35f42b0e10034177ccaaff3e.txt b/test/snapshots/parser/yield_test/test_0005_yield_inside_case_when_cc5ad76c35f42b0e10034177ccaaff3e.txt new file mode 100644 index 000000000..767b3f9fe --- /dev/null +++ b/test/snapshots/parser/yield_test/test_0005_yield_inside_case_when_cc5ad76c35f42b0e10034177ccaaff3e.txt @@ -0,0 +1,65 @@ +--- +source: "Parser::YieldTest#test_0005_yield inside case/when" +input: |2- +<% case a %> +<% when String %> + <%= yield :string %> +<% when Integer %> + <%= yield :integer %> +<% end %> +--- +@ DocumentNode (location: (1:0)-(7:0)) +└── children: (2 items) + ├── @ ERBCaseNode (location: (1:0)-(6:9)) + │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) + │ ├── content: " case a " (location: (1:2)-(1:10)) + │ ├── tag_closing: "%>" (location: (1:10)-(1:12)) + │ ├── children: (1 item) + │ │ └── @ HTMLTextNode (location: (1:12)-(2:0)) + │ │ └── content: "\n" + │ │ + │ ├── conditions: (2 items) + │ │ ├── @ ERBWhenNode (location: (2:0)-(2:17)) + │ │ │ ├── tag_opening: "<%" (location: (2:0)-(2:2)) + │ │ │ ├── content: " when String " (location: (2:2)-(2:15)) + │ │ │ ├── tag_closing: "%>" (location: (2:15)-(2:17)) + │ │ │ └── statements: (3 items) + │ │ │ ├── @ HTMLTextNode (location: (2:17)-(3:2)) + │ │ │ │ └── content: "\n " + │ │ │ │ + │ │ │ ├── @ ERBYieldNode (location: (3:2)-(3:22)) + │ │ │ │ ├── tag_opening: "<%=" (location: (3:2)-(3:5)) + │ │ │ │ ├── content: " yield :string " (location: (3:5)-(3:20)) + │ │ │ │ └── tag_closing: "%>" (location: (3:20)-(3:22)) + │ │ │ │ + │ │ │ └── @ HTMLTextNode (location: (3:22)-(4:0)) + │ │ │ └── content: "\n" + │ │ │ + │ │ │ + │ │ └── @ ERBWhenNode (location: (4:0)-(4:18)) + │ │ ├── tag_opening: "<%" (location: (4:0)-(4:2)) + │ │ ├── content: " when Integer " (location: (4:2)-(4:16)) + │ │ ├── tag_closing: "%>" (location: (4:16)-(4:18)) + │ │ └── statements: (3 items) + │ │ ├── @ HTMLTextNode (location: (4:18)-(5:2)) + │ │ │ └── content: "\n " + │ │ │ + │ │ ├── @ ERBYieldNode (location: (5:2)-(5:23)) + │ │ │ ├── tag_opening: "<%=" (location: (5:2)-(5:5)) + │ │ │ ├── content: " yield :integer " (location: (5:5)-(5:21)) + │ │ │ └── tag_closing: "%>" (location: (5:21)-(5:23)) + │ │ │ + │ │ └── @ HTMLTextNode (location: (5:23)-(6:0)) + │ │ └── content: "\n" + │ │ + │ │ + │ ├── else_clause: ∅ + │ └── end_node: + │ └── @ ERBEndNode (location: (6:0)-(6:9)) + │ ├── tag_opening: "<%" (location: (6:0)-(6:2)) + │ ├── content: " end " (location: (6:2)-(6:7)) + │ └── tag_closing: "%>" (location: (6:7)-(6:9)) + │ + │ + └── @ HTMLTextNode (location: (6:9)-(7:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/test/snapshots/parser/comments_test/test_0005_HTML_comment_with_if_04813d5ad89aa775065eb87d881ab931.txt b/test/test/snapshots/parser/comments_test/test_0005_HTML_comment_with_if_04813d5ad89aa775065eb87d881ab931.txt new file mode 100644 index 000000000..7e6a98fb5 --- /dev/null +++ b/test/test/snapshots/parser/comments_test/test_0005_HTML_comment_with_if_04813d5ad89aa775065eb87d881ab931.txt @@ -0,0 +1,50 @@ +--- +source: "Parser::CommentsTest#test_0005_HTML comment with if" +input: |2- + +--- +@ DocumentNode (location: (1:0)-(6:0)) +└── children: (2 items) + ├── @ HTMLCommentNode (location: (1:0)-(5:3)) + │ ├── comment_start: "" (location: (5:0)-(5:3)) + │ + └── @ HTMLTextNode (location: (5:3)-(6:0)) + └── content: "\n" \ No newline at end of file diff --git a/test/visitor_test.rb b/test/visitor_test.rb index 1d909cc82..adc57d2a7 100644 --- a/test/visitor_test.rb +++ b/test/visitor_test.rb @@ -37,6 +37,7 @@ def visit_child_nodes(node) "Herb::AST::HTMLCloseTagNode" ] + assert result.success? assert_equal expected_nodes, visitor.visited_nodes.map(&:class).map(&:to_s) end @@ -48,11 +49,11 @@ def visit_child_nodes(node) expected_nodes = [ "Herb::AST::DocumentNode", - "Herb::AST::HTMLElementNode", "Herb::AST::HTMLOpenTagNode", "Herb::AST::HTMLTextNode" ] + assert result.failed? assert_equal expected_nodes, visitor.visited_nodes.map(&:class).map(&:to_s) end end