From 44d8c2b82481cc5558aafb4410a5e6f468e749b6 Mon Sep 17 00:00:00 2001 From: Yuji Sugiura <6259812+leaysgur@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:00:59 +0900 Subject: [PATCH] feat(formatter_htmlish): Implement HTML-ish formatter `Html` variant (#28272) Part of #28263 --- .gitattributes | 4 + .github/CODEOWNERS | 1 + Cargo.lock | 39 +- Cargo.toml | 1 + crates/oxc_formatter_htmlish/AGENTS.md | 53 ++ crates/oxc_formatter_htmlish/Cargo.toml | 39 + crates/oxc_formatter_htmlish/DIVERGENCES.md | 789 ++++++++++++++++++ crates/oxc_formatter_htmlish/build.rs | 15 + .../examples/htmlish_formatter.rs | 61 ++ crates/oxc_formatter_htmlish/src/context.rs | 33 + crates/oxc_formatter_htmlish/src/format.rs | 228 +++++ crates/oxc_formatter_htmlish/src/lib.rs | 16 + crates/oxc_formatter_htmlish/src/options.rs | 82 ++ .../src/print/attribute.rs | 320 +++++++ .../oxc_formatter_htmlish/src/print/block.rs | 138 +++ .../src/print/children.rs | 292 +++++++ .../src/print/element.rs | 222 +++++ crates/oxc_formatter_htmlish/src/print/mod.rs | 136 +++ crates/oxc_formatter_htmlish/src/print/tag.rs | 211 +++++ .../oxc_formatter_htmlish/src/print/text.rs | 117 +++ crates/oxc_formatter_htmlish/src/rules.rs | 37 + .../oxc_formatter_htmlish/src/tree/build.rs | 684 +++++++++++++++ .../oxc_formatter_htmlish/src/tree/display.rs | 73 ++ crates/oxc_formatter_htmlish/src/tree/mod.rs | 317 +++++++ .../oxc_formatter_htmlish/src/tree/names.rs | 586 +++++++++++++ .../tests/conformance.rs | 74 ++ ...-block-name-with-line-break.component.html | 6 + ...k-name-with-line-break.component.html.snap | 35 + ...angular-glued-block-markers.component.html | 5 + ...ar-glued-block-markers.component.html.snap | 34 + .../attribute-name-case.component.html | 2 + .../attribute-name-case.component.html.snap | 21 + .../fixtures/angular/blocks.component.html | 24 + .../angular/blocks.component.html.snap | 107 +++ .../bracketed-attribute.component.html | 1 + .../bracketed-attribute.component.html.snap | 18 + .../angular/empty-block.component.html | 1 + .../angular/empty-block.component.html.snap | 32 + .../tests/fixtures/fingerprint.rs | 144 ++++ .../tests/fixtures/html/basic.html | 1 + .../tests/fixtures/html/basic.html.snap | 22 + .../tests/fixtures/html/cdata-as-text.html | 18 + .../fixtures/html/cdata-as-text.html.snap | 69 ++ .../tests/fixtures/html/cdata-as-written.html | 1 + .../fixtures/html/cdata-as-written.html.snap | 25 + .../fixtures/html/class-ascii-whitespace.html | 2 + .../html/class-ascii-whitespace.html.snap | 21 + .../tests/fixtures/html/crlf/options.json | 4 + .../tests/fixtures/html/crlf/verbatim.html | 27 + .../fixtures/html/crlf/verbatim.html.snap | 174 ++++ .../fixtures/html/dialog-out-of-flow.html | 7 + .../html/dialog-out-of-flow.html.snap | 46 + .../html/doctype-html-in-one-pass.html | 2 + .../html/doctype-html-in-one-pass.html.snap | 21 + .../fixtures/html/doctype-in-xhtml.xhtml | 2 + .../fixtures/html/doctype-in-xhtml.xhtml.snap | 29 + .../html/end-tag-indentation-as-printed.html | 12 + .../end-tag-indentation-as-printed.html.snap | 49 ++ .../html/end-tag-with-space-is-comment.html | 1 + .../end-tag-with-space-is-comment.html.snap | 18 + .../html/htm-closing-tag-as-written.html | 2 + .../html/htm-closing-tag-as-written.html.snap | 21 + .../html/ignore/cdata-merged-text.html | 2 + .../html/ignore/cdata-merged-text.html.snap | 21 + .../tests/fixtures/html/ignore/options.json | 1 + .../html/ignored-closing-end-once.html | 4 + .../html/ignored-closing-end-once.html.snap | 31 + .../html/line-break-before-borrowed-end.html | 4 + .../line-break-before-borrowed-end.html.snap | 25 + .../fixtures/html/lowercase-known-names.html | 8 + .../html/lowercase-known-names.html.snap | 45 + .../fixtures/html/merged-inline-element.html | 6 + .../html/merged-inline-element.html.snap | 29 + .../fixtures/html/nul/nul-as-written.html | Bin 0 -> 36 bytes .../html/nul/nul-as-written.html.snap | Bin 0 -> 430 bytes .../fixtures/html/omitted-end-tag-at-eof.html | 4 + .../html/omitted-end-tag-at-eof.html.snap | 27 + .../tests/fixtures/html/omitted-end-tag.html | 6 + .../fixtures/html/omitted-end-tag.html.snap | 33 + .../html/pre-content-without-end-tag.html | 14 + .../pre-content-without-end-tag.html.snap | 73 ++ .../fixtures/html/prefixed-script-style.html | 3 + .../html/prefixed-script-style.html.snap | 24 + .../html/prettier-ignore-attribute.html | 8 + .../html/prettier-ignore-attribute.html.snap | 45 + .../fixtures/html/script-display-none.html | 13 + .../html/script-display-none.html.snap | 118 +++ .../html/self-closing-with-end-tag.html | 2 + .../html/self-closing-with-end-tag.html.snap | 29 + .../tests/fixtures/html/self-closing.html | 13 + .../fixtures/html/self-closing.html.snap | 58 ++ .../fixtures/html/srcset-zero-density.html | 2 + .../html/srcset-zero-density.html.snap | 21 + .../line-break-before-borrowed-end.html | 3 + .../line-break-before-borrowed-end.html.snap | 20 + .../tests/fixtures/html/strict/options.json | 1 + .../pre-after-borrowed-closing-end.html | 3 + .../pre-after-borrowed-closing-end.html.snap | 24 + .../fixtures/html/strict/self-closing.html | 4 + .../html/strict/self-closing.html.snap | 23 + .../html/svg-text-content-inline.html | 4 + .../html/svg-text-content-inline.html.snap | 56 ++ .../tests/fixtures/html/tag-name-case.xhtml | 1 + .../fixtures/html/tag-name-case.xhtml.snap | 18 + .../fixtures/html/text-display-width.html | 1 + .../html/text-display-width.html.snap | 18 + .../html/unquoted-value-with-slash.html | 2 + .../html/unquoted-value-with-slash.html.snap | 21 + .../end-tag-indentation-as-printed.html | 7 + .../end-tag-indentation-as-printed.html.snap | 36 + .../tests/fixtures/html/use-tabs/options.json | 1 + .../fixtures/html/whitespace-only-pre.html | 2 + .../html/whitespace-only-pre.html.snap | 21 + .../html/written-names-and-end-tags.html | 5 + .../html/written-names-and-end-tags.html.snap | 32 + .../tests/fixtures/mod.rs | 67 ++ .../tests/fixtures/options.rs | 36 + .../tests/fixtures/vue/doctype-case.vue | 2 + .../tests/fixtures/vue/doctype-case.vue.snap | 29 + .../fixtures/vue/vue-element-gap-kind.vue | 10 + .../vue/vue-element-gap-kind.vue.snap | 52 ++ crates/oxc_formatter_htmlish/tests/mod.rs | 1 + .../snapshots/conformance__prettier-html.snap | 15 + 123 files changed, 6750 insertions(+), 6 deletions(-) create mode 100644 crates/oxc_formatter_htmlish/AGENTS.md create mode 100644 crates/oxc_formatter_htmlish/Cargo.toml create mode 100644 crates/oxc_formatter_htmlish/DIVERGENCES.md create mode 100644 crates/oxc_formatter_htmlish/build.rs create mode 100644 crates/oxc_formatter_htmlish/examples/htmlish_formatter.rs create mode 100644 crates/oxc_formatter_htmlish/src/context.rs create mode 100644 crates/oxc_formatter_htmlish/src/format.rs create mode 100644 crates/oxc_formatter_htmlish/src/lib.rs create mode 100644 crates/oxc_formatter_htmlish/src/options.rs create mode 100644 crates/oxc_formatter_htmlish/src/print/attribute.rs create mode 100644 crates/oxc_formatter_htmlish/src/print/block.rs create mode 100644 crates/oxc_formatter_htmlish/src/print/children.rs create mode 100644 crates/oxc_formatter_htmlish/src/print/element.rs create mode 100644 crates/oxc_formatter_htmlish/src/print/mod.rs create mode 100644 crates/oxc_formatter_htmlish/src/print/tag.rs create mode 100644 crates/oxc_formatter_htmlish/src/print/text.rs create mode 100644 crates/oxc_formatter_htmlish/src/rules.rs create mode 100644 crates/oxc_formatter_htmlish/src/tree/build.rs create mode 100644 crates/oxc_formatter_htmlish/src/tree/display.rs create mode 100644 crates/oxc_formatter_htmlish/src/tree/mod.rs create mode 100644 crates/oxc_formatter_htmlish/src/tree/names.rs create mode 100644 crates/oxc_formatter_htmlish/tests/conformance.rs create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/angular/angular-block-name-with-line-break.component.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/angular/angular-block-name-with-line-break.component.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/angular/angular-glued-block-markers.component.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/angular/angular-glued-block-markers.component.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/angular/attribute-name-case.component.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/angular/attribute-name-case.component.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/angular/blocks.component.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/angular/blocks.component.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/angular/bracketed-attribute.component.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/angular/bracketed-attribute.component.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/angular/empty-block.component.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/angular/empty-block.component.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/fingerprint.rs create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/basic.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/basic.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/cdata-as-text.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/cdata-as-text.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/cdata-as-written.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/cdata-as-written.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/class-ascii-whitespace.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/class-ascii-whitespace.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/crlf/options.json create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/crlf/verbatim.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/crlf/verbatim.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/dialog-out-of-flow.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/dialog-out-of-flow.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-html-in-one-pass.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-html-in-one-pass.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-in-xhtml.xhtml create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-in-xhtml.xhtml.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-indentation-as-printed.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-indentation-as-printed.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-with-space-is-comment.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-with-space-is-comment.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/htm-closing-tag-as-written.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/htm-closing-tag-as-written.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/ignore/cdata-merged-text.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/ignore/cdata-merged-text.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/ignore/options.json create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/ignored-closing-end-once.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/ignored-closing-end-once.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/line-break-before-borrowed-end.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/line-break-before-borrowed-end.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/lowercase-known-names.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/lowercase-known-names.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/merged-inline-element.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/merged-inline-element.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/nul/nul-as-written.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/nul/nul-as-written.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/omitted-end-tag-at-eof.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/omitted-end-tag-at-eof.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/omitted-end-tag.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/omitted-end-tag.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/pre-content-without-end-tag.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/pre-content-without-end-tag.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/prefixed-script-style.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/prefixed-script-style.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/prettier-ignore-attribute.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/prettier-ignore-attribute.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/script-display-none.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/script-display-none.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing-with-end-tag.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing-with-end-tag.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/srcset-zero-density.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/srcset-zero-density.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/strict/line-break-before-borrowed-end.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/strict/line-break-before-borrowed-end.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/strict/options.json create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/strict/pre-after-borrowed-closing-end.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/strict/pre-after-borrowed-closing-end.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/strict/self-closing.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/strict/self-closing.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/svg-text-content-inline.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/svg-text-content-inline.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/tag-name-case.xhtml create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/tag-name-case.xhtml.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/text-display-width.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/text-display-width.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/unquoted-value-with-slash.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/unquoted-value-with-slash.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/use-tabs/end-tag-indentation-as-printed.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/use-tabs/end-tag-indentation-as-printed.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/use-tabs/options.json create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/whitespace-only-pre.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/whitespace-only-pre.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/written-names-and-end-tags.html create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/html/written-names-and-end-tags.html.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/mod.rs create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/options.rs create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/vue/doctype-case.vue create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/vue/doctype-case.vue.snap create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/vue/vue-element-gap-kind.vue create mode 100644 crates/oxc_formatter_htmlish/tests/fixtures/vue/vue-element-gap-kind.vue.snap create mode 100644 crates/oxc_formatter_htmlish/tests/mod.rs create mode 100644 crates/oxc_formatter_htmlish/tests/snapshots/conformance__prettier-html.snap diff --git a/.gitattributes b/.gitattributes index f566f3343d..6168438f90 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,6 +5,10 @@ crates/oxc_formatter_json/tests/fixtures/** text=auto eol=lf crates/oxc_formatter_json/tests/fixtures/jsonc/crlf/** text=auto eol=crlf crates/oxc_formatter_graphql/tests/fixtures/** text=auto eol=lf crates/oxc_formatter_graphql/tests/fixtures/graphql/crlf/** text=auto eol=crlf +crates/oxc_formatter_htmlish/tests/fixtures/** text=auto eol=lf +crates/oxc_formatter_htmlish/tests/fixtures/html/crlf/** text=auto eol=crlf +# NUL bytes would make git read these as binary +crates/oxc_formatter_htmlish/tests/fixtures/html/nul/** text eol=lf diff crates/oxc_formatter_yaml/tests/fixtures/** text=auto eol=lf crates/oxc_formatter_yaml/tests/fixtures/yaml/crlf/** text=auto eol=crlf crates/oxc_formatter_markdown/tests/fixtures/** text=auto eol=lf diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7291cd5f3c..18fb8cb4d8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -31,6 +31,7 @@ pnpm-lock.yaml /crates/oxc_formatter_core @leaysgur /crates/oxc_formatter_json @leaysgur /crates/oxc_formatter_graphql @leaysgur +/crates/oxc_formatter_htmlish @leaysgur /crates/oxc_formatter_css @leaysgur /crates/oxc_formatter_yaml @leaysgur /crates/oxc_formatter_markdown @leaysgur diff --git a/Cargo.lock b/Cargo.lock index 558dd7e918..ff61273c36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,7 +280,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -682,7 +682,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1517,7 +1517,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1703,6 +1703,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "oxc-htmlish-parser" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909fd32d29f076e5129a27244801bed7de5ce33a974310c710f8777dee016495" +dependencies = [ + "memchr", + "oxc_allocator", +] + [[package]] name = "oxc-markdown-parser" version = "0.0.3" @@ -2123,6 +2133,23 @@ dependencies = [ "pico-args", ] +[[package]] +name = "oxc_formatter_htmlish" +version = "0.75.0" +dependencies = [ + "cow-utils", + "insta", + "memchr", + "oxc-htmlish-parser", + "oxc_allocator", + "oxc_diagnostics", + "oxc_formatter_core", + "oxc_formatter_tests", + "oxc_span", + "pico-args", + "zmij_ecma", +] + [[package]] name = "oxc_formatter_json" version = "0.75.0" @@ -3379,7 +3406,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3798,7 +3825,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4332,7 +4359,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4e58d94ea0..483647a5ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -242,6 +242,7 @@ num-bigint = "0.5.0" # Big integers num-traits = "0.2.19" # Numeric traits oxc-css-parser = "0.0.18" # CSS/SCSS/Less parser (raffia 0.12.3 fork: adds `template_placeholder` typed placeholders for css-in-js + valid-syntax coverage fixes (see crates/oxc_formatter_css/AGENTS.md)) oxc-graphql-parser = "0.0.9" # GraphQL parser (direct AST; apollo-parser 0.8.6 fork aligned with graphql-js 17 syntax) +oxc-htmlish-parser = "0.0.1" # HTML / Vue / Angular parser (loose, span-faithful AST) oxc-markdown-parser = "0.0.3" # CommonMark + GFM parser (span-faithful, style-preserving typed AST) oxc-toml = "0.15.1" # TOML parser and formatter oxc-yaml-parser = "0.0.7" # YAML 1.2 parser (comment-preserving typed AST, node naming from `yaml-unist-parser`) diff --git a/crates/oxc_formatter_htmlish/AGENTS.md b/crates/oxc_formatter_htmlish/AGENTS.md new file mode 100644 index 0000000000..e2db192406 --- /dev/null +++ b/crates/oxc_formatter_htmlish/AGENTS.md @@ -0,0 +1,53 @@ +# Coding agent guides for `crates/oxc_formatter_htmlish` + +Follow @../oxc_formatter_core/FORMATTER_POLICY.md , this file holds only the htmlish-specific rules. +Known divergences live in DIVERGENCES.md. + +## Overview + +Prettier compatible formatter for HTML and the template languages built on it (Vue, Angular, ...), on `oxc_formatter_core`. + +### Parser + +Parses with [`oxc-htmlish-parser`](https://github.com/oxc-project/oxc-htmlish-parser). +Parse behavior and its own divergences are that repo's `AGENTS.md` / `DIVERGENCES.md`. + +### Dialects + +What a dialect changes in the shared rules is a `DialectRules` field (`src/rules.rs`), resolved once per document; +a dialect adds a field there, not a `Dialect` check at the use site. + +### Preprocess + +The facts Prettier's `print-preprocess` computes (CSS display, whitespace sensitivity, kept children, borrowed tag markers) +are a table keyed by `NodeId` (`src/tree/`). +The AST is never rewritten. + +### Tags + +An end tag the source omits is never added (DIVERGENCES.md#unclosed-at-end, DIVERGENCES.md#omitted-end-tag). +A normalization is applied only where it is known safe for every reader, as in `oxc_formatter_css` (its AGENTS.md "Case"): +a void element prints `/>`, a doctype `` but in XHTML, which an XML parser may read (DIVERGENCES.md#doctype-case), +and the tag and attribute names Prettier knows print lowercase in a `.html` file only (DIVERGENCES.md#tag-name-case). + +The borrow rules (`src/print/tag.rs`) are shaped like Prettier's `print/tag.js` against the policy, kept deliberately (why: its module doc). + +## Verification + +```sh +cargo run -p oxc_formatter_htmlish --example htmlish_formatter [filename] +DUMP_IR=1 cargo run -p oxc_formatter_htmlish --example htmlish_formatter [filename] + +# The oracle. The parser follows the file name as the example's variant does (`*.component.html` is angular). +node apps/oxfmt/node_modules/prettier/bin/prettier.cjs [filename] +``` + +### Prettier conformance + +`tests/conformance.rs` runs `tests/format/html`. +Files whose output depends on embedded formatting (`b

+ + +

+ ab +

+ + +

+ a + + b +

+``` + +```html + +
Price: 10 USD, and some more text that wraps the line
+ + +
+ Price: 10 USD, + and some more text that wraps the line +
+ + +
+ Price: 10 USD, and + some more text that wraps the line +
+``` + +An element with the UA stylesheet's `display: none` (`script`, `style`, `link`, `meta`, `title`, ...) renders nothing: +the whitespace on both its sides is one run between the content around it, judged as if the element were not there. +- Between inline content, a run without whitespace stays glued (`>` borrowed); + one with whitespace may break at any of its gaps, as it renders one space either way. +- Next to a block or a parent's edge the run renders nothing and breaks freely, + though a gap between two non-blocks stays on the line where it fits (`

x

`). + +Prettier lays `script` out as a block and puts it on its own line, which turns `a` and `b` glued around it into `a b`: +it did so for glued scripts between blocks (`b` puts the `script` on its own line). + /// A run that renders nothing keeps its gaps as written, or a line break printed next to a block would spread next time. + /// A gap's two sides agree on spaces and sensitivity, so a pass each way carries it. + fn spread_spaces_past_hidden(&mut self, parent: NodeId) { + let mut next = self.tree[parent].first_child; + while let Some(id) = next { + next = self.tree[id].next; + let facts = &self.tree[id]; + if self.is_hidden(id) && facts.has_leading_spaces && facts.leading_sensitive { + self.facts(id).has_trailing_spaces = true; + if let Some(next) = next { + self.facts(next).has_leading_spaces = true; + } + } + } + let mut prev = self.tree[parent].last_child; + while let Some(id) = prev { + prev = self.tree[id].prev; + let facts = &self.tree[id]; + if self.is_hidden(id) && facts.has_trailing_spaces && facts.trailing_sensitive { + self.facts(id).has_leading_spaces = true; + if let Some(prev) = prev { + self.facts(prev).has_trailing_spaces = true; + } + } + } + } + + /// Where known names print lowercase (`DialectRules::lowercase_known_names`): + /// marks foreign content, under which they keep their case, and a tag name written in another case. + fn mark_name_case(&mut self, id: NodeId, parent: NodeId) { + let t = &self.tree; + let Some(el) = t.element(id) else { return }; + let foreign = t[parent].foreign || el.namespace != Namespace::Html; + let uppercase = |name: Span| name.slice(t.source).bytes().any(|b| b.is_ascii_uppercase()); + let lowercase_name = (uppercase(el.name) + || el.end_tag.is_some_and(|tag| uppercase(tag.name))) + && t.lowercases_names(id, el); + let facts = self.facts(id); + facts.foreign = foreign; + facts.lowercase_name = lowercase_name; + } + + /// A node's display, also for one in a block whose children are not built yet. + fn display_of(&self, id: NodeId) -> Display { + if self.tree[id].parent.is_some() { self.tree[id].display } else { self.display(id) } + } + + /// The nearest box: `id`, or the nearest ancestor that is not a block. + fn flow_parent(&self, mut id: NodeId) -> NodeId { + while self.tree.is_block(id) { + id = self.tree.parent(id); + } + id + } +} + +fn is_whitespace_text(node: &Node<'_>) -> bool { + matches!(&node.kind, NodeKind::Text(text) if text.is_whitespace_only()) +} + +/// CSS `white-space: pre*`. +fn is_pre_like(el: &Element<'_>) -> bool { + el.namespace != Namespace::Svg + && matches!(el.key, "pre" | "textarea" | "listing" | "plaintext" | "xmp") +} + +/// A void element, or one written `/>` without an end tag. +/// +/// HTML ignores the `/` of `` and an ancestor's end tag closes it, but the tag reads as self-closing (as Prettier reads it): +/// its content follows it as siblings (`next_child`). +/// The text prints the same either way; whitespace inside such an element is judged as whitespace between siblings, +/// so `
⏎
` drops the `⏎` HTML puts in `my-el`, collapsible at the end of a block and never rendered. +/// Except in `
`, whose content keeps its whitespace: re-indented as siblings, it would render differently
+/// (DIVERGENCES.md#pre-content-without-end-tag).
+fn closes_at_start_tag(node: &Node<'_>) -> bool {
+    let NodeKind::Element(el) = &node.kind else { return false };
+    el.void
+        || (el.self_closing
+            && el.end_tag.is_none()
+            && (!is_pre_like(el) || node.first_child.is_none()))
+}
+
+/// The parser's first child, unless the content follows the element as siblings (`next_child`).
+fn printed_first_child(node: &Node<'_>) -> Option {
+    node.first_child.filter(|_| !closes_at_start_tag(node))
+}
+
+/// The node after `id` among `parent`'s children,
+/// with the content of an element whose start tag closes it following it as siblings.
+fn next_child(root: &Root<'_>, parent: NodeId, mut id: NodeId) -> Option {
+    if closes_at_start_tag(&root[id])
+        && let Some(first) = root[id].first_child
+    {
+        return Some(first);
+    }
+    loop {
+        if let Some(next) = root[id].next {
+            return Some(next);
+        }
+        id = root[id].parent.filter(|&up| up != parent)?;
+    }
+}
diff --git a/crates/oxc_formatter_htmlish/src/tree/display.rs b/crates/oxc_formatter_htmlish/src/tree/display.rs
new file mode 100644
index 0000000000..51df878234
--- /dev/null
+++ b/crates/oxc_formatter_htmlish/src/tree/display.rs
@@ -0,0 +1,73 @@
+/// The CSS `display` an element gets from the UA stylesheet (Prettier's table, `constants.evaluate.js`).
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub enum Display {
+    #[default]
+    Inline,
+    Block,
+    InlineBlock,
+    ListItem,
+    TableCell,
+    /// Every other `table*` value.
+    Table,
+    None,
+    /// `contents`, `ruby`, or a value from a `` comment.
+    Other,
+}
+
+impl Display {
+    fn block_like(self) -> bool {
+        matches!(self, Self::Block | Self::ListItem | Self::Table | Self::TableCell)
+    }
+
+    /// Whitespace at the start of the first child / the end of the last child / of an empty element is significant.
+    pub fn edges_sensitive(self) -> bool {
+        !self.block_like() && self != Self::InlineBlock
+    }
+
+    /// Whitespace between this and a sibling is significant.
+    pub fn sides_sensitive(self) -> bool {
+        !self.block_like()
+    }
+}
+
+/// `` (Prettier's `/^\s*display:\s*([a-z]+)\s*$/`).
+pub fn display_comment(content: &str) -> Option {
+    let value = content.trim().strip_prefix("display:")?.trim_start();
+    if value.is_empty() || !value.bytes().all(|b| b.is_ascii_lowercase()) {
+        return None;
+    }
+    Some(match value {
+        "block" => Display::Block,
+        "inline" => Display::Inline,
+        "table" => Display::Table,
+        "none" => Display::None,
+        _ => Display::Other,
+    })
+}
+
+/// The UA stylesheet's `display` for an HTML element, as Prettier tabulates it.
+pub fn ua_display(tag: &str) -> Display {
+    match tag {
+        // `script` as the UA has it;
+        // Prettier lays it out as a block (DIVERGENCES.md#script-display-none).
+        "area" | "base" | "basefont" | "datalist" | "head" | "link" | "meta" | "noembed"
+        | "noframes" | "rp" | "script" | "style" | "title" => Display::None,
+        "param" | "html" | "body" | "address" | "blockquote" | "center" | "div" | "figure"
+        | "figcaption" | "footer" | "form" | "header" | "hr" | "legend" | "listing" | "main"
+        | "p" | "plaintext" | "pre" | "search" | "xmp" | "article" | "aside" | "h1" | "h2"
+        | "h3" | "h4" | "h5" | "h6" | "hgroup" | "nav" | "section" | "dir" | "dd" | "dl" | "dt"
+        | "menu" | "ol" | "ul" | "fieldset" | "details" | "summary" | "option" | "optgroup"
+        | "source" | "track" => Display::Block,
+        "slot" | "ruby" | "rt" => Display::Other,
+        "li" => Display::ListItem,
+        "table" | "caption" | "colgroup" | "col" | "thead" | "tbody" | "tfoot" | "tr" => {
+            Display::Table
+        }
+        "td" | "th" => Display::TableCell,
+        // `dialog`: out of flow (`display: none` closed, `position: absolute` open), with a box for its content;
+        // Prettier lays it out as a block (DIVERGENCES.md#dialog-out-of-flow).
+        "input" | "button" | "marquee" | "select" | "meter" | "progress" | "object" | "video"
+        | "audio" | "dialog" => Display::InlineBlock,
+        _ => Display::Inline,
+    }
+}
diff --git a/crates/oxc_formatter_htmlish/src/tree/mod.rs b/crates/oxc_formatter_htmlish/src/tree/mod.rs
new file mode 100644
index 0000000000..6c71a6840e
--- /dev/null
+++ b/crates/oxc_formatter_htmlish/src/tree/mod.rs
@@ -0,0 +1,317 @@
+//! The facts Prettier's `print-preprocess` attaches to its tree, as a table keyed by the parser's [`NodeId`].
+//!
+//! The AST is not rewritten; names, kinds and the tag table's verdict are read from it.
+//! What the table adds:
+//! - the nodes that print, linked by `parent` / `prev` / `next` / `first_child` / `last_child`:
+//!   a whitespace-only text of a whitespace-insensitive parent is skipped (it becomes `has_*_spaces` of its neighbors),
+//!   and the content of an element that reads as self-closing (``) follows it as siblings;
+//! - CSS display and whitespace sensitivity, which depend on the options;
+//! - facts the printer asks repeatedly (self-closing, `ends_in_text_like`, `prettier-ignore`).
+
+mod build;
+mod display;
+mod names;
+
+use oxc_allocator::{Allocator, ArenaVec, StringBuilder};
+use oxc_htmlish_parser::{
+    Span,
+    ast::{Element, Namespace, NodeId, NodeKind, Root},
+};
+
+use crate::rules::DialectRules;
+
+pub use display::Display;
+
+#[derive(Clone, Copy, Default)]
+pub struct Facts {
+    /// Prettier's `sourceSpan`: a text in a whitespace-insensitive parent is trimmed.
+    pub span: Span,
+    /// The parent, siblings and children that print.
+    pub parent: Option,
+    pub prev: Option,
+    pub next: Option,
+    pub first_child: Option,
+    pub last_child: Option,
+    pub display: Display,
+    /// Text and comments: printed without tags whose markers could be borrowed.
+    pub text_like: bool,
+    /// No children and no end tag: the start tag closes the node (Prettier's `isSelfClosing`).
+    /// Text-like nodes count too.
+    pub self_closing: bool,
+    /// CSS `white-space: pre*`: `pre`, `textarea`, `listing`, `plaintext`, `xmp`, outside SVG.
+    /// Children keep their whitespace and indentation.
+    pub pre_like: bool,
+    /// `script` / `style`: children keep their whitespace.
+    pub script_like: bool,
+    /// The deepest last descendant is text-like, or the node itself is
+    /// (an interpolation ends in its text).
+    pub ends_in_text_like: bool,
+    /// The node is a `pre` without an end tag, or ends in one through its last children without end tags:
+    /// what follows it up to the end tag that closes them is `pre` content.
+    pub ends_in_pre: bool,
+    /// A CDATA section, or a text that merged one: its sections are never re-wrapped.
+    pub has_cdata: bool,
+    /// The previous sibling is ``.
+    pub prettier_ignore: bool,
+    /// An SVG / MathML element, or one under it (`DialectRules::lowercase_known_names` only).
+    pub foreign: bool,
+    /// The tag name prints lowercase, written otherwise in the start or end tag (`Tree::printed_name`).
+    pub lowercase_name: bool,
+    pub has_leading_spaces: bool,
+    pub has_trailing_spaces: bool,
+    pub leading_sensitive: bool,
+    pub trailing_sensitive: bool,
+    /// An element without children had only whitespace inside.
+    pub has_dangling_spaces: bool,
+    pub dangling_sensitive: bool,
+    /// An element whose start tag absorbs `` right before it and `` right after it
+    /// (``): those two comments, which do not print on their own.
+    /// The start comment; the end comment is the element's first child.
+    pub condition: Option,
+    pub borrows: Borrows,
+}
+
+/// The tag markers a node prints for its neighbors (the borrow rules in `print/tag.rs`);
+/// the owners omit them (`*_borrowed`).
+#[derive(Clone, Copy, Default)]
+pub struct Borrows {
+    /// `123`: the first child prints its parent's opening `>`.
+    pub parent_opening_end: bool,
+    /// `

123`: the previous sibling's closing `>`. + pub prev_closing_end: bool, + /// ``: the last child's closing `>`, before the node's own end tag. + pub last_child_closing_end: bool, + /// `123`: the last text prints its parent's ``: the text prints its next sibling's ` bool { + self.pre_like || self.script_like + } + + /// Whitespace before the node is significant and there is none. + pub fn glued_before(&self) -> bool { + self.leading_sensitive && !self.has_leading_spaces + } + + /// Whitespace after the node is significant and there is none. + pub fn glued_after(&self) -> bool { + self.trailing_sensitive && !self.has_trailing_spaces + } + + /// Exactly one child prints. + pub fn only_child(&self) -> Option { + self.first_child.filter(|_| self.first_child == self.last_child) + } +} + +pub struct Tree<'a> { + allocator: &'a Allocator, + rules: DialectRules, + root: &'a Root<'a>, + source: &'a str, + facts: ArenaVec<'a, Facts>, + /// Texts whose merged value differs from their source + /// (an element merged in had whitespace in a tag), sorted by `NodeId`. + merged_texts: ArenaVec<'a, (NodeId, &'a str)>, +} + +impl std::ops::Index for Tree<'_> { + type Output = Facts; + + fn index(&self, id: NodeId) -> &Facts { + &self.facts[id.index()] + } +} + +impl<'a> Tree<'a> { + pub fn source(&self) -> &'a str { + self.source + } + + pub fn kind(&self, id: NodeId) -> &'a NodeKind<'a> { + &self.root[id].kind + } + + pub fn rules(&self) -> DialectRules { + self.rules + } + + /// An Angular control flow block: transparent, its markers print no box (`@if (a) {` … `}`). + pub fn is_block(&self, id: NodeId) -> bool { + self.rules.has_blocks && matches!(self.kind(id), NodeKind::Block(_)) + } + + /// A `display: none` element in a parent that renders: the whitespace on both its sides is one run + /// (DIVERGENCES.md#script-display-none). + #[inline] + pub fn is_transparent(&self, id: NodeId) -> bool { + self[id].display == Display::None + && self.element(id).is_some() + && self[self.parent(id)].display != Display::None + } + + pub fn is_interpolation(&self, id: NodeId) -> bool { + matches!(self.kind(id), NodeKind::Interpolation(_)) + } + + /// A text or a CDATA section, which Prettier merges into text (`mergeCdataIntoText`). + pub fn is_text(&self, id: NodeId) -> bool { + matches!(self.kind(id), NodeKind::Text(_) | NodeKind::Cdata) + } + + pub fn element(&self, id: NodeId) -> Option<&'a Element<'a>> { + match &self.root[id].kind { + NodeKind::Element(el) => Some(el), + _ => None, + } + } + + /// The tag name to print (DIVERGENCES.md#tag-name-case); empty for other nodes. + #[inline] + pub fn name(&self, id: NodeId) -> &'a str { + self.element(id).map_or("", |el| self.printed_name(id, el, el.name.slice(self.source))) + } + + /// A start or end tag's name `written`, or the element's lowercase name where it prints so (DIVERGENCES.md#tag-name-case); + /// htm's `` closes as written. + #[inline] + pub fn printed_name(&self, id: NodeId, el: &Element<'a>, written: &'a str) -> &'a str { + if self[id].lowercase_name && written != "/" { el.key } else { written } + } + + /// An attribute's name written `written` to print: as written, or Prettier's known name in lowercase + /// (DIVERGENCES.md#tag-name-case). + #[inline] + pub fn attribute_name(&self, element: NodeId, written: &'a str) -> &'a str { + if !self.rules.lowercase_known_names || !written.bytes().any(|b| b.is_ascii_uppercase()) { + return written; + } + match self.element(element) { + Some(el) if self.lowercases_names(element, el) => { + names::known_attribute(el.key, written).unwrap_or(written) + } + _ => written, + } + } + + /// Whether the element's known names print lowercase: a known HTML element outside foreign content. + /// `svg` / `math` themselves are known; under them Prettier keeps every name, `foreignObject`'s HTML too. + pub fn lowercases_names(&self, id: NodeId, el: &Element<'a>) -> bool { + !self[id].parent.is_some_and(|parent| self[parent].foreign) + && (el.namespace == Namespace::Html || matches!(el.key, "svg" | "math")) + && names::is_known_element(el.key) + } + + /// Whether an attribute written `written` is the one named `name` (lowercase). + /// Prettier lowercases only the attributes it knows for the element; any name here is close enough. + pub fn attribute_is(&self, written: &str, name: &str) -> bool { + if self.rules.names_ignore_case { + written.eq_ignore_ascii_case(name) + } else { + written == name + } + } + + /// A text's value: its source, unless a merge rewrote it. + pub fn text(&self, id: NodeId) -> &'a str { + match self.merged_texts.binary_search_by_key(&id, |(text, _)| *text) { + Ok(index) => self.merged_texts[index].1, + Err(_) => self[id].span.slice(self.source), + } + } + + /// A conditional comment's condition or a doctype's value, whitespace collapsed. + pub fn collapsed(&self, span: Span) -> &'a str { + collapse_whitespace(self.allocator, span.slice(self.source)) + } + + /// The name the HTML tables are keyed by (the parser's, per dialect); empty for other nodes. + pub fn tag(&self, id: NodeId) -> &'a str { + self.element(id).map_or("", |el| el.key) + } + + pub fn parent(&self, id: NodeId) -> NodeId { + self[id].parent.expect("only the document has no parent") + } + + /// The printed children, in order. + pub fn children(&self, id: NodeId) -> impl Iterator + '_ { + std::iter::successors(self[id].first_child, move |&child| self[child].next) + } + + /// Where the content between the tags starts (Prettier's `startSourceSpan.end`). + pub fn content_start(&self, id: NodeId) -> u32 { + if self[id].condition.is_some() + && let Some(end) = self.root[id].first_child + { + return self.root[end].span.end; + } + match &self.root[id].kind { + NodeKind::Element(el) => el.start_tag.end, + NodeKind::ConditionalComment(comment) => comment.content.start, + NodeKind::Block(block) => block.open.end, + _ => self[id].span.start, + } + } + + /// Where the end tag starts, when there is one (Prettier's `endSourceSpan.start`). + pub fn end_tag_start(&self, id: NodeId) -> Option { + match &self.root[id].kind { + NodeKind::Element(el) => el.end_tag.map(|tag| tag.span.start), + NodeKind::ConditionalComment(comment) => Some(comment.content.end), + NodeKind::Block(block) => block.close.map(|close| close.start), + _ => None, + } + } + + /// An element without an end tag (closed by an ancestor's, a sibling's or the end of input) + /// that does not read as self-closing. + pub fn is_unclosed(&self, id: NodeId) -> bool { + self.element(id).is_some_and(|el| el.end_tag.is_none()) && !self[id].self_closing + } + + /// Where Prettier's `sourceSpan` of the node ends: + /// an element without an end tag ends at its start tag, children aside. + pub fn source_end(&self, id: NodeId) -> u32 { + match &self.root[id].kind { + NodeKind::Element(el) if el.end_tag.is_none() => el.start_tag.end, + _ => self[id].span.end, + } + } + + /// Line breaks in `[from, to)`, counted up to 2: + /// Prettier compares line numbers, so a gap is on the same line, the next line, or further. + pub fn line_breaks(&self, from: u32, to: u32) -> usize { + if from >= to { + return 0; + } + memchr::memchr_iter(b'\n', &self.source.as_bytes()[from as usize..to as usize]) + .take(2) + .count() + } +} + +/// `text` trimmed, with its whitespace runs collapsed to single spaces; borrowed when already so. +pub fn collapse_whitespace<'a>(allocator: &'a Allocator, text: &'a str) -> &'a str { + let text = text.trim_ascii(); + let collapsed = text + .as_bytes() + .windows(2) + .all(|pair| !pair[1].is_ascii_whitespace() || (pair[1] == b' ' && pair[0] != b' ')); + if collapsed { + return text; + } + let mut out = StringBuilder::new_in(allocator); + for (i, word) in text.split_ascii_whitespace().enumerate() { + if i > 0 { + out.push(' '); + } + out.push_str(word); + } + out.into_str() +} diff --git a/crates/oxc_formatter_htmlish/src/tree/names.rs b/crates/oxc_formatter_htmlish/src/tree/names.rs new file mode 100644 index 0000000000..77eb4bf106 --- /dev/null +++ b/crates/oxc_formatter_htmlish/src/tree/names.rs @@ -0,0 +1,586 @@ +//! The names Prettier's HTML parser lowercases (DIVERGENCES.md#tag-name-case): +//! `@prettier/html-tags` 2.2.0 and `html-element-attributes` 3.5.0, as the pin bundles them. +//! Read only for a name holding an uppercase letter, so the tables are sorted slices. + +const ELEMENTS: &[&str] = &[ + "a", + "abbr", + "acronym", + "address", + "applet", + "area", + "article", + "aside", + "audio", + "b", + "base", + "basefont", + "bdi", + "bdo", + "bgsound", + "big", + "blink", + "blockquote", + "body", + "br", + "button", + "canvas", + "caption", + "center", + "cite", + "code", + "col", + "colgroup", + "command", + "content", + "data", + "datalist", + "dd", + "del", + "details", + "dfn", + "dialog", + "dir", + "div", + "dl", + "dt", + "em", + "embed", + "fencedframe", + "fieldset", + "figcaption", + "figure", + "font", + "footer", + "form", + "frame", + "frameset", + "geolocation", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hgroup", + "hr", + "html", + "i", + "iframe", + "image", + "img", + "input", + "ins", + "isindex", + "kbd", + "keygen", + "label", + "legend", + "li", + "link", + "listing", + "main", + "map", + "mark", + "marquee", + "math", + "menu", + "menuitem", + "meta", + "meter", + "multicol", + "nav", + "nextid", + "nobr", + "noembed", + "noframes", + "noscript", + "object", + "ol", + "optgroup", + "option", + "output", + "p", + "param", + "picture", + "plaintext", + "pre", + "progress", + "q", + "rb", + "rbc", + "rp", + "rt", + "rtc", + "ruby", + "s", + "samp", + "script", + "search", + "section", + "select", + "selectedcontent", + "shadow", + "slot", + "small", + "source", + "spacer", + "span", + "strike", + "strong", + "style", + "sub", + "summary", + "sup", + "svg", + "table", + "tbody", + "td", + "template", + "textarea", + "tfoot", + "th", + "thead", + "time", + "title", + "tr", + "track", + "tt", + "u", + "ul", + "var", + "video", + "wbr", + "xmp", +]; + +const GLOBAL_ATTRIBUTES: &[&str] = &[ + "accesskey", + "autocapitalize", + "autocorrect", + "autofocus", + "class", + "contenteditable", + "dir", + "draggable", + "enterkeyhint", + "exportparts", + "hidden", + "id", + "inert", + "inputmode", + "is", + "itemid", + "itemprop", + "itemref", + "itemscope", + "itemtype", + "lang", + "nonce", + "part", + "popover", + "slot", + "spellcheck", + "style", + "tabindex", + "title", + "translate", + "writingsuggestions", +]; + +/// By element, each list sorted. +const ELEMENT_ATTRIBUTES: &[(&str, &[&str])] = &[ + ( + "a", + &[ + "charset", + "coords", + "download", + "href", + "hreflang", + "name", + "ping", + "referrerpolicy", + "rel", + "rev", + "shape", + "target", + "type", + ], + ), + ( + "applet", + &[ + "align", "alt", "archive", "code", "codebase", "height", "hspace", "name", "object", + "vspace", "width", + ], + ), + ( + "area", + &[ + "alt", + "coords", + "download", + "href", + "hreflang", + "nohref", + "ping", + "referrerpolicy", + "rel", + "shape", + "target", + "type", + ], + ), + ("audio", &["autoplay", "controls", "crossorigin", "loop", "muted", "preload", "src"]), + ("base", &["href", "target"]), + ("basefont", &["color", "face", "size"]), + ("blockquote", &["cite"]), + ("body", &["alink", "background", "bgcolor", "link", "text", "vlink"]), + ("br", &["clear"]), + ( + "button", + &[ + "command", + "commandfor", + "disabled", + "form", + "formaction", + "formenctype", + "formmethod", + "formnovalidate", + "formtarget", + "name", + "popovertarget", + "popovertargetaction", + "type", + "value", + ], + ), + ("canvas", &["height", "width"]), + ("caption", &["align"]), + ("col", &["align", "char", "charoff", "span", "valign", "width"]), + ("colgroup", &["align", "char", "charoff", "span", "valign", "width"]), + ("data", &["value"]), + ("del", &["cite", "datetime"]), + ("details", &["name", "open"]), + ("dialog", &["closedby", "open"]), + ("dir", &["compact"]), + ("div", &["align"]), + ("dl", &["compact"]), + ("embed", &["height", "src", "type", "width"]), + ("fieldset", &["disabled", "form", "name"]), + ("font", &["color", "face", "size"]), + ( + "form", + &[ + "accept", + "accept-charset", + "action", + "autocomplete", + "enctype", + "method", + "name", + "novalidate", + "target", + ], + ), + ( + "frame", + &[ + "frameborder", + "longdesc", + "marginheight", + "marginwidth", + "name", + "noresize", + "scrolling", + "src", + ], + ), + ("frameset", &["cols", "rows"]), + ("h1", &["align"]), + ("h2", &["align"]), + ("h3", &["align"]), + ("h4", &["align"]), + ("h5", &["align"]), + ("h6", &["align"]), + ("head", &["profile"]), + ("hr", &["align", "noshade", "size", "width"]), + ("html", &["manifest", "version"]), + ( + "iframe", + &[ + "align", + "allow", + "allowfullscreen", + "allowpaymentrequest", + "allowusermedia", + "frameborder", + "height", + "loading", + "longdesc", + "marginheight", + "marginwidth", + "name", + "referrerpolicy", + "sandbox", + "scrolling", + "src", + "srcdoc", + "width", + ], + ), + ( + "img", + &[ + "align", + "alt", + "border", + "crossorigin", + "decoding", + "fetchpriority", + "height", + "hspace", + "ismap", + "loading", + "longdesc", + "name", + "referrerpolicy", + "sizes", + "src", + "srcset", + "usemap", + "vspace", + "width", + ], + ), + ( + "input", + &[ + "accept", + "align", + "alpha", + "alt", + "autocomplete", + "checked", + "colorspace", + "dirname", + "disabled", + "form", + "formaction", + "formenctype", + "formmethod", + "formnovalidate", + "formtarget", + "height", + "ismap", + "list", + "max", + "maxlength", + "min", + "minlength", + "multiple", + "name", + "pattern", + "placeholder", + "popovertarget", + "popovertargetaction", + "readonly", + "required", + "size", + "src", + "step", + "type", + "usemap", + "value", + "width", + ], + ), + ("ins", &["cite", "datetime"]), + ("isindex", &["prompt"]), + ("label", &["for", "form"]), + ("legend", &["align"]), + ("li", &["type", "value"]), + ( + "link", + &[ + "as", + "blocking", + "charset", + "color", + "crossorigin", + "disabled", + "fetchpriority", + "href", + "hreflang", + "imagesizes", + "imagesrcset", + "integrity", + "media", + "referrerpolicy", + "rel", + "rev", + "sizes", + "target", + "type", + ], + ), + ("map", &["name"]), + ("menu", &["compact"]), + ("meta", &["charset", "content", "http-equiv", "media", "name", "scheme"]), + ("meter", &["high", "low", "max", "min", "optimum", "value"]), + ( + "object", + &[ + "align", + "archive", + "border", + "classid", + "codebase", + "codetype", + "data", + "declare", + "form", + "height", + "hspace", + "name", + "standby", + "type", + "typemustmatch", + "usemap", + "vspace", + "width", + ], + ), + ("ol", &["compact", "reversed", "start", "type"]), + ("optgroup", &["disabled", "label"]), + ("option", &["disabled", "label", "selected", "value"]), + ("output", &["for", "form", "name"]), + ("p", &["align"]), + ("param", &["name", "type", "value", "valuetype"]), + ("pre", &["width"]), + ("progress", &["max", "value"]), + ("q", &["cite"]), + ( + "script", + &[ + "async", + "blocking", + "charset", + "crossorigin", + "defer", + "fetchpriority", + "integrity", + "language", + "nomodule", + "referrerpolicy", + "src", + "type", + ], + ), + ("select", &["autocomplete", "disabled", "form", "multiple", "name", "required", "size"]), + ("slot", &["name"]), + ("source", &["height", "media", "sizes", "src", "srcset", "type", "width"]), + ("style", &["blocking", "media", "type"]), + ( + "table", + &[ + "align", + "bgcolor", + "border", + "cellpadding", + "cellspacing", + "frame", + "rules", + "summary", + "width", + ], + ), + ("tbody", &["align", "char", "charoff", "valign"]), + ( + "td", + &[ + "abbr", "align", "axis", "bgcolor", "char", "charoff", "colspan", "headers", "height", + "nowrap", "rowspan", "scope", "valign", "width", + ], + ), + ( + "template", + &[ + "shadowrootclonable", + "shadowrootcustomelementregistry", + "shadowrootdelegatesfocus", + "shadowrootmode", + "shadowrootserializable", + ], + ), + ( + "textarea", + &[ + "autocomplete", + "cols", + "dirname", + "disabled", + "form", + "maxlength", + "minlength", + "name", + "placeholder", + "readonly", + "required", + "rows", + "wrap", + ], + ), + ("tfoot", &["align", "char", "charoff", "valign"]), + ( + "th", + &[ + "abbr", "align", "axis", "bgcolor", "char", "charoff", "colspan", "headers", "height", + "nowrap", "rowspan", "scope", "valign", "width", + ], + ), + ("thead", &["align", "char", "charoff", "valign"]), + ("time", &["datetime"]), + ("tr", &["align", "bgcolor", "char", "charoff", "valign"]), + ("track", &["default", "kind", "label", "src", "srclang"]), + ("ul", &["compact", "type"]), + ( + "video", + &[ + "autoplay", + "controls", + "crossorigin", + "height", + "loop", + "muted", + "playsinline", + "poster", + "preload", + "src", + "width", + ], + ), +]; + +/// An HTML element Prettier knows, by its lowercase name. +pub fn is_known_element(lowercase: &str) -> bool { + ELEMENTS.binary_search(&lowercase).is_ok() +} + +/// The lowercase name of an attribute Prettier knows on `element` (lowercase), written `written` in any case: +/// a global one, but only on an element the attribute table lists. +pub fn known_attribute(element: &str, written: &str) -> Option<&'static str> { + let i = ELEMENT_ATTRIBUTES.binary_search_by_key(&element, |(name, _)| name).ok()?; + find(GLOBAL_ATTRIBUTES, written).or_else(|| find(ELEMENT_ATTRIBUTES[i].1, written)) +} + +fn find(names: &[&'static str], written: &str) -> Option<&'static str> { + names + .binary_search_by(|name| name.bytes().cmp(written.bytes().map(|b| b.to_ascii_lowercase()))) + .ok() + .map(|i| names[i]) +} diff --git a/crates/oxc_formatter_htmlish/tests/conformance.rs b/crates/oxc_formatter_htmlish/tests/conformance.rs new file mode 100644 index 0000000000..6ceb00c57c --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/conformance.rs @@ -0,0 +1,74 @@ +//! Prettier conformance for HTML. +//! +//! Compares output against the Prettier suite's `tests/format/html` snapshots via `oxc_formatter_tests::conformance`; +//! the failure report is pinned with `insta`. +//! +//! Debug a specific test: `PRETTIER_FILTER= cargo test -p oxc_formatter_htmlish --test conformance -- --nocapture` + +use std::path::Path; + +use oxc_allocator::Allocator; +use oxc_formatter_core::LineWidth; +use oxc_formatter_htmlish::{HtmlishFormatOptions, format}; +use oxc_formatter_tests::{ + OptionSet, + conformance::{ConformanceConfig, run_conformance}, +}; + +#[path = "fixtures/options.rs"] +mod options; +use options::apply_htmlish_options; + +const CONFIG: ConformanceConfig = ConformanceConfig { + language: "html", + fixture_roots: &["html"], + exact_parser: Some("html"), + ignore: &[ + // Cursor tracking is an editor feature, not formatting. + "html/cursor/", + // Embedded languages need the dispatcher; covered by oxfmt's E2E conformance. + "html/css/", + "html/js/", + "html/multiparser/", + "html/script/", + "html/svg/embeded/", + "html/bracket-same-line/embed.html", + "html/prettier_ignore/issue-15738.html", + "html/basics/html5-boilerplate.html", + "html/case/case.html", + // ` + +

x

+ + diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/crlf/verbatim.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/crlf/verbatim.html.snap new file mode 100644 index 0000000000..c36ecded4c --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/crlf/verbatim.html.snap @@ -0,0 +1,174 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +
+ +
+ kept as written +
+
+  line 1
+    line 2
+
+ + + +

x

+ +
+ +==================== Output ==================== +----------------------------------- +{ endOfLine: "lf", printWidth: 80 } +----------------------------------- +
+ +
+ kept as written +
+
+  line 1
+    line 2
+
+ + + +

+ x +

+ +
+ +------------------------------------ +{ endOfLine: "lf", printWidth: 100 } +------------------------------------ +
+ +
+ kept as written +
+
+  line 1
+    line 2
+
+ + + +

+ x +

+ +
+ +------------------------------------- +{ endOfLine: "crlf", printWidth: 80 } +------------------------------------- +
+ +
+ kept as written +
+
+  line 1
+    line 2
+
+ + + +

+ x +

+ +
+ +-------------------------------------- +{ endOfLine: "crlf", printWidth: 100 } +-------------------------------------- +
+ +
+ kept as written +
+
+  line 1
+    line 2
+
+ + + +

+ x +

+ +
+ +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/dialog-out-of-flow.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/dialog-out-of-flow.html new file mode 100644 index 0000000000..24bd7c4a4f --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/dialog-out-of-flow.html @@ -0,0 +1,7 @@ +
ab
+

Open the

settings panel

+ +
before
+

Are you sure?

+
after
+ diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/dialog-out-of-flow.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/dialog-out-of-flow.html.snap new file mode 100644 index 0000000000..098162f99f --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/dialog-out-of-flow.html.snap @@ -0,0 +1,46 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +
ab
+

Open the

settings panel

+ +
before
+

Are you sure?

+
after
+ + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +
ab
+

Open the

settings panel

+ +
before
+ +
+

Are you sure?

+ +
+
+
after
+ + +------------------- +{ printWidth: 100 } +------------------- +
ab
+

Open the

settings panel

+ +
before
+ +
+

Are you sure?

+ +
+
+
after
+ + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-html-in-one-pass.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-html-in-one-pass.html new file mode 100644 index 0000000000..b0c218f6dd --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-html-in-one-pass.html @@ -0,0 +1,2 @@ + +

x

diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-html-in-one-pass.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-html-in-one-pass.html.snap new file mode 100644 index 0000000000..c504e7759c --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-html-in-one-pass.html.snap @@ -0,0 +1,21 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== + +

x

+ +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ + +

x

+ +------------------- +{ printWidth: 100 } +------------------- + +

x

+ +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-in-xhtml.xhtml b/crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-in-xhtml.xhtml new file mode 100644 index 0000000000..554567e0c3 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-in-xhtml.xhtml @@ -0,0 +1,2 @@ + +

a
b

diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-in-xhtml.xhtml.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-in-xhtml.xhtml.snap new file mode 100644 index 0000000000..f58521c7b3 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/doctype-in-xhtml.xhtml.snap @@ -0,0 +1,29 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== + +

a
b

+ +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ + + + +

a
b

+ + + +------------------- +{ printWidth: 100 } +------------------- + + + +

a
b

+ + + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-indentation-as-printed.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-indentation-as-printed.html new file mode 100644 index 0000000000..95be20c801 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-indentation-as-printed.html @@ -0,0 +1,12 @@ +
+
+ +
+
+ +
+
diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-indentation-as-printed.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-indentation-as-printed.html.snap new file mode 100644 index 0000000000..b4c7198ed4 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-indentation-as-printed.html.snap @@ -0,0 +1,49 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +
+
+ +
+
+ +
+
+ +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +
+
+ +
+
+
+
+ +------------------- +{ printWidth: 100 } +------------------- +
+
+ +
+
+
+
+ +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-with-space-is-comment.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-with-space-is-comment.html new file mode 100644 index 0000000000..8818051f65 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-with-space-is-comment.html @@ -0,0 +1 @@ +
ab diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-with-space-is-comment.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-with-space-is-comment.html.snap new file mode 100644 index 0000000000..1e7467e9fb --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/end-tag-with-space-is-comment.html.snap @@ -0,0 +1,18 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +
ab + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +
ab + +------------------- +{ printWidth: 100 } +------------------- +
ab + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/htm-closing-tag-as-written.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/htm-closing-tag-as-written.html new file mode 100644 index 0000000000..8b98bc49d0 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/htm-closing-tag-as-written.html @@ -0,0 +1,2 @@ +

a

+axc diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/htm-closing-tag-as-written.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/htm-closing-tag-as-written.html.snap new file mode 100644 index 0000000000..fde0a696e7 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/htm-closing-tag-as-written.html.snap @@ -0,0 +1,21 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +

a

+axc + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +

a

+axc + +------------------- +{ printWidth: 100 } +------------------- +

a

+axc + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/ignore/cdata-merged-text.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/ignore/cdata-merged-text.html new file mode 100644 index 0000000000..c413e77e67 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/ignore/cdata-merged-text.html @@ -0,0 +1,2 @@ +

CDATA: here.

+

a b c

diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/ignore/cdata-merged-text.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/ignore/cdata-merged-text.html.snap new file mode 100644 index 0000000000..4049512a47 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/ignore/cdata-merged-text.html.snap @@ -0,0 +1,21 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +

CDATA: here.

+

a b c

+ +==================== Output ==================== +------------------------------------------------------- +{ htmlWhitespaceSensitivity: "ignore", printWidth: 80 } +------------------------------------------------------- +

CDATA: here.

+

a b c

+ +-------------------------------------------------------- +{ htmlWhitespaceSensitivity: "ignore", printWidth: 100 } +-------------------------------------------------------- +

CDATA: here.

+

a b c

+ +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/ignore/options.json b/crates/oxc_formatter_htmlish/tests/fixtures/html/ignore/options.json new file mode 100644 index 0000000000..9948b1325e --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/ignore/options.json @@ -0,0 +1 @@ +[{ "htmlWhitespaceSensitivity": "ignore" }] diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/ignored-closing-end-once.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/ignored-closing-end-once.html new file mode 100644 index 0000000000..4d759c258b --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/ignored-closing-end-once.html @@ -0,0 +1,4 @@ + diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/ignored-closing-end-once.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/ignored-closing-end-once.html.snap new file mode 100644 index 0000000000..ba6cf5e3c8 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/ignored-closing-end-once.html.snap @@ -0,0 +1,31 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== + + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ + + +------------------- +{ printWidth: 100 } +------------------- + + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/line-break-before-borrowed-end.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/line-break-before-borrowed-end.html new file mode 100644 index 0000000000..ba108447c2 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/line-break-before-borrowed-end.html @@ -0,0 +1,4 @@ +
x +amet, + +
diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/line-break-before-borrowed-end.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/line-break-before-borrowed-end.html.snap new file mode 100644 index 0000000000..614db3d936 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/line-break-before-borrowed-end.html.snap @@ -0,0 +1,25 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +
x +amet, + +
+ +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +
+ x amet, +
+ +------------------- +{ printWidth: 100 } +------------------- +
+ x amet, +
+ +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/lowercase-known-names.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/lowercase-known-names.html new file mode 100644 index 0000000000..4676a24dea --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/lowercase-known-names.html @@ -0,0 +1,8 @@ +x +
x
+
y
+ + +x +
x +
axc
diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/lowercase-known-names.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/lowercase-known-names.html.snap new file mode 100644 index 0000000000..e1d29efcc6 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/lowercase-known-names.html.snap @@ -0,0 +1,45 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +x +
x
+
y
+ + +x +
x +
axc
+ +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +x +
x
+ + +
y
+
+ + +x +
x +
axc
+ +------------------- +{ printWidth: 100 } +------------------- +x +
x
+ + +
y
+
+ + +x +
x +
axc
+ +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/merged-inline-element.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/merged-inline-element.html new file mode 100644 index 0000000000..94e24318be --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/merged-inline-element.html @@ -0,0 +1,6 @@ +
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaxcydze +
+

axcydze and qrst

diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/merged-inline-element.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/merged-inline-element.html.snap new file mode 100644 index 0000000000..aab28beb5e --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/merged-inline-element.html.snap @@ -0,0 +1,29 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaxcydze +
+

axcydze and qrst

+ +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaxcydze +
+

axcydze and qrst

+ +------------------- +{ printWidth: 100 } +------------------- +
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaxcydze +
+

axcydze and qrst

+ +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/nul/nul-as-written.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/nul/nul-as-written.html new file mode 100644 index 0000000000000000000000000000000000000000..6daeabfbd55ea8b6c8b129b9ad2b9c11565453af GIT binary patch literal 36 hcmcC1P*6}vv`ezlFRdsNP#ke;&!QEHUOtm2-g4r literal 0 HcmV?d00001 diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/nul/nul-as-written.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/nul/nul-as-written.html.snap new file mode 100644 index 0000000000000000000000000000000000000000..53f2a6d3ae0030ffe1852dacac8767cfb7ed7205 GIT binary patch literal 430 zcmaiwu@1r@5Qd$3ihBX1Zl)=04o;4aO$C~g*h1h)ni$_*YIGrI{HHta`10jA=Pdag zwQWJi0c{d~)&1z>JRsWGPdMLQnnamk5~Eu&$FekgvyX}K@?8>v^Aa)wF_Ekb05GUq zCxR-C(mV4h%e%7gnucC7{-hR@D(nFvx&^P!;MBsgf#>F1Wz!*Lz9!ZF)>h<0L! + +
+
Site
diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/omitted-end-tag-at-eof.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/omitted-end-tag-at-eof.html.snap new file mode 100644 index 0000000000..ab34d77c9f --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/omitted-end-tag-at-eof.html.snap @@ -0,0 +1,27 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== + + +
+
Site
+ +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ + + +
+
Site
+ +------------------- +{ printWidth: 100 } +------------------- + + +
+
Site
+ +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/omitted-end-tag.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/omitted-end-tag.html new file mode 100644 index 0000000000..4733527aa2 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/omitted-end-tag.html @@ -0,0 +1,6 @@ +
    +
  • one +
  • two +
+

para +

block
diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/omitted-end-tag.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/omitted-end-tag.html.snap new file mode 100644 index 0000000000..ddc2e4d4b7 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/omitted-end-tag.html.snap @@ -0,0 +1,33 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +
    +
  • one +
  • two +
+

para +

block
+ +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +
    +
  • one +
  • two +
+

para +

block
+ +------------------- +{ printWidth: 100 } +------------------- +
    +
  • one +
  • two +
+

para +

block
+ +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/pre-content-without-end-tag.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/pre-content-without-end-tag.html new file mode 100644 index 0000000000..98a5acbefb --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/pre-content-without-end-tag.html @@ -0,0 +1,14 @@ +
  • +x
+
  • +x
+
+x  y
+
+x
++ y + +
+<b>x</b></div> +<pre><b>x</b> unclosed diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/pre-content-without-end-tag.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/pre-content-without-end-tag.html.snap new file mode 100644 index 0000000000..3d8cc95035 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/pre-content-without-end-tag.html.snap @@ -0,0 +1,73 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<div><ul><li><pre /> +<b>x</b></ul></div> +<ul><li><pre /> +<b>x</b></ul> +<div><pre /> +x y</div> +<div><pre /> +<b>x</b></div> +<span><listing /> + <i>y</i> +</span> +<div><xmp /> +<b>x</b></div> +<pre><b>x</b> unclosed + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<div> + <ul> + <li> + <pre /> +<b>x</b></ul> +</div> +<ul> + <li> + <pre /> +<b>x</b></ul> +<div><pre />x y</div> +<div> + <pre /> +<b>x</b></div> +<span> + <listing /> + <i>y</i> +</span> +<div> + <xmp /> +<b>x</b></div> +<pre><b>x</b> unclosed + +------------------- +{ printWidth: 100 } +------------------- +<div> + <ul> + <li> + <pre /> +<b>x</b></ul> +</div> +<ul> + <li> + <pre /> +<b>x</b></ul> +<div><pre />x y</div> +<div> + <pre /> +<b>x</b></div> +<span> + <listing /> + <i>y</i> +</span> +<div> + <xmp /> +<b>x</b></div> +<pre><b>x</b> unclosed + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/prefixed-script-style.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/prefixed-script-style.html new file mode 100644 index 0000000000..8cf7e65d22 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/prefixed-script-style.html @@ -0,0 +1,3 @@ +<div><html:style>.a{color:red}</html:style></div> +<div>x<html:script>let a = 1</html:script>y</div> +<div>x<html:p>p</html:p>y</div> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/prefixed-script-style.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/prefixed-script-style.html.snap new file mode 100644 index 0000000000..135a61e643 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/prefixed-script-style.html.snap @@ -0,0 +1,24 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<div><html:style>.a{color:red}</html:style></div> +<div>x<html:script>let a = 1</html:script>y</div> +<div>x<html:p>p</html:p>y</div> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<div><html:style>.a{color:red}</html:style></div> +<div>x<html:script>let a = 1</html:script>y</div> +<div>x<html:p>p</html:p>y</div> + +------------------- +{ printWidth: 100 } +------------------- +<div><html:style>.a{color:red}</html:style></div> +<div>x<html:script>let a = 1</html:script>y</div> +<div>x<html:p>p</html:p>y</div> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/prettier-ignore-attribute.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/prettier-ignore-attribute.html new file mode 100644 index 0000000000..22e2b6a58a --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/prettier-ignore-attribute.html @@ -0,0 +1,8 @@ +<!-- prettier-ignore-attribute --> +<div class="a b" + title="x + y" id="z"></div> +<!-- prettier-ignore-attribute class --> +<div class="a b" CLASS="a b" title="x"></div> +<!-- oxfmt-ignore-attribute --> +<div class="a b"></div> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/prettier-ignore-attribute.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/prettier-ignore-attribute.html.snap new file mode 100644 index 0000000000..e79f7e24e5 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/prettier-ignore-attribute.html.snap @@ -0,0 +1,45 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<!-- prettier-ignore-attribute --> +<div class="a b" + title="x + y" id="z"></div> +<!-- prettier-ignore-attribute class --> +<div class="a b" CLASS="a b" title="x"></div> +<!-- oxfmt-ignore-attribute --> +<div class="a b"></div> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<!-- prettier-ignore-attribute --> +<div + class="a b" + title="x + y" + id="z" +></div> +<!-- prettier-ignore-attribute class --> +<div class="a b" CLASS="a b" title="x"></div> +<!-- oxfmt-ignore-attribute --> +<div class="a b"></div> + +------------------- +{ printWidth: 100 } +------------------- +<!-- prettier-ignore-attribute --> +<div + class="a b" + title="x + y" + id="z" +></div> +<!-- prettier-ignore-attribute class --> +<div class="a b" CLASS="a b" title="x"></div> +<!-- oxfmt-ignore-attribute --> +<div class="a b"></div> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/script-display-none.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/script-display-none.html new file mode 100644 index 0000000000..20e87ba67e --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/script-display-none.html @@ -0,0 +1,13 @@ +<p><span>a</span><script src="a.js"></script><span>b</span></p> +<div>text<script src="a.js"></script>more text that is long enough to wrap around the line width limit</div> +<div> + <span>a</span> + <script src="a.js"></script> + <span>b</span> +</div> +<p>x<script>var y=1</script>z</p> +<p><span>a</span><style>a{color:red}</style><span>b</span></p> +<div>x</div><script>a()</script><script src="b.js"></script><div>y</div> +<div>x</div><link rel="stylesheet" href="a.css"><meta name="a" content="b"><div>y</div> +<p><span>aaaaaaaaaaaaaaaaaaaaaaaa</span> <script src="a.js"></script><span>bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb</span></p> +<div>Price: <span>10</span> <meta itemprop="priceCurrency" content="USD">USD, and some more text that wraps the line</div> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/script-display-none.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/script-display-none.html.snap new file mode 100644 index 0000000000..9db0b76e63 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/script-display-none.html.snap @@ -0,0 +1,118 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<p><span>a</span><script src="a.js"></script><span>b</span></p> +<div>text<script src="a.js"></script>more text that is long enough to wrap around the line width limit</div> +<div> + <span>a</span> + <script src="a.js"></script> + <span>b</span> +</div> +<p>x<script>var y=1</script>z</p> +<p><span>a</span><style>a{color:red}</style><span>b</span></p> +<div>x</div><script>a()</script><script src="b.js"></script><div>y</div> +<div>x</div><link rel="stylesheet" href="a.css"><meta name="a" content="b"><div>y</div> +<p><span>aaaaaaaaaaaaaaaaaaaaaaaa</span> <script src="a.js"></script><span>bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb</span></p> +<div>Price: <span>10</span> <meta itemprop="priceCurrency" content="USD">USD, and some more text that wraps the line</div> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<p> + <span>a</span + ><script src="a.js"></script + ><span>b</span> +</p> +<div> + text<script src="a.js"></script + >more text that is long enough to wrap around the line width limit +</div> +<div> + <span>a</span> + <script src="a.js"></script> + <span>b</span> +</div> +<p> + x<script> + var y=1 + </script + >z +</p> +<p> + <span>a</span + ><style> + a{color:red} + </style + ><span>b</span> +</p> +<div>x</div> +<script> + a() +</script> +<script src="b.js"></script> +<div>y</div> +<div>x</div> +<link rel="stylesheet" href="a.css" /><meta name="a" content="b" /> +<div>y</div> +<p> + <span>aaaaaaaaaaaaaaaaaaaaaaaa</span> + <script src="a.js"></script> + <span>bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb</span> +</p> +<div> + Price: <span>10</span> <meta itemprop="priceCurrency" content="USD" /> USD, + and some more text that wraps the line +</div> + +------------------- +{ printWidth: 100 } +------------------- +<p> + <span>a</span + ><script src="a.js"></script + ><span>b</span> +</p> +<div> + text<script src="a.js"></script + >more text that is long enough to wrap around the line width limit +</div> +<div> + <span>a</span> + <script src="a.js"></script> + <span>b</span> +</div> +<p> + x<script> + var y=1 + </script + >z +</p> +<p> + <span>a</span + ><style> + a{color:red} + </style + ><span>b</span> +</p> +<div>x</div> +<script> + a() +</script> +<script src="b.js"></script> +<div>y</div> +<div>x</div> +<link rel="stylesheet" href="a.css" /><meta name="a" content="b" /> +<div>y</div> +<p> + <span>aaaaaaaaaaaaaaaaaaaaaaaa</span> + <script src="a.js"></script> + <span>bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb</span> +</p> +<div> + Price: <span>10</span> <meta itemprop="priceCurrency" content="USD" /> USD, and some more text + that wraps the line +</div> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing-with-end-tag.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing-with-end-tag.html new file mode 100644 index 0000000000..f57edd0d5c --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing-with-end-tag.html @@ -0,0 +1,2 @@ +<div />after</div> +<section><div />a<p>b</p></section> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing-with-end-tag.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing-with-end-tag.html.snap new file mode 100644 index 0000000000..9dae538326 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing-with-end-tag.html.snap @@ -0,0 +1,29 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<div />after</div> +<section><div />a<p>b</p></section> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<div />after</div> +<section> + <div /> + a + <p>b</p> +</section> + +------------------- +{ printWidth: 100 } +------------------- +<div />after</div> +<section> + <div /> + a + <p>b</p> +</section> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing.html new file mode 100644 index 0000000000..90993d508d --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing.html @@ -0,0 +1,13 @@ +<span><i class="a" /><i class="b" /></span> +<div><my-el /> +</div> +<div><my-el /> + <p>x</p> +</div> +<span><my-el /><!-- c --></span> +<div><a-el /><b-el />text</div> +<!--[if IE]><!--><my-el /><!--<![endif]--> +<ul><li><my-el /></li></ul> +<!-- prettier-ignore --> +<my-el /> +<p>after</p> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing.html.snap new file mode 100644 index 0000000000..59a14c490a --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/self-closing.html.snap @@ -0,0 +1,58 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<span><i class="a" /><i class="b" /></span> +<div><my-el /> +</div> +<div><my-el /> + <p>x</p> +</div> +<span><my-el /><!-- c --></span> +<div><a-el /><b-el />text</div> +<!--[if IE]><!--><my-el /><!--<![endif]--> +<ul><li><my-el /></li></ul> +<!-- prettier-ignore --> +<my-el /> +<p>after</p> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<span><i class="a" /><i class="b" /></span> +<div><my-el /></div> +<div> + <my-el /> + <p>x</p> +</div> +<span><my-el /><!-- c --></span> +<div><a-el /><b-el />text</div> +<!--[if IE]><!--><my-el /><!--<![endif]--> +<ul> + <li><my-el /></li> +</ul> +<!-- prettier-ignore --> +<my-el /> +<p>after</p> + +------------------- +{ printWidth: 100 } +------------------- +<span><i class="a" /><i class="b" /></span> +<div><my-el /></div> +<div> + <my-el /> + <p>x</p> +</div> +<span><my-el /><!-- c --></span> +<div><a-el /><b-el />text</div> +<!--[if IE]><!--><my-el /><!--<![endif]--> +<ul> + <li><my-el /></li> +</ul> +<!-- prettier-ignore --> +<my-el /> +<p>after</p> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/srcset-zero-density.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/srcset-zero-density.html new file mode 100644 index 0000000000..d0b49a5306 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/srcset-zero-density.html @@ -0,0 +1,2 @@ +<img srcset="a.png 0x, b.png 2x"> +<img srcset="a.png -0x, b.png 1e21x"> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/srcset-zero-density.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/srcset-zero-density.html.snap new file mode 100644 index 0000000000..78c60a4bcb --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/srcset-zero-density.html.snap @@ -0,0 +1,21 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<img srcset="a.png 0x, b.png 2x"> +<img srcset="a.png -0x, b.png 1e21x"> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<img srcset="a.png 0x, b.png 2x" /> +<img srcset="a.png 0x, b.png 1e+21x" /> + +------------------- +{ printWidth: 100 } +------------------- +<img srcset="a.png 0x, b.png 2x" /> +<img srcset="a.png 0x, b.png 1e+21x" /> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/line-break-before-borrowed-end.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/line-break-before-borrowed-end.html new file mode 100644 index 0000000000..c260448e7c --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/line-break-before-borrowed-end.html @@ -0,0 +1,3 @@ +<p><b>a</b> + b +</p><i></i> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/line-break-before-borrowed-end.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/line-break-before-borrowed-end.html.snap new file mode 100644 index 0000000000..152ab42474 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/line-break-before-borrowed-end.html.snap @@ -0,0 +1,20 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<p><b>a</b> + b +</p><i></i> + +==================== Output ==================== +------------------------------------------------------- +{ htmlWhitespaceSensitivity: "strict", printWidth: 80 } +------------------------------------------------------- +<p><b>a</b> b </p><i></i> + +-------------------------------------------------------- +{ htmlWhitespaceSensitivity: "strict", printWidth: 100 } +-------------------------------------------------------- +<p><b>a</b> b </p><i></i> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/options.json b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/options.json new file mode 100644 index 0000000000..e05267cdb6 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/options.json @@ -0,0 +1 @@ +[{ "htmlWhitespaceSensitivity": "strict" }] diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/pre-after-borrowed-closing-end.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/pre-after-borrowed-closing-end.html new file mode 100644 index 0000000000..252ba85b44 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/pre-after-borrowed-closing-end.html @@ -0,0 +1,3 @@ +<b>a</b><pre><b>x</b></pre> +<b>a</b><pre> +<b>x</b></pre> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/pre-after-borrowed-closing-end.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/pre-after-borrowed-closing-end.html.snap new file mode 100644 index 0000000000..c2160750d3 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/pre-after-borrowed-closing-end.html.snap @@ -0,0 +1,24 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<b>a</b><pre><b>x</b></pre> +<b>a</b><pre> +<b>x</b></pre> + +==================== Output ==================== +------------------------------------------------------- +{ htmlWhitespaceSensitivity: "strict", printWidth: 80 } +------------------------------------------------------- +<b>a</b><pre><b>x</b></pre> <b>a</b +><pre> +<b>x</b></pre> + +-------------------------------------------------------- +{ htmlWhitespaceSensitivity: "strict", printWidth: 100 } +-------------------------------------------------------- +<b>a</b><pre><b>x</b></pre> <b>a</b +><pre> +<b>x</b></pre> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/self-closing.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/self-closing.html new file mode 100644 index 0000000000..046b478744 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/self-closing.html @@ -0,0 +1,4 @@ +<span><pre /> +</span> +<span>a<pre /> +</span> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/self-closing.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/self-closing.html.snap new file mode 100644 index 0000000000..a7e42fe278 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/strict/self-closing.html.snap @@ -0,0 +1,23 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<span><pre /> +</span> +<span>a<pre /> +</span> + +==================== Output ==================== +------------------------------------------------------- +{ htmlWhitespaceSensitivity: "strict", printWidth: 80 } +------------------------------------------------------- +<span><pre /></span> +<span>a<pre /></span> + +-------------------------------------------------------- +{ htmlWhitespaceSensitivity: "strict", printWidth: 100 } +-------------------------------------------------------- +<span><pre /></span> +<span>a<pre /></span> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/svg-text-content-inline.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/svg-text-content-inline.html new file mode 100644 index 0000000000..25cea0ac93 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/svg-text-content-inline.html @@ -0,0 +1,4 @@ +<svg><text x="0" y="10">H<tspan baseline-shift="sub">2</tspan>O</text></svg> +<svg><text>see<a href="x">here</a>.</text><g><rect/><circle/></g></svg> +<svg><text>a<tspan> </tspan>b</text></svg> +<svg><text x="0" y="10">a very long label that keeps going and going past the line width limit <tspan>b</tspan> c</text></svg> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/svg-text-content-inline.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/svg-text-content-inline.html.snap new file mode 100644 index 0000000000..e299798c9f --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/svg-text-content-inline.html.snap @@ -0,0 +1,56 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<svg><text x="0" y="10">H<tspan baseline-shift="sub">2</tspan>O</text></svg> +<svg><text>see<a href="x">here</a>.</text><g><rect/><circle/></g></svg> +<svg><text>a<tspan> </tspan>b</text></svg> +<svg><text x="0" y="10">a very long label that keeps going and going past the line width limit <tspan>b</tspan> c</text></svg> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<svg> + <text x="0" y="10">H<tspan baseline-shift="sub">2</tspan>O</text> +</svg> +<svg> + <text>see<a href="x">here</a>.</text> + <g> + <rect /> + <circle /> + </g> +</svg> +<svg> + <text>a<tspan> </tspan>b</text> +</svg> +<svg> + <text x="0" y="10"> + a very long label that keeps going and going past the line width limit + <tspan>b</tspan> c + </text> +</svg> + +------------------- +{ printWidth: 100 } +------------------- +<svg> + <text x="0" y="10">H<tspan baseline-shift="sub">2</tspan>O</text> +</svg> +<svg> + <text>see<a href="x">here</a>.</text> + <g> + <rect /> + <circle /> + </g> +</svg> +<svg> + <text>a<tspan> </tspan>b</text> +</svg> +<svg> + <text x="0" y="10"> + a very long label that keeps going and going past the line width limit <tspan>b</tspan> c + </text> +</svg> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/tag-name-case.xhtml b/crates/oxc_formatter_htmlish/tests/fixtures/html/tag-name-case.xhtml new file mode 100644 index 0000000000..eae65a0d6c --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/tag-name-case.xhtml @@ -0,0 +1 @@ +<DIV CLASS="a"><P>Text</P></DIV> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/tag-name-case.xhtml.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/tag-name-case.xhtml.snap new file mode 100644 index 0000000000..75b2460c21 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/tag-name-case.xhtml.snap @@ -0,0 +1,18 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<DIV CLASS="a"><P>Text</P></DIV> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<DIV CLASS="a"><P>Text</P></DIV> + +------------------- +{ printWidth: 100 } +------------------- +<DIV CLASS="a"><P>Text</P></DIV> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/text-display-width.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/text-display-width.html new file mode 100644 index 0000000000..4a7ecc3c47 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/text-display-width.html @@ -0,0 +1 @@ +<div> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/text-display-width.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/text-display-width.html.snap new file mode 100644 index 0000000000..a2ea65e7de --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/text-display-width.html.snap @@ -0,0 +1,18 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<div> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<div> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div> + +------------------- +{ printWidth: 100 } +------------------- +<div> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/unquoted-value-with-slash.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/unquoted-value-with-slash.html new file mode 100644 index 0000000000..e28a004c7b --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/unquoted-value-with-slash.html @@ -0,0 +1,2 @@ +<a href=https://www.mozilla.org/>favorite website</a> +<img src=a.png/> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/unquoted-value-with-slash.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/unquoted-value-with-slash.html.snap new file mode 100644 index 0000000000..6d811996f0 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/unquoted-value-with-slash.html.snap @@ -0,0 +1,21 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<a href=https://www.mozilla.org/>favorite website</a> +<img src=a.png/> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<a href="https://www.mozilla.org/">favorite website</a> +<img src="a.png/" /> + +------------------- +{ printWidth: 100 } +------------------- +<a href="https://www.mozilla.org/">favorite website</a> +<img src="a.png/" /> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/use-tabs/end-tag-indentation-as-printed.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/use-tabs/end-tag-indentation-as-printed.html new file mode 100644 index 0000000000..ee7e85803f --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/use-tabs/end-tag-indentation-as-printed.html @@ -0,0 +1,7 @@ +<div> + <div> + <!-- + comment + --> + </div> +</div> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/use-tabs/end-tag-indentation-as-printed.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/use-tabs/end-tag-indentation-as-printed.html.snap new file mode 100644 index 0000000000..41227b6b42 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/use-tabs/end-tag-indentation-as-printed.html.snap @@ -0,0 +1,36 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<div> + <div> + <!-- + comment + --> + </div> +</div> + +==================== Output ==================== +--------------------------------- +{ printWidth: 80, useTabs: true } +--------------------------------- +<div> + <div> + <!-- + comment + --> + </div> +</div> + +---------------------------------- +{ printWidth: 100, useTabs: true } +---------------------------------- +<div> + <div> + <!-- + comment + --> + </div> +</div> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/use-tabs/options.json b/crates/oxc_formatter_htmlish/tests/fixtures/html/use-tabs/options.json new file mode 100644 index 0000000000..5ede0e70f9 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/use-tabs/options.json @@ -0,0 +1 @@ +[{ "useTabs": true }] diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/whitespace-only-pre.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/whitespace-only-pre.html new file mode 100644 index 0000000000..3de53ec5cf --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/whitespace-only-pre.html @@ -0,0 +1,2 @@ +<pre> </pre> +<textarea> </textarea> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/whitespace-only-pre.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/whitespace-only-pre.html.snap new file mode 100644 index 0000000000..24beb94f84 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/whitespace-only-pre.html.snap @@ -0,0 +1,21 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<pre> </pre> +<textarea> </textarea> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<pre> </pre> +<textarea> </textarea> + +------------------- +{ printWidth: 100 } +------------------- +<pre> </pre> +<textarea> </textarea> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/written-names-and-end-tags.html b/crates/oxc_formatter_htmlish/tests/fixtures/html/written-names-and-end-tags.html new file mode 100644 index 0000000000..6ee774b611 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/written-names-and-end-tags.html @@ -0,0 +1,5 @@ +<div>a</div foo>b +<p>x<b>y</b foo>z</p> +<p=e>x</p=e> +<a<C /> +<div <span>x</div> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/html/written-names-and-end-tags.html.snap b/crates/oxc_formatter_htmlish/tests/fixtures/html/written-names-and-end-tags.html.snap new file mode 100644 index 0000000000..216147a9d5 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/html/written-names-and-end-tags.html.snap @@ -0,0 +1,32 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<div>a</div foo>b +<p>x<b>y</b foo>z</p> +<p=e>x</p=e> +<a<C /> +<div <span>x</div> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<div>a</div foo> +b +<p>x<b>y</b foo>z</p> +<p=e>x</p=e> +<a<C /> +<div <span>x</div> + +------------------- +{ printWidth: 100 } +------------------- +<div>a</div foo> +b +<p>x<b>y</b foo>z</p> +<p=e>x</p=e> +<a<C /> +<div <span>x</div> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/mod.rs b/crates/oxc_formatter_htmlish/tests/fixtures/mod.rs new file mode 100644 index 0000000000..a8354df3ea --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/mod.rs @@ -0,0 +1,67 @@ +use std::path::Path; + +use oxc_allocator::Allocator; +use oxc_formatter_htmlish::{HtmlishFormatOptions, HtmlishVariant, format, parse_for_format}; +use oxc_formatter_tests::{FixtureFormatter, OptionSet, build_fixture_snapshot}; + +mod fingerprint; +mod options; +use options::apply_htmlish_options; + +/// `.vue` reads as Vue, `.xhtml` / `.xht` as XHTML, `.component.html` as Angular (as the example reads them). +fn variant_of(path: &Path) -> HtmlishVariant { + match path.extension().and_then(|ext| ext.to_str()) { + Some("vue") => HtmlishVariant::Vue, + Some("xhtml" | "xht") => HtmlishVariant::Xhtml, + _ if path.to_string_lossy().ends_with(".component.html") => HtmlishVariant::Angular, + _ => HtmlishVariant::Html, + } +} + +struct HtmlishHarness; + +impl FixtureFormatter for HtmlishHarness { + type Options = HtmlishFormatOptions; + type Fingerprint = String; + + fn parse_options(json: &OptionSet) -> Self::Options { + let mut options = HtmlishFormatOptions::default(); + apply_htmlish_options(&mut options, json); + options + } + + fn format(source: &str, path: &Path, options: &Self::Options) -> String { + let allocator = Allocator::default(); + let options = HtmlishFormatOptions { variant: variant_of(path), ..*options }; + format(&allocator, source, options) + .expect("format should succeed") + .print() + .expect("print should succeed") + .into_code() + } + + fn fingerprint(source: &str, path: &Path, options: &Self::Options) -> String { + let options = HtmlishFormatOptions { variant: variant_of(path), ..*options }; + let allocator = Allocator::default(); + let parsed = parse_for_format(&allocator, source, &options).expect("source should parse"); + fingerprint::fingerprint( + parsed.source, + parsed.root, + options.variant == HtmlishVariant::Html, + ) + } +} + +fn test_file(path: &Path) { + let snap = build_fixture_snapshot::<HtmlishHarness>(path); + insta::with_settings!({ + snapshot_path => snap.path, + prepend_module_to_snapshot => false, + snapshot_suffix => "", + omit_expression => true, + }, { + insta::assert_snapshot!(snap.name, snap.body); + }); +} + +include!(concat!(env!("OUT_DIR"), "/generated_tests.rs")); diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/options.rs b/crates/oxc_formatter_htmlish/tests/fixtures/options.rs new file mode 100644 index 0000000000..f895a4e785 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/options.rs @@ -0,0 +1,36 @@ +//! Prettier option-set → `HtmlishFormatOptions` mapping. +//! +//! Shared by the fixture harness and the conformance target via `#[path]`. + +use oxc_formatter_htmlish::{HtmlishFormatOptions, WhitespaceSensitivity}; +use oxc_formatter_tests::{OptionSet, apply_core_options}; + +/// Applies the four core options plus the HTML-specific keys onto `options`. +/// Parsing is lenient like `apply_core_options`: unknown or invalid values are ignored. +pub fn apply_htmlish_options(options: &mut HtmlishFormatOptions, json: &OptionSet) { + apply_core_options(options, json); + for (key, value) in json { + match key.as_str() { + "htmlWhitespaceSensitivity" => { + if let Some(s) = value.as_str() { + options.whitespace_sensitivity = match s { + "strict" => WhitespaceSensitivity::Strict, + "ignore" => WhitespaceSensitivity::Ignore, + _ => WhitespaceSensitivity::Css, + }; + } + } + "bracketSameLine" => { + if let Some(b) = value.as_bool() { + options.bracket_same_line = b; + } + } + "singleAttributePerLine" => { + if let Some(b) = value.as_bool() { + options.single_attribute_per_line = b; + } + } + _ => {} + } + } +} diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/vue/doctype-case.vue b/crates/oxc_formatter_htmlish/tests/fixtures/vue/doctype-case.vue new file mode 100644 index 0000000000..cdf7a4ffae --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/vue/doctype-case.vue @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<html><body><p>{{ a }}</p></body></html> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/vue/doctype-case.vue.snap b/crates/oxc_formatter_htmlish/tests/fixtures/vue/doctype-case.vue.snap new file mode 100644 index 0000000000..54e001071b --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/vue/doctype-case.vue.snap @@ -0,0 +1,29 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<!DOCTYPE html> +<html><body><p>{{ a }}</p></body></html> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<!doctype html> +<html> + <body> + <p>{{ a }}</p> + </body> +</html> + +------------------- +{ printWidth: 100 } +------------------- +<!doctype html> +<html> + <body> + <p>{{ a }}</p> + </body> +</html> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/vue/vue-element-gap-kind.vue b/crates/oxc_formatter_htmlish/tests/fixtures/vue/vue-element-gap-kind.vue new file mode 100644 index 0000000000..26a9291849 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/vue/vue-element-gap-kind.vue @@ -0,0 +1,10 @@ +<template> + <ul> + <li> + <router-link to="/using-store">/using-store</router-link> — <b>universal</b> + <code>reactive()</code> stores. + </li> + </ul> + <p><span>a long enough first element here</span> <span>and a second one that does not fit</span></p> + <p><Foo /><Bar /></p> +</template> diff --git a/crates/oxc_formatter_htmlish/tests/fixtures/vue/vue-element-gap-kind.vue.snap b/crates/oxc_formatter_htmlish/tests/fixtures/vue/vue-element-gap-kind.vue.snap new file mode 100644 index 0000000000..fd3a70e5d5 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/fixtures/vue/vue-element-gap-kind.vue.snap @@ -0,0 +1,52 @@ +--- +source: crates/oxc_formatter_htmlish/tests/fixtures/mod.rs +--- +==================== Input ==================== +<template> + <ul> + <li> + <router-link to="/using-store">/using-store</router-link> — <b>universal</b> + <code>reactive()</code> stores. + </li> + </ul> + <p><span>a long enough first element here</span> <span>and a second one that does not fit</span></p> + <p><Foo /><Bar /></p> +</template> + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +<template> + <ul> + <li> + <router-link to="/using-store">/using-store</router-link> — + <b>universal</b> + <code>reactive()</code> stores. + </li> + </ul> + <p> + <span>a long enough first element here</span> <span + >and a second one that does not fit</span + > + </p> + <p><Foo /><Bar /></p> +</template> + +------------------- +{ printWidth: 100 } +------------------- +<template> + <ul> + <li> + <router-link to="/using-store">/using-store</router-link> — <b>universal</b> + <code>reactive()</code> stores. + </li> + </ul> + <p> + <span>a long enough first element here</span> <span>and a second one that does not fit</span> + </p> + <p><Foo /><Bar /></p> +</template> + +===================== End ===================== diff --git a/crates/oxc_formatter_htmlish/tests/mod.rs b/crates/oxc_formatter_htmlish/tests/mod.rs new file mode 100644 index 0000000000..681564e4bf --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/mod.rs @@ -0,0 +1 @@ +mod fixtures; diff --git a/crates/oxc_formatter_htmlish/tests/snapshots/conformance__prettier-html.snap b/crates/oxc_formatter_htmlish/tests/snapshots/conformance__prettier-html.snap new file mode 100644 index 0000000000..9d35b04588 --- /dev/null +++ b/crates/oxc_formatter_htmlish/tests/snapshots/conformance__prettier-html.snap @@ -0,0 +1,15 @@ +--- +source: crates/oxc_formatter_htmlish/tests/conformance.rs +expression: report +--- +html compatibility: 162/168 (96.43%), 0 files skipped + +# Failed + +| Spec path | Failed or Passed | Match ratio | +| :-------- | :--------------: | :---------: | +| html/basics/broken-html.html | 💥 | 88.89% | +| html/basics/with-colon.html | 💥 | 93.33% | +| html/prettier_ignore/unclosed2.html | 💥 | 80.00% | +| html/tags/tags.html | 💥✨✨✨ | 99.82% | +| html/tags/tags2.html | 💥💥✨✨ | 97.17% |