├── .bundle └── config ├── .clang-format ├── .clang-format-ignore ├── .clang-tidy ├── .doxygen ├── frame.css ├── frame.js └── header.html ├── .gitattributes ├── .github └── workflows │ ├── build.yml │ ├── deploy.yml │ ├── javascript.yml │ └── release.yml ├── .gitignore ├── .node-version ├── .prettierignore ├── .prettierrc.json ├── .rubocop.yml ├── .ruby-version ├── .verdaccio └── config.yml ├── Aptfile ├── Brewfile ├── Brewfile.lock.json ├── Doxyfile ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── Makefile ├── Procfile ├── README.md ├── Rakefile ├── bin ├── debug ├── debug_lex ├── debug_parse ├── force_update_snapshots ├── format ├── gem-build ├── gem-build-local ├── gem-platform ├── integration ├── leaks ├── leaks_lex ├── leaks_parse ├── lint ├── setup ├── test ├── test_debug ├── tidy └── update_snapshots ├── config.yml ├── docs ├── .gitignore ├── .vitepress │ ├── assets │ │ └── herb.svg │ ├── config.mts │ └── theme │ │ ├── components │ │ └── GitHubContributors.vue │ │ ├── custom.css │ │ └── index.ts ├── docs │ ├── about.md │ ├── bindings │ │ ├── javascript │ │ │ ├── index.md │ │ │ └── reference.md │ │ └── ruby │ │ │ ├── index.md │ │ │ └── reference.md │ ├── c-reference │ │ ├── enum-values.md │ │ ├── enums.md │ │ ├── index.md │ │ ├── nodes.md │ │ ├── structs.md │ │ └── tokens.md │ ├── index.md │ ├── playground.md │ ├── public │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── herb.png │ │ ├── herb.svg │ │ └── social.png │ ├── specification.md │ └── specification │ │ ├── antlers.md │ │ ├── blade.md │ │ ├── ejs.md │ │ ├── erb.md │ │ ├── haml.md │ │ ├── handlebars.md │ │ ├── html.md │ │ ├── jinja.md │ │ ├── liquid.md │ │ ├── phlex.md │ │ ├── rbexy.md │ │ ├── rux.md │ │ └── slim.md ├── package.json └── project.json ├── examples ├── attributes_with_empty_value.html.erb ├── begin.html.erb ├── block.html.erb ├── case_when.html.erb ├── comment.html.erb ├── doctype.html.erb ├── erb.html.erb ├── for.html.erb ├── if_else.html.erb ├── nested_if_and_blocks.html.erb ├── simple_block.html.erb ├── simple_erb.html.erb ├── test.html.erb ├── until.html.erb └── while.html.erb ├── exe └── herb ├── ext └── herb │ ├── extconf.rb │ ├── extension.c │ ├── extension.h │ ├── extension_helpers.c │ └── extension_helpers.h ├── herb.gemspec ├── javascript ├── .gitignore ├── packages │ ├── browser │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ ├── types │ │ │ │ └── build.d.ts │ │ │ └── wasm-backend.ts │ │ ├── test │ │ │ └── browser.test.ts │ │ ├── tsconfig.json │ │ └── vitest.config.ts │ ├── core │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── ast.ts │ │ │ ├── backend.ts │ │ │ ├── error.ts │ │ │ ├── herb-backend.ts │ │ │ ├── index.ts │ │ │ ├── lex-result.ts │ │ │ ├── location.ts │ │ │ ├── node.ts │ │ │ ├── parse-result.ts │ │ │ ├── position.ts │ │ │ ├── range.ts │ │ │ ├── result.ts │ │ │ ├── token-list.ts │ │ │ ├── token.ts │ │ │ ├── util.ts │ │ │ ├── visitor.ts │ │ │ └── warning.ts │ │ ├── test │ │ │ └── core.test.ts │ │ └── tsconfig.json │ └── node │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── bin │ │ ├── build_extension │ │ ├── publish_draft │ │ ├── publish_release │ │ └── vendor-prism.cjs │ │ ├── binding.gyp │ │ ├── extension │ │ ├── extension_helpers.cpp │ │ ├── extension_helpers.h │ │ └── herb.cpp │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src │ │ ├── index-cjs.cts │ │ ├── index-esm.mts │ │ ├── node-backend.ts │ │ ├── types │ │ │ └── global.d.ts │ │ └── util.ts │ │ ├── test.cjs │ │ ├── test.html.erb │ │ ├── test.mjs │ │ ├── test │ │ └── node.test.ts │ │ └── tsconfig.json └── tools │ └── scripts │ └── release.ts ├── lib ├── herb.rb └── herb │ ├── ast.rb │ ├── ast │ └── node.rb │ ├── cli.rb │ ├── lex_result.rb │ ├── libherb.rb │ ├── libherb │ ├── array.rb │ ├── ast_node.rb │ ├── buffer.rb │ ├── extract_result.rb │ ├── lex_result.rb │ ├── libherb.rb │ ├── parse_result.rb │ └── token.rb │ ├── location.rb │ ├── parse_result.rb │ ├── position.rb │ ├── project.rb │ ├── range.rb │ ├── result.rb │ ├── token.rb │ ├── token_list.rb │ └── version.rb ├── nx.json ├── package.json ├── playground ├── .gitignore ├── index.html ├── package.json ├── project.json ├── src │ ├── analyze.ts │ ├── controllers │ │ ├── iframe_controller.js │ │ ├── index.ts │ │ └── playground_controller.js │ ├── entry-client.ts │ ├── monaco.js │ ├── prism.ts │ ├── ranges.js │ └── style.css ├── tsconfig.json └── vite.config.ts ├── project.json ├── rakelib └── version_tasks.rake ├── src ├── analyze.c ├── analyze_helpers.c ├── analyzed_ruby.c ├── array.c ├── ast_node.c ├── buffer.c ├── extract.c ├── herb.c ├── html_util.c ├── include │ ├── analyze.h │ ├── analyze_helpers.h │ ├── analyzed_ruby.h │ ├── array.h │ ├── ast_node.h │ ├── buffer.h │ ├── extract.h │ ├── herb.h │ ├── html_util.h │ ├── io.h │ ├── json.h │ ├── lexer.h │ ├── lexer_peek_helpers.h │ ├── lexer_struct.h │ ├── location.h │ ├── macros.h │ ├── memory.h │ ├── parser.h │ ├── parser_helpers.h │ ├── position.h │ ├── pretty_print.h │ ├── prism_helpers.h │ ├── range.h │ ├── ruby_parser.h │ ├── token.h │ ├── token_matchers.h │ ├── token_struct.h │ ├── util.h │ ├── version.h │ └── visitor.h ├── io.c ├── json.c ├── lexer.c ├── lexer_peek_helpers.c ├── location.c ├── main.c ├── memory.c ├── parser.c ├── parser_helpers.c ├── position.c ├── pretty_print.c ├── prism_helpers.c ├── range.c ├── ruby_parser.c ├── token.c ├── token_matchers.c └── util.c ├── templates ├── ext │ └── herb │ │ ├── error_helpers.c.erb │ │ ├── error_helpers.h.erb │ │ ├── nodes.c.erb │ │ └── nodes.h.erb ├── javascript │ └── packages │ │ ├── core │ │ └── src │ │ │ ├── errors.ts.erb │ │ │ └── nodes.ts.erb │ │ └── node │ │ └── extension │ │ ├── error_helpers.cpp.erb │ │ ├── error_helpers.h.erb │ │ ├── nodes.cpp.erb │ │ └── nodes.h.erb ├── lib │ └── herb │ │ ├── ast │ │ └── nodes.rb.erb │ │ └── errors.rb.erb ├── src │ ├── ast_nodes.c.erb │ ├── ast_pretty_print.c.erb │ ├── errors.c.erb │ ├── include │ │ ├── ast_nodes.h.erb │ │ ├── ast_pretty_print.h.erb │ │ └── errors.h.erb │ └── visitor.c.erb ├── template.rb └── wasm │ ├── error_helpers.cpp.erb │ ├── error_helpers.h.erb │ ├── nodes.cpp.erb │ └── nodes.h.erb ├── test ├── analyze │ ├── action_view │ │ ├── asset_tag_helper │ │ │ └── image_tag_test.rb │ │ ├── form_helper │ │ │ ├── form_for_test.rb │ │ │ ├── form_with_test.rb │ │ │ └── text_field_test.rb │ │ ├── tag_helper │ │ │ ├── content_tag_test.rb │ │ │ └── tag_test.rb │ │ └── url_helper │ │ │ ├── button_to_test.rb │ │ │ ├── link_to_if_test.rb │ │ │ ├── link_to_test.rb │ │ │ ├── link_to_unless_current_test.rb │ │ │ ├── link_to_unless_test.rb │ │ │ ├── mail_to_test.rb │ │ │ ├── phone_to_test.rb │ │ │ └── sms_to_test.rb │ ├── begin_test.rb │ ├── block_test.rb │ ├── case_in_test.rb │ ├── case_test.rb │ ├── for_test.rb │ ├── if_test.rb │ ├── rails │ │ ├── content_tag_helper_test.rb │ │ └── tag_helper_test.rb │ ├── unless_test.rb │ ├── until_test.rb │ ├── while_test.rb │ └── yield_test.rb ├── c │ ├── include │ │ └── test.h │ ├── main.c │ ├── test_array.c │ ├── test_buffer.c │ ├── test_herb.c │ ├── test_html_util.c │ ├── test_io.c │ ├── test_json.c │ ├── test_lex.c │ ├── test_token.c │ └── test_util.c ├── extractor │ ├── extract_html_test.rb │ └── extract_ruby_test.rb ├── fork_helper.rb ├── lexer │ ├── attributes_test.rb │ ├── boolean_attributes_test.rb │ ├── comments_test.rb │ ├── doctype_test.rb │ ├── erb_test.rb │ ├── html_entities_test.rb │ ├── lexer_test.rb │ ├── newlines_test.rb │ ├── tags_test.rb │ ├── text_content_test.rb │ └── token_test.rb ├── libherb │ └── buffer_test.rb ├── parser │ ├── attributes_test.rb │ ├── boolean_attributes_test.rb │ ├── comments_test.rb │ ├── doctype_test.rb │ ├── erb_test.rb │ ├── newlines_test.rb │ ├── parser_test.rb │ ├── svg_test.rb │ ├── tags_test.rb │ ├── text_content_test.rb │ └── utf8_test.rb ├── snapshot_utils.rb ├── snapshots │ ├── analyze │ │ ├── begin_test │ │ │ ├── test_0001_single-line_begin_8c71cb17a972281fb729047b56de5718.txt │ │ │ ├── test_0002_begin_statement_310fa78692cbba763c6e961782acf077.txt │ │ │ ├── test_0003_begin_statement_wrapped_in_element_90dff6e715c904a974e86da2330e3795.txt │ │ │ ├── test_0004_begin_with_rescue_0abd830734a05969547e9b7f744deccd.txt │ │ │ ├── test_0005_begin_with_ensure_652af15ee9468a11e54d5661deb00e99.txt │ │ │ ├── test_0006_begin_with_else_85973b5966518a564209caa19c09f1e2.txt │ │ │ ├── test_0007_begin_with_rescue_and_else_5ff2a3e329235a33a89faff9c8647173.txt │ │ │ ├── test_0008_begin_with_rescue_and_ensure_97df51331597aef274a1e1a6d1fa05ab.txt │ │ │ ├── test_0009_begin_with_multiple_rescues_b391d083604ab993f87d7b5f7da784de.txt │ │ │ ├── test_0010_begin_with_rescue,_ensure,_and_else_7e05a59142cd6be0bdc4a05fbe46ca11.txt │ │ │ └── test_0011_nested_begin_statements_df42b2e75ecfbebde2efc7179c9a6a88.txt │ │ ├── block_test │ │ │ ├── test_0001_single_line_block_6e95c5888a720e157b1e6ec923700b92.txt │ │ │ ├── test_0002_block_bb47a5f1953b6af8f63923f01e574d97.txt │ │ │ ├── test_0003_block_with_curlies_1f0aa977e8f63e43e55f5c51eeb809c6.txt │ │ │ ├── test_0004_block_wrapped_in_element_39a5ad73c7d101fb93261a1168948d5b.txt │ │ │ ├── test_0005_each_block_12aad82c1dcc0c44ce4506dbdac4fbd5.txt │ │ │ ├── test_0006_each_with_index_block_203756003c6f59594914718691890834.txt │ │ │ ├── test_0007_times_block_1533a1664dd8eebdaa2ebe88f35f1316.txt │ │ │ ├── test_0008_upto_block_8163697cb75c2f0c085bbbbc587fe892.txt │ │ │ ├── test_0009_step_block_46c8130e027d324f1bc60a66ac2fd9d9.txt │ │ │ ├── test_0010_loop_block_b6292ce38564be66a47b39c7cd7c0154.txt │ │ │ └── test_0011_nested_blocks_5c1db84edbee554e4c974962dd664115.txt │ │ ├── case_in_test │ │ │ ├── test_0001_case_in_statement_894c684a7c2d0c6e8fe725ed9e413c27.txt │ │ │ ├── test_0002_case_in_statement_with_multiple_in_a2c476da06ea1cf6477de09d267c225d.txt │ │ │ ├── test_0003_case_in_statement_with_else_627b23a581abbe9abf0dfe9014d759d7.txt │ │ │ ├── test_0004_case_in_statement_with_multiple_in_and_else_db516a41f10230a12864d988e29f289e.txt │ │ │ ├── test_0005_case_in_statement_wrapped_in_element_61e488dd472b63b98202b2d0e83c9610.txt │ │ │ └── test_0007_case_in_with_children_before_first_in_48daaea0cd005bda78c99588a3d1fea2.txt │ │ ├── case_test │ │ │ ├── test_0001_case_statement_29ef35b6c9d10898aadb09e127bb39ca.txt │ │ │ ├── test_0002_case_statement_with_multiple_when_9e60754ef4d0f4e9601d4c74134c594d.txt │ │ │ ├── test_0003_case_statement_with_else_9707b40dd4e4f86f58c2dcd952525f80.txt │ │ │ ├── test_0004_case_statement_with_multiple_when_and_else_6ecf2eb1af1609c6f998834e6454ce75.txt │ │ │ ├── test_0005_case_statement_wrapped_in_element_8c0bd39fa334273e82d5abc8a735d433.txt │ │ │ └── test_0007_case_with_children_before_first_when_f236195655edf63829effb2806e34ce0.txt │ │ ├── for_test │ │ │ ├── test_0001_for_loop_7ec5a7ce89b03a36c5579d1a65c14274.txt │ │ │ ├── test_0002_for_loop_with_children_cad2b2f8aa5d54b817d3c82e82818659.txt │ │ │ ├── test_0003_for_loop_wrapped_in_element_dc5c7df187a9c834fe5bb14c4805f146.txt │ │ │ └── test_0004_nested_for_loops_491936055618c1eff4f83651ea92e8e9.txt │ │ ├── if_test │ │ │ ├── test_0001_if_statement_6d66066a7761ec6dd8a7cc99cdc3489c.txt │ │ │ ├── test_0002_if_else_statement_5af49f43e6d63267cffc095e54e5ffcd.txt │ │ │ ├── test_0003_if_elsif_else_statement_189c9341230fd91e47ed767621771ae6.txt │ │ │ ├── test_0004_if_elsif_elsif_else_statement_dda9e4f2ba454ab6d841390c684e222f.txt │ │ │ ├── test_0005_if_elsif_else_statement_with_children_2f01997aa5b65191b261fb73a3268eb2.txt │ │ │ ├── test_0006_if_elsif_else_statement_wrapped_in_element_45a48a89838b00831a86d6bc5b8a29f6.txt │ │ │ ├── test_0007_nested_if_statements_d2803320721afe4e5e45cc05ece9915e.txt │ │ │ ├── test_0008_if_else_statement_in_opening_tag_value_cc409a1f9d234299bd306bda568b32b4.txt │ │ │ └── test_0009_if_else_statement_in_attribute_value_8dd410c9c918399254e71570caeb17dc.txt │ │ ├── unless_test │ │ │ ├── test_0001_unless_statement_b9069f5ae184133162ab0372cfb63c39.txt │ │ │ ├── test_0002_unless_statement_wrapped_in_element_da5f138eef1f987f71ce62c984ec4b98.txt │ │ │ ├── test_0003_unless_statement_with_multiple_children_3e9be163722a3cc21d0ee1c07ebbbfd4.txt │ │ │ ├── test_0004_unless_statement_with_multiple_children_wrapped_in_element_68d73e451f7629a849c27b9d53bf6a77.txt │ │ │ ├── test_0005_unless_statement_with_else_70949ac6a2833b2b972508ab1ca3f701.txt │ │ │ └── test_0006_nested_unless_statements_f80d305e3bf8d4792b9110c87196318d.txt │ │ ├── until_test │ │ │ ├── test_0001_until_statement_cebc8340ea1e761f6ce3885d88328dc1.txt │ │ │ ├── test_0002_until_statement_wrapped_in_element_1f3ffcb5fe8fc6596865e33d2ab7252e.txt │ │ │ ├── test_0003_until_statement_with_multiple_children_329404c0b9dc64b1825b2b64f49640f7.txt │ │ │ ├── test_0004_until_statement_with_multiple_children_wrapped_in_element_b1ef79555d9533d568edd30004f8f031.txt │ │ │ ├── test_0005_nested_until_statements_a09465aa6b8033b408b97fb325d7ab66.txt │ │ │ ├── test_0006_until_statement_with_break_a05643dc6a16774c25549930740f8c33.txt │ │ │ └── test_0007_until_statement_with_next_360523760a88c79cd2e2a120fe723de7.txt │ │ ├── while_test │ │ │ ├── test_0001_while_statement_80ec9198a269a436b3bc6b3751352e04.txt │ │ │ ├── test_0002_while_statement_wrapped_in_element_0f8b0693370dfebe34b6705db39fd7ea.txt │ │ │ ├── test_0003_while_statement_with_multiple_children_655e4c05f774ea93289049d3910a144f.txt │ │ │ ├── test_0004_while_statement_with_multiple_children_wrapped_in_element_fc5e9e2adcc5aff28ce2d7b0fb074464.txt │ │ │ ├── test_0005_while_statement_with_multiple_children_and_multiple_while_adcbb3ba9e1af65fc56a9e4fd9ed0570.txt │ │ │ ├── test_0006_while_statement_with_break_7fe1a7737e0d01a3e8f78341ce5a31ea.txt │ │ │ ├── test_0007_while_statement_with_next_c60dcf0135ff764cb78cf5ebab562962.txt │ │ │ ├── test_0008_while_statement_with_redo_48925bae257116b8279a76ad623f9657.txt │ │ │ └── test_0009_nested_while_statements_1b9c93b2610e46293a5d14116c6b2f96.txt │ │ └── yield_test │ │ │ ├── test_0001_yield_811e738151285db6da8e3a493b8d6e2b.txt │ │ │ └── test_0002_yield_with_symbol_0f3dddc66e3930ac6552f5a273092594.txt │ ├── lexer │ │ ├── attributes_test │ │ │ ├── test_0001_attribute_value_double_quotes_24d3faf147396fd00701c040dd088d1c.txt │ │ │ ├── test_0002_attribute_value_single_quotes_8b3b7fed8756aacca4ddc48cda37862c.txt │ │ │ ├── test_0003_attribute_value_empty_double_quotes_with_whitespace_aa45c5ab55dcf5752db629787123d549.txt │ │ │ ├── test_0004_attribute_value_empty_double_quotes_without_whitespace_dc0893bda6faff8ecc29985263b68f44.txt │ │ │ ├── test_0005_attribute_value_empty_single_quotes_with_whitespace_2e5584859fa6a3d0830bbc5ad9020767.txt │ │ │ ├── test_0006_attribute_value_empty_single_quotes_without_whitespace_ec5b4690851677e250ac5506d70c355f.txt │ │ │ ├── test_0007_attribute_value_single_quotes_with_slash_gt_ea0ed04bb6803d8a6cb24afe526c5a4a.txt │ │ │ ├── test_0008_attribute_value_double_quotes_with_slash_gt_f890220bd65269c4616e9ce562492257.txt │ │ │ ├── test_0009_attribute_value_single_quotes_with_>_value_2e196056650e47d46886938eb88136f7.txt │ │ │ ├── test_0010_attribute_value_double_quotes_with_slash_98c2bda4f37e2def75e128c157a85691.txt │ │ │ ├── test_0011_attribute_value_double_quotes_with_>_value_bf55684b694d2aa471b24fa539546339.txt │ │ │ ├── test_0012_attribute_value_single_quotes_with_slash_value_ef9fee65e67c623b1cd991d44c9c0dc5.txt │ │ │ ├── test_0013_attribute_value_double_quotes_with_single_quote_value_bd2cc15e28c80a4cfdafb4467f6f0555.txt │ │ │ ├── test_0014_attribute_value_single_quotes_with_double_quote_value_3b99fbfb2dbe3aba937cc20311e7d331.txt │ │ │ ├── test_0015_attribute_value_empty_quotes_followed_by_another_attribute_f25f6219b2cf10d288d83c451540e958.txt │ │ │ ├── test_0016_attribute_value_with_a_period_ce133d95b3be56c458be6147bbb4418b.txt │ │ │ ├── test_0017_attribute_value_with_a_slash_6db33df13355465e338194afbebd9174.txt │ │ │ └── test_0018_attribute_value_with_an_URL_0d5203d491fd23e18ad9d18972871f99.txt │ │ ├── boolean_attributes_test │ │ │ ├── test_0001_boolean_attribute_bb7e4ba2925fabbda530de889e749a52.txt │ │ │ ├── test_0002_boolean_attribute_without_whitespace_and_with_self-closing_tag_c6b0a14a338dd8edc4a12c0d19e7f542.txt │ │ │ └── test_0003_boolean_attribute_without_whitespace_and_without_self-closing_tag_64b6a6bf9a284ab86a9db5974b31b466.txt │ │ ├── comments_test │ │ │ ├── test_0001_HTML_comment_with_padding_whitespace_9cb56068ef4732067422a670d943c08e.txt │ │ │ ├── test_0002_HTML_comment_with_no_whitespace_4644299a23e9ee76cdc970a26168b559.txt │ │ │ ├── test_0003_HTML_comment_followed_by_html_tag_573b93df7df7d12ef12722008abfc7ee.txt │ │ │ └── test_0004_HTML_comment_followed_by_html_tag_with_nested_comment_f4042eb3e6a5d62fcc86e4162fe10cf2.txt │ │ ├── doctype_test │ │ │ ├── test_0001_doctype_d50c7b32f19e4848938905f3dc675f44.txt │ │ │ ├── test_0002_doctype_with_space_6bc67d7bee174842987d55662f6e3d1c.txt │ │ │ ├── test_0003_doctype_with_html_fe364450e1391215f596d043488f989f.txt │ │ │ ├── test_0004_html4_doctype_737d71ee3fb171f813731282cd802c3e.txt │ │ │ ├── test_0005_doctype_case_insensitivity_2f56dc84fb5f8e284f42eae7d1f1c597.txt │ │ │ ├── test_0005_doctype_case_insensitivity_3e5902254c42ff2df6f6321086040dbe.txt │ │ │ └── test_0005_doctype_case_insensitivity_8bc9e797250fadb2f5af79f38cf7d74e.txt │ │ ├── erb_test │ │ │ ├── test_0001_erb_<%_%>_e97ed3ba194342cfe8febc1a40a3d603.txt │ │ │ ├── test_0002_erb_<%=_%>_2d5c53c5a986076f8a5df2585ce5aa74.txt │ │ │ ├── test_0003_erb_<%-_%>_b305aff64a566ea549c116aacb0ce86c.txt │ │ │ ├── test_0004_erb_<%-_-%>_34d2696185efe003bf3f2c2dddb924e3.txt │ │ │ ├── test_0005_erb_<%#_%>_d59bec1a45925c1618c6d42541cb652b.txt │ │ │ ├── test_0006_erb_<%%_%%>_5856d025d70301b0cc95e2287a1d324f.txt │ │ │ ├── test_0007_erb_output_inside_HTML_attribute_value_91d881ce0dd66286e4866c07a91e025c.txt │ │ │ ├── test_0008_erb_output_inside_HTML_attribute_value_with_value_before_e474134558bf8ffccb829ba880271d99.txt │ │ │ ├── test_0009_erb_output_inside_HTML_attribute_value_with_value_before_and_after_4ce9cd67e9f49e97d785cb27fb5e287a.txt │ │ │ └── test_0010_erb_output_inside_HTML_attribute_value_with_value_and_after_e07f0e9a593189f256789bd0ab7b5a82.txt │ │ ├── html_entities_test │ │ │ ├── test_0001_<_87acb03b9542ddbc824f5bbd080a5cd4.txt │ │ │ ├── test_0002_>_58ba3bb1a1772a74392e5e86bd2be4b7.txt │ │ │ ├── test_0003_ _cc7819055cde3194bb3b136bad5cf58d.txt │ │ │ ├── test_0004_"_eb6439de53405a48b124e7cf89ba71d3.txt │ │ │ ├── test_0005_'_e38c1fb206aebfcf7289482b93815826.txt │ │ │ ├── test_0006_ampersand_c2249209343ab488c055da76368f04a0.txt │ │ │ └── test_0007_literal_ampersand_6cff047854f19ac2aa52aac51bf3af4a.txt │ │ ├── lexer_test │ │ │ ├── test_0001_nil_560d7cfe153ff63e50fbb9a506bd32ba.txt │ │ │ └── test_0002_empty_file_d41d8cd98f00b204e9800998ecf8427e.txt │ │ ├── newlines_test │ │ │ ├── test_0001_line_feed_68b329da9893e34099c7d8ad5cb9c940.txt │ │ │ ├── test_0002_carriage_return_dcb9be2f604e5df91deb9659bed4748d.txt │ │ │ ├── test_0003_carriage_return_and_line_feed_81051bcc2cf1bedf378224b0a93e2877.txt │ │ │ ├── test_0004_two_newlines_e1c06d85ae7b8b032bef47e42e4c08f9.txt │ │ │ ├── test_0005_newline_after_space_d784fa8b6d98d27699781bd9a7cf19f0.txt │ │ │ ├── test_0006_text_content_before_and_after_68423faee48fc1bab08291a512861446.txt │ │ │ ├── test_0007_newline_between_text_content_f41121a903eafadf258962abc57c8644.txt │ │ │ ├── test_0008_newline_between_html_elements_832f3c170675c57ee6c4b54c3dc60462.txt │ │ │ ├── test_0009_newlines_between_html_elements_abaab2c016a126725eb69381cc13978a.txt │ │ │ └── test_0010_newline_inside_html_elements_5b50a444e2f5be5630be45cec35181eb.txt │ │ ├── tags_test │ │ │ ├── test_0001_basic_tag_c83301425b2ad1d496473a5ff3d9ecca.txt │ │ │ ├── test_0002_basic_void_tag_0a22925ab034fd09eb49cc4224720dcb.txt │ │ │ ├── test_0003_basic_void_tag_without_whitespace_917eccff1c0d00dbdc9bbe20d07c939c.txt │ │ │ ├── test_0004_namespaced_tag_45f3ec5566563217212e18f9f9983f0a.txt │ │ │ ├── test_0005_colon_inside_html__b7b46dcd10fad620a00a95b27daab204.txt │ │ │ ├── test_0006_text_content_f797031f3210ce6494466d619610926c.txt │ │ │ ├── test_0007_attribute_with_no_quotes_value_and_whitespace_and_self-closing_tag_fed80eb194a51acda7fe05820e850451.txt │ │ │ ├── test_0008_attribute_with_no_quotes_value,_no_whitespace_and_self-closing_tag_82af2795699fe4f5b34f32ed1491228a.txt │ │ │ ├── test_0009_attribute_with_no_quotes_value,_no_whitespace,_and_non_self-closing_tag_f00fb4a0aa00f7c763be88670c0bc05d.txt │ │ │ └── test_0010_link_tag_aab5814bdfc527d6d55d8957f089b911.txt │ │ ├── text_content_test │ │ │ ├── test_0001_text_content_4dc7bb0f7aaadf0c36061e92ff8d581b.txt │ │ │ ├── test_0002_text_content_with_period_d654eb23e13156b3de00db20412d10d4.txt │ │ │ ├── test_0003_text_content_that_exceeds_initial_buffer_T_size_and_needs_to_resize_once_(ca._4K)_998d9cbd9b8794fa030c888bfa09cad4.txt │ │ │ └── test_0004_text_content_that_exceeds_initial_buffer_T_size_and_needs_to_resize_twice_(ca._8K)_45e3ecb8edc7dbec7d68b72b5d083439.txt │ │ └── token_test │ │ │ ├── test_0001_whitespace_7215ee9c7d9dc229d2921a40e899ec5f.txt │ │ │ ├── test_0002_multiple_whitespace_0cf31b2c283ce3431794586df7b0996d.txt │ │ │ ├── test_0003_multiple_whitespace_with_newlines__ff50772e7d3bdf3652ee7b345823b611.txt │ │ │ ├── test_0004_non-breaking_space_8df6fbcc43d31d99e5112eb009ed8a2d.txt │ │ │ ├── test_0005_newline_68b329da9893e34099c7d8ad5cb9c940.txt │ │ │ ├── test_0006_!_9033e0e305f247c0c3c80d0c7848c8b3.txt │ │ │ ├── test_0007_slash_6666cd76f96956469e7be39d750cc7d9.txt │ │ │ ├── test_0008_dash_336d5ebc5436534e61d16e63ddfca327.txt │ │ │ ├── test_0009_underscore_b14a7b8059d9c055954c92674ce60032.txt │ │ │ ├── test_0010_percent_0bcef9c45bd8a48eda1b26eb0c61c869.txt │ │ │ ├── test_0011_colon_853ae90f0351324bd73ea615e6487517.txt │ │ │ ├── test_0012_equals_43ec3e5dee6e706af7766fffea512721.txt │ │ │ ├── test_0013_double_quote_b15835f133ff2e27c7cb28117bfae8f4.txt │ │ │ ├── test_0014_single_quote_3590cb8af0bbb9e78c343b52b93773c9.txt │ │ │ ├── test_0015_less_than_signs_83699a49d8884e65d9291885a6448e44.txt │ │ │ ├── test_0016_greater_than_signs_4a8c3066e1720f3068d5559fc41acb0c.txt │ │ │ └── test_0017_LT,_GT_and_PERCENT_signs_6f6dcf1103a1b99a952c36d32a89a2ec.txt │ └── parser │ │ ├── attributes_test │ │ ├── test_0001_attributes_58a307bbb974b5a37bc8c4923f05efa0.txt │ │ ├── test_0002_duplicate_attributes_bf952621ac1542b125217fd3c3171e28.txt │ │ ├── test_0003_attribute_with_no_quotes_value_and_whitespace_and_self-closing_tag_fed80eb194a51acda7fe05820e850451.txt │ │ ├── test_0004_attribute_with_no_quotes_value,_no_whitespace_and_self-closing_tag_82af2795699fe4f5b34f32ed1491228a.txt │ │ ├── test_0005_attribute_with_no_quotes_value,_no_whitespace,_and_non_self-closing_tag_f00fb4a0aa00f7c763be88670c0bc05d.txt │ │ ├── test_0006_attribute_value_with_space_after_equal_sign_60301c34c290d45874e5f104c39feb48.txt │ │ └── test_0007_attribute_value_with_exclamation_point_d037dd6a201e88e55e6ab86bd2595ba9.txt │ │ ├── boolean_attributes_test │ │ ├── test_0001_boolean_attribute_a42f2f02865b5934ab801e14209484bb.txt │ │ ├── test_0002_boolean_attribute_without_whitespace_eb449fdccded7b58eaadee0d6a709b70.txt │ │ ├── test_0003_boolean_attribute_without_whitespace_and_without_self-closing_tag_10073da0ec7061aa368fad226ff8dd33.txt │ │ ├── test_0004_boolean_attribute_followed_by_regular_attribute_2f302cf3eda407efbc8e40d14cd26656.txt │ │ ├── test_0005_boolean_attribute_after_regular_attribute_ac9036a484da8a6accae805201ef2dca.txt │ │ └── test_0006_boolean_attribute_surrounded_by_regular_attributes_d3642056798166a35948c97b38bde43d.txt │ │ ├── comments_test │ │ ├── test_0001_HTML_comment_with_padding_whitespace_9cb56068ef4732067422a670d943c08e.txt │ │ ├── test_0002_HTML_comment_with_no_whitespace_4644299a23e9ee76cdc970a26168b559.txt │ │ ├── test_0003_HTML_comment_followed_by_html_tag_573b93df7df7d12ef12722008abfc7ee.txt │ │ └── test_0004_HTML_comment_followed_by_html_tag_with_nested_comment_f4042eb3e6a5d62fcc86e4162fe10cf2.txt │ │ ├── doctype_test │ │ ├── test_0001_doctype_d50c7b32f19e4848938905f3dc675f44.txt │ │ ├── test_0002_doctype_with_space_6bc67d7bee174842987d55662f6e3d1c.txt │ │ ├── test_0003_doctype_with_html_fe364450e1391215f596d043488f989f.txt │ │ ├── test_0004_two_doctypes_9b920c07c2b2e29b9dcf6cd03f842e08.txt │ │ ├── test_0005_html4_doctype_737d71ee3fb171f813731282cd802c3e.txt │ │ ├── test_0006_doctype_case_insensitivity_03935adb0833c1f800be8baa0a8aca3a.txt │ │ ├── test_0006_doctype_case_insensitivity_2f56dc84fb5f8e284f42eae7d1f1c597.txt │ │ ├── test_0006_doctype_case_insensitivity_3e5902254c42ff2df6f6321086040dbe.txt │ │ ├── test_0006_doctype_case_insensitivity_8bc9e797250fadb2f5af79f38cf7d74e.txt │ │ └── test_0006_doctype_case_insensitivity_d1d92cd6681949b6e66a00ae3723d8db.txt │ │ ├── erb_test │ │ ├── test_0001_interpolate_on_top_level_486c2e36d7c731369b8bc10cc66d671e.txt │ │ ├── test_0002_interpolate_in_element_body_439bfe606142d918aa22c2f4185d92dc.txt │ │ ├── test_0003_interpolate_in_element_body_followed_by_text_content_7508a1f47d3869a85e963b96503528a8.txt │ │ ├── test_0004_interpolate_in_element_body_after_text_content_0a6d20b02f933f919dd2a5fc4993ff3a.txt │ │ ├── test_0005_interpolate_in_element_body_surrounded_by_text_content_b2e0f59912288eae0790d132dc089534.txt │ │ ├── test_0006_interpolate_inside_tag_eab2e64d663c26be3ae86f89610a672c.txt │ │ ├── test_0007_interpolate_inside_attribute_value_2e13bc9eabd550dcbe23f58ac7a01f99.txt │ │ ├── test_0008_interpolate_after_attribute_name_871cced3d8488a845c21475c5f4a6bf1.txt │ │ ├── test_0009_interpolate_inside_attribute_value_with_static_content_before_47cae9bfcf12e434a763c78aad910e38.txt │ │ ├── test_0010_interpolate_inside_attribute_value_with_static_content_after_c840a0e926fd1dd829420021b6f5da39.txt │ │ ├── test_0011_interpolate_inside_attribute_value_with_static_content_around_2d529a29e9c00085642b4f843fd79c5c.txt │ │ ├── test_0012_interpolate_inside_comment_bfdabf4936ec72a10ffc2e12adfb446a.txt │ │ ├── test_0013_conditional_tags_53b63ea8caff68a33c4251d086ba42b9.txt │ │ ├── test_0014_conditional_attributes_a13d36a12e1c3d8ff1d625b0a32793fa.txt │ │ ├── test_0015_comment_e95f45b0f3e230c24e11f4b083de23f5.txt │ │ ├── test_0016_multi-line_comment_14c1f84ba7b4627b3e0dc3ba74179bb8.txt │ │ └── test_0017_multi-line_comment_with_Ruby_keyword_dd7885019a51280cc6cfef734bf1161a.txt │ │ ├── newlines_test │ │ ├── test_0001_line_feed_68b329da9893e34099c7d8ad5cb9c940.txt │ │ ├── test_0002_carriage_return_dcb9be2f604e5df91deb9659bed4748d.txt │ │ ├── test_0003_carriage_return_and_line_feed_81051bcc2cf1bedf378224b0a93e2877.txt │ │ ├── test_0004_two_newlines_e1c06d85ae7b8b032bef47e42e4c08f9.txt │ │ ├── test_0005_newline_after_space_d784fa8b6d98d27699781bd9a7cf19f0.txt │ │ ├── test_0006_text_content_before_and_after_68423faee48fc1bab08291a512861446.txt │ │ ├── test_0007_newline_between_text_content_f41121a903eafadf258962abc57c8644.txt │ │ ├── test_0008_newline_between_html_elements_832f3c170675c57ee6c4b54c3dc60462.txt │ │ ├── test_0009_newlines_between_html_elements_abaab2c016a126725eb69381cc13978a.txt │ │ ├── test_0010_newline_inside_html_elements_5b50a444e2f5be5630be45cec35181eb.txt │ │ └── test_0011_newlines_inside_open_tags_baeb3672776170cce3b96907cd7c9bb7.txt │ │ ├── parser_test │ │ ├── test_0001_nil_560d7cfe153ff63e50fbb9a506bd32ba.txt │ │ └── test_0002_empty_file_d41d8cd98f00b204e9800998ecf8427e.txt │ │ ├── svg_test │ │ ├── test_0001_svg_7b56e1eab00ec8000da9331a4888cb35.txt │ │ ├── test_0002_svg_with_void_path_element_cf28475021c890620282e29c683e5d2c.txt │ │ ├── test_0003_svg_with_non-void_path_element_f3bf2c27b88d669126ee1c419f491e08.txt │ │ ├── test_0004_svg_with_nested_void_path_element_da5904bd14662799ee327966686eafdf.txt │ │ ├── test_0005_HTML_void_can_be_non-void_within_svg_1072d4c2f81d845d5c1f0fd86e40e23b.txt │ │ └── test_0006_svg_unclosed_element_reports_an_error_e086a53256a215470da47ef3ee745599.txt │ │ ├── tags_test │ │ ├── test_0001_empty_tag_dfc9870d38d11e806e61bd5a448afc82.txt │ │ ├── test_0002_empty_tag_with_whitespace_1e2608bc54aecef5ea56dede40640b51.txt │ │ ├── test_0003_empty_tag_with_newline_d07900ededde58845d8cc2ee728ace9f.txt │ │ ├── test_0004_void_element_shouldn't_expect_a_closing_tag_8b0f0ea73162b7552dda3c149b6c045d.txt │ │ ├── test_0005_void_element_with_open_and_close_tag_199572db6113ce1e4ad9d3fca66efc11.txt │ │ ├── test_0006_br_self-closing_tag_c5a0e9b5c299ec39a2fb26fa8b1c0dcf.txt │ │ ├── test_0007_closing_tag_without_an_opening_tag_for_a_void_element_fa5881a4aafaf2629472fd2300394826.txt │ │ ├── test_0008_closing_tag_without_an_opening_tag_for_a_non-void_element_0a3a0b592b9c285e050805307cee87c2.txt │ │ ├── test_0009_multiple_closing_tags_without_opening_tags_a371ac3aeb62b4174f84c9a365449896.txt │ │ ├── test_0010_basic_tag_c83301425b2ad1d496473a5ff3d9ecca.txt │ │ ├── test_0011_mismatched_closing_tag_7165dc23abec2b6bf23cbd14f51aa23b.txt │ │ ├── test_0012_nested_tags_f5b31fa25627c8dfbb3b8527c3cc2b02.txt │ │ ├── test_0013_basic_void_tag_0a22925ab034fd09eb49cc4224720dcb.txt │ │ ├── test_0014_basic_void_tag_without_whitespace_917eccff1c0d00dbdc9bbe20d07c939c.txt │ │ ├── test_0015_namespaced_tag_45f3ec5566563217212e18f9f9983f0a.txt │ │ ├── test_0016_colon_inside_html_tag_b7b46dcd10fad620a00a95b27daab204.txt │ │ ├── test_0017_link_tag_aab5814bdfc527d6d55d8957f089b911.txt │ │ ├── test_0018_element_has_a_self-closing_tag_for_a_void_element_at_the_position_where_closing_tag_of_parent_is_expected_6e2c034f618cde6b7e1ce8830aebdbcf.txt │ │ ├── test_0019_multiple_void_elements_shouldn't_expect_a_closing_tag_367b18f93b441feceb00bf866bb7a9a7.txt │ │ ├── test_0020_too_many_closing_tags_b3702347c5b2426cdbc11f7c38dd944b.txt │ │ ├── test_0021_missing_closing_tag_55697eb571d997f32e6851b1abb5aecb.txt │ │ ├── test_0022_missing_multiple_closing_tags_6ad485b119da439f33eef2025393e3ea.txt │ │ ├── test_0023_should_recover_from_out_of_order_closing_tags_f273b8d48121740a82fdcc71150f3710.txt │ │ └── test_0025_should_recover_from_void_elements_used_as_closing_tag_8899d88bdd54fed8935467e2c9284fd1.txt │ │ ├── text_content_test │ │ ├── test_0001_text_content_b10a8db164e0754105b7a99be72e3fe5.txt │ │ ├── test_0002_text_content_inside_tag_f797031f3210ce6494466d619610926c.txt │ │ ├── test_0003_text_content_with_tag_after_72f22f51d0be53cd7b1017d11f441721.txt │ │ ├── test_0004_text_content_with_tag_before_75206a4d9262b9f527267fab91d7fac4.txt │ │ ├── test_0005_text_content_with_tag_around_36c530447a30a5bccff1c61bc5e4465d.txt │ │ ├── test_0006_text_content_that_exceeds_initial_buffer_T_size_(ca._4K)_998d9cbd9b8794fa030c888bfa09cad4.txt │ │ ├── test_0007_text_content_that_exceeds_initial_buffer_T_size_(ca._8K)_45e3ecb8edc7dbec7d68b72b5d083439.txt │ │ ├── test_0007_text_content_that_exceeds_initial_buffer_T_size_(ca._8K)_650426493c68b593cdb7ea9e4da86ed0.txt │ │ ├── test_0008_exclamation_as_only_content_9c8b60515462c86eadd2d66da6a0ce2b.txt │ │ ├── test_0008_exclamation_point_ed076287532e86365e841e92bfc50d8c.txt │ │ ├── test_0009_comma_as_only_content_674270ea117383eb846ea45b78e9e1ba.txt │ │ ├── test_0009_exclamation_point_in_element_a2d1cb24f24b322a7dad520414c523e9.txt │ │ ├── test_0010_dollar_sign_as_only_content_78055134c44e1a49f3c26e4b6374cfbc.txt │ │ ├── test_0011_dash_as_only_content_f53ad262a8d52fd1cab61f9120cf892a.txt │ │ ├── test_0012_period_as_only_content_40a2513c491b7ca4305c3896365639cc.txt │ │ ├── test_0013_percent_as_only_content_0c16eab1f0efae1c3decd29d686ebf92.txt │ │ ├── test_0014_slash_as_only_content_a92547d4939f0d1193fc1687ead68a69.txt │ │ ├── test_0015_underscore_as_only_content_58ddd13aa94e0673c3d5a0b0117eac43.txt │ │ ├── test_0016_colon_as_only_content_e4a58fb0e007fd23a3ecb20c2d56ac80.txt │ │ ├── test_0017_semicolon_as_only_content_6dead327b63bdf32ce5db6c692032e76.txt │ │ ├── test_0018_ampersand_as_only_content_9334b21b7f71cf0efa54e9bf47bc375d.txt │ │ ├── test_0019_equals_as_only_content_50f7d033b0fe8ad1380a1ce908786917.txt │ │ ├── test_0020_a-umlaut_as_only_content_87def4693e30550387f2812dec2219e6.txt │ │ ├── test_0021_o-umlaut_as_only_content_9103b0fde88de0288ca8ca03532d66b7.txt │ │ ├── test_0022_u-umlaut_as_only_content_fc95de76ebff55679d202ded6f4f04d0.txt │ │ └── test_0023_emoji_as_only_content_400a6eb23cb6b83dd9a0bd4ddeff8409.txt │ │ └── utf8_test │ │ ├── test_0001_opening_guillemet_856d313d5fa2c08f87ea3b9a79a870b0.txt │ │ ├── test_0002_closing_guillemet_46999175f7a57d90bf4ac8417289630d.txt │ │ ├── test_0003_single_opening_guillemet_39365d03f7c1b51be8ba359dcdda448d.txt │ │ └── test_0004_single_closing_guillemet_9259b210ee4ee3e7703e1a42face4fcf.txt └── test_helper.rb ├── wasm ├── Makefile ├── extension_helpers.cpp ├── extension_helpers.h └── herb-wasm.cpp ├── workspace.json └── yarn.lock /.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_PATH: "vendor/bundle" 3 | BUNDLE_CACHE_ALL: "true" 4 | -------------------------------------------------------------------------------- /.clang-format-ignore: -------------------------------------------------------------------------------- 1 | ext/herb/error_helpers.c 2 | ext/herb/nodes.c 3 | src/ast_nodes.c 4 | src/ast_pretty_print.c 5 | src/errors.c 6 | src/include/ast_nodes.h 7 | src/include/ast_pretty_print.h 8 | src/include/errors.h 9 | src/visitor.c 10 | -------------------------------------------------------------------------------- /.doxygen/frame.css: -------------------------------------------------------------------------------- 1 | .inside-iframe { 2 | #top { 3 | display: none; 4 | } 5 | 6 | #main-nav { 7 | display: none; 8 | } 9 | 10 | .sm.sm-dox { 11 | display: none; 12 | } 13 | 14 | #nav-path ul { 15 | border-top: none; 16 | } 17 | 18 | #side-nav { 19 | display: none; 20 | } 21 | 22 | #doc-content { 23 | margin-left: 0 !important; 24 | padding-left: 0 !important; 25 | border-left: none !important; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.doxygen/frame.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", () => { 2 | if (window.parent.location == window.location) { 3 | document.body.classList.add("not-inside-iframe") 4 | } else { 5 | document.body.classList.add("inside-iframe") 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | templates/**/*.c.erb linguist-language=C 2 | templates/**/*.h.erb linguist-language=C 3 | templates/**/*.rb.erb linguist-language=Ruby 4 | templates/**/*.cpp.erb linguist-language=C++ 5 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 20.14.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/.git 2 | **/.hg 3 | **/node_modules 4 | **/dist/ 5 | **/build/ 6 | **/.vitepress/cache/ 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "semi": false, 4 | "trailingComma": "all", 5 | "bracketSpacing": true 6 | } 7 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.4.1 2 | -------------------------------------------------------------------------------- /.verdaccio/config.yml: -------------------------------------------------------------------------------- 1 | # path to a directory with all packages 2 | storage: ../tmp/local-registry/storage 3 | 4 | # a list of other known repositories we can talk to 5 | uplinks: 6 | npmjs: 7 | url: https://registry.npmjs.org/ 8 | maxage: 60m 9 | 10 | packages: 11 | "**": 12 | # give all users (including non-authenticated users) full access 13 | # because it is a local registry 14 | access: $all 15 | publish: $all 16 | unpublish: $all 17 | 18 | # if package is not available locally, proxy requests to npm registry 19 | proxy: npmjs 20 | 21 | # log settings 22 | log: 23 | type: stdout 24 | format: pretty 25 | level: warn 26 | 27 | publish: 28 | allow_offline: true # set offline to true to allow publish offline 29 | -------------------------------------------------------------------------------- /Aptfile: -------------------------------------------------------------------------------- 1 | check 2 | clang-19 3 | clang-format-19 4 | clang-tidy-19 5 | emscripten 6 | doxygen 7 | -------------------------------------------------------------------------------- /Brewfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | brew "check" 4 | brew "llvm@19" # for clang, clang-format and clang-tidy 5 | brew "emscripten" 6 | brew "doxygen" 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gemspec 6 | 7 | gem "prism", github: "ruby/prism", tag: "v1.4.0" 8 | 9 | gem "lz_string" 10 | gem "maxitest" 11 | gem "minitest-difftastic", "~> 0.2" 12 | gem "rake", "~> 13.2" 13 | gem "rake-compiler", "~> 1.2" 14 | gem "rake-compiler-dock", "~> 1.9" 15 | gem "rubocop", "~> 1.71" 16 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: cd playground && yarn serve 2 | -------------------------------------------------------------------------------- /bin/debug: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | echo "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | lldb -- "$@" 9 | -------------------------------------------------------------------------------- /bin/debug_lex: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | echo "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | lldb -- ./herb lex "$1" 9 | -------------------------------------------------------------------------------- /bin/debug_parse: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | echo "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | lldb -- ./herb parse "$1" 9 | -------------------------------------------------------------------------------- /bin/force_update_snapshots: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # Exit on error 4 | 5 | FORCE_UPDATE_SNAPSHOTS=true bundle exec rake test 6 | 7 | echo "" 8 | echo "Force updated snapshots!" 9 | -------------------------------------------------------------------------------- /bin/format: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # Exit on error 4 | 5 | make format 6 | 7 | echo "Formatting complete!" 8 | -------------------------------------------------------------------------------- /bin/gem-build: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | # 3 | # Credit: 4 | # https://github.com/flavorjones/ruby-c-extensions-explained/blob/8d5cdae81bbde48ab572c3963c972c3bf9bd37ef/precompiled/bin/test-gem-build 5 | # 6 | 7 | if [[ $# -lt 2 ]] ; then 8 | echo "usage: $(basename $0) " 9 | exit 1 10 | fi 11 | 12 | set -e -u 13 | 14 | OUTPUT_DIR=$1 15 | BUILD_NATIVE_GEM=$2 16 | 17 | test -e /etc/os-release && cat /etc/os-release 18 | 19 | set -x 20 | 21 | bundle install --local || bundle install 22 | bundle exec rake set-version-to-timestamp 23 | 24 | if [[ "${BUILD_NATIVE_GEM}" == "ruby" ]] ; then 25 | bundle exec rake clean compile 26 | bundle exec rake gem 27 | elif [[ "${BUILD_NATIVE_GEM}" == "java" ]] ; then 28 | bundle exec rake java gem 29 | else 30 | bundle exec rake gem:${BUILD_NATIVE_GEM} 31 | fi 32 | 33 | mkdir -p ${OUTPUT_DIR} 34 | cp -v pkg/*.gem ${OUTPUT_DIR} 35 | ls -l ${OUTPUT_DIR}/* 36 | -------------------------------------------------------------------------------- /bin/gem-build-local: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | PLATFORM="$(bin/gem-platform)" 4 | 5 | echo "Platform: '${PLATFORM}'" 6 | 7 | if [[ -z "$PLATFORM" ]]; then 8 | echo "Error: PLATFORM is empty." >&2 9 | exit 1 10 | else 11 | echo "Building gem for '${PLATFORM}'..." 12 | sleep 1 13 | fi 14 | 15 | bin/gem-build pkg/ "$PLATFORM" 16 | -------------------------------------------------------------------------------- /bin/gem-platform: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "rbconfig" 5 | 6 | host_cpu = RbConfig::CONFIG["host_cpu"] 7 | host_os = RbConfig::CONFIG["host_os"] 8 | 9 | arch_map = { 10 | "x86_64" => "x86_64", 11 | "aarch64" => "arm64", 12 | "arm64" => "arm64", 13 | "armv7l" => "arm", 14 | "i686" => "x86", 15 | "i386" => "x86", 16 | } 17 | 18 | arch = arch_map[host_cpu] || host_cpu 19 | 20 | if host_os =~ /darwin/ 21 | os = "darwin" 22 | platform = "#{arch}-#{os}" 23 | else 24 | libc = File.exist?("/lib/libc.musl-x86_64.so.1") || `ldd --version 2>&1`.include?("musl") ? "musl" : "gnu" 25 | os = "linux" 26 | platform = "#{arch}-#{os}-#{libc}" 27 | end 28 | 29 | puts platform 30 | -------------------------------------------------------------------------------- /bin/integration: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # Exit on error 4 | 5 | rake clean 6 | rake 7 | 8 | ./run_herb_tests 9 | 10 | ./herb lex examples/test.html.erb 11 | ./herb ruby examples/test.html.erb 12 | ./herb html examples/test.html.erb 13 | ./herb parse examples/test.html.erb 14 | ./herb prism examples/test.html.erb 15 | 16 | bin/leaks_lex examples/test.html.erb 17 | bin/leaks_parse examples/test.html.erb 18 | 19 | bin/lint 20 | bin/tidy 21 | bundle exec rubocop 22 | 23 | echo "" 24 | echo "Integration successful!" 25 | -------------------------------------------------------------------------------- /bin/leaks: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | echo "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | if [[ "$(uname)" != "Darwin" ]]; then 9 | echo "Not on macOS, skipping leaks check." 10 | exit 0 11 | fi 12 | 13 | leaks --atExit -- "$@" 14 | -------------------------------------------------------------------------------- /bin/leaks_lex: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | echo "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | if [[ "$(uname)" != "Darwin" ]]; then 9 | echo "Not on macOS, skipping leaks lex check." 10 | exit 0 11 | fi 12 | 13 | leaks --atExit -- ./herb lex "$1" 14 | -------------------------------------------------------------------------------- /bin/leaks_parse: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | echo "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | if [[ "$(uname)" != "Darwin" ]]; then 9 | echo "Not on macOS, skipping leaks parse check." 10 | exit 0 11 | fi 12 | 13 | leaks --atExit -- ./herb parse "$1" 14 | -------------------------------------------------------------------------------- /bin/lint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # Exit on error 4 | 5 | make lint 6 | 7 | echo "Linting complete!" 8 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # Exit on error 4 | 5 | make prism 6 | make all 7 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # Exit on error 4 | 5 | make all 6 | ./run_herb_tests 7 | bundle exec rake test 8 | 9 | echo "Tests successful!" 10 | -------------------------------------------------------------------------------- /bin/test_debug: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # Exit on error 4 | 5 | make all 6 | ./run_herb_tests 7 | NO_TIMEOUT=true bundle exec rake test 8 | 9 | echo "Tests successful!" 10 | -------------------------------------------------------------------------------- /bin/tidy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # Exit on error 4 | 5 | make tidy 6 | 7 | echo "Tidy complete!" 8 | -------------------------------------------------------------------------------- /bin/update_snapshots: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # Exit on error 4 | 5 | UPDATE_SNAPSHOTS=true bundle exec rake test 6 | 7 | echo "" 8 | echo "Updated snapshots!" 9 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | cache/ 3 | docs/public/assets 4 | docs/public/playground-embed.html 5 | .vitepress/data/contributors.json 6 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import type { EnhanceAppContext } from "vitepress" 2 | import Theme from "vitepress/theme" 3 | 4 | import TwoslashFloatingVue from "@shikijs/vitepress-twoslash/client" 5 | 6 | import GitHubContributors from "./components/GitHubContributors.vue" 7 | 8 | import "virtual:group-icons.css" 9 | import "./custom.css" 10 | import "@shikijs/vitepress-twoslash/style.css" 11 | 12 | export default { 13 | extends: Theme, 14 | enhanceApp({ app }: EnhanceAppContext) { 15 | app.use(TwoslashFloatingVue) 16 | app.component("GitHubContributors", GitHubContributors) 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /docs/docs/about.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | # About Herb 18 | 19 | The Herb Project was created and is lead by Marco Roth. 20 | 21 | 22 | 23 | ## Project Contributors 24 | 25 | Herb wouldn't be possible without all it's contributors. Thank you to all the amazing people who have directly contributed to the project: 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/docs/bindings/ruby/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Herb Ruby Bindings 6 | 7 | Herb is bundled and packaged up as a precompiled RubyGem and available to be installed from [RubyGems.org](https://rubygems.org). 8 | 9 | > [!TIP] More Language Bindings 10 | > Herb also has [bindings for JavaScript/Node.js](/bindings/javascript/) 11 | 12 | ## Installation 13 | 14 | Add the gem to your `Gemfile`: 15 | 16 | :::code-group 17 | ```ruby [Gemfile] 18 | gem "herb" 19 | ``` 20 | ::: 21 | 22 | or use `bundler` to add the dependency to your project: 23 | 24 | :::code-group 25 | ```shell 26 | bundle add herb 27 | ``` 28 | ::: 29 | 30 | or add it to your gemspec when you want to use Herb in a gem: 31 | 32 | :::code-group 33 | ```ruby [yourgem.gemspec] 34 | spec.add_dependency "herb", "~> 0.1" 35 | ``` 36 | ::: 37 | 38 | 39 | ## Getting Started 40 | 41 | In your project `require` the gem: 42 | 43 | :::code-group 44 | ```ruby 45 | require "herb" 46 | ``` 47 | ::: 48 | 49 | You are now ready to parse HTML+ERB in Ruby. 50 | -------------------------------------------------------------------------------- /docs/docs/c-reference/enum-values.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: C-Reference 3 | layout: page 4 | sidebar: true 5 | footer: false 6 | --- 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/docs/c-reference/enums.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: C-Reference 3 | layout: page 4 | sidebar: true 5 | footer: false 6 | --- 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/docs/c-reference/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: C-Reference 3 | layout: page 4 | sidebar: true 5 | footer: false 6 | --- 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/docs/c-reference/nodes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: C-Reference 3 | layout: page 4 | sidebar: true 5 | footer: false 6 | --- 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/docs/c-reference/structs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: C-Reference 3 | layout: page 4 | sidebar: true 5 | footer: false 6 | --- 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/docs/c-reference/tokens.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: C-Reference 3 | layout: page 4 | sidebar: true 5 | footer: false 6 | --- 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/docs/playground.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Playground 3 | layout: page 4 | sidebar: false 5 | footer: false 6 | --- 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/docs/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcoroth/herb/2d945b03cf7aa368bd345d9be525cfa1ea9333ab/docs/docs/public/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/docs/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcoroth/herb/2d945b03cf7aa368bd345d9be525cfa1ea9333ab/docs/docs/public/favicon-16x16.png -------------------------------------------------------------------------------- /docs/docs/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcoroth/herb/2d945b03cf7aa368bd345d9be525cfa1ea9333ab/docs/docs/public/favicon-32x32.png -------------------------------------------------------------------------------- /docs/docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcoroth/herb/2d945b03cf7aa368bd345d9be525cfa1ea9333ab/docs/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/docs/public/herb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcoroth/herb/2d945b03cf7aa368bd345d9be525cfa1ea9333ab/docs/docs/public/herb.png -------------------------------------------------------------------------------- /docs/docs/public/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcoroth/herb/2d945b03cf7aa368bd345d9be525cfa1ea9333ab/docs/docs/public/social.png -------------------------------------------------------------------------------- /docs/docs/specification.md: -------------------------------------------------------------------------------- 1 | # Specification 2 | 3 | > [!NOTE] TODO 4 | 5 | 6 | * [ERB](/specification/html) 7 | * [HTML+ERB](/specification/erb) 8 | -------------------------------------------------------------------------------- /docs/docs/specification/antlers.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Antlers Templates 6 | 7 | https://statamic.dev/antlers 8 | -------------------------------------------------------------------------------- /docs/docs/specification/blade.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Blade 6 | 7 | ## Basic 8 | 9 | ### Output 10 | 11 | ::: code-group 12 | ```html [Blade] 13 | {{ name }} 14 | ``` 15 | 16 | ```json [AST] 17 | { 18 | type: "blade-output", 19 | language: "php", 20 | value: { 21 | // PHP parser return value 22 | } 23 | } 24 | ``` 25 | ::: 26 | 27 | ## Helpers 28 | 29 | ### Merge attributes 30 | 31 | ::: code-group 32 | ```html [Blade] 33 |
merge(["data-controller" => "this-should-autocomplete"]) }}> 34 | {{ $message }} 35 |
36 | ``` 37 | 38 | ```json [AST] 39 | { 40 | type: "blade-output", 41 | language: "php", 42 | value: { 43 | // PHP parser return value 44 | } 45 | } 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/docs/specification/ejs.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # EJS 6 | -------------------------------------------------------------------------------- /docs/docs/specification/haml.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Haml 6 | -------------------------------------------------------------------------------- /docs/docs/specification/handlebars.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Handlebars 6 | -------------------------------------------------------------------------------- /docs/docs/specification/jinja.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Jinja 6 | -------------------------------------------------------------------------------- /docs/docs/specification/liquid.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Liquid 6 | -------------------------------------------------------------------------------- /docs/docs/specification/phlex.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Phlex 6 | 7 | https://phlex.fun 8 | -------------------------------------------------------------------------------- /docs/docs/specification/rbexy.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # rbexy 6 | 7 | https://github.com/patbenatar/rbexy 8 | -------------------------------------------------------------------------------- /docs/docs/specification/rux.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Rux 6 | 7 | https://github.com/camertron/rux 8 | -------------------------------------------------------------------------------- /docs/docs/specification/slim.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Slim 6 | -------------------------------------------------------------------------------- /docs/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "$schema": "../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "docs", 5 | "projectType": "application", 6 | "targets": { 7 | "dev": { 8 | "executor": "nx:run-script", 9 | "options": { 10 | "script": "dev" 11 | } 12 | }, 13 | "preview": { 14 | "executor": "nx:run-script", 15 | "options": { 16 | "script": "preview" 17 | } 18 | }, 19 | "serve": { 20 | "executor": "nx:run-script", 21 | "options": { 22 | "script": "serve" 23 | } 24 | } 25 | }, 26 | "tags": [] 27 | } 28 | -------------------------------------------------------------------------------- /examples/attributes_with_empty_value.html.erb: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /examples/begin.html.erb: -------------------------------------------------------------------------------- 1 | <% begin %> 2 |

Begin

3 | <% rescue StandardError => e %> 4 |

Rescue

5 | <% ensure %> 6 |

Ensure

7 | <% end %> 8 | -------------------------------------------------------------------------------- /examples/block.html.erb: -------------------------------------------------------------------------------- 1 | <% array = ["One", "Two", "Three"] %> 2 | 3 |
    4 | <% array.each do |item| %> 5 |
  • <%= item %>
  • 6 | <% end %> 7 |
8 | -------------------------------------------------------------------------------- /examples/case_when.html.erb: -------------------------------------------------------------------------------- 1 | <% case Date.today.cwday %> 2 | <% when 0 %> 3 |

Today is Sunday

4 | <% when 1 %> 5 |

Today is Monday

6 | <% when 2 %> 7 |

Today is Tuesday

8 | <% when 3 %> 9 |

Today is Wednesday

10 | <% when 4 %> 11 |

Today is Thursday

12 | <% when 5 %> 13 |

Today is Friday

14 | <% when 6 %> 15 |

Today is Saturday

16 | <% else %> 17 |

Today is not a day of the week

18 | <% end %> 19 | -------------------------------------------------------------------------------- /examples/comment.html.erb: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/doctype.html.erb: -------------------------------------------------------------------------------- 1 | hello world this is inside the doctype> 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/erb.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

3 | <%= title %> 4 |

5 |
6 | -------------------------------------------------------------------------------- /examples/for.html.erb: -------------------------------------------------------------------------------- 1 | <% names = ["Alice", "Bob", "John"] %> 2 | 3 | <% for name in names %> 4 |

Hello, <%= name %>!

5 | <% end %> 6 | -------------------------------------------------------------------------------- /examples/if_else.html.erb: -------------------------------------------------------------------------------- 1 |

2 | <% if true %> 3 |
Text1
4 | <% elsif false %> 5 |
Text2
6 | <% else %> 7 |
Text3
8 | <% end %> 9 |

10 | -------------------------------------------------------------------------------- /examples/nested_if_and_blocks.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= tag.div class: "div" do %> 3 | <% if Date.today.friday? %> 4 |

5 | Happy 6 | <% current_hour = Time.now.hour %> 7 | 8 | <% if current_hour < 12 %> 9 | Early Friday 10 | <% elsif current_hour >= 18 %> 11 | Late Friday 12 | <% else %> 13 | Friday 14 | <% end %> 15 |

16 | <% elsif Date.today.saturday? %> 17 |

18 | It's 19 | Saturday 20 | <%= " - Time to relax!" %> 21 |

22 | <% else %> 23 |

24 | Oh no, it's 25 | Not Friday 26 | <%= " - Keep going!" %> 27 |

28 | <% end %> 29 | <% end %> 30 |
31 | -------------------------------------------------------------------------------- /examples/simple_block.html.erb: -------------------------------------------------------------------------------- 1 | <% tag.div do %> 2 | <% if true %> 3 | Hello1 4 | <% else %> 5 | Hello2 6 | <% end %> 7 | <% end %> 8 | -------------------------------------------------------------------------------- /examples/simple_erb.html.erb: -------------------------------------------------------------------------------- 1 | <% title %> 2 | -------------------------------------------------------------------------------- /examples/test.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Hello World <%= RUBY_VERSION %> 5 |

6 | -------------------------------------------------------------------------------- /examples/until.html.erb: -------------------------------------------------------------------------------- 1 | <% i = 0 %> 2 | 3 | <% until i == 10 %> 4 | <%= i %> is less than 10 5 | <% i += 1 %> 6 | <% end %> 7 | -------------------------------------------------------------------------------- /examples/while.html.erb: -------------------------------------------------------------------------------- 1 | <% while i < 10 %> 2 | <%= i %> is less than 10 3 | <% i += 1 %> 4 | <% end %> 5 | -------------------------------------------------------------------------------- /exe/herb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require_relative "../lib/herb" 5 | Herb::CLI.new(ARGV).call 6 | -------------------------------------------------------------------------------- /ext/herb/extension.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_EXTENSION_H 2 | #define HERB_EXTENSION_H 3 | 4 | void Init_herb(void); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /ext/herb/extension_helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_EXTENSION_HERB_H 2 | #define HERB_EXTENSION_HERB_H 3 | 4 | #include 5 | 6 | #include "../../src/include/herb.h" 7 | #include "../../src/include/location.h" 8 | #include "../../src/include/position.h" 9 | #include "../../src/include/range.h" 10 | #include "../../src/include/token.h" 11 | 12 | const char* check_string(VALUE value); 13 | VALUE read_file_to_ruby_string(const char* file_path); 14 | 15 | VALUE rb_position_from_c_struct(position_T* position); 16 | VALUE rb_location_from_c_struct(location_T* location); 17 | 18 | VALUE rb_token_from_c_struct(token_T* token); 19 | VALUE rb_range_from_c_struct(range_T* range); 20 | 21 | VALUE create_lex_result(array_T* tokens, VALUE source); 22 | VALUE create_parse_result(AST_DOCUMENT_NODE_T* root, VALUE source); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /javascript/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files. 2 | 3 | # compiled output 4 | dist 5 | tmp 6 | out-tsc 7 | 8 | # dependencies 9 | node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | vite.config.*.timestamp* 42 | vitest.config.*.timestamp* 43 | -------------------------------------------------------------------------------- /javascript/packages/browser/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /javascript/packages/browser/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.1 (2025-04-20) 2 | 3 | This was a version bump only for @herb-tools/browser to align it with other projects, there were no code changes. 4 | 5 | ## 0.1.0 (2025-04-15) 6 | 7 | This was a version bump only for @herb-tools/browser to align it with other projects, there were no code changes. -------------------------------------------------------------------------------- /javascript/packages/browser/README.md: -------------------------------------------------------------------------------- 1 | # `@herb-tools/browser` 2 | 3 | ## Building 4 | 5 | Run `nx build browser` to build the library. 6 | 7 | ## Running unit tests 8 | 9 | Run `nx test browser` to execute the unit tests via [Vitest](https://vitest.dev/). 10 | -------------------------------------------------------------------------------- /javascript/packages/browser/rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from "@rollup/plugin-typescript" 2 | import json from "@rollup/plugin-json" 3 | import { nodeResolve } from "@rollup/plugin-node-resolve" 4 | 5 | export default { 6 | input: "src/index.ts", 7 | output: [ 8 | { 9 | file: "dist/herb-browser.esm.js", 10 | format: "esm", 11 | sourcemap: true, 12 | }, 13 | { 14 | file: "dist/herb-browser.umd.js", 15 | format: "iife", 16 | name: "Herb", 17 | sourcemap: true, 18 | }, 19 | ], 20 | plugins: [ 21 | nodeResolve(), 22 | json(), 23 | typescript({ 24 | tsconfig: "./tsconfig.json", 25 | declaration: true, 26 | declarationDir: "./dist/types", 27 | rootDir: "src/", 28 | }), 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /javascript/packages/browser/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "@herb-tools/core" 2 | 3 | import { HerbBackendWASM } from "./wasm-backend" 4 | 5 | import LibHerb from "../build/libherb.js" 6 | 7 | /** 8 | * An instance of the `Herb` class using a browser backend. 9 | * This loads `libherb` using WebAssembly (WASM). 10 | */ 11 | const Herb = new HerbBackendWASM(LibHerb) 12 | 13 | export { Herb, HerbBackendWASM } 14 | -------------------------------------------------------------------------------- /javascript/packages/browser/src/types/build.d.ts: -------------------------------------------------------------------------------- 1 | declare module "../build/*" { 2 | const value: any 3 | export default value 4 | } 5 | -------------------------------------------------------------------------------- /javascript/packages/browser/src/wasm-backend.ts: -------------------------------------------------------------------------------- 1 | import { name, version } from "../package.json" 2 | 3 | import { HerbBackend } from "@herb-tools/core" 4 | 5 | export class HerbBackendWASM extends HerbBackend { 6 | lexFile(): never { 7 | throw new Error("File system operations are not supported in the browser.") 8 | } 9 | 10 | parseFile(): never { 11 | throw new Error("File system operations are not supported in the browser.") 12 | } 13 | 14 | backendVersion(): string { 15 | return `${name}@${version}` 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /javascript/packages/browser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "moduleResolution": "bundler", 6 | "lib": ["ES2022", "DOM"], 7 | "outDir": "./dist", 8 | "declaration": true, 9 | "declarationDir": "./dist/types", 10 | "esModuleInterop": true, 11 | "strict": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "resolveJsonModule": true, 15 | "sourceMap": true 16 | }, 17 | "include": ["src/**/*"], 18 | "exclude": ["node_modules", "dist", "build"] 19 | } 20 | -------------------------------------------------------------------------------- /javascript/packages/browser/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config" 2 | 3 | export default defineConfig({ 4 | test: { 5 | browser: { 6 | enabled: true, 7 | provider: "playwright", 8 | headless: true, 9 | // https://vitest.dev/guide/browser/playwright 10 | instances: [{ browser: "chromium" }], 11 | }, 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /javascript/packages/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.1 (2025-04-20) 2 | 3 | This was a version bump only for @herb-tools/core to align it with other projects, there were no code changes. 4 | 5 | ## 0.1.0 (2025-04-15) 6 | 7 | This was a version bump only for @herb-tools/core to align it with other projects, there were no code changes. -------------------------------------------------------------------------------- /javascript/packages/core/README.md: -------------------------------------------------------------------------------- 1 | # `@herb-tools/core` 2 | 3 | ## Building 4 | 5 | Run `nx build core` to build the library. 6 | 7 | ## Running unit tests 8 | 9 | Run `nx test core` to execute the unit tests via [Vitest](https://vitest.dev/). 10 | -------------------------------------------------------------------------------- /javascript/packages/core/src/ast.ts: -------------------------------------------------------------------------------- 1 | export class ASTNode { 2 | readonly errors: any[] 3 | 4 | constructor() { 5 | this.errors = [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /javascript/packages/core/src/error.ts: -------------------------------------------------------------------------------- 1 | import { Location, SerializedLocation } from "./location.js" 2 | import { fromSerializedError } from "./errors.js" 3 | 4 | export interface SerializedHerbError { 5 | type: string 6 | message: string 7 | location: SerializedLocation 8 | } 9 | 10 | export abstract class HerbError { 11 | readonly type: string 12 | readonly message: string 13 | readonly location: Location 14 | 15 | static from(error: SerializedHerbError): HerbError { 16 | return fromSerializedError(error) 17 | } 18 | 19 | constructor(type: string, message: string, location: Location) { 20 | this.type = type 21 | this.message = message 22 | this.location = location 23 | } 24 | 25 | toJSON(): SerializedHerbError { 26 | return { 27 | type: this.type, 28 | message: this.message, 29 | location: this.location.toJSON(), 30 | } 31 | } 32 | 33 | inspect(): string { 34 | return this.treeInspect(0) 35 | } 36 | 37 | abstract treeInspect(indent?: number): string 38 | } 39 | -------------------------------------------------------------------------------- /javascript/packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ast.js" 2 | export * from "./backend.js" 3 | export * from "./errors.js" 4 | export * from "./herb-backend.js" 5 | export * from "./lex-result.js" 6 | export * from "./location.js" 7 | export * from "./nodes.js" 8 | export * from "./parse-result.js" 9 | export * from "./position.js" 10 | export * from "./range.js" 11 | export * from "./result.js" 12 | export * from "./token-list.js" 13 | export * from "./token.js" 14 | export * from "./util.js" 15 | export * from "./visitor.js" 16 | -------------------------------------------------------------------------------- /javascript/packages/core/src/position.ts: -------------------------------------------------------------------------------- 1 | export type SerializedPosition = { 2 | line: number 3 | column: number 4 | } 5 | 6 | export class Position { 7 | readonly line: number 8 | readonly column: number 9 | 10 | static from(position: SerializedPosition) { 11 | return new Position(position.line, position.column) 12 | } 13 | 14 | constructor(line: number, column: number) { 15 | this.line = line 16 | this.column = column 17 | } 18 | 19 | toHash(): SerializedPosition { 20 | return { line: this.line, column: this.column } 21 | } 22 | 23 | toJSON(): SerializedPosition { 24 | return this.toHash() 25 | } 26 | 27 | treeInspect(): string { 28 | return `(${this.line}:${this.column})` 29 | } 30 | 31 | inspect(): string { 32 | return `#` 33 | } 34 | 35 | toString(): string { 36 | return this.inspect() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /javascript/packages/core/src/range.ts: -------------------------------------------------------------------------------- 1 | export type SerializedRange = [number, number] 2 | 3 | export class Range { 4 | readonly start: number 5 | readonly end: number 6 | 7 | static from(range: SerializedRange) { 8 | return new Range(range[0], range[1]) 9 | } 10 | 11 | constructor(start: number, end: number) { 12 | this.start = start 13 | this.end = end 14 | } 15 | 16 | toArray(): SerializedRange { 17 | return [this.start, this.end] 18 | } 19 | 20 | toJSON(): SerializedRange { 21 | return this.toArray() 22 | } 23 | 24 | treeInspect(): string { 25 | return `[${this.start}, ${this.end}]` 26 | } 27 | 28 | inspect(): string { 29 | return `#` 30 | } 31 | 32 | toString(): string { 33 | return this.inspect() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /javascript/packages/core/src/result.ts: -------------------------------------------------------------------------------- 1 | import { HerbError } from "./error.js" 2 | import { HerbWarning } from "./warning.js" 3 | 4 | export class Result { 5 | readonly source: string 6 | readonly warnings: HerbWarning[] 7 | readonly errors: HerbError[] 8 | 9 | constructor( 10 | source: string, 11 | warnings: HerbWarning[] = [], 12 | errors: HerbError[] = [], 13 | ) { 14 | this.source = source 15 | this.warnings = warnings || [] 16 | this.errors = errors || [] 17 | } 18 | 19 | success(): boolean { 20 | return false 21 | } 22 | 23 | failed(): boolean { 24 | return true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /javascript/packages/core/src/util.ts: -------------------------------------------------------------------------------- 1 | export function ensureString(object: any): string { 2 | if (typeof object === "string") { 3 | return object 4 | } 5 | 6 | throw new TypeError("Argument must be a string") 7 | } 8 | 9 | export function convertToUTF8(string: string) { 10 | const bytes = [] 11 | 12 | for (let i = 0; i < string.length; i++) { 13 | bytes.push(string.charCodeAt(i)) 14 | } 15 | 16 | const decoder = new TextDecoder("utf-8") 17 | 18 | return decoder.decode(new Uint8Array(bytes)) 19 | } 20 | -------------------------------------------------------------------------------- /javascript/packages/core/src/visitor.ts: -------------------------------------------------------------------------------- 1 | import { Node } from "./node.js" 2 | 3 | /** 4 | * Represents a visitor that can traverse nodes. 5 | */ 6 | export class Visitor { 7 | /** 8 | * Visits a node and performs an action. 9 | * @param node - The node to visit. 10 | */ 11 | visit(node: Node) { 12 | console.log("Node", node) // TODO: implement 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /javascript/packages/core/src/warning.ts: -------------------------------------------------------------------------------- 1 | import { Location, SerializedLocation } from "./location.js" 2 | 3 | export interface SerializedHerbWarning { 4 | message: string 5 | location: SerializedLocation 6 | } 7 | 8 | export class HerbWarning { 9 | message: string 10 | location: Location 11 | 12 | static from(warning: SerializedHerbWarning) { 13 | return new HerbWarning(warning.message, Location.from(warning.location)) 14 | } 15 | 16 | constructor(message: string, location: Location) { 17 | this.message = message 18 | this.location = location 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /javascript/packages/core/test/core.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from "vitest" 2 | import { HerbBackend } from "../src" 3 | 4 | describe("@herb-tools/core", () => { 5 | test("is defined", () => { 6 | expect(HerbBackend).toBeDefined() 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /javascript/packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "moduleResolution": "bundler", 6 | "lib": ["ES2022"], 7 | "outDir": "./dist", 8 | "declaration": true, 9 | "declarationDir": "./dist/types", 10 | "esModuleInterop": true, 11 | "strict": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "resolveJsonModule": true, 15 | "sourceMap": true 16 | }, 17 | "include": ["src/**/*", "types/**/*"], 18 | "exclude": ["node_modules", "dist"] 19 | } 20 | -------------------------------------------------------------------------------- /javascript/packages/node/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | build/ 3 | extension/libherb/ 4 | extension/prism 5 | -------------------------------------------------------------------------------- /javascript/packages/node/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.1 (2025-04-20) 2 | 3 | This was a version bump only for @herb-tools/node to align it with other projects, there were no code changes. 4 | 5 | ## 0.1.0 (2025-04-15) 6 | 7 | This was a version bump only for @herb-tools/node to align it with other projects, there were no code changes. -------------------------------------------------------------------------------- /javascript/packages/node/README.md: -------------------------------------------------------------------------------- 1 | # `@herb-tools/node` 2 | 3 | ## Building 4 | 5 | Run `nx build node` to build the library. 6 | 7 | ## Running unit tests 8 | 9 | Run `nx test node` to execute the unit tests via [Vitest](https://vitest.dev/). 10 | -------------------------------------------------------------------------------- /javascript/packages/node/bin/build_extension: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # Exit on error 4 | 5 | yarn node-pre-gyp configure 6 | yarn node-pre-gyp build 7 | yarn node-pre-gyp package 8 | -------------------------------------------------------------------------------- /javascript/packages/node/bin/publish_draft: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # Exit on error 4 | 5 | bin/build_extension 6 | 7 | yarn node-pre-gyp-github publish 8 | -------------------------------------------------------------------------------- /javascript/packages/node/bin/publish_release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e # Exit on error 4 | 5 | bin/build_extension 6 | 7 | yarn node-pre-gyp-github publish --release 8 | -------------------------------------------------------------------------------- /javascript/packages/node/extension/extension_helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_NODE_EXTENSION_HELPERS_H 2 | #define HERB_NODE_EXTENSION_HELPERS_H 3 | 4 | #include 5 | 6 | extern "C" { 7 | #include "../extension/libherb/include/array.h" 8 | #include "../extension/libherb/include/ast_nodes.h" 9 | } 10 | 11 | char* CheckString(napi_env env, napi_value value); 12 | napi_value CreateString(napi_env env, const char* str); 13 | napi_value ReadFileToString(napi_env env, const char* file_path); 14 | napi_value CreateLexResult(napi_env env, array_T* tokens, napi_value source); 15 | napi_value CreateParseResult(napi_env env, AST_DOCUMENT_NODE_T* root, napi_value source); 16 | 17 | napi_value CreateLocation(napi_env env, location_T* location); 18 | napi_value CreateToken(napi_env env, token_T* token); 19 | napi_value CreatePosition(napi_env env, position_T* position); 20 | napi_value CreateRange(napi_env env, range_T* range); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /javascript/packages/node/src/index-cjs.cts: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const binary = require("@mapbox/node-pre-gyp") 3 | 4 | const { Visitor } = require("@herb-tools/core") 5 | const { HerbBackendNode } = require("./node-backend.js") 6 | 7 | const packagePath = path.resolve(__dirname, "../package.json") 8 | const libherbPath = binary.find(packagePath) 9 | const libHerbBinary = require(libherbPath) 10 | 11 | /** 12 | * An instance of the `Herb` class using a Node.js backend. 13 | * This loads `libherb` in a Node.js C++ native extension. 14 | */ 15 | const Herb = new HerbBackendNode( 16 | new Promise((resolve, _reject) => resolve(libHerbBinary)), 17 | ) 18 | 19 | module.exports = { 20 | Herb: Herb, 21 | Visitor: Visitor, 22 | } 23 | -------------------------------------------------------------------------------- /javascript/packages/node/src/index-esm.mts: -------------------------------------------------------------------------------- 1 | export * from "@herb-tools/core" 2 | 3 | import path from "path" 4 | import binary from "@mapbox/node-pre-gyp" 5 | 6 | import { createRequire } from "module" 7 | import { fileURLToPath } from "url" 8 | 9 | const __filename = fileURLToPath(import.meta.url) 10 | const __dirname = path.dirname(__filename) 11 | const require = createRequire(import.meta.url) 12 | 13 | const packagePath = path.resolve(__dirname, "../package.json") 14 | const libherbPath = binary.find(packagePath) 15 | 16 | const libHerbBinary = require(libherbPath) 17 | 18 | import { HerbBackendNode } from "./node-backend.js" 19 | 20 | /** 21 | * An instance of the `Herb` class using a Node.js backend. 22 | * This loads `libherb` in a Node.js C++ native extension. 23 | */ 24 | const Herb = new HerbBackendNode( 25 | () => new Promise((resolve, _reject) => resolve(libHerbBinary)), 26 | ) 27 | 28 | export { Herb, HerbBackendNode } 29 | -------------------------------------------------------------------------------- /javascript/packages/node/src/node-backend.ts: -------------------------------------------------------------------------------- 1 | import packageJSON from "../package.json" with { type: "json" } 2 | 3 | import { HerbBackend } from "@herb-tools/core" 4 | 5 | export class HerbBackendNode extends HerbBackend { 6 | backendVersion(): string { 7 | return `${packageJSON.name}@${packageJSON.version}` 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /javascript/packages/node/src/types/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.node" { 2 | const content: any 3 | export default content 4 | } 5 | 6 | declare module "@mapbox/node-pre-gyp" { 7 | export interface FindOptions { 8 | module_root?: string 9 | [key: string]: any 10 | } 11 | 12 | export interface NodePreGyp { 13 | find(packageJsonPath: string, opts?: FindOptions): string 14 | } 15 | 16 | declare const nodePreGyp: NodePreGyp 17 | export default nodePreGyp 18 | } 19 | -------------------------------------------------------------------------------- /javascript/packages/node/test.cjs: -------------------------------------------------------------------------------- 1 | const { Herb } = require("@herb-tools/node") 2 | 3 | console.log(Herb) 4 | console.log(Herb.version) 5 | console.log(Herb.lex("hello world")) 6 | console.log(Herb.parse("hello world")) 7 | -------------------------------------------------------------------------------- /javascript/packages/node/test.html.erb: -------------------------------------------------------------------------------- 1 |
<%= hello world %>
2 | -------------------------------------------------------------------------------- /javascript/packages/node/test.mjs: -------------------------------------------------------------------------------- 1 | import { Herb } from "@herb-tools/node" 2 | 3 | await Herb.load() 4 | 5 | console.log(Herb) 6 | console.log(Herb.version) 7 | 8 | console.log(Herb.lex("hello world")) 9 | console.log(Herb.lexFile("./test.html.erb")) 10 | console.log(Herb.parse("hello world")) 11 | console.log(Herb.parseFile("./test.html.erb")) 12 | -------------------------------------------------------------------------------- /javascript/packages/node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "lib": ["ES2022"], 7 | "outDir": "./dist", 8 | "declaration": true, 9 | "declarationDir": "./dist/types", 10 | "esModuleInterop": true, 11 | "strict": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "resolveJsonModule": true, 15 | "erasableSyntaxOnly": true 16 | }, 17 | "include": ["src/**/*", "types/**/*"], 18 | "exclude": ["node_modules", "dist"] 19 | } 20 | -------------------------------------------------------------------------------- /lib/herb.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "herb/range" 4 | require_relative "herb/position" 5 | require_relative "herb/location" 6 | 7 | require_relative "herb/token" 8 | require_relative "herb/token_list" 9 | 10 | require_relative "herb/result" 11 | require_relative "herb/lex_result" 12 | require_relative "herb/parse_result" 13 | 14 | require_relative "herb/ast" 15 | require_relative "herb/ast/node" 16 | require_relative "herb/ast/nodes" 17 | 18 | require_relative "herb/errors" 19 | 20 | require_relative "herb/cli" 21 | require_relative "herb/project" 22 | 23 | require_relative "herb/version" 24 | 25 | begin 26 | require_relative "herb/#{RUBY_VERSION.split(".")[...2].join(".")}/herb" 27 | rescue LoadError 28 | require_relative "herb/herb" 29 | end 30 | 31 | module Herb 32 | end 33 | -------------------------------------------------------------------------------- /lib/herb/ast.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Herb 4 | module AST 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/herb/lex_result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Herb 4 | class LexResult < Result 5 | attr_reader :value 6 | 7 | def initialize(value, source, warnings, errors) 8 | @value = TokenList.new(value) 9 | super(source, warnings, errors) 10 | end 11 | 12 | def success? 13 | errors.empty? 14 | end 15 | 16 | def failed? 17 | errors.any? 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/herb/libherb.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "ffi" 4 | require "rbconfig" 5 | 6 | module Herb 7 | module LibHerb 8 | extend FFI::Library 9 | 10 | def self.library_extension 11 | RbConfig::CONFIG["DLEXT"] 12 | end 13 | 14 | def self.library_name 15 | "libherb.#{library_extension}" 16 | end 17 | 18 | def self.library_path 19 | File.expand_path("../../#{library_name}", __dir__) 20 | end 21 | 22 | ffi_lib(library_path) 23 | 24 | attach_function :herb_lex_to_buffer, [:pointer, :pointer], :void 25 | attach_function :herb_lex_json_to_buffer, [:pointer, :pointer], :void 26 | attach_function :herb_lex, [:pointer], :pointer 27 | attach_function :herb_parse, [:pointer], :pointer 28 | attach_function :herb_extract_ruby_to_buffer, [:pointer, :pointer], :void 29 | attach_function :herb_extract_html_to_buffer, [:pointer, :pointer], :void 30 | attach_function :herb_version, [], :pointer 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/herb/libherb/extract_result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "forwardable" 4 | 5 | module Herb 6 | class ExtractResult 7 | extend Forwardable 8 | 9 | def_delegators :@buffer, :read 10 | 11 | attr_accessor :buffer 12 | 13 | def initialize(pointer) 14 | @buffer = LibHerb::Buffer.new(pointer) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/herb/libherb/lex_result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "forwardable" 4 | 5 | module Herb 6 | class LexResult 7 | extend Forwardable 8 | 9 | def_delegators :@array, :items, :size, :capacity 10 | 11 | attr_accessor :array 12 | 13 | def initialize(pointer) 14 | @array = LibHerb::Array.new(pointer, LibHerb::Token) 15 | end 16 | 17 | def as_json 18 | JSON.parse(to_json) 19 | end 20 | 21 | def to_json(*_args) 22 | "[#{@array.items.map(&:to_json).join(", ")}]" 23 | end 24 | 25 | def inspect 26 | @array.items.map(&:inspect).join("\n") 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/herb/libherb/parse_result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "forwardable" 4 | 5 | module Herb 6 | class ParseResult 7 | extend Forwardable 8 | 9 | def_delegators :@root_node, :type, :child_count 10 | 11 | attr_accessor :root_node 12 | 13 | def initialize(pointer) 14 | @root_node = LibHerb::ASTNode.new(pointer) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/herb/location.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Herb 4 | class Location 5 | attr_reader :start, :end 6 | 7 | def initialize(start_position, end_position) 8 | @start = start_position 9 | @end = end_position 10 | end 11 | 12 | def self.from(start_line, start_column, end_line, end_column) 13 | new( 14 | Position.new(start_line, start_column), 15 | Position.new(end_line, end_column) 16 | ) 17 | end 18 | 19 | def self.[](...) 20 | from(...) 21 | end 22 | 23 | def to_hash 24 | { 25 | start: start, 26 | end: self.end, 27 | } 28 | end 29 | 30 | def to_json(*args) 31 | to_hash.to_json(*args) 32 | end 33 | 34 | def tree_inspect 35 | %((location: #{start.tree_inspect}-#{self.end.tree_inspect})) 36 | end 37 | 38 | def inspect 39 | %(#) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/herb/parse_result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "json" 4 | 5 | module Herb 6 | class ParseResult < Result 7 | attr_reader :value 8 | 9 | def initialize(value, source, warnings, errors) 10 | @value = value 11 | super(source, warnings, errors) 12 | end 13 | 14 | def failed? 15 | errors.any? || value.errors.any? # TODO: this should probably be recursive 16 | end 17 | 18 | def success? 19 | !failed? 20 | end 21 | 22 | def pretty_errors 23 | JSON.pretty_generate(errors + value.errors) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/herb/position.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Herb 4 | class Position 5 | attr_reader :line, :column 6 | 7 | def initialize(line, column) 8 | @line = line 9 | @column = column 10 | end 11 | 12 | def self.[](...) 13 | new(...) 14 | end 15 | 16 | def self.from(...) 17 | new(...) 18 | end 19 | 20 | def to_hash 21 | { line: line, column: column } 22 | end 23 | 24 | def to_json(*args) 25 | to_hash.to_json(*args) 26 | end 27 | 28 | def tree_inspect 29 | "(#{line}:#{column})" 30 | end 31 | 32 | def inspect 33 | %(#) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/herb/range.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Herb 4 | class Range 5 | attr_reader :from, :to 6 | 7 | def initialize(from, to) 8 | @from = from 9 | @to = to 10 | end 11 | 12 | def self.[](...) 13 | new(...) 14 | end 15 | 16 | def self.from(...) 17 | new(...) 18 | end 19 | 20 | def to_a 21 | [from, to] 22 | end 23 | 24 | def to_json(*args) 25 | to_a.to_json(*args) 26 | end 27 | 28 | def tree_inspect 29 | to_a.to_s 30 | end 31 | 32 | def inspect 33 | %(#) 34 | end 35 | 36 | def to_s 37 | inspect 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/herb/result.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Herb 4 | class Result 5 | attr_reader :source, :warnings, :errors 6 | 7 | def initialize(source, warnings, errors) 8 | @source = source 9 | @warnings = warnings 10 | @errors = errors 11 | end 12 | 13 | def success? 14 | false 15 | end 16 | 17 | def failed? 18 | true 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/herb/token.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Herb 4 | class Token 5 | attr_reader :value, :range, :location, :type 6 | 7 | def initialize(value, range, location, type) 8 | @value = value 9 | @range = range 10 | @location = location 11 | @type = type 12 | end 13 | 14 | def to_hash 15 | { 16 | value: value, 17 | range: range&.to_a, 18 | location: location&.to_hash, 19 | type: type, 20 | } 21 | end 22 | 23 | def to_json(*args) 24 | to_hash.to_json(*args) 25 | end 26 | 27 | def tree_inspect 28 | %("#{value.force_encoding("utf-8")}" #{location.tree_inspect}) 29 | end 30 | 31 | def value_inspect 32 | if type == "TOKEN_EOF" 33 | "".inspect 34 | else 35 | value.inspect 36 | end 37 | end 38 | 39 | def inspect 40 | %(#) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/herb/token_list.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "delegate" 4 | 5 | module Herb 6 | class TokenList < SimpleDelegator 7 | def inspect 8 | "#{itself.map(&:inspect).join("\n").force_encoding("utf-8")}\n" 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/herb/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Herb 4 | VERSION = "0.1.1" 5 | end 6 | -------------------------------------------------------------------------------- /playground/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@herb-tools/playground", 3 | "version": "0.0.0", 4 | "private": true, 5 | "homepage": "https://playground.herb-tools.dev", 6 | "bugs": "https://github.com/marcoroth/herb/issues", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/marcoroth/herb.git", 10 | "directory": "playground" 11 | }, 12 | "type": "module", 13 | "scripts": { 14 | "build": "vite build", 15 | "dev": "vite dev", 16 | "preview": " vite preview", 17 | "serve": "vite serve --host", 18 | "clean": "rimraf dist/" 19 | }, 20 | "dependencies": { 21 | "@alenaksu/json-viewer": "^2.0.1", 22 | "@herb-tools/browser": "^0.1.1", 23 | "@hotwired/stimulus": "^3.2.2", 24 | "dedent": "^1.6.0", 25 | "express": "^5.1.0", 26 | "lz-string": "^1.5.0", 27 | "prismjs": "^1.30.0", 28 | "vite": "^6.3.5" 29 | }, 30 | "devDependencies": { 31 | "@types/express": "^4.17.21", 32 | "@types/node": "^20.10.5", 33 | "monaco-editor": "^0.52.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /playground/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "$schema": "../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "playground", 5 | "projectType": "application", 6 | "targets": { 7 | "dev": { 8 | "executor": "nx:run-script", 9 | "options": { 10 | "script": "dev" 11 | } 12 | }, 13 | "build": { 14 | "executor": "nx:run-script", 15 | "options": { 16 | "script": "build" 17 | } 18 | }, 19 | "build:client": { 20 | "executor": "nx:run-script", 21 | "options": { 22 | "script": "build:client" 23 | } 24 | }, 25 | "preview": { 26 | "executor": "nx:run-script", 27 | "options": { 28 | "script": "preview" 29 | } 30 | }, 31 | "serve": { 32 | "executor": "nx:run-script", 33 | "options": { 34 | "script": "serve" 35 | } 36 | } 37 | }, 38 | "tags": [] 39 | } 40 | -------------------------------------------------------------------------------- /playground/src/controllers/iframe_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus" 2 | 3 | export default class extends Controller { 4 | static targets = ["hide"] 5 | 6 | connect() { 7 | if (window.frameElement) { 8 | this.hideTargets.forEach((item) => item.classList.add("hidden")) 9 | } else { 10 | this.hideTargets.forEach((item) => item.classList.remove("hidden")) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /playground/src/controllers/index.ts: -------------------------------------------------------------------------------- 1 | import { Application } from "@hotwired/stimulus" 2 | import PlaygroundController from "./playground_controller" 3 | import IFrameController from "./iframe_controller" 4 | 5 | const application = Application.start() 6 | 7 | application.register("playground", PlaygroundController) 8 | application.register("iframe", IFrameController) 9 | -------------------------------------------------------------------------------- /playground/src/entry-client.ts: -------------------------------------------------------------------------------- 1 | import "./controllers" 2 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | "moduleResolution": "Bundler", 9 | "allowImportingTsExtensions": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "strict": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noFallthroughCasesInSwitch": true 17 | }, 18 | "include": ["src"] 19 | } 20 | -------------------------------------------------------------------------------- /project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@herb-tools/source", 3 | "$schema": "node_modules/nx/schemas/project-schema.json", 4 | "targets": { 5 | "local-registry": { 6 | "executor": "@nx/js:verdaccio", 7 | "options": { 8 | "port": 4873, 9 | "config": ".verdaccio/config.yml", 10 | "storage": "tmp/local-registry/storage" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/include/analyze.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_ANALYZE_H 2 | #define HERB_ANALYZE_H 3 | 4 | #include "analyzed_ruby.h" 5 | #include "array.h" 6 | #include "ast_nodes.h" 7 | 8 | typedef struct ANALYZE_RUBY_CONTEXT_STRUCT { 9 | AST_DOCUMENT_NODE_T* document; 10 | AST_NODE_T* parent; 11 | array_T* ruby_context_stack; 12 | } analyze_ruby_context_T; 13 | 14 | typedef enum { 15 | CONTROL_TYPE_IF, 16 | CONTROL_TYPE_ELSIF, 17 | CONTROL_TYPE_ELSE, 18 | CONTROL_TYPE_END, 19 | CONTROL_TYPE_CASE, 20 | CONTROL_TYPE_CASE_MATCH, 21 | CONTROL_TYPE_WHEN, 22 | CONTROL_TYPE_IN, 23 | CONTROL_TYPE_BEGIN, 24 | CONTROL_TYPE_RESCUE, 25 | CONTROL_TYPE_ENSURE, 26 | CONTROL_TYPE_UNLESS, 27 | CONTROL_TYPE_WHILE, 28 | CONTROL_TYPE_UNTIL, 29 | CONTROL_TYPE_FOR, 30 | CONTROL_TYPE_BLOCK, 31 | CONTROL_TYPE_BLOCK_CLOSE, 32 | CONTROL_TYPE_YIELD, 33 | CONTROL_TYPE_UNKNOWN 34 | } control_type_t; 35 | 36 | void herb_analyze_parse_errors(AST_DOCUMENT_NODE_T* document, const char* source); 37 | void herb_analyze_parse_tree(AST_DOCUMENT_NODE_T* document, const char* source); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /src/include/analyzed_ruby.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_ANALYZED_RUBY_H 2 | #define HERB_ANALYZED_RUBY_H 3 | 4 | #include "array.h" 5 | 6 | #include 7 | 8 | typedef struct ANALYZED_RUBY_STRUCT { 9 | pm_parser_t parser; 10 | pm_node_t* root; 11 | bool valid; 12 | bool parsed; 13 | bool has_if_node; 14 | bool has_elsif_node; 15 | bool has_else_node; 16 | bool has_end; 17 | bool has_block_closing; 18 | bool has_block_node; 19 | bool has_case_node; 20 | bool has_case_match_node; 21 | bool has_when_node; 22 | bool has_in_node; 23 | bool has_for_node; 24 | bool has_while_node; 25 | bool has_until_node; 26 | bool has_begin_node; 27 | bool has_rescue_node; 28 | bool has_ensure_node; 29 | bool has_unless_node; 30 | bool has_yield_node; 31 | } analyzed_ruby_T; 32 | 33 | analyzed_ruby_T* init_analyzed_ruby(char* source); 34 | void free_analyzed_ruby(analyzed_ruby_T* analyzed); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/include/array.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_ARRAY_H 2 | #define HERB_ARRAY_H 3 | 4 | #include 5 | 6 | typedef struct ARRAY_STRUCT { 7 | void** items; 8 | size_t size; 9 | size_t capacity; 10 | } array_T; 11 | 12 | array_T* array_init(size_t capacity); 13 | 14 | void* array_get(const array_T* array, size_t index); 15 | void* array_first(array_T* array); 16 | void* array_last(array_T* array); 17 | 18 | void array_append(array_T* array, void* item); 19 | void array_set(const array_T* array, size_t index, void* item); 20 | void array_free(array_T** array); 21 | void array_remove(array_T* array, size_t index); 22 | 23 | size_t array_index_of(array_T* array, void* item); 24 | void array_remove_item(array_T* array, void* item); 25 | 26 | void array_push(array_T* array, void* item); 27 | void* array_pop(array_T* array); 28 | 29 | size_t array_capacity(const array_T* array); 30 | size_t array_size(const array_T* array); 31 | size_t array_sizeof(void); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/include/extract.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_EXTRACT_H 2 | #define HERB_EXTRACT_H 3 | 4 | #include "buffer.h" 5 | 6 | typedef enum { 7 | HERB_EXTRACT_LANGUAGE_RUBY, 8 | HERB_EXTRACT_LANGUAGE_HTML, 9 | } herb_extract_language_T; 10 | 11 | void herb_extract_ruby_to_buffer(const char* source, buffer_T* output); 12 | void herb_extract_html_to_buffer(const char* source, buffer_T* output); 13 | 14 | char* herb_extract_ruby_with_semicolons(const char* source); 15 | void herb_extract_ruby_to_buffer_with_semicolons(const char* source, buffer_T* output); 16 | 17 | char* herb_extract(const char* source, herb_extract_language_T language); 18 | char* herb_extract_from_file(const char* path, herb_extract_language_T language); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/include/herb.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_H 2 | #define HERB_H 3 | 4 | #include "array.h" 5 | #include "ast_node.h" 6 | #include "buffer.h" 7 | #include "extract.h" 8 | 9 | #include 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | void herb_lex_to_buffer(const char* source, buffer_T* output); 16 | void herb_lex_json_to_buffer(const char* source, buffer_T* output); 17 | 18 | array_T* herb_lex(const char* source); 19 | array_T* herb_lex_file(const char* path); 20 | 21 | AST_DOCUMENT_NODE_T* herb_parse(const char* source); 22 | 23 | const char* herb_version(void); 24 | const char* herb_prism_version(void); 25 | 26 | void herb_free_tokens(array_T** tokens); 27 | 28 | #ifdef __cplusplus 29 | } 30 | #endif 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/include/html_util.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_HTML_UTIL_H 2 | #define HERB_HTML_UTIL_H 3 | 4 | #include 5 | 6 | bool is_void_element(const char* tag_name); 7 | bool is_html4_void_element(const char* tag_name); 8 | 9 | char* html_opening_tag_string(const char* tag_name); 10 | char* html_closing_tag_string(const char* tag_name); 11 | char* html_self_closing_tag_string(const char* tag_name); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/include/io.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_IO_H 2 | #define HERB_IO_H 3 | 4 | #include 5 | #include 6 | 7 | char* herb_read_file(const char* filename); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/include/json.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_JSON_H 2 | #define HERB_JSON_H 3 | 4 | #include "buffer.h" 5 | 6 | void json_start_root_object(buffer_T* json); 7 | void json_start_root_array(buffer_T* json); 8 | 9 | void json_escape_string(buffer_T* json, const char* string); 10 | 11 | void json_add_string(buffer_T* json, const char* key, const char* value); 12 | void json_add_int(buffer_T* json, const char* key, int value); 13 | void json_add_size_t(buffer_T* json, const char* key, size_t value); 14 | void json_add_double(buffer_T* json, const char* key, double value); 15 | void json_add_bool(buffer_T* json, const char* key, int value); 16 | 17 | void json_add_raw_string(buffer_T* json, const char* string); 18 | 19 | void json_start_object(buffer_T* json, const char* key); 20 | void json_end_object(buffer_T* json); 21 | 22 | void json_start_array(buffer_T* json, const char* key); 23 | void json_end_array(buffer_T* json); 24 | 25 | void json_double_to_string(double value, char* buffer); 26 | void json_int_to_string(int value, char* buffer); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/include/lexer.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_LEXER_H 2 | #define HERB_LEXER_H 3 | 4 | #include "lexer_struct.h" 5 | #include "token_struct.h" 6 | 7 | lexer_T* lexer_init(const char* source); 8 | token_T* lexer_next_token(lexer_T* lexer); 9 | token_T* lexer_error(lexer_T* lexer, const char* message); 10 | 11 | void lexer_free(lexer_T* lexer); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/include/lexer_peek_helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_LEXER_PEEK_H 2 | #define HERB_LEXER_PEEK_H 3 | 4 | #include "lexer_struct.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | char lexer_peek(const lexer_T* lexer, int offset); 11 | bool lexer_peek_for_doctype(const lexer_T* lexer, int offset); 12 | 13 | bool lexer_peek_for_html_comment_start(const lexer_T* lexer, int offset); 14 | bool lexer_peek_for_html_comment_end(const lexer_T* lexer, int offset); 15 | 16 | bool lexer_peek_erb_close_tag(const lexer_T* lexer, int offset); 17 | bool lexer_peek_erb_dash_close_tag(const lexer_T* lexer, int offset); 18 | bool lexer_peek_erb_percent_close_tag(const lexer_T* lexer, int offset); 19 | bool lexer_peek_erb_end(const lexer_T* lexer, int offset); 20 | 21 | char lexer_backtrack(const lexer_T* lexer, int offset); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/include/lexer_struct.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_LEXER_STRUCT_H 2 | #define HERB_LEXER_STRUCT_H 3 | 4 | #include 5 | #include 6 | 7 | typedef enum { 8 | STATE_DATA, 9 | STATE_ERB_CONTENT, 10 | STATE_ERB_CLOSE, 11 | } lexer_state_T; 12 | 13 | typedef struct LEXER_STRUCT { 14 | const char* source; 15 | size_t source_length; 16 | 17 | size_t current_line; 18 | size_t current_column; 19 | size_t current_position; 20 | 21 | size_t previous_line; 22 | size_t previous_column; 23 | size_t previous_position; 24 | 25 | char current_character; 26 | lexer_state_T state; 27 | size_t stall_counter; 28 | size_t last_position; 29 | bool stalled; 30 | } lexer_T; 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/include/location.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_LOCATION_H 2 | #define HERB_LOCATION_H 3 | 4 | #include 5 | 6 | #include "position.h" 7 | 8 | typedef struct LOCATION_STRUCT { 9 | position_T* start; 10 | position_T* end; 11 | } location_T; 12 | 13 | location_T* location_init(position_T* start, position_T* end); 14 | location_T* location_from(size_t start_line, size_t start_column, size_t end_line, size_t end_column); 15 | 16 | position_T* location_start(location_T* location); 17 | position_T* location_end_(location_T* location); 18 | 19 | size_t location_sizeof(void); 20 | 21 | location_T* location_copy(location_T* location); 22 | 23 | void location_free(location_T* location); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/include/macros.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_MACROS_H 2 | #define HERB_MACROS_H 3 | 4 | #define MAX(a, b) (a) > (b) ? (a) : (b) 5 | 6 | #define MIN(a, b) (a) < (b) ? (a) : (b) 7 | 8 | #define unlikely(x) __builtin_expect(!!(x), 0) 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/include/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_MEMORY_H 2 | #define HERB_MEMORY_H 3 | 4 | #include 5 | 6 | void* safe_malloc(size_t size); 7 | void* safe_realloc(void* pointer, size_t new_size); 8 | 9 | void* nullable_safe_malloc(size_t size); 10 | void* nullable_safe_realloc(void* pointer, size_t new_size); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/include/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_PARSER_H 2 | #define HERB_PARSER_H 3 | 4 | #include "array.h" 5 | #include "ast_node.h" 6 | #include "lexer.h" 7 | 8 | typedef struct PARSER_STRUCT { 9 | lexer_T* lexer; 10 | token_T* current_token; 11 | array_T* open_tags_stack; 12 | } parser_T; 13 | 14 | parser_T* parser_init(lexer_T* lexer); 15 | 16 | AST_DOCUMENT_NODE_T* parser_parse(parser_T* parser); 17 | 18 | size_t parser_sizeof(void); 19 | 20 | void parser_free(parser_T* parser); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/include/position.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_POSITION_H 2 | #define HERB_POSITION_H 3 | 4 | #include 5 | 6 | typedef struct POSITION_STRUCT { 7 | size_t line; 8 | size_t column; 9 | } position_T; 10 | 11 | position_T* position_init(size_t line, size_t column); 12 | 13 | size_t position_line(const position_T* position); 14 | size_t position_column(const position_T* position); 15 | 16 | size_t position_sizeof(void); 17 | 18 | position_T* position_copy(position_T* position); 19 | 20 | void position_free(position_T* position); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/include/prism_helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_PRISM_HELPERS_H 2 | #define HERB_PRISM_HELPERS_H 3 | 4 | #include "ast_nodes.h" 5 | #include "errors.h" 6 | #include "position.h" 7 | 8 | #include 9 | 10 | const char* pm_error_level_to_string(pm_error_level_t level); 11 | 12 | RUBY_PARSE_ERROR_T* ruby_parse_error_from_prism_error( 13 | const pm_diagnostic_t* error, const AST_NODE_T* node, const char* source, pm_parser_t* parser 14 | ); 15 | 16 | position_T* position_from_source_with_offset(const char* source, size_t offset); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/include/range.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_RANGE_H 2 | #define HERB_RANGE_H 3 | 4 | #include 5 | 6 | typedef struct RANGE_STRUCT { 7 | size_t from; 8 | size_t to; 9 | } range_T; 10 | 11 | range_T* range_init(size_t from, size_t to); 12 | 13 | size_t range_from(const range_T* range); 14 | size_t range_to(const range_T* range); 15 | size_t range_length(range_T* range); 16 | 17 | range_T* range_copy(range_T* range); 18 | 19 | size_t range_sizeof(void); 20 | 21 | void range_free(range_T* range); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/include/ruby_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_RUBY_PARSER_H 2 | #define HERB_RUBY_PARSER_H 3 | 4 | void herb_parse_ruby_to_stdout(char* source); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /src/include/token.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_TOKEN_H 2 | #define HERB_TOKEN_H 3 | 4 | #include "lexer_struct.h" 5 | #include "position.h" 6 | #include "token_struct.h" 7 | 8 | token_T* token_init(const char* value, token_type_T type, lexer_T* lexer); 9 | char* token_to_string(const token_T* token); 10 | char* token_to_json(const token_T* token); 11 | const char* token_type_to_string(token_type_T type); 12 | 13 | char* token_value(const token_T* token); 14 | int token_type(const token_T* token); 15 | 16 | position_T* token_start_position(token_T* token); 17 | position_T* token_end_position(token_T* token); 18 | 19 | size_t token_sizeof(void); 20 | 21 | token_T* token_copy(token_T* token); 22 | 23 | void token_free(token_T* token); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/include/token_matchers.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_TOKEN_MATCHERS_H 2 | #define HERB_TOKEN_MATCHERS_H 3 | 4 | #include "parser.h" 5 | #include "token.h" 6 | 7 | #include 8 | #include 9 | 10 | // This "TOKEN" is used to terminate the va_list arguments in the token_matches_any function 11 | #define TOKEN_SENTINEL 99999999 12 | 13 | bool token_is(parser_T* parser, token_type_T expected_type); 14 | bool token_is_not(parser_T* parser, token_type_T type); 15 | 16 | bool token_matches_any(token_type_T current_token, token_type_T first_token, ...); 17 | 18 | #define token_is_any_of(parser, ...) (token_matches_any((parser)->current_token->type, __VA_ARGS__, TOKEN_SENTINEL)) 19 | #define token_is_none_of(parser, ...) (!token_matches_any((parser)->current_token->type, __VA_ARGS__, TOKEN_SENTINEL)) 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/include/util.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_UTIL_H 2 | #define HERB_UTIL_H 3 | 4 | #include 5 | #include 6 | 7 | int is_whitespace(int character); 8 | int is_newline(int character); 9 | 10 | int count_in_string(const char* string, char character); 11 | int count_newlines(const char* string); 12 | 13 | char* replace_char(char* string, char find, char replace); 14 | char* escape_newlines(const char* input); 15 | char* quoted_string(const char* input); 16 | char* wrap_string(const char* input, char character); 17 | 18 | bool string_blank(const char* input); 19 | bool string_present(const char* input); 20 | 21 | char* herb_strdup(const char* s); 22 | 23 | char* size_t_to_string(size_t value); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/include/version.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_VERSION_H 2 | #define HERB_VERSION_H 3 | 4 | #define HERB_VERSION "0.1.1" 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /src/include/visitor.h: -------------------------------------------------------------------------------- 1 | #ifndef HERB_VISITOR_H 2 | #define HERB_VISITOR_H 3 | 4 | #include "array.h" 5 | #include "ast_node.h" 6 | #include "ast_nodes.h" 7 | 8 | void herb_visit_node(const AST_NODE_T* node, bool (*visitor)(const AST_NODE_T*, void*), void* data); 9 | void herb_visit_child_nodes(const AST_NODE_T* node, bool (*visitor)(const AST_NODE_T* node, void* data), void* data); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/io.c: -------------------------------------------------------------------------------- 1 | #include "include/io.h" 2 | #include "include/buffer.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define FILE_READ_CHUNK 4096 9 | 10 | char* herb_read_file(const char* filename) { 11 | FILE* fp = fopen(filename, "rb"); 12 | 13 | if (fp == NULL) { 14 | fprintf(stderr, "Could not read file '%s'\n", filename); 15 | exit(1); 16 | } 17 | 18 | buffer_T buffer = buffer_new(); 19 | 20 | char chunk[FILE_READ_CHUNK]; 21 | size_t bytes_read; 22 | 23 | while ((bytes_read = fread(chunk, 1, FILE_READ_CHUNK, fp)) > 0) { 24 | buffer_append_with_length(&buffer, chunk, bytes_read); 25 | } 26 | 27 | fclose(fp); 28 | 29 | return buffer_value(&buffer); 30 | } 31 | -------------------------------------------------------------------------------- /src/position.c: -------------------------------------------------------------------------------- 1 | #include "include/position.h" 2 | #include "include/memory.h" 3 | 4 | size_t position_sizeof(void) { 5 | return sizeof(position_T); 6 | } 7 | 8 | position_T* position_init(const size_t line, const size_t column) { 9 | position_T* position = safe_malloc(position_sizeof()); 10 | 11 | position->line = line; 12 | position->column = column; 13 | 14 | return position; 15 | } 16 | 17 | size_t position_line(const position_T* position) { 18 | return position->line; 19 | } 20 | 21 | size_t position_column(const position_T* position) { 22 | return position->column; 23 | } 24 | 25 | position_T* position_copy(position_T* position) { 26 | if (position == NULL) { return NULL; } 27 | 28 | return position_init(position_line(position), position_column(position)); 29 | } 30 | 31 | void position_free(position_T* position) { 32 | free(position); 33 | } 34 | -------------------------------------------------------------------------------- /src/range.c: -------------------------------------------------------------------------------- 1 | #include "include/range.h" 2 | 3 | size_t range_sizeof(void) { 4 | return sizeof(range_T); 5 | } 6 | 7 | range_T* range_init(const size_t from, const size_t to) { 8 | range_T* range = calloc(1, range_sizeof()); 9 | 10 | range->from = from; 11 | range->to = to; 12 | 13 | return range; 14 | } 15 | 16 | size_t range_from(const range_T* range) { 17 | return range->from; 18 | } 19 | 20 | size_t range_to(const range_T* range) { 21 | return range->to; 22 | } 23 | 24 | size_t range_length(range_T* range) { 25 | return range_to(range) - range_from(range); 26 | } 27 | 28 | range_T* range_copy(range_T* range) { 29 | if (!range) { return NULL; } 30 | 31 | return range_init(range_from(range), range_to(range)); 32 | } 33 | 34 | void range_free(range_T* range) { 35 | if (range == NULL) { return; } 36 | 37 | free(range); 38 | } 39 | -------------------------------------------------------------------------------- /src/token_matchers.c: -------------------------------------------------------------------------------- 1 | #include "include/token_matchers.h" 2 | #include "include/parser.h" 3 | #include "include/token.h" 4 | 5 | #include 6 | #include 7 | 8 | bool token_is(parser_T* parser, token_type_T expected_type) { 9 | return parser->current_token->type == expected_type; 10 | } 11 | 12 | bool token_is_not(parser_T* parser, token_type_T type) { 13 | return parser->current_token->type != type; 14 | } 15 | 16 | bool token_matches_any(token_type_T current_token, token_type_T first_token, ...) { 17 | if (current_token == first_token) { return true; } 18 | 19 | va_list tokens; 20 | va_start(tokens, first_token); 21 | token_type_T token; 22 | 23 | while ((token = va_arg(tokens, token_type_T)) != TOKEN_SENTINEL) { 24 | if (current_token == token) { 25 | va_end(tokens); 26 | return true; 27 | } 28 | } 29 | 30 | va_end(tokens); 31 | return false; 32 | } 33 | -------------------------------------------------------------------------------- /templates/ext/herb/error_helpers.h.erb: -------------------------------------------------------------------------------- 1 | #ifndef HERB_EXTENSION_ERROR_HELPERS_H 2 | #define HERB_EXTENSION_ERROR_HELPERS_H 3 | 4 | #include "../../src/include/errors.h" 5 | #include "../../src/include/herb.h" 6 | 7 | #include 8 | 9 | VALUE rb_error_from_c_struct(ERROR_T* error); 10 | VALUE rb_errors_array_from_c_array(array_T* array); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /templates/ext/herb/nodes.h.erb: -------------------------------------------------------------------------------- 1 | #ifndef HERB_EXTENSION_NODES_H 2 | #define HERB_EXTENSION_NODES_H 3 | 4 | #include "../../src/include/herb.h" 5 | #include 6 | 7 | VALUE rb_node_from_c_struct(AST_NODE_T* node); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /templates/javascript/packages/node/extension/error_helpers.h.erb: -------------------------------------------------------------------------------- 1 | #ifndef HERB_EXTENSION_ERRORS_H 2 | #define HERB_EXTENSION_ERRORS_H 3 | 4 | #include 5 | 6 | extern "C" { 7 | #include "../extension/libherb/include/herb.h" 8 | } 9 | 10 | napi_value ErrorFromCStruct(napi_env env, ERROR_T* error); 11 | napi_value ErrorsArrayFromCArray(napi_env env, array_T* array); 12 | 13 | <%- errors.each do |error| -%> 14 | napi_value <%= error.name %>FromCStruct(napi_env env, <%= error.struct_type %>* <%= error.human %>); 15 | <%- end -%> 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /templates/javascript/packages/node/extension/nodes.h.erb: -------------------------------------------------------------------------------- 1 | #ifndef HERB_EXTENSION_NODES_H 2 | #define HERB_EXTENSION_NODES_H 3 | 4 | #include 5 | 6 | extern "C" { 7 | #include "../extension/libherb/include/herb.h" 8 | } 9 | 10 | napi_value NodeFromCStruct(napi_env env, AST_NODE_T* node); 11 | napi_value NodesArrayFromCArray(napi_env env, array_T* array); 12 | 13 | <%- nodes.each do |node| -%> 14 | napi_value <%= node.human %>NodeFromCStruct(napi_env env, <%= node.struct_type %>* <%= node.human %>); 15 | <%- end -%> 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /templates/src/include/ast_pretty_print.h.erb: -------------------------------------------------------------------------------- 1 | #ifndef HERB_AST_PRETTY_PRINT_H 2 | #define HERB_AST_PRETTY_PRINT_H 3 | 4 | #include "ast_nodes.h" 5 | #include "buffer.h" 6 | 7 | void ast_pretty_print_node( 8 | AST_NODE_T* node, 9 | size_t indent, 10 | size_t relative_indent, 11 | buffer_T* buffer 12 | ); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /templates/wasm/error_helpers.h.erb: -------------------------------------------------------------------------------- 1 | #ifndef HERB_EXTENSION_ERRORS_H 2 | #define HERB_EXTENSION_ERRORS_H 3 | 4 | extern "C" { 5 | #include "../src/include/herb.h" 6 | } 7 | 8 | emscripten::val ErrorFromCStruct(ERROR_T* error); 9 | emscripten::val ErrorsArrayFromCArray(array_T* array); 10 | 11 | <%- errors.each do |error| -%> 12 | emscripten::val <%= error.name %>FromCStruct(<%= error.struct_type %>* <%= error.human %>); 13 | <%- end -%> 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /templates/wasm/nodes.h.erb: -------------------------------------------------------------------------------- 1 | #ifndef HERB_EXTENSION_NODES_H 2 | #define HERB_EXTENSION_NODES_H 3 | 4 | extern "C" { 5 | #include "../src/include/herb.h" 6 | } 7 | 8 | emscripten::val NodeFromCStruct(AST_NODE_T* node); 9 | emscripten::val NodesArrayFromCArray(array_T* array); 10 | 11 | <%- nodes.each do |node| -%> 12 | emscripten::val <%= node.human %>NodeFromCStruct(<%= node.struct_type %>* <%= node.human %>); 13 | <%- end -%> 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /test/analyze/action_view/form_helper/text_field_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../../../test_helper" 4 | 5 | module Analyze::ActionView::FormHelper 6 | class TextFieldTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | before do 10 | skip 11 | end 12 | 13 | test "form_with with form block argument" do 14 | assert_parsed_snapshot(<<~HTML) 15 | <%= form_with url: posts_path do |form| %> 16 | <%= form.text_field :title %> 17 | <% end %> 18 | HTML 19 | end 20 | 21 | test "form_with with f block argument" do 22 | assert_parsed_snapshot(<<~HTML) 23 | <%= form_with url: posts_path do |f| %> 24 | <%= f.text_field :title %> 25 | <% end %> 26 | HTML 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/analyze/action_view/url_helper/link_to_if_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../../../test_helper" 4 | 5 | module Analyze::ActionView::UrlHelper 6 | class LinkToIfTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | before do 10 | skip 11 | end 12 | 13 | test "link_to_if" do 14 | assert_parsed_snapshot(<<~HTML) 15 | <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %> 16 | HTML 17 | end 18 | 19 | test "link_to_if with block" do 20 | assert_parsed_snapshot(<<~HTML) 21 | <%= 22 | link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) do 23 | link_to(@current_user.login, { controller: "accounts", action: "show", id: @current_user }) 24 | end 25 | %> 26 | HTML 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/analyze/action_view/url_helper/link_to_unless_current_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../../../test_helper" 4 | 5 | module Analyze::ActionView::UrlHelper 6 | class LinkToUnlessCurrentTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | before do 10 | skip 11 | end 12 | 13 | test "link_to_unless_current" do 14 | assert_parsed_snapshot(<<~HTML) 15 | <%= link_to_unless_current("Home", { action: "index" }) %> 16 | HTML 17 | end 18 | 19 | test "link_to_unless_current with block" do 20 | assert_parsed_snapshot(<<~HTML) 21 | <%= 22 | link_to_unless_current("Comment", { controller: "comments", action: "new" }) do 23 | link_to("Go back", { controller: "posts", action: "index" }) 24 | end 25 | %> 26 | HTML 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/analyze/action_view/url_helper/link_to_unless_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../../../test_helper" 4 | 5 | module Analyze::ActionView::UrlHelper 6 | class LinkToUnlessTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | before do 10 | skip 11 | end 12 | 13 | test "link_to_if" do 14 | assert_parsed_snapshot(<<~HTML) 15 | <%= link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) %> 16 | HTML 17 | end 18 | 19 | test "link_to_if with block" do 20 | assert_parsed_snapshot(<<~HTML) 21 | <%= 22 | link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) do |name| 23 | link_to(name, { controller: "accounts", action: "signup" }) 24 | end 25 | %> 26 | HTML 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/analyze/action_view/url_helper/mail_to_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../../../test_helper" 4 | 5 | module Analyze::ActionView::UrlHelper 6 | class MailToTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | before do 10 | skip 11 | end 12 | 13 | test "mail_to" do 14 | assert_parsed_snapshot(<<~HTML) 15 | <%= mail_to "me@domain.com" %> 16 | HTML 17 | end 18 | 19 | test "mail_to with content" do 20 | assert_parsed_snapshot(<<~HTML) 21 | <%= mail_to "me@domain.com", "My email" %> 22 | HTML 23 | end 24 | 25 | test "mail_to with cc and subject" do 26 | assert_parsed_snapshot(<<~HTML) 27 | <%= mail_to "me@domain.com", cc: "ccaddress@domain.com", subject: "This is an example email" %> 28 | HTML 29 | end 30 | 31 | test "mail_to with block" do 32 | assert_parsed_snapshot(<<~HTML) 33 | <%= mail_to "me@domain.com" do %> 34 | Email me: me@domain.com 35 | <% end %> 36 | HTML 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/analyze/action_view/url_helper/phone_to_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../../../test_helper" 4 | 5 | module Analyze::ActionView::UrlHelper 6 | class PhoneToTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | before do 10 | skip 11 | end 12 | 13 | test "phone_to" do 14 | assert_parsed_snapshot(<<~HTML) 15 | <%= phone_to "1234567890" %> 16 | HTML 17 | end 18 | 19 | test "phone_to with content" do 20 | assert_parsed_snapshot(<<~HTML) 21 | <%= phone_to "1234567890", "Phone me" %> 22 | HTML 23 | end 24 | 25 | test "phone_to with country_code" do 26 | assert_parsed_snapshot(<<~HTML) 27 | <%= phone_to "1234567890", country_code: "01" %> 28 | HTML 29 | end 30 | 31 | test "phone_to with block" do 32 | assert_parsed_snapshot(<<~HTML) 33 | <%= phone_to "1234567890" do %> 34 | Phone me: 35 | <% end %> 36 | HTML 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/analyze/for_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | module Analyze 6 | class ForTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | test "for loop" do 10 | assert_parsed_snapshot(<<~HTML) 11 | <% for i in 1..5 %> 12 | <%= i %> 13 | <% end %> 14 | HTML 15 | end 16 | 17 | test "for loop with children" do 18 | assert_parsed_snapshot(<<~HTML) 19 | <% for fruit in ["apple", "banana", "orange"] %> 20 | <%= fruit %> 21 | <% end %> 22 | HTML 23 | end 24 | 25 | test "for loop wrapped in element" do 26 | assert_parsed_snapshot(<<~HTML) 27 |

28 | <% for i in 1..5 %> 29 | <%= i %> 30 | <% end %> 31 |

32 | HTML 33 | end 34 | 35 | test "nested for loops" do 36 | assert_parsed_snapshot(<<~HTML) 37 | <% for i in 1..5 %> 38 | <% for j in 1..5 %> 39 | <%= i %>, <%= j %> 40 | <% end %> 41 | <% end %> 42 | HTML 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/analyze/yield_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | module Analyze 6 | class YieldTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | test "yield" do 10 | assert_parsed_snapshot(<<~HTML) 11 | <%= yield %> 12 | HTML 13 | end 14 | 15 | test "yield with symbol" do 16 | assert_parsed_snapshot(<<~HTML) 17 | <%= yield :symbol %> 18 | HTML 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/c/include/test.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_H 2 | #define TEST_H 3 | 4 | #include 5 | 6 | #define TEST(name) START_TEST(name) { 7 | #define END } END_TEST 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /test/c/test_herb.c: -------------------------------------------------------------------------------- 1 | #include "include/test.h" 2 | #include "../../src/include/herb.h" 3 | 4 | TEST(test_herb_version) 5 | ck_assert_str_eq(herb_version(), "0.1.1"); 6 | END 7 | 8 | TCase *herb_tests(void) { 9 | TCase *herb = tcase_create("Herb"); 10 | 11 | tcase_add_test(herb, test_herb_version); 12 | 13 | return herb; 14 | } 15 | -------------------------------------------------------------------------------- /test/c/test_io.c: -------------------------------------------------------------------------------- 1 | #include "include/test.h" 2 | #include "../../src/include/io.h" 3 | 4 | // Create a temporary file for testing 5 | void create_test_file(const char* filename, const char* content) { 6 | FILE* fp = fopen(filename, "w"); 7 | 8 | ck_assert_ptr_nonnull(fp); // Ensure file opened successfully 9 | 10 | fputs(content, fp); 11 | fclose(fp); 12 | } 13 | 14 | // Test reading from a file 15 | TEST(test_herb_read_file) 16 | const char* filename = "test_herb_read_file.txt"; 17 | const char* file_content = "Hello, World!\nThis is a test file.\n"; 18 | 19 | create_test_file(filename, file_content); 20 | 21 | char* result = herb_read_file(filename); 22 | 23 | ck_assert_ptr_nonnull(result); 24 | ck_assert_str_eq(result, file_content); 25 | 26 | free(result); 27 | remove(filename); 28 | END 29 | 30 | TCase* io_tests(void) { 31 | TCase* io = tcase_create("IO"); 32 | 33 | tcase_add_test(io, test_herb_read_file); 34 | 35 | return io; 36 | } 37 | -------------------------------------------------------------------------------- /test/c/test_util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "include/test.h" 3 | #include "../../src/include/herb.h" 4 | #include "../../src/include/util.h" 5 | 6 | TEST(util_count_newlines) 7 | ck_assert_int_eq(count_newlines("\n"), 1); 8 | ck_assert_int_eq(count_newlines("\n\n"), 2); 9 | 10 | ck_assert_int_eq(count_newlines("\r"), 1); 11 | ck_assert_int_eq(count_newlines("\r\r"), 2); 12 | 13 | ck_assert_int_eq(count_newlines("\r\n"), 1); 14 | ck_assert_int_eq(count_newlines("\r\n\r\n"), 2); 15 | END 16 | 17 | TEST(util_is_newline) 18 | ck_assert_int_eq(is_newline('\n'), 1); 19 | ck_assert_int_eq(is_newline('\r'), 1); 20 | 21 | ck_assert_int_eq(is_newline(' '), 0); 22 | ck_assert_int_eq(is_newline('a'), 0); 23 | END 24 | 25 | TCase *util_tests(void) { 26 | TCase *util = tcase_create("Util"); 27 | 28 | tcase_add_test(util, util_count_newlines); 29 | tcase_add_test(util, util_is_newline); 30 | 31 | return util; 32 | } 33 | -------------------------------------------------------------------------------- /test/fork_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "timeout" 4 | 5 | class Minitest::Spec 6 | TIMEOUT_THRESHOLD = ENV["UPDATE_SNAPSHOTS"].nil? ? 0.1 : 5 # seconds 7 | 8 | puts "Using fork_helper with timeout: #{TIMEOUT_THRESHOLD} seconds" 9 | 10 | def run 11 | result = Tempfile.new 12 | 13 | pid = fork do 14 | data = Marshal.dump(super) 15 | 16 | result.write(data) 17 | result.rewind 18 | 19 | exit! 20 | end 21 | 22 | begin 23 | Timeout.timeout(TIMEOUT_THRESHOLD) do 24 | Process.wait(pid) # Wait for the test to finish 25 | end 26 | 27 | Marshal.load(result.read) # Retrieve test result 28 | rescue Timeout::Error, Timeout::ExitException 29 | Process.kill("TERM", pid) # Gracefully terminate 30 | 31 | sleep 1 # Give it time to exit 32 | 33 | begin 34 | Process.kill("KILL", pid) 35 | rescue StandardError 36 | nil 37 | end 38 | 39 | self.fail "Test '#{name}' exceeded timeout of #{TIMEOUT_THRESHOLD} seconds" 40 | ensure 41 | result.unlink 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/lexer/boolean_attributes_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | module Lexer 6 | class BooleanAttributesTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | test "boolean attribute" do 10 | assert_lexed_snapshot("") 11 | end 12 | 13 | test "boolean attribute without whitespace and with self-closing tag" do 14 | assert_lexed_snapshot("") 15 | end 16 | 17 | test "boolean attribute without whitespace and without self-closing tag" do 18 | assert_lexed_snapshot("") 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/lexer/comments_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | module Lexer 6 | class CommentsTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | test "HTML comment with padding whitespace" do 10 | assert_lexed_snapshot(%()) 11 | end 12 | 13 | test "HTML comment with no whitespace" do 14 | assert_lexed_snapshot(%()) 15 | end 16 | 17 | test "HTML comment followed by html tag" do 18 | assert_lexed_snapshot(%(

Hello

)) 19 | end 20 | 21 | test "HTML comment followed by html tag with nested comment" do 22 | assert_lexed_snapshot(%( 23 | 24 |

25 | )) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/lexer/doctype_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | module Lexer 6 | class DoctypeTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | test "doctype" do 10 | assert_lexed_snapshot("") 11 | end 12 | 13 | test "doctype with space" do 14 | assert_lexed_snapshot("") 15 | end 16 | 17 | test "doctype with html" do 18 | assert_lexed_snapshot("") 19 | end 20 | 21 | test "html4 doctype" do 22 | assert_lexed_snapshot(%()) 23 | end 24 | 25 | test "doctype case insensitivity" do 26 | doctypes = ["", "", ""] 27 | doctypes.each do |doctype| 28 | assert_lexed_snapshot(doctype) 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/lexer/html_entities_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | module Lexer 6 | class HTMLEntitiesTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | test "<" do 10 | assert_lexed_snapshot("<") 11 | end 12 | 13 | test ">" do 14 | assert_lexed_snapshot(">") 15 | end 16 | 17 | test " " do 18 | assert_lexed_snapshot(" ") 19 | end 20 | 21 | test """ do 22 | assert_lexed_snapshot(""") 23 | end 24 | 25 | test "'" do 26 | assert_lexed_snapshot("'") 27 | end 28 | 29 | test "ampersand" do 30 | assert_lexed_snapshot("&") 31 | end 32 | 33 | test "literal ampersand" do 34 | assert_lexed_snapshot("&") 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/parser/boolean_attributes_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | module Parser 6 | class BooleanAttributesTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | test "boolean attribute" do 10 | assert_parsed_snapshot(%()) 11 | end 12 | 13 | test "boolean attribute without whitespace" do 14 | assert_parsed_snapshot(%()) 15 | end 16 | 17 | test "boolean attribute without whitespace and without self-closing tag" do 18 | assert_parsed_snapshot(%()) 19 | end 20 | 21 | test "boolean attribute followed by regular attribute" do 22 | assert_parsed_snapshot(%()) 23 | end 24 | 25 | test "boolean attribute after regular attribute" do 26 | assert_parsed_snapshot(%()) 27 | end 28 | 29 | test "boolean attribute surrounded by regular attributes" do 30 | assert_parsed_snapshot(%()) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/parser/comments_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | module Parser 6 | class CommentsTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | test "HTML comment with padding whitespace" do 10 | assert_parsed_snapshot(%()) 11 | end 12 | 13 | test "HTML comment with no whitespace" do 14 | assert_parsed_snapshot(%()) 15 | end 16 | 17 | test "HTML comment followed by html tag" do 18 | assert_parsed_snapshot(%(

Hello

)) 19 | end 20 | 21 | test "HTML comment followed by html tag with nested comment" do 22 | assert_parsed_snapshot(%( 23 | 24 |

25 | )) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/parser/doctype_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | module Parser 6 | class DoctypeTest < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | test "doctype" do 10 | assert_parsed_snapshot("") 11 | end 12 | 13 | test "doctype with space" do 14 | assert_parsed_snapshot("") 15 | end 16 | 17 | test "doctype with html" do 18 | assert_parsed_snapshot("") 19 | end 20 | 21 | test "two doctypes" do 22 | assert_parsed_snapshot("") 23 | end 24 | 25 | test "html4 doctype" do 26 | assert_parsed_snapshot(%()) 27 | end 28 | 29 | test "doctype case insensitivity" do 30 | doctypes = ["", "", "", "", ""] 31 | 32 | doctypes.each do |doctype| 33 | assert_parsed_snapshot(doctype) 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/parser/utf8_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | module Parser 6 | class UTF8Test < Minitest::Spec 7 | include SnapshotUtils 8 | 9 | test "opening guillemet" do 10 | assert_parsed_snapshot(<<~ERB) 11 | <%= link_to '«', url %> 12 | ERB 13 | end 14 | 15 | test "closing guillemet" do 16 | assert_parsed_snapshot(<<~ERB) 17 | <%= link_to '»', url %> 18 | ERB 19 | end 20 | 21 | test "single opening guillemet" do 22 | assert_parsed_snapshot(<<~ERB) 23 | <%= link_to '‹', url %> 24 | ERB 25 | end 26 | 27 | test "single closing guillemet" do 28 | assert_parsed_snapshot(<<~ERB) 29 | <%= link_to '›', url %> 30 | ERB 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/snapshots/analyze/begin_test/test_0001_single-line_begin_8c71cb17a972281fb729047b56de5718.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (2 items) 3 | ├── @ ERBContentNode (location: (1:0)-(1:17)) 4 | │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) 5 | │ ├── content: " begin; end; " (location: (1:2)-(1:15)) 6 | │ ├── tag_closing: "%>" (location: (1:15)-(1:17)) 7 | │ ├── parsed: true 8 | │ └── valid: true 9 | │ 10 | └── @ HTMLTextNode (location: (1:17)-(2:0)) 11 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/analyze/begin_test/test_0002_begin_statement_310fa78692cbba763c6e961782acf077.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(4:0)) 2 | └── children: (2 items) 3 | ├── @ ERBBeginNode (location: (1:0)-(3:9)) 4 | │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) 5 | │ ├── content: " begin " (location: (1:2)-(1:9)) 6 | │ ├── tag_closing: "%>" (location: (1:9)-(1:11)) 7 | │ ├── statements: (1 item) 8 | │ │ └── @ HTMLTextNode (location: (1:11)-(3:0)) 9 | │ │ └── content: "\n begin\n" 10 | │ │ 11 | │ ├── rescue_clause: ∅ 12 | │ ├── else_clause: ∅ 13 | │ ├── ensure_clause: ∅ 14 | │ └── end_node: 15 | │ └── @ ERBEndNode (location: (3:0)-(3:9)) 16 | │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) 17 | │ ├── content: " end " (location: (3:2)-(3:7)) 18 | │ └── tag_closing: "%>" (location: (3:7)-(3:9)) 19 | │ 20 | │ 21 | └── @ HTMLTextNode (location: (3:9)-(4:0)) 22 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/analyze/block_test/test_0001_single_line_block_6e95c5888a720e157b1e6ec923700b92.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (2 items) 3 | ├── @ ERBContentNode (location: (1:0)-(1:41)) 4 | │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) 5 | │ ├── content: " numbers.map { |number| number * 2 } " (location: (1:2)-(1:39)) 6 | │ ├── tag_closing: "%>" (location: (1:39)-(1:41)) 7 | │ ├── parsed: true 8 | │ └── valid: true 9 | │ 10 | └── @ HTMLTextNode (location: (1:41)-(2:0)) 11 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/analyze/block_test/test_0002_block_bb47a5f1953b6af8f63923f01e574d97.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(4:0)) 2 | └── children: (2 items) 3 | ├── @ ERBBlockNode (location: (1:0)-(3:9)) 4 | │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) 5 | │ ├── content: " block do " (location: (1:2)-(1:12)) 6 | │ ├── tag_closing: "%>" (location: (1:12)-(1:14)) 7 | │ ├── body: (1 item) 8 | │ │ └── @ HTMLTextNode (location: (1:14)-(3:0)) 9 | │ │ └── content: "\n Content\n" 10 | │ │ 11 | │ └── end_node: 12 | │ └── @ ERBEndNode (location: (3:0)-(3:9)) 13 | │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) 14 | │ ├── content: " end " (location: (3:2)-(3:7)) 15 | │ └── tag_closing: "%>" (location: (3:7)-(3:9)) 16 | │ 17 | │ 18 | └── @ HTMLTextNode (location: (3:9)-(4:0)) 19 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/analyze/block_test/test_0003_block_with_curlies_1f0aa977e8f63e43e55f5c51eeb809c6.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(4:0)) 2 | └── children: (2 items) 3 | ├── @ ERBBlockNode (location: (1:0)-(3:7)) 4 | │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) 5 | │ ├── content: " block { " (location: (1:2)-(1:11)) 6 | │ ├── tag_closing: "%>" (location: (1:11)-(1:13)) 7 | │ ├── body: (1 item) 8 | │ │ └── @ HTMLTextNode (location: (1:13)-(3:0)) 9 | │ │ └── content: "\n Content\n" 10 | │ │ 11 | │ └── end_node: 12 | │ └── @ ERBEndNode (location: (3:0)-(3:7)) 13 | │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) 14 | │ ├── content: " } " (location: (3:2)-(3:5)) 15 | │ └── tag_closing: "%>" (location: (3:5)-(3:7)) 16 | │ 17 | │ 18 | └── @ HTMLTextNode (location: (3:7)-(4:0)) 19 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/analyze/block_test/test_0010_loop_block_b6292ce38564be66a47b39c7cd7c0154.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(4:0)) 2 | └── children: (2 items) 3 | ├── @ ERBBlockNode (location: (1:0)-(3:9)) 4 | │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) 5 | │ ├── content: " loop do " (location: (1:2)-(1:11)) 6 | │ ├── tag_closing: "%>" (location: (1:11)-(1:13)) 7 | │ ├── body: (1 item) 8 | │ │ └── @ HTMLTextNode (location: (1:13)-(3:0)) 9 | │ │ └── content: "\n loop\n" 10 | │ │ 11 | │ └── end_node: 12 | │ └── @ ERBEndNode (location: (3:0)-(3:9)) 13 | │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) 14 | │ ├── content: " end " (location: (3:2)-(3:7)) 15 | │ └── tag_closing: "%>" (location: (3:7)-(3:9)) 16 | │ 17 | │ 18 | └── @ HTMLTextNode (location: (3:9)-(4:0)) 19 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/analyze/if_test/test_0001_if_statement_6d66066a7761ec6dd8a7cc99cdc3489c.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(3:0)) 2 | └── children: (2 items) 3 | ├── @ ERBIfNode (location: (1:0)-(2:9)) 4 | │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) 5 | │ ├── content: " if true " (location: (1:2)-(1:11)) 6 | │ ├── tag_closing: "%>" (location: (1:11)-(1:13)) 7 | │ ├── statements: (1 item) 8 | │ │ └── @ HTMLTextNode (location: (1:13)-(2:0)) 9 | │ │ └── content: "\n" 10 | │ │ 11 | │ ├── subsequent: ∅ 12 | │ └── end_node: 13 | │ └── @ ERBEndNode (location: (2:0)-(2:9)) 14 | │ ├── tag_opening: "<%" (location: (2:0)-(2:2)) 15 | │ ├── content: " end " (location: (2:2)-(2:7)) 16 | │ └── tag_closing: "%>" (location: (2:7)-(2:9)) 17 | │ 18 | │ 19 | └── @ HTMLTextNode (location: (2:9)-(3:0)) 20 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/analyze/unless_test/test_0001_unless_statement_b9069f5ae184133162ab0372cfb63c39.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(4:0)) 2 | └── children: (2 items) 3 | ├── @ ERBUnlessNode (location: (1:0)-(3:9)) 4 | │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) 5 | │ ├── content: " unless true " (location: (1:2)-(1:15)) 6 | │ ├── tag_closing: "%>" (location: (1:15)-(1:17)) 7 | │ ├── statements: (1 item) 8 | │ │ └── @ HTMLTextNode (location: (1:17)-(3:0)) 9 | │ │ └── content: "\n true\n" 10 | │ │ 11 | │ ├── else_clause: ∅ 12 | │ └── end_node: 13 | │ └── @ ERBEndNode (location: (3:0)-(3:9)) 14 | │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) 15 | │ ├── content: " end " (location: (3:2)-(3:7)) 16 | │ └── tag_closing: "%>" (location: (3:7)-(3:9)) 17 | │ 18 | │ 19 | └── @ HTMLTextNode (location: (3:9)-(4:0)) 20 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/analyze/until_test/test_0001_until_statement_cebc8340ea1e761f6ce3885d88328dc1.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(4:0)) 2 | └── children: (2 items) 3 | ├── @ ERBUntilNode (location: (1:0)-(3:9)) 4 | │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) 5 | │ ├── content: " until true " (location: (1:2)-(1:14)) 6 | │ ├── tag_closing: "%>" (location: (1:14)-(1:16)) 7 | │ ├── statements: (1 item) 8 | │ │ └── @ HTMLTextNode (location: (1:16)-(3:0)) 9 | │ │ └── content: "\n true\n" 10 | │ │ 11 | │ └── end_node: 12 | │ └── @ ERBEndNode (location: (3:0)-(3:9)) 13 | │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) 14 | │ ├── content: " end " (location: (3:2)-(3:7)) 15 | │ └── tag_closing: "%>" (location: (3:7)-(3:9)) 16 | │ 17 | │ 18 | └── @ HTMLTextNode (location: (3:9)-(4:0)) 19 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/analyze/while_test/test_0001_while_statement_80ec9198a269a436b3bc6b3751352e04.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(4:0)) 2 | └── children: (2 items) 3 | ├── @ ERBWhileNode (location: (1:0)-(3:9)) 4 | │ ├── tag_opening: "<%" (location: (1:0)-(1:2)) 5 | │ ├── content: " while true " (location: (1:2)-(1:14)) 6 | │ ├── tag_closing: "%>" (location: (1:14)-(1:16)) 7 | │ ├── statements: (1 item) 8 | │ │ └── @ HTMLTextNode (location: (1:16)-(3:0)) 9 | │ │ └── content: "\n true\n" 10 | │ │ 11 | │ └── end_node: 12 | │ └── @ ERBEndNode (location: (3:0)-(3:9)) 13 | │ ├── tag_opening: "<%" (location: (3:0)-(3:2)) 14 | │ ├── content: " end " (location: (3:2)-(3:7)) 15 | │ └── tag_closing: "%>" (location: (3:7)-(3:9)) 16 | │ 17 | │ 18 | └── @ HTMLTextNode (location: (3:9)-(4:0)) 19 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/analyze/yield_test/test_0001_yield_811e738151285db6da8e3a493b8d6e2b.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (1 item) 3 | └── @ ERBYieldNode (location: (1:0)-(2:0)) 4 | ├── tag_opening: "<%=" (location: (1:0)-(1:3)) 5 | ├── content: " yield " (location: (1:3)-(1:10)) 6 | └── tag_closing: "%>" (location: (1:10)-(1:12)) -------------------------------------------------------------------------------- /test/snapshots/analyze/yield_test/test_0002_yield_with_symbol_0f3dddc66e3930ac6552f5a273092594.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (1 item) 3 | └── @ ERBYieldNode (location: (1:0)-(2:0)) 4 | ├── tag_opening: "<%=" (location: (1:0)-(1:3)) 5 | ├── content: " yield :symbol " (location: (1:3)-(1:18)) 6 | └── tag_closing: "%>" (location: (1:18)-(1:20)) -------------------------------------------------------------------------------- /test/snapshots/lexer/attributes_test/test_0003_attribute_value_empty_double_quotes_with_whitespace_aa45c5ab55dcf5752db629787123d549.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | # 11 | -------------------------------------------------------------------------------- /test/snapshots/lexer/attributes_test/test_0004_attribute_value_empty_double_quotes_without_whitespace_dc0893bda6faff8ecc29985263b68f44.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | -------------------------------------------------------------------------------- /test/snapshots/lexer/attributes_test/test_0005_attribute_value_empty_single_quotes_with_whitespace_2e5584859fa6a3d0830bbc5ad9020767.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | # 11 | -------------------------------------------------------------------------------- /test/snapshots/lexer/attributes_test/test_0006_attribute_value_empty_single_quotes_without_whitespace_ec5b4690851677e250ac5506d70c355f.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | -------------------------------------------------------------------------------- /test/snapshots/lexer/attributes_test/test_0007_attribute_value_single_quotes_with_slash_gt_ea0ed04bb6803d8a6cb24afe526c5a4a.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | # 11 | -------------------------------------------------------------------------------- /test/snapshots/lexer/attributes_test/test_0008_attribute_value_double_quotes_with_slash_gt_f890220bd65269c4616e9ce562492257.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | # 11 | -------------------------------------------------------------------------------- /test/snapshots/lexer/attributes_test/test_0009_attribute_value_single_quotes_with_>_value_2e196056650e47d46886938eb88136f7.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | # 11 | -------------------------------------------------------------------------------- /test/snapshots/lexer/attributes_test/test_0010_attribute_value_double_quotes_with_slash_98c2bda4f37e2def75e128c157a85691.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | # 11 | -------------------------------------------------------------------------------- /test/snapshots/lexer/attributes_test/test_0011_attribute_value_double_quotes_with_>_value_bf55684b694d2aa471b24fa539546339.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | # 11 | -------------------------------------------------------------------------------- /test/snapshots/lexer/attributes_test/test_0012_attribute_value_single_quotes_with_slash_value_ef9fee65e67c623b1cd991d44c9c0dc5.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | # 11 | -------------------------------------------------------------------------------- /test/snapshots/lexer/attributes_test/test_0013_attribute_value_double_quotes_with_single_quote_value_bd2cc15e28c80a4cfdafb4467f6f0555.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | # 11 | # 12 | -------------------------------------------------------------------------------- /test/snapshots/lexer/attributes_test/test_0014_attribute_value_single_quotes_with_double_quote_value_3b99fbfb2dbe3aba937cc20311e7d331.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | # 11 | # 12 | -------------------------------------------------------------------------------- /test/snapshots/lexer/boolean_attributes_test/test_0001_boolean_attribute_bb7e4ba2925fabbda530de889e749a52.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | -------------------------------------------------------------------------------- /test/snapshots/lexer/boolean_attributes_test/test_0002_boolean_attribute_without_whitespace_and_with_self-closing_tag_c6b0a14a338dd8edc4a12c0d19e7f542.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | -------------------------------------------------------------------------------- /test/snapshots/lexer/boolean_attributes_test/test_0003_boolean_attribute_without_whitespace_and_without_self-closing_tag_64b6a6bf9a284ab86a9db5974b31b466.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | -------------------------------------------------------------------------------- /test/snapshots/lexer/comments_test/test_0001_HTML_comment_with_padding_whitespace_9cb56068ef4732067422a670d943c08e.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | -------------------------------------------------------------------------------- /test/snapshots/lexer/comments_test/test_0002_HTML_comment_with_no_whitespace_4644299a23e9ee76cdc970a26168b559.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | -------------------------------------------------------------------------------- /test/snapshots/lexer/doctype_test/test_0001_doctype_d50c7b32f19e4848938905f3dc675f44.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | -------------------------------------------------------------------------------- /test/snapshots/lexer/doctype_test/test_0002_doctype_with_space_6bc67d7bee174842987d55662f6e3d1c.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/doctype_test/test_0003_doctype_with_html_fe364450e1391215f596d043488f989f.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | -------------------------------------------------------------------------------- /test/snapshots/lexer/doctype_test/test_0005_doctype_case_insensitivity_2f56dc84fb5f8e284f42eae7d1f1c597.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | -------------------------------------------------------------------------------- /test/snapshots/lexer/doctype_test/test_0005_doctype_case_insensitivity_3e5902254c42ff2df6f6321086040dbe.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | -------------------------------------------------------------------------------- /test/snapshots/lexer/doctype_test/test_0005_doctype_case_insensitivity_8bc9e797250fadb2f5af79f38cf7d74e.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | -------------------------------------------------------------------------------- /test/snapshots/lexer/erb_test/test_0001_erb_<%_%>_e97ed3ba194342cfe8febc1a40a3d603.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/erb_test/test_0002_erb_<%=_%>_2d5c53c5a986076f8a5df2585ce5aa74.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/erb_test/test_0003_erb_<%-_%>_b305aff64a566ea549c116aacb0ce86c.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/erb_test/test_0004_erb_<%-_-%>_34d2696185efe003bf3f2c2dddb924e3.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/erb_test/test_0005_erb_<%#_%>_d59bec1a45925c1618c6d42541cb652b.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/erb_test/test_0006_erb_<%%_%%>_5856d025d70301b0cc95e2287a1d324f.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/html_entities_test/test_0001_<_87acb03b9542ddbc824f5bbd080a5cd4.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/html_entities_test/test_0002_>_58ba3bb1a1772a74392e5e86bd2be4b7.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/html_entities_test/test_0003_ _cc7819055cde3194bb3b136bad5cf58d.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/html_entities_test/test_0004_"_eb6439de53405a48b124e7cf89ba71d3.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/html_entities_test/test_0005_'_e38c1fb206aebfcf7289482b93815826.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/html_entities_test/test_0006_ampersand_c2249209343ab488c055da76368f04a0.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/html_entities_test/test_0007_literal_ampersand_6cff047854f19ac2aa52aac51bf3af4a.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/lexer_test/test_0001_nil_560d7cfe153ff63e50fbb9a506bd32ba.txt: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /test/snapshots/lexer/lexer_test/test_0002_empty_file_d41d8cd98f00b204e9800998ecf8427e.txt: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /test/snapshots/lexer/newlines_test/test_0001_line_feed_68b329da9893e34099c7d8ad5cb9c940.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/newlines_test/test_0002_carriage_return_dcb9be2f604e5df91deb9659bed4748d.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/newlines_test/test_0003_carriage_return_and_line_feed_81051bcc2cf1bedf378224b0a93e2877.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/newlines_test/test_0004_two_newlines_e1c06d85ae7b8b032bef47e42e4c08f9.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | -------------------------------------------------------------------------------- /test/snapshots/lexer/newlines_test/test_0005_newline_after_space_d784fa8b6d98d27699781bd9a7cf19f0.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | -------------------------------------------------------------------------------- /test/snapshots/lexer/newlines_test/test_0006_text_content_before_and_after_68423faee48fc1bab08291a512861446.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | -------------------------------------------------------------------------------- /test/snapshots/lexer/newlines_test/test_0007_newline_between_text_content_f41121a903eafadf258962abc57c8644.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | -------------------------------------------------------------------------------- /test/snapshots/lexer/tags_test/test_0001_basic_tag_c83301425b2ad1d496473a5ff3d9ecca.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | -------------------------------------------------------------------------------- /test/snapshots/lexer/tags_test/test_0002_basic_void_tag_0a22925ab034fd09eb49cc4224720dcb.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | -------------------------------------------------------------------------------- /test/snapshots/lexer/tags_test/test_0003_basic_void_tag_without_whitespace_917eccff1c0d00dbdc9bbe20d07c939c.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | -------------------------------------------------------------------------------- /test/snapshots/lexer/tags_test/test_0004_namespaced_tag_45f3ec5566563217212e18f9f9983f0a.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | -------------------------------------------------------------------------------- /test/snapshots/lexer/tags_test/test_0006_text_content_f797031f3210ce6494466d619610926c.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | # 11 | -------------------------------------------------------------------------------- /test/snapshots/lexer/tags_test/test_0007_attribute_with_no_quotes_value_and_whitespace_and_self-closing_tag_fed80eb194a51acda7fe05820e850451.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | -------------------------------------------------------------------------------- /test/snapshots/lexer/tags_test/test_0008_attribute_with_no_quotes_value,_no_whitespace_and_self-closing_tag_82af2795699fe4f5b34f32ed1491228a.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | -------------------------------------------------------------------------------- /test/snapshots/lexer/tags_test/test_0009_attribute_with_no_quotes_value,_no_whitespace,_and_non_self-closing_tag_f00fb4a0aa00f7c763be88670c0bc05d.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | -------------------------------------------------------------------------------- /test/snapshots/lexer/text_content_test/test_0001_text_content_4dc7bb0f7aaadf0c36061e92ff8d581b.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | # 10 | # 11 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0001_whitespace_7215ee9c7d9dc229d2921a40e899ec5f.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0002_multiple_whitespace_0cf31b2c283ce3431794586df7b0996d.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0003_multiple_whitespace_with_newlines__ff50772e7d3bdf3652ee7b345823b611.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0004_non-breaking_space_8df6fbcc43d31d99e5112eb009ed8a2d.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0005_newline_68b329da9893e34099c7d8ad5cb9c940.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0006_!_9033e0e305f247c0c3c80d0c7848c8b3.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0007_slash_6666cd76f96956469e7be39d750cc7d9.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0008_dash_336d5ebc5436534e61d16e63ddfca327.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0009_underscore_b14a7b8059d9c055954c92674ce60032.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0010_percent_0bcef9c45bd8a48eda1b26eb0c61c869.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0011_colon_853ae90f0351324bd73ea615e6487517.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0012_equals_43ec3e5dee6e706af7766fffea512721.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0013_double_quote_b15835f133ff2e27c7cb28117bfae8f4.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0014_single_quote_3590cb8af0bbb9e78c343b52b93773c9.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0015_less_than_signs_83699a49d8884e65d9291885a6448e44.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0016_greater_than_signs_4a8c3066e1720f3068d5559fc41acb0c.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | -------------------------------------------------------------------------------- /test/snapshots/lexer/token_test/test_0017_LT,_GT_and_PERCENT_signs_6f6dcf1103a1b99a952c36d32a89a2ec.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | # 7 | # 8 | # 9 | -------------------------------------------------------------------------------- /test/snapshots/parser/comments_test/test_0001_HTML_comment_with_padding_whitespace_9cb56068ef4732067422a670d943c08e.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:20)) 2 | └── children: (1 item) 3 | └── @ HTMLCommentNode (location: (1:0)-(1:20)) 4 | ├── comment_start: "" (location: (1:17)-(1:20)) -------------------------------------------------------------------------------- /test/snapshots/parser/comments_test/test_0002_HTML_comment_with_no_whitespace_4644299a23e9ee76cdc970a26168b559.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:18)) 2 | └── children: (1 item) 3 | └── @ HTMLCommentNode (location: (1:0)-(1:18)) 4 | ├── comment_start: "" (location: (1:15)-(1:18)) -------------------------------------------------------------------------------- /test/snapshots/parser/doctype_test/test_0001_doctype_d50c7b32f19e4848938905f3dc675f44.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:10)) 2 | └── children: (1 item) 3 | └── @ HTMLDoctypeNode (location: (1:0)-(1:10)) 4 | ├── tag_opening: "" (location: (1:9)-(1:10)) -------------------------------------------------------------------------------- /test/snapshots/parser/doctype_test/test_0002_doctype_with_space_6bc67d7bee174842987d55662f6e3d1c.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:11)) 2 | └── children: (1 item) 3 | └── @ HTMLDoctypeNode (location: (1:0)-(1:11)) 4 | ├── tag_opening: "" (location: (1:10)-(1:11)) -------------------------------------------------------------------------------- /test/snapshots/parser/doctype_test/test_0003_doctype_with_html_fe364450e1391215f596d043488f989f.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:15)) 2 | └── children: (1 item) 3 | └── @ HTMLDoctypeNode (location: (1:0)-(1:15)) 4 | ├── tag_opening: "" (location: (1:14)-(1:15)) -------------------------------------------------------------------------------- /test/snapshots/parser/doctype_test/test_0004_two_doctypes_9b920c07c2b2e29b9dcf6cd03f842e08.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:30)) 2 | └── children: (2 items) 3 | ├── @ HTMLDoctypeNode (location: (1:0)-(1:15)) 4 | │ ├── tag_opening: "" (location: (1:14)-(1:15)) 10 | │ 11 | └── @ HTMLDoctypeNode (location: (1:15)-(1:30)) 12 | ├── tag_opening: "" (location: (1:29)-(1:30)) -------------------------------------------------------------------------------- /test/snapshots/parser/doctype_test/test_0005_html4_doctype_737d71ee3fb171f813731282cd802c3e.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:90)) 2 | └── children: (1 item) 3 | └── @ HTMLDoctypeNode (location: (1:0)-(1:90)) 4 | ├── tag_opening: "" (location: (1:89)-(1:90)) -------------------------------------------------------------------------------- /test/snapshots/parser/doctype_test/test_0006_doctype_case_insensitivity_03935adb0833c1f800be8baa0a8aca3a.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:10)) 2 | └── children: (1 item) 3 | └── @ HTMLDoctypeNode (location: (1:0)-(1:10)) 4 | ├── tag_opening: "" (location: (1:9)-(1:10)) -------------------------------------------------------------------------------- /test/snapshots/parser/doctype_test/test_0006_doctype_case_insensitivity_2f56dc84fb5f8e284f42eae7d1f1c597.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:10)) 2 | └── children: (1 item) 3 | └── @ HTMLDoctypeNode (location: (1:0)-(1:10)) 4 | ├── tag_opening: "" (location: (1:9)-(1:10)) -------------------------------------------------------------------------------- /test/snapshots/parser/doctype_test/test_0006_doctype_case_insensitivity_3e5902254c42ff2df6f6321086040dbe.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:10)) 2 | └── children: (1 item) 3 | └── @ HTMLDoctypeNode (location: (1:0)-(1:10)) 4 | ├── tag_opening: "" (location: (1:9)-(1:10)) -------------------------------------------------------------------------------- /test/snapshots/parser/doctype_test/test_0006_doctype_case_insensitivity_8bc9e797250fadb2f5af79f38cf7d74e.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:10)) 2 | └── children: (1 item) 3 | └── @ HTMLDoctypeNode (location: (1:0)-(1:10)) 4 | ├── tag_opening: "" (location: (1:9)-(1:10)) -------------------------------------------------------------------------------- /test/snapshots/parser/doctype_test/test_0006_doctype_case_insensitivity_d1d92cd6681949b6e66a00ae3723d8db.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:10)) 2 | └── children: (1 item) 3 | └── @ HTMLDoctypeNode (location: (1:0)-(1:10)) 4 | ├── tag_opening: "" (location: (1:9)-(1:10)) -------------------------------------------------------------------------------- /test/snapshots/parser/erb_test/test_0001_interpolate_on_top_level_486c2e36d7c731369b8bc10cc66d671e.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:12)) 2 | └── children: (1 item) 3 | └── @ ERBContentNode (location: (1:0)-(1:12)) 4 | ├── tag_opening: "<%=" (location: (1:0)-(1:3)) 5 | ├── content: " hello " (location: (1:3)-(1:10)) 6 | ├── tag_closing: "%>" (location: (1:10)-(1:12)) 7 | ├── parsed: true 8 | └── valid: true -------------------------------------------------------------------------------- /test/snapshots/parser/erb_test/test_0012_interpolate_inside_comment_bfdabf4936ec72a10ffc2e12adfb446a.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:25)) 2 | └── children: (1 item) 3 | └── @ HTMLCommentNode (location: (1:0)-(1:25)) 4 | ├── comment_start: "" (location: (1:22)-(1:25)) -------------------------------------------------------------------------------- /test/snapshots/parser/erb_test/test_0015_comment_e95f45b0f3e230c24e11f4b083de23f5.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:59)) 2 | └── children: (1 item) 3 | └── @ ERBContentNode (location: (1:0)-(1:59)) 4 | ├── tag_opening: "<%#" (location: (1:0)-(1:3)) 5 | ├── content: " comment with a single qutote(') and double quote (") " (location: (1:3)-(1:57)) 6 | ├── tag_closing: "%>" (location: (1:57)-(1:59)) 7 | ├── parsed: true 8 | └── valid: false -------------------------------------------------------------------------------- /test/snapshots/parser/erb_test/test_0016_multi-line_comment_14c1f84ba7b4627b3e0dc3ba74179bb8.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (2 items) 3 | ├── @ ERBContentNode (location: (1:0)-(1:14)) 4 | │ ├── tag_opening: "<%#" (location: (1:0)-(1:3)) 5 | │ ├── content: " 6 | │ comment 7 | │ " (location: (1:3)-(1:12)) 8 | │ ├── tag_closing: "%>" (location: (1:12)-(1:14)) 9 | │ ├── parsed: true 10 | │ └── valid: true 11 | │ 12 | └── @ HTMLTextNode (location: (1:14)-(2:0)) 13 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/parser/erb_test/test_0017_multi-line_comment_with_Ruby_keyword_dd7885019a51280cc6cfef734bf1161a.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (2 items) 3 | ├── @ ERBContentNode (location: (1:0)-(1:10)) 4 | │ ├── tag_opening: "<%#" (location: (1:0)-(1:3)) 5 | │ ├── content: " 6 | │ end 7 | │ " (location: (1:3)-(1:8)) 8 | │ ├── tag_closing: "%>" (location: (1:8)-(1:10)) 9 | │ ├── parsed: true 10 | │ └── valid: false 11 | │ 12 | └── @ HTMLTextNode (location: (1:10)-(2:0)) 13 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/parser/newlines_test/test_0001_line_feed_68b329da9893e34099c7d8ad5cb9c940.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (1 item) 3 | └── @ HTMLTextNode (location: (1:0)-(2:0)) 4 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/parser/newlines_test/test_0002_carriage_return_dcb9be2f604e5df91deb9659bed4748d.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (1 item) 3 | └── @ HTMLTextNode (location: (1:0)-(2:0)) 4 | └── content: "\r" -------------------------------------------------------------------------------- /test/snapshots/parser/newlines_test/test_0003_carriage_return_and_line_feed_81051bcc2cf1bedf378224b0a93e2877.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (1 item) 3 | └── @ HTMLTextNode (location: (1:0)-(2:0)) 4 | └── content: "\r\n" -------------------------------------------------------------------------------- /test/snapshots/parser/newlines_test/test_0004_two_newlines_e1c06d85ae7b8b032bef47e42e4c08f9.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(3:0)) 2 | └── children: (1 item) 3 | └── @ HTMLTextNode (location: (1:0)-(3:0)) 4 | └── content: "\n\n" -------------------------------------------------------------------------------- /test/snapshots/parser/newlines_test/test_0005_newline_after_space_d784fa8b6d98d27699781bd9a7cf19f0.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (1 item) 3 | └── @ HTMLTextNode (location: (1:0)-(2:0)) 4 | └── content: " \n" -------------------------------------------------------------------------------- /test/snapshots/parser/newlines_test/test_0006_text_content_before_and_after_68423faee48fc1bab08291a512861446.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(3:5)) 2 | └── children: (1 item) 3 | └── @ HTMLTextNode (location: (1:0)-(3:5)) 4 | └── content: "Hello\n\nWorld" -------------------------------------------------------------------------------- /test/snapshots/parser/newlines_test/test_0007_newline_between_text_content_f41121a903eafadf258962abc57c8644.txt: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # 5 | # 6 | -------------------------------------------------------------------------------- /test/snapshots/parser/parser_test/test_0001_nil_560d7cfe153ff63e50fbb9a506bd32ba.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:0)) 2 | └── children: [] -------------------------------------------------------------------------------- /test/snapshots/parser/parser_test/test_0002_empty_file_d41d8cd98f00b204e9800998ecf8427e.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:0)) 2 | └── children: [] -------------------------------------------------------------------------------- /test/snapshots/parser/svg_test/test_0001_svg_7b56e1eab00ec8000da9331a4888cb35.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:11)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:11)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "svg" (location: (1:1)-(1:4)) 8 | │ ├── tag_closing: ">" (location: (1:4)-(1:5)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "svg" (location: (1:1)-(1:4)) 13 | ├── body: [] 14 | ├── close_tag: 15 | │ └── @ HTMLCloseTagNode (location: (1:5)-(1:11)) 16 | │ ├── tag_opening: "" (location: (1:10)-(1:11)) 19 | │ 20 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/tags_test/test_0001_empty_tag_dfc9870d38d11e806e61bd5a448afc82.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:13)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:13)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:6)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "span" (location: (1:1)-(1:5)) 8 | │ ├── tag_closing: ">" (location: (1:5)-(1:6)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "span" (location: (1:1)-(1:5)) 13 | ├── body: [] 14 | ├── close_tag: 15 | │ └── @ HTMLCloseTagNode (location: (1:6)-(1:13)) 16 | │ ├── tag_opening: "" (location: (1:12)-(1:13)) 19 | │ 20 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/tags_test/test_0002_empty_tag_with_whitespace_1e2608bc54aecef5ea56dede40640b51.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:14)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:14)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:6)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "span" (location: (1:1)-(1:5)) 8 | │ ├── tag_closing: ">" (location: (1:5)-(1:6)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "span" (location: (1:1)-(1:5)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:6)-(1:7)) 15 | │ └── content: " " 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:7)-(1:14)) 19 | │ ├── tag_opening: "" (location: (1:13)-(1:14)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/tags_test/test_0003_empty_tag_with_newline_d07900ededde58845d8cc2ee728ace9f.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:7)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(2:7)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:6)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "span" (location: (1:1)-(1:5)) 8 | │ ├── tag_closing: ">" (location: (1:5)-(1:6)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "span" (location: (1:1)-(1:5)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:6)-(2:0)) 15 | │ └── content: "\n" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (2:0)-(2:7)) 19 | │ ├── tag_opening: "" (location: (2:6)-(2:7)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/tags_test/test_0004_void_element_shouldn't_expect_a_closing_tag_8b0f0ea73162b7552dda3c149b6c045d.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:4)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:4)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:4)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "br" (location: (1:1)-(1:3)) 8 | │ ├── tag_closing: ">" (location: (1:3)-(1:4)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "br" (location: (1:1)-(1:3)) 13 | ├── body: [] 14 | ├── close_tag: ∅ 15 | └── is_void: true -------------------------------------------------------------------------------- /test/snapshots/parser/tags_test/test_0006_br_self-closing_tag_c5a0e9b5c299ec39a2fb26fa8b1c0dcf.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:5)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:5)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "br" (location: (1:1)-(1:3)) 8 | │ ├── tag_closing: "/>" (location: (1:3)-(1:5)) 9 | │ ├── children: [] 10 | │ └── is_void: true 11 | │ 12 | ├── tag_name: "br" (location: (1:1)-(1:3)) 13 | ├── body: [] 14 | ├── close_tag: ∅ 15 | └── is_void: true -------------------------------------------------------------------------------- /test/snapshots/parser/tags_test/test_0007_closing_tag_without_an_opening_tag_for_a_void_element_fa5881a4aafaf2629472fd2300394826.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:5)) 2 | └── children: (1 item) 3 | └── @ HTMLCloseTagNode (location: (1:0)-(1:5)) 4 | ├── errors: (1 error) 5 | │ └── @ VoidElementClosingTagError (location: (1:0)-(1:5)) 6 | │ ├── message: "`br` is a void element and should not be used as a closing tag. Use `
` or `
` instead of `
`." 7 | │ ├── tag_name: "br" (location: (1:2)-(1:4)) 8 | │ ├── expected: "
" 9 | │ └── found: "
" 10 | │ 11 | ├── tag_opening: "" (location: (1:4)-(1:5)) -------------------------------------------------------------------------------- /test/snapshots/parser/tags_test/test_0008_closing_tag_without_an_opening_tag_for_a_non-void_element_0a3a0b592b9c285e050805307cee87c2.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:6)) 2 | └── children: (1 item) 3 | └── @ HTMLCloseTagNode (location: (1:0)-(1:6)) 4 | ├── errors: (1 error) 5 | │ └── @ MissingOpeningTagError (location: (1:0)-(1:6)) 6 | │ ├── message: "Found closing tag `` at (1:2) without a matching opening tag." 7 | │ └── closing_tag: "div" (location: (1:2)-(1:5)) 8 | │ 9 | ├── tag_opening: "" (location: (1:5)-(1:6)) -------------------------------------------------------------------------------- /test/snapshots/parser/tags_test/test_0010_basic_tag_c83301425b2ad1d496473a5ff3d9ecca.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:13)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:13)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:6)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "html" (location: (1:1)-(1:5)) 8 | │ ├── tag_closing: ">" (location: (1:5)-(1:6)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "html" (location: (1:1)-(1:5)) 13 | ├── body: [] 14 | ├── close_tag: 15 | │ └── @ HTMLCloseTagNode (location: (1:6)-(1:13)) 16 | │ ├── tag_opening: "" (location: (1:12)-(1:13)) 19 | │ 20 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/tags_test/test_0013_basic_void_tag_0a22925ab034fd09eb49cc4224720dcb.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:7)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:7)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:7)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "img" (location: (1:1)-(1:4)) 8 | │ ├── tag_closing: "/>" (location: (1:5)-(1:7)) 9 | │ ├── children: [] 10 | │ └── is_void: true 11 | │ 12 | ├── tag_name: "img" (location: (1:1)-(1:4)) 13 | ├── body: [] 14 | ├── close_tag: ∅ 15 | └── is_void: true -------------------------------------------------------------------------------- /test/snapshots/parser/tags_test/test_0014_basic_void_tag_without_whitespace_917eccff1c0d00dbdc9bbe20d07c939c.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:6)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:6)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:6)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "img" (location: (1:1)-(1:4)) 8 | │ ├── tag_closing: "/>" (location: (1:4)-(1:6)) 9 | │ ├── children: [] 10 | │ └── is_void: true 11 | │ 12 | ├── tag_name: "img" (location: (1:1)-(1:4)) 13 | ├── body: [] 14 | ├── close_tag: ∅ 15 | └── is_void: true -------------------------------------------------------------------------------- /test/snapshots/parser/tags_test/test_0015_namespaced_tag_45f3ec5566563217212e18f9f9983f0a.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:21)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:21)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:10)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "ns:table" (location: (1:1)-(1:9)) 8 | │ ├── tag_closing: ">" (location: (1:9)-(1:10)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "ns:table" (location: (1:1)-(1:9)) 13 | ├── body: [] 14 | ├── close_tag: 15 | │ └── @ HTMLCloseTagNode (location: (1:10)-(1:21)) 16 | │ ├── tag_opening: "" (location: (1:20)-(1:21)) 19 | │ 20 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0001_text_content_b10a8db164e0754105b7a99be72e3fe5.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:11)) 2 | └── children: (1 item) 3 | └── @ HTMLTextNode (location: (1:0)-(1:11)) 4 | └── content: "Hello World" -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0002_text_content_inside_tag_f797031f3210ce6494466d619610926c.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:20)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:20)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:4)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "h1" (location: (1:1)-(1:3)) 8 | │ ├── tag_closing: ">" (location: (1:3)-(1:4)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "h1" (location: (1:1)-(1:3)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:4)-(1:15)) 15 | │ └── content: "Hello World" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:15)-(1:20)) 19 | │ ├── tag_opening: "" (location: (1:19)-(1:20)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0008_exclamation_as_only_content_9c8b60515462c86eadd2d66da6a0ce2b.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:8)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:8)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:4)) 15 | │ └── content: "!" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:4)-(1:8)) 19 | │ ├── tag_opening: "" (location: (1:7)-(1:8)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0008_exclamation_point_ed076287532e86365e841e92bfc50d8c.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:12)) 2 | └── children: (1 item) 3 | └── @ HTMLTextNode (location: (1:0)-(1:12)) 4 | └── content: "Hello World!" -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0009_comma_as_only_content_674270ea117383eb846ea45b78e9e1ba.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:8)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:8)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:4)) 15 | │ └── content: "," 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:4)-(1:8)) 19 | │ ├── tag_opening: "" (location: (1:7)-(1:8)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0009_exclamation_point_in_element_a2d1cb24f24b322a7dad520414c523e9.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:21)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:21)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:4)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "h1" (location: (1:1)-(1:3)) 8 | │ ├── tag_closing: ">" (location: (1:3)-(1:4)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "h1" (location: (1:1)-(1:3)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:4)-(1:16)) 15 | │ └── content: "Hello World!" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:16)-(1:21)) 19 | │ ├── tag_opening: "" (location: (1:20)-(1:21)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0010_dollar_sign_as_only_content_78055134c44e1a49f3c26e4b6374cfbc.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:8)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:8)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:4)) 15 | │ └── content: "$" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:4)-(1:8)) 19 | │ ├── tag_opening: "" (location: (1:7)-(1:8)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0011_dash_as_only_content_f53ad262a8d52fd1cab61f9120cf892a.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:8)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:8)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:4)) 15 | │ └── content: "-" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:4)-(1:8)) 19 | │ ├── tag_opening: "" (location: (1:7)-(1:8)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0012_period_as_only_content_40a2513c491b7ca4305c3896365639cc.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:8)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:8)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:4)) 15 | │ └── content: "." 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:4)-(1:8)) 19 | │ ├── tag_opening: "" (location: (1:7)-(1:8)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0013_percent_as_only_content_0c16eab1f0efae1c3decd29d686ebf92.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:8)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:8)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:4)) 15 | │ └── content: "%" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:4)-(1:8)) 19 | │ ├── tag_opening: "" (location: (1:7)-(1:8)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0014_slash_as_only_content_a92547d4939f0d1193fc1687ead68a69.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:8)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:8)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:4)) 15 | │ └── content: "/" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:4)-(1:8)) 19 | │ ├── tag_opening: "" (location: (1:7)-(1:8)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0015_underscore_as_only_content_58ddd13aa94e0673c3d5a0b0117eac43.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:8)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:8)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:4)) 15 | │ └── content: "_" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:4)-(1:8)) 19 | │ ├── tag_opening: "" (location: (1:7)-(1:8)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0016_colon_as_only_content_e4a58fb0e007fd23a3ecb20c2d56ac80.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:8)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:8)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:4)) 15 | │ └── content: ":" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:4)-(1:8)) 19 | │ ├── tag_opening: "" (location: (1:7)-(1:8)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0017_semicolon_as_only_content_6dead327b63bdf32ce5db6c692032e76.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:8)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:8)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:4)) 15 | │ └── content: ";" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:4)-(1:8)) 19 | │ ├── tag_opening: "" (location: (1:7)-(1:8)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0018_ampersand_as_only_content_9334b21b7f71cf0efa54e9bf47bc375d.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:8)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:8)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:4)) 15 | │ └── content: "&" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:4)-(1:8)) 19 | │ ├── tag_opening: "" (location: (1:7)-(1:8)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0019_equals_as_only_content_50f7d033b0fe8ad1380a1ce908786917.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:8)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:8)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:4)) 15 | │ └── content: "=" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:4)-(1:8)) 19 | │ ├── tag_opening: "" (location: (1:7)-(1:8)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0020_a-umlaut_as_only_content_87def4693e30550387f2812dec2219e6.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:9)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:9)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:5)) 15 | │ └── content: "ä" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:5)-(1:9)) 19 | │ ├── tag_opening: "" (location: (1:8)-(1:9)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0021_o-umlaut_as_only_content_9103b0fde88de0288ca8ca03532d66b7.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:9)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:9)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:5)) 15 | │ └── content: "ö" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:5)-(1:9)) 19 | │ ├── tag_opening: "" (location: (1:8)-(1:9)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0022_u-umlaut_as_only_content_fc95de76ebff55679d202ded6f4f04d0.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:9)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:9)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:5)) 15 | │ └── content: "ü" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:5)-(1:9)) 19 | │ ├── tag_opening: "" (location: (1:8)-(1:9)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/text_content_test/test_0023_emoji_as_only_content_400a6eb23cb6b83dd9a0bd4ddeff8409.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(1:11)) 2 | └── children: (1 item) 3 | └── @ HTMLElementNode (location: (1:0)-(1:11)) 4 | ├── open_tag: 5 | │ └── @ HTMLOpenTagNode (location: (1:0)-(1:3)) 6 | │ ├── tag_opening: "<" (location: (1:0)-(1:1)) 7 | │ ├── tag_name: "b" (location: (1:1)-(1:2)) 8 | │ ├── tag_closing: ">" (location: (1:2)-(1:3)) 9 | │ ├── children: [] 10 | │ └── is_void: false 11 | │ 12 | ├── tag_name: "b" (location: (1:1)-(1:2)) 13 | ├── body: (1 item) 14 | │ └── @ HTMLTextNode (location: (1:3)-(1:7)) 15 | │ └── content: "🌿" 16 | │ 17 | ├── close_tag: 18 | │ └── @ HTMLCloseTagNode (location: (1:7)-(1:11)) 19 | │ ├── tag_opening: "" (location: (1:10)-(1:11)) 22 | │ 23 | └── is_void: false -------------------------------------------------------------------------------- /test/snapshots/parser/utf8_test/test_0001_opening_guillemet_856d313d5fa2c08f87ea3b9a79a870b0.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (2 items) 3 | ├── @ ERBContentNode (location: (1:0)-(1:24)) 4 | │ ├── tag_opening: "<%=" (location: (1:0)-(1:3)) 5 | │ ├── content: " link_to '«', url " (location: (1:3)-(1:22)) 6 | │ ├── tag_closing: "%>" (location: (1:22)-(1:24)) 7 | │ ├── parsed: true 8 | │ └── valid: true 9 | │ 10 | └── @ HTMLTextNode (location: (1:24)-(2:0)) 11 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/parser/utf8_test/test_0002_closing_guillemet_46999175f7a57d90bf4ac8417289630d.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (2 items) 3 | ├── @ ERBContentNode (location: (1:0)-(1:24)) 4 | │ ├── tag_opening: "<%=" (location: (1:0)-(1:3)) 5 | │ ├── content: " link_to '»', url " (location: (1:3)-(1:22)) 6 | │ ├── tag_closing: "%>" (location: (1:22)-(1:24)) 7 | │ ├── parsed: true 8 | │ └── valid: true 9 | │ 10 | └── @ HTMLTextNode (location: (1:24)-(2:0)) 11 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/parser/utf8_test/test_0003_single_opening_guillemet_39365d03f7c1b51be8ba359dcdda448d.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (2 items) 3 | ├── @ ERBContentNode (location: (1:0)-(1:25)) 4 | │ ├── tag_opening: "<%=" (location: (1:0)-(1:3)) 5 | │ ├── content: " link_to '‹', url " (location: (1:3)-(1:23)) 6 | │ ├── tag_closing: "%>" (location: (1:23)-(1:25)) 7 | │ ├── parsed: true 8 | │ └── valid: true 9 | │ 10 | └── @ HTMLTextNode (location: (1:25)-(2:0)) 11 | └── content: "\n" -------------------------------------------------------------------------------- /test/snapshots/parser/utf8_test/test_0004_single_closing_guillemet_9259b210ee4ee3e7703e1a42face4fcf.txt: -------------------------------------------------------------------------------- 1 | @ DocumentNode (location: (1:0)-(2:0)) 2 | └── children: (2 items) 3 | ├── @ ERBContentNode (location: (1:0)-(1:25)) 4 | │ ├── tag_opening: "<%=" (location: (1:0)-(1:3)) 5 | │ ├── content: " link_to '›', url " (location: (1:3)-(1:23)) 6 | │ ├── tag_closing: "%>" (location: (1:23)-(1:25)) 7 | │ ├── parsed: true 8 | │ └── valid: true 9 | │ 10 | └── @ HTMLTextNode (location: (1:25)-(2:0)) 11 | └── content: "\n" -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__) 4 | 5 | require "herb" 6 | require "pathname" 7 | require "maxitest/autorun" 8 | require "minitest/spec" 9 | 10 | require_relative "fork_helper" if ENV["NO_TIMEOUT"].nil? 11 | 12 | require_relative "snapshot_utils" 13 | 14 | Minitest::Spec::DSL.send(:alias_method, :test, :it) 15 | Minitest::Spec::DSL.send(:alias_method, :xtest, :xit) 16 | 17 | def cyclic_string(length) 18 | sequence = ("a".."z").to_a + ("0".."9").to_a 19 | sequence.cycle.take(length).join 20 | end 21 | 22 | module Analyze 23 | module ActionView 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /wasm/extension_helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef EXTENSION_HELPERS_H 2 | #define EXTENSION_HELPERS_H 3 | 4 | #include 5 | #include 6 | 7 | extern "C" { 8 | #include "../src/include/position.h" 9 | #include "../src/include/location.h" 10 | #include "../src/include/range.h" 11 | #include "../src/include/token.h" 12 | #include "../src/include/ast_node.h" 13 | #include "../src/include/ast_nodes.h" 14 | } 15 | 16 | emscripten::val CreateString(const char* string); 17 | emscripten::val CreatePosition(position_T* position); 18 | emscripten::val CreateLocation(location_T* location); 19 | emscripten::val CreateRange(range_T* range); 20 | emscripten::val CreateToken(token_T* token); 21 | emscripten::val CreateLexResult(array_T* tokens, const std::string& source); 22 | emscripten::val CreateParseResult(AST_DOCUMENT_NODE_T *root, const std::string& source); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "projects": { 4 | "@herb-tools/browser": "javascript/packages/browser", 5 | "@herb-tools/core": "javascript/packages/core", 6 | "@herb-tools/node": "javascript/packages/node", 7 | "docs": "./docs", 8 | "playground": "./playground" 9 | } 10 | } 11 | --------------------------------------------------------------------------------