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

Issue:
Always move component documentation comment to the top
You can create documentation for a Svelte component [like this](https://svelte.dev/faq#how-do-i-document-my-components). I think it makes sense to always have this at the top of the file. 

Current result when using `scripts-markup-style`:

```html
<script>
  export let name = 'world';
</script>

<!--
@component
Here's some documentation for this component.
It will show up on hover.
-->
<h1>Hello, {name}</h1>

<style>
   h1 {
     color: blue;
   }
</style>
```

Desired result:


```html
<!--
@component
Here's some documentation for this component.
It will show up on hover.
-->

<script>
  export let name = 'world';
</script>

<h1>Hello, {name}</h1>

<style>
   h1 {
     color: blue;
   }
</style>
```

This would be a breaking change.

Thoughts?

Pull request description:
Preserve top-level comments
To fix #137, if there are comments on a `style`, `script` or `template` tag, keep them together with the following tag when reformatting.

e.g. format 

```
<!-- add your styles here -->
<style>
</style>

<!-- here's the actual HTML -->
<h1>Title</h1>

<!-- code goes here -->
<script>
</script>
```

to

```
<!-- code goes here -->
<script>
</script>

<!-- add your styles here -->
<style>
</style>

<!-- here's the actual HTML -->
<h1>Title</h1>
```

Patch:
diff --git a/src/embed.ts b/src/embed.ts
index 6b0b00a..d10eb86 100644
--- a/src/embed.ts
+++ b/src/embed.ts
@@ -62,7 +62,7 @@ export function embed(
 }
 
 function forceIntoExpression(statement: string) {
-    // note the trailing newline: if the statement ends in a // comment, 
+    // note the trailing newline: if the statement ends in a // comment,
     // we can't add the closing bracket right afterwards
     return `(${statement}\n)`;
 }
@@ -169,10 +169,12 @@ function embedTag(
 ) {
     const node: Node = path.getNode();
     const content = getSnippedContent(node);
-    const isIgnored = isIgnoreDirective(getPreviousNode(path));
+
+    const previousNode = getPreviousNode(path);
+    const previousComment = previousNode && previousNode.type === 'Comment' ? previousNode : null;
 
     const body: Doc =
-        isNodeSupportedLanguage(node) && !isIgnored
+        isNodeSupportedLanguage(node) && !isIgnoreDirective(previousComment)
             ? content.trim() !== ''
                 ? formatBodyContent(content)
                 : hardline
@@ -194,10 +196,10 @@ function embedTag(
 
     if (isTopLevel) {
         // top level embedded nodes have been moved from their normal position in the
-        // node tree. if there is an ignore directive referring to it, it must be
-        // recreated at the new position.
-        if (isIgnored) {
-            result = concat(['<!-- prettier-ignore -->', hardline, result]);
+        // node tree. if there is a comment referring to it, it must be recreated at
+        // the new position.
+        if (previousComment) {
+            result = concat(['<!--', previousComment.data, '-->', hardline, result, hardline]);
         } else {
             result = concat([result, hardline]);
         }
diff --git a/src/print/index.ts b/src/print/index.ts
index f5ad53d..628d183 100644
--- a/src/print/index.ts
+++ b/src/print/index.ts
@@ -16,8 +16,8 @@ import {
     isNodeSupportedLanguage,
     isLoneMustacheTag,
     isOrCanBeConvertedToShorthand,
-    isIgnoreDirective,
     getNextNode,
+    getNodeEnd,
 } from './node-helpers';
 import {
     isLine,
@@ -437,18 +437,17 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
         case 'Ref':
             return concat([line, 'ref:', node.name]);
         case 'Comment': {
-            if (isIgnoreDirective(node)) {
-                /**
-                 * If there no sibling node that starts right after us, that means that node
-                 * was actually an embedded `<style>` or `<script>` node that was cut out.
-                 * If so, the ignore directive does not refer to the next line we will see.
-                 * The `embed` function handles printing the ignore directive in the right place.
-                 */
-                if (!getNextNode(path)) {
-                    return '';
-                } else {
-                    ignoreNext = true;
-                }
+            /**
+             * 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 (node.end < getNodeEnd(path.getParentNode(), path) && !getNextNode(path)) {
+                return '';
+            } else {
+                ignoreNext = true;
             }
 
             let text = node.data;
@@ -607,9 +606,11 @@ function printChildren(path: FastPath, print: PrintFn): Doc[] {
         } else {
             flush();
 
-            outputChildDoc(isLine(childDoc) ? childDoc : concat([breakParent, childDoc]), [
-                childNode,
-            ]);
+            if (childDoc !== '') {
+                outputChildDoc(isLine(childDoc) ? childDoc : concat([breakParent, childDoc]), [
+                    childNode,
+                ]);
+            }
         }
     }, 'children');
 
diff --git a/src/print/node-helpers.ts b/src/print/node-helpers.ts
index 843ded4..20df0d3 100644
--- a/src/print/node-helpers.ts
+++ b/src/print/node-helpers.ts
@@ -60,7 +60,7 @@ export function isInlineNode(node: Node): boolean {
     }
 }
 
-export function isNodeWithChildren(node: Node): node is (Node & {children: Node[]}) {
+export function isNodeWithChildren(node: Node): node is Node & { children: Node[] } {
     return (node as any).children;
 }
 
@@ -96,11 +96,28 @@ export function getNextNode(path: FastPath): Node | undefined {
     return getChildren(parent).find((child) => child.start === node.end);
 }
 
+/**
+ * Returns the position that used to be the end of the node before the embed elements were cut out. 
+ */
+export function getNodeEnd(node: Node, path: FastPath) {
+    const root = path.stack[0];
+
+    if (root.html === node) {
+        return Math.max(
+            ...([root.css, root.html, root.instance, root.js, root.module] as Node[])
+                .filter((n) => !!n)
+                .map((n) => n.end),
+        );
+    } else {
+        return node.end;
+    }
+}
+
 export function isEmptyNode(node: Node): boolean {
     return node.type === 'Text' && (node.raw || node.data).trim() === '';
 }
 
-export function isIgnoreDirective(node: Node | undefined): boolean {
+export function isIgnoreDirective(node: Node | undefined | null): boolean {
     return !!node && node.type === 'Comment' && node.data.trim() === 'prettier-ignore';
 }
 


Test patch:
diff --git a/test/formatting/samples/top-level-comments/input.html b/test/formatting/samples/top-level-comments/input.html
new file mode 100644
index 0000000..7c41222
--- /dev/null
+++ b/test/formatting/samples/top-level-comments/input.html
@@ -0,0 +1,10 @@
+<!-- add your styles here -->
+<style>
+</style>
+
+<!-- here's the actual HTML -->
+<h1>Title</h1>
+
+<!-- code goes here -->
+<script>
+</script>
diff --git a/test/formatting/samples/top-level-comments/output.html b/test/formatting/samples/top-level-comments/output.html
new file mode 100644
index 0000000..4cc26de
--- /dev/null
+++ b/test/formatting/samples/top-level-comments/output.html
@@ -0,0 +1,10 @@
+<!-- code goes here -->
+<script>
+</script>
+
+<!-- add your styles here -->
+<style>
+</style>
+
+<!-- here's the actual HTML -->
+<h1>Title</h1>