Repository: sveltejs/prettier-plugin-svelte (ts)

Issue:
prettier-ignore-start does not seem to work
**Describe the bug**
In the HTML of a Svelte file, `<!-- prettier-ignore -->` works but `<!-- prettier-ignore-start -->` does not seem to disable Prettier.

**To Reproduce**
```
<!-- prettier-ignore-->
<div class="this works this works this works this works this works this works this works this works this works" />

<!-- prettier-ignore-start -->
<div
  class="still gets formatted still gets formatted still gets formatted still gets formatted still gets formatted "
/>
<!-- prettier-ignore-end -->
```

**System (please complete the following information):**
 - OS: MacOS
 - IDE: VSCode
 - Plugin/Package: Svelte for VS Code v104.12.0


Pull request:
(feat) Support range ignores in HTML top level
`<!-- prettier-ignore-start --> ... <!-- prettier-ignore-end -->` is now supported at HTML top level
Closes #225

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f925c7..1a3eafa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,13 @@
 # prettier-plugin-svelte changelog
 
-## 2.2.1 (Unreleased)
+## 2.3.0 (Unreleased)
 
 * (fix) adjust snip regex to not break commented-out script/style tags ([#212](https://github.com/sveltejs/prettier-plugin-svelte/issues/212))
 * (fix) Don't snip style tag that is inside script tag ([#219](https://github.com/sveltejs/prettier-plugin-svelte/issues/219), [#70](https://github.com/sveltejs/prettier-plugin-svelte/issues/70))
 * (fix) Better formatting of long attribute values with mustache tags inbetween ([#221](https://github.com/sveltejs/prettier-plugin-svelte/pull/221))
 * (fix) Format properly when using Prettier 2.3.0+ ([#222](https://github.com/sveltejs/prettier-plugin-svelte/pull/222))
 * (fix) Keep parantheses in script tags for which JS is assumed ([#218](https://github.com/sveltejs/prettier-plugin-svelte/issues/218))
+* (feat) Support range ignores in HTML top level ([#225](https://github.com/sveltejs/prettier-plugin-svelte/issues/225))
 
 ## 2.2.0
 
diff --git a/src/print/index.ts b/src/print/index.ts
index 4d2ab62..6651aa0 100644
--- a/src/print/index.ts
+++ b/src/print/index.ts
@@ -33,6 +33,9 @@ import {
     trimTextNodeRight,
     canOmitSoftlineBeforeClosingTag,
     getNextNode,
+    isIgnoreStartDirective,
+    isIgnoreEndDirective,
+    isNodeTopLevelHTML,
 } from './node-helpers';
 import {
     ASTNode,
@@ -71,6 +74,7 @@ declare module 'prettier' {
 }
 
 let ignoreNext = false;
+let ignoreRange = false;
 let svelteOptionsDoc: Doc | undefined;
 
 function groupConcat(contents: doc.builders.Doc[]): doc.builders.Doc {
@@ -95,8 +99,13 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
     ];
     const node = n as Node;
 
-    if (ignoreNext && (node.type !== 'Text' || !isEmptyTextNode(node))) {
-        ignoreNext = false;
+    if (
+        (ignoreNext || (ignoreRange && !isIgnoreEndDirective(node))) &&
+        (node.type !== 'Text' || !isEmptyTextNode(node))
+    ) {
+        if (ignoreNext) {
+            ignoreNext = false;
+        }
         return concat(
             flatten(
                 options.originalText
@@ -578,14 +587,16 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
         case 'Comment': {
             const nodeAfterComment = getNextNode(path);
 
-            /**
-             * If there is no sibling node that starts right after us but the parent indicates
-             * that there used to be, that means that node was actually an embedded `<style>`
-             * or `<script>` node that was cut out.
-             * If so, the comment does not refer to the next line we will see.
-             * The `embed` function handles printing the comment in the right place.
-             */
-            if (
+            if (isIgnoreStartDirective(node) && isNodeTopLevelHTML(node, path)) {
+                ignoreRange = true;
+            } else if (isIgnoreEndDirective(node) && isNodeTopLevelHTML(node, path)) {
+                ignoreRange = false;
+            } else if (
+                // If there is no sibling node that starts right after us but the parent indicates
+                // that there used to be, that means that node was actually an embedded `<style>`
+                // or `<script>` node that was cut out.
+                // If so, the comment does not refer to the next line we will see.
+                // The `embed` function handles printing the comment in the right place.
                 doesEmbedStartAfterNode(node, path) ||
                 (isEmptyTextNode(nodeAfterComment) &&
                     doesEmbedStartAfterNode(nodeAfterComment, path))
@@ -678,6 +689,7 @@ function printTopLevelParts(
 
     // Need to reset these because they are global and could affect the next formatting run
     ignoreNext = false;
+    ignoreRange = false;
     svelteOptionsDoc = undefined;
 
     // If this is invoked as an embed of markdown, remove the last hardline.
@@ -967,7 +979,7 @@ function prepareChildren(children: Node[], path: FastPath, print: PrintFn): Node
     }
 
     function isCommentFollowedByOptions(node: Node, idx: number): node is CommentNode {
-        if (node.type !== 'Comment') {
+        if (node.type !== 'Comment' || isIgnoreEndDirective(node) || isIgnoreStartDirective(node)) {
             return false;
         }
 
diff --git a/src/print/node-helpers.ts b/src/print/node-helpers.ts
index cf053f3..4dc8c4c 100644
--- a/src/print/node-helpers.ts
+++ b/src/print/node-helpers.ts
@@ -111,7 +111,11 @@ export function getLeadingComment(path: FastPath): CommentNode | undefined {
     let node: Node = path.getNode();
     let prev: Node | undefined = siblings.find((child) => child.end === node.start);
     while (prev) {
-        if (prev.type === 'Comment') {
+        if (
+            prev.type === 'Comment' &&
+            !isIgnoreStartDirective(prev) &&
+            !isIgnoreEndDirective(prev)
+        ) {
             return prev;
         } else if (isEmptyTextNode(prev)) {
             node = prev;
@@ -127,20 +131,26 @@ export function getLeadingComment(path: FastPath): CommentNode | undefined {
  * at the specified position?
  */
 export function doesEmbedStartAfterNode(node: Node, path: FastPath, siblings = getSiblings(path)) {
-    const position = node.end;
-    const root = path.stack[0];
     // If node is not at the top level of html, an embed cannot start after it,
     // because embeds are only at the top level
-    if (!root.html || !root.html.children || !root.html.children.includes(node)) {
+    if (!isNodeTopLevelHTML(node, path)) {
         return false;
     }
 
+    const position = node.end;
+    const root = path.stack[0];
+
     const embeds = [root.css, root.html, root.instance, root.js, root.module] as Node[];
 
     const nextNode = siblings[siblings.indexOf(node) + 1];
     return embeds.find((n) => n && n.start >= position && (!nextNode || n.end <= nextNode.start));
 }
 
+export function isNodeTopLevelHTML(node: Node, path: FastPath): boolean {
+    const root = path.stack[0];
+    return !!root.html && !!root.html.children && root.html.children.includes(node);
+}
+
 export function isEmptyTextNode(node: Node | undefined): node is TextNode {
     return !!node && node.type === 'Text' && getUnencodedText(node).trim() === '';
 }
@@ -149,6 +159,14 @@ export function isIgnoreDirective(node: Node | undefined | null): boolean {
     return !!node && node.type === 'Comment' && node.data.trim() === 'prettier-ignore';
 }
 
+export function isIgnoreStartDirective(node: Node | undefined | null): boolean {
+    return !!node && node.type === 'Comment' && node.data.trim() === 'prettier-ignore-start';
+}
+
+export function isIgnoreEndDirective(node: Node | undefined | null): boolean {
+    return !!node && node.type === 'Comment' && node.data.trim() === 'prettier-ignore-end';
+}
+
 export function printRaw(
     node:
         | ElementNode


Test patch:
diff --git a/test/formatting/samples/prettier-ignore-range-1/input.html b/test/formatting/samples/prettier-ignore-range-1/input.html
new file mode 100644
index 0000000..a5948cb
--- /dev/null
+++ b/test/formatting/samples/prettier-ignore-range-1/input.html
@@ -0,0 +1,23 @@
+<script>
+    const a = true;
+</script>
+
+<div>
+    
+    I need to adhere sadly</div>
+
+<!-- prettier-ignore-start -->
+
+<div      > format madness here </div>
+
+<p>
+    I'm
+    freeeee!
+</p>
+
+<!-- prettier-ignore-end -->
+
+<div>I need to adhere sadly
+
+    
+</div>
diff --git a/test/formatting/samples/prettier-ignore-range-1/output.html b/test/formatting/samples/prettier-ignore-range-1/output.html
new file mode 100644
index 0000000..238d896
--- /dev/null
+++ b/test/formatting/samples/prettier-ignore-range-1/output.html
@@ -0,0 +1,18 @@
+<script>
+    const a = true;
+</script>
+
+<div>I need to adhere sadly</div>
+
+<!-- prettier-ignore-start -->
+
+<div      > format madness here </div>
+
+<p>
+    I'm
+    freeeee!
+</p>
+
+<!-- prettier-ignore-end -->
+
+<div>I need to adhere sadly</div>
diff --git a/test/formatting/samples/prettier-ignore-range-2/input.html b/test/formatting/samples/prettier-ignore-range-2/input.html
new file mode 100644
index 0000000..02f9267
--- /dev/null
+++ b/test/formatting/samples/prettier-ignore-range-2/input.html
@@ -0,0 +1,17 @@
+<div>
+    
+    I need to adhere sadly</div>
+
+<!-- prettier-ignore-start -->
+
+<div      > format madness here </div>
+
+<p>
+    I'm
+    freeeee!
+</p>
+
+<!-- prettier-ignore-end -->
+<script>
+    const a = true;
+</script>
diff --git a/test/formatting/samples/prettier-ignore-range-2/output.html b/test/formatting/samples/prettier-ignore-range-2/output.html
new file mode 100644
index 0000000..ccafbc2
--- /dev/null
+++ b/test/formatting/samples/prettier-ignore-range-2/output.html
@@ -0,0 +1,16 @@
+<script>
+    const a = true;
+</script>
+
+<div>I need to adhere sadly</div>
+
+<!-- prettier-ignore-start -->
+
+<div      > format madness here </div>
+
+<p>
+    I'm
+    freeeee!
+</p>
+
+<!-- prettier-ignore-end -->
diff --git a/test/formatting/samples/prettier-ignore-range-wrong/input.html b/test/formatting/samples/prettier-ignore-range-wrong/input.html
new file mode 100644
index 0000000..f44bcec
--- /dev/null
+++ b/test/formatting/samples/prettier-ignore-range-wrong/input.html
@@ -0,0 +1,14 @@
+<div>
+<!-- prettier-ignore-start -->
+
+<div      > no format madness here </div>
+
+<p>
+    no
+    toplevel
+</p>
+
+<!-- prettier-ignore-end -->
+</div>
+
+<div>We all need to adhere sadly</div>
diff --git a/test/formatting/samples/prettier-ignore-range-wrong/output.html b/test/formatting/samples/prettier-ignore-range-wrong/output.html
new file mode 100644
index 0000000..3f37393
--- /dev/null
+++ b/test/formatting/samples/prettier-ignore-range-wrong/output.html
@@ -0,0 +1,11 @@
+<div>
+    <!-- prettier-ignore-start -->
+
+    <div>no format madness here</div>
+
+    <p>no toplevel</p>
+
+    <!-- prettier-ignore-end -->
+</div>
+
+<div>We all need to adhere sadly</div>
diff --git a/test/formatting/samples/prettier-ignore-ranges-and-lines/input.html b/test/formatting/samples/prettier-ignore-ranges-and-lines/input.html
new file mode 100644
index 0000000..b2d0044
--- /dev/null
+++ b/test/formatting/samples/prettier-ignore-ranges-and-lines/input.html
@@ -0,0 +1,40 @@
+<div>
+    
+    I need to adhere sadly</div>
+
+<!-- prettier-ignore-start -->
+
+<div      > format madness here </div>
+
+<p>
+    I'm
+    freeeee!
+</p>
+
+<!-- prettier-ignore-end -->
+
+<div>I need to adhere sadly
+
+    
+</div>
+
+<!-- prettier-ignore-start -->
+
+<div      > format madness here </div>
+
+<p>
+    I'm
+    freeeee!
+</p>
+
+<!-- prettier-ignore-end -->
+
+<!-- prettier-ignore -->
+<p>
+    I'm
+    freeeee!
+</p>
+<div>I need to adhere sadly
+
+    
+</div>
diff --git a/test/formatting/samples/prettier-ignore-ranges-and-lines/output.html b/test/formatting/samples/prettier-ignore-ranges-and-lines/output.html
new file mode 100644
index 0000000..50f5908
--- /dev/null
+++ b/test/formatting/samples/prettier-ignore-ranges-and-lines/output.html
@@ -0,0 +1,32 @@
+<div>I need to adhere sadly</div>
+
+<!-- prettier-ignore-start -->
+
+<div      > format madness here </div>
+
+<p>
+    I'm
+    freeeee!
+</p>
+
+<!-- prettier-ignore-end -->
+
+<div>I need to adhere sadly</div>
+
+<!-- prettier-ignore-start -->
+
+<div      > format madness here </div>
+
+<p>
+    I'm
+    freeeee!
+</p>
+
+<!-- prettier-ignore-end -->
+
+<!-- prettier-ignore -->
+<p>
+    I'm
+    freeeee!
+</p>
+<div>I need to adhere sadly</div>
diff --git a/test/printer/samples/prettier-ignore-range.html b/test/printer/samples/prettier-ignore-range.html
new file mode 100644
index 0000000..238d896
--- /dev/null
+++ b/test/printer/samples/prettier-ignore-range.html
@@ -0,0 +1,18 @@
+<script>
+    const a = true;
+</script>
+
+<div>I need to adhere sadly</div>
+
+<!-- prettier-ignore-start -->
+
+<div      > format madness here </div>
+
+<p>
+    I'm
+    freeeee!
+</p>
+
+<!-- prettier-ignore-end -->
+
+<div>I need to adhere sadly</div>