Repository: prettier/prettier (JavaScript)

Issue:
Prettier removes unterminated HTML tag & content
<details><summary>old text</summary>

**Prettier 3.1.0**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEAeAFnAhgEwHyoD0mueAOlKgDYCWUA1hQARMBOcVAvGSAM4wBPKnF6Y4MHsybp2AM24h0MGAAdeSQoTA4oAOgBWvHBxoA3VrqjjCUFQFtCAdyzwLYXrwACAJkIQArjBOLnBuHpJQhOSU-EJweHZYrPRwOAC0djhIUBAwABRIxrJ0qQCUTMBMODS8KlRYAkhMOVZMAL5EscLRqLxgrDQqMEyCKnAKdhA4-sI8TLysYAq6uoRYgRAGvDwEhH0DQz0AhGlpTCrsyjShaTQA5jnsTKc9icmpGThVNXUNCgBGVAgYEYIAIvzo8AAHjBogBiJgACQ4QKYjggrCoOCOFAoAAMCUo7FQKKgVHgADIQCD0JguEaYXhwJi9FRYKBMGg4BREklgxEAFQAshSRlg7rxOmyoLtyaTqqYmGB6h4ARAoTsBZgBHT2FAAOTDf5wOh3FmQYx4SGhC7iVJEC3xOm8OlgGD+LBUJHCilEBXRAl43FQABCgQZNW9Is5LtQb3oeCwbo9VCoOrZrCZOCI8bwTH0-n4TFoKSYACoy49ElQK7pg3nUHA7HghUl6DgII5KIQm3nhDAXQIApyoMr-MYWfxWNA7onWP8aDBWEkBJ0lzOG1h-v9WHhBSKiFud3m6JyYHWoCAADQgCBDGjQXjIUBJaeOAAKSQQT5QnucAifG8dyTFIYAAZSwOw4ApEpkFkT0mSA5cQXEMC2TAU1kCXfw4BvJk7BoLDWBwm84ChMYBig2BPQFUIoCSa4f3gqhEL4U1hAARX8XI4DghDcJAQwoTA9i4C4ni+JYgSAEduPgd9pzUZAQCwXg0isVJUmvEAlywGhaCgO4AGEIDsRJlM9KhtN4USAEFlAGf5Ajgd9QhgqxJNY3kAHV0EXER0LgMDv0XMxFwEZT3EAkBTBwgBJKBjFgMD+kGGBbMSsDBGETyBIuCAmW85cVGU20mVYUxeJvOhypgBTxXMpBmNYjNyuU3ltIuSFvK5GB0GQAAOAAGG92Fkmh2Hqu5GuagSYC3HqcD65BvBvQs4AFLcmP40i7GNHBjBwCl2TuD07jgAAxDFEiuQyLI2EA2jaIA)
<!-- prettier-ignore -->
```sh
--parser html
```

**Input:**
<!-- prettier-ignore -->
````html
<head></head>
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"
/>
<style>marked-md:not(:defined) { display: none }</style>
<script type="module" src="../auto.js"></script>
<!-- prettier-ignore -->
<marked-md display="block"><plaintext>
# Hello world!

```html
<p>Look at these <span id="html">HTML tags</span></p>
<div class="box">They aren't being <code>interpreted</code> as actual HTML</div>
```

But this HTML is <mark>actually parsed</mark> just like **normal**.

> <em>Markdown</em> lets you include <strong>arbitrary</strong> <abbr>HTML</abbr> in it.

````

**Output:**
<!-- prettier-ignore -->
```html
<head></head>
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"
/>
<style>
  marked-md:not(:defined) {
    display: none;
  }
</style>
<script type="module" src="../auto.js"></script>
<!-- prettier-ignore -->
<marked-md display="block">

```

**Expected behavior:**
idk but it should definitely NOT eat my entire html block for breakfast lol 😅

</details>

## PART 1

**Prettier 3.1.0**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEAeAtgTwLQFdZwBO6AllAIbwAm2YuAzjBOtnADZzoIwB8q9YQiQAOMAAQxMwuAF4qlcjwA6UAMrM4EuAA8YucmzHyY5MamE8A7iRgALMfQ1iAEgBUAsgBlsbEgGs4VBMAc3oeMXIoKjF0CEJNIPJQiKiJWzJQ5SgAdQhLAEIxTwgYejEIADMzNy9sTwBJAGkAUXDjUzIxWyJNO0oxSwSqCDEoEvDLSPEmMQAjTWFyQno4aPJ6FVQaz3DZ3HEyRjhyaIArBnEAKkvF+hWqa4iy00YhKGCJEbtNOkZmMXYnG4KhAABoQBBRCRoPRkKAloQ8gAFJYIWEoAyTTCw8GzQjkMABGCqchcTxkODICoGFa4-GEuDExZgDLIGCEXBwcErUhsjlckA6aRCLiwAyuIgUIRwdHUti0kD0DIcACKuBKlKQcoVp3o2lUyrgao1VJpAoAjur4EjEcJ0SB1tgoHBVqswSB2eQSL53gBhZjocjIB1sNjupXvDgAQRg7JIe2tRHJztN8oFthg6DY2XS8HozLgqjRNhIADcbJhg2A7u7S5z6lFuKpBCIYFGoqpJBxUwrhIiVtl8cJg32ZURS5TwYciDAbUlAz2BYtlkRgxms+6+2QYNkSFQ7MgABwABnB8UtJHic+CC61ZvBJlmu-3tmQACZwQw4K5yLNZffBXQeYqCoVZPEiYJ9GCOAADE4kDWNWQxfYIBAABfNCgA)
<!-- prettier-ignore -->
```sh
--parser html
```

**Input:**
<!-- prettier-ignore -->
```html
<my-unterminated-custom-element><script type=data>
Some textual data <p>with some HTML-like<tags> and more <tags and things>
Wow! Lots of <HTML-LIKE> data in here that we <do not> want to be parsed as
<HTML> but instead just **passed** as a string to the custom element

```

**Output:**
<!-- prettier-ignore -->
```html
SyntaxError: Unexpected character "EOF" (5:1)
  3 | Wow! Lots of <HTML-LIKE> data in here that we <do not> want to be parsed as
  4 | <HTML> but instead just **passed** as a string to the custom element
> 5 |
    | ^
```

**Expected behavior:**
not error lol

## PART 2

**Prettier 3.1.0**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEAeAhAWkwAgA4BOcMMAlnAZqQOZQRE7YB8AOlKgLYCemArrBQ6koAQ3gATTGF4BnGBA6Y4AGzgcEMJqhlgCpPDBwwueOAF5xYkaygBlBXCNwAHjF4jlOSzBE5UeJgB3UhgACxwZBxwACQAVAFkAGUxlUgBrOFQfahkmHBEocRwOekcskRz8wqNQ4RybAHUIQPQcRIgYGRwIADM-OKTMRIBJAGkAUTzvX2EcUIpHMLEcQLLxCBw6TRWCw3kcACNHPBECGTgikRk2VAHEvIPeQ2E5OBEigCtZQwAqH5OZOdxH98l1fHI9FBqEYNmFHNI5AocCo1BobgB6HR6AxMEAAGhAEAMpGgMmQoFOBGaAAVTggySgPIERFwyQSDgQRGAMjBbCJ1IlhHBkD0POd2ZzucRbCcwHVkDACLw4ATzkIFUqVSAXKY9OpYB5YhRRHo4AzRcpxSAZHVVABFXgdYVIC1Wj4yZy2W1wB1OkVirUAR0d8GpVLwDJAV0wUDgFwu+JAipEpFSUIAwgoOCJkFHlMpEzaoaoAIIkPSPUMUQWx-2WrWhGAcZQNWrwGSyuC2ekhUgANxCXFzYEBib7yuGhQ0tl0+hgJcKtmMqjrVsIEHODU5eFzhDNFD7woJLwoMDDFWzq61JzOFFzjebicIwhgDVI4jCyAAHAAGAlEYNSCIc9qEvF0AwJHwDjfD9QmQAAmAlZDgWIRAOc0IO1DgjnEcQLkSApqHcag4AAMXobMSHlRknggEAAF96KAA)
<!-- prettier-ignore -->
```sh
--parser html
```

**Input:**
<!-- prettier-ignore -->
```html
<!-- prettier-ignore -->
<my-unterminated-custom-element><script type=data>
Some textual data <p>with some HTML-like<tags> and more <tags and things>
Wow! Lots of <HTML-LIKE> data in here that we <do not> want to be parsed as
<HTML> but instead just **passed** as a string to the custom element
</script>
```

**Output:**
<!-- prettier-ignore -->
```html
<!-- prettier-ignore -->
<my-unterminated-custom-element>

```

**Expected behavior:**
not delete the element. it only seems to delete the element when the prettier-ignore directive is there but it might also happen at other times too idk.


Hints:

Patch:
diff --git a/src/language-html/print/children.js b/src/language-html/print/children.js
--- a/src/language-html/print/children.js
+++ b/src/language-html/print/children.js
@@ -7,6 +7,8 @@ import {
   hardline,
 } from "../../document/builders.js";
 import { replaceEndOfLine } from "../../document/utils.js";
+import isNonEmptyArray from "../../utils/is-non-empty-array.js";
+import htmlWhitespaceUtils from "../../utils/html-whitespace-utils.js";
 import { locStart, locEnd } from "../loc.js";
 import {
   forceBreakChildren,
@@ -25,22 +27,41 @@ import {
   needsToBorrowParentClosingTagStartMarker,
 } from "./tag.js";
 
+function getEndLocation(node) {
+  const endLocation = locEnd(node);
+
+  // Element can be unclosed
+  if (
+    node.type === "element" &&
+    !node.endSourceSpan &&
+    isNonEmptyArray(node.children)
+  ) {
+    return Math.max(endLocation, getEndLocation(node.children.at(-1)));
+  }
+
+  return endLocation;
+}
+
 function printChild(childPath, options, print) {
   const child = childPath.node;
 
   if (hasPrettierIgnore(child)) {
+    const endLocation = getEndLocation(child);
+
     return [
       printOpeningTagPrefix(child, options),
       replaceEndOfLine(
-        options.originalText.slice(
-          locStart(child) +
-            (child.prev && needsToBorrowNextOpeningTagStartMarker(child.prev)
-              ? printOpeningTagStartMarker(child).length
-              : 0),
-          locEnd(child) -
-            (child.next && needsToBorrowPrevClosingTagEndMarker(child.next)
-              ? printClosingTagEndMarker(child, options).length
-              : 0),
+        htmlWhitespaceUtils.trimEnd(
+          options.originalText.slice(
+            locStart(child) +
+              (child.prev && needsToBorrowNextOpeningTagStartMarker(child.prev)
+                ? printOpeningTagStartMarker(child).length
+                : 0),
+            endLocation -
+              (child.next && needsToBorrowPrevClosingTagEndMarker(child.next)
+                ? printClosingTagEndMarker(child, options).length
+                : 0),
+          ),
         ),
       ),
       printClosingTagSuffix(child, options),


Test patch:
diff --git a/tests/format/html/prettier_ignore/__snapshots__/jsfmt.spec.js.snap b/tests/format/html/prettier_ignore/__snapshots__/jsfmt.spec.js.snap
--- a/tests/format/html/prettier_ignore/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/html/prettier_ignore/__snapshots__/jsfmt.spec.js.snap
@@ -117,6 +117,60 @@ printWidth: 80
 ================================================================================
 `;
 
+exports[`issue-15738.html format 1`] = `
+====================================options=====================================
+parsers: ["html"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+<head></head>
+<link
+  rel="stylesheet"
+  href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"
+/>
+<style>marked-md:not(:defined) { display: none }</style>
+<script type="module" src="../auto.js"></script>
+<!-- prettier-ignore -->
+<marked-md display="block"><plaintext>
+# Hello world!
+
+\`\`\`html
+<p>Look at these <span id="html">HTML tags</span></p>
+<div class="box">They aren't being <code>interpreted</code> as actual HTML</div>
+\`\`\`
+
+But this HTML is <mark>actually parsed</mark> just like **normal**.
+
+> <em>Markdown</em> lets you include <strong>arbitrary</strong> <abbr>HTML</abbr> in it.
+
+=====================================output=====================================
+<head></head>
+<link
+  rel="stylesheet"
+  href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"
+/>
+<style>
+  marked-md:not(:defined) {
+    display: none;
+  }
+</style>
+<script type="module" src="../auto.js"></script>
+<!-- prettier-ignore -->
+<marked-md display="block"><plaintext>
+# Hello world!
+
+\`\`\`html
+<p>Look at these <span id="html">HTML tags</span></p>
+<div class="box">They aren't being <code>interpreted</code> as actual HTML</div>
+\`\`\`
+
+But this HTML is <mark>actually parsed</mark> just like **normal**.
+
+> <em>Markdown</em> lets you include <strong>arbitrary</strong> <abbr>HTML</abbr> in it.
+
+================================================================================
+`;
+
 exports[`long_lines.html format 1`] = `
 ====================================options=====================================
 parsers: ["html"]
@@ -160,3 +214,44 @@ A super long string that has been marked as ignore because it was probably gener
 
 ================================================================================
 `;
+
+exports[`unclosed.html format 1`] = `
+====================================options=====================================
+parsers: ["html"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+<!-- prettier-ignore -->
+<div>
+# Hello world!
+<span>
+
+=====================================output=====================================
+<!-- prettier-ignore -->
+<div>
+# Hello world!
+<span>
+
+================================================================================
+`;
+
+exports[`unclosed2.html format 1`] = `
+====================================options=====================================
+parsers: ["html"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+<!-- prettier-ignore -->
+<p>
+# Hello world!
+<div>
+
+=====================================output=====================================
+<!-- prettier-ignore -->
+<p>
+# Hello world!
+
+<div></div>
+
+================================================================================
+`;
diff --git a/tests/format/html/prettier_ignore/issue-15738.html b/tests/format/html/prettier_ignore/issue-15738.html
new file mode 100644
--- /dev/null
+++ b/tests/format/html/prettier_ignore/issue-15738.html
@@ -0,0 +1,19 @@
+<head></head>
+<link
+  rel="stylesheet"
+  href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"
+/>
+<style>marked-md:not(:defined) { display: none }</style>
+<script type="module" src="../auto.js"></script>
+<!-- prettier-ignore -->
+<marked-md display="block"><plaintext>
+# Hello world!
+
+```html
+<p>Look at these <span id="html">HTML tags</span></p>
+<div class="box">They aren't being <code>interpreted</code> as actual HTML</div>
+```
+
+But this HTML is <mark>actually parsed</mark> just like **normal**.
+
+> <em>Markdown</em> lets you include <strong>arbitrary</strong> <abbr>HTML</abbr> in it.
diff --git a/tests/format/html/prettier_ignore/unclosed.html b/tests/format/html/prettier_ignore/unclosed.html
new file mode 100644
--- /dev/null
+++ b/tests/format/html/prettier_ignore/unclosed.html
@@ -0,0 +1,4 @@
+<!-- prettier-ignore -->
+<div>
+# Hello world!
+<span>
diff --git a/tests/format/html/prettier_ignore/unclosed2.html b/tests/format/html/prettier_ignore/unclosed2.html
new file mode 100644
--- /dev/null
+++ b/tests/format/html/prettier_ignore/unclosed2.html
@@ -0,0 +1,4 @@
+<!-- prettier-ignore -->
+<p>
+# Hello world!
+<div>