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

Issue:
Add `svelteSortOrder: none` option
Implement a new `svelteSortOrder` option which is called `none` and essentially leaves script/style/markup etc exactly where it is (other things like "shrink number of newlines to 1" would still apply).

This would close #111 and #258 by allowing an arbitrary number of comments inbetween sections.

Pull request description:
(feat) add `svelteSortOrder: none`
Closes #314

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a7da802..cdf0aef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
 ## 2.8.0 (unreleased)
 
 * (feat) support `singleAttributePerLine` Prettier option ([#305](https://github.com/sveltejs/prettier-plugin-svelte/issues/305))
+* (feat) add `svelteSortOrder: none` Prettier option which skips reordering scripts/styles/html ([#305](https://github.com/sveltejs/prettier-plugin-svelte/issues/314))
 
 ## 2.7.1
 
diff --git a/README.md b/README.md
index 60cda0f..8f01871 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ Make a `.prettierrc` file in your project directory and add your preferred [opti
 
 Sort order for `svelte:options`, scripts, markup, and styles.
 
-Format: join the keywords `options`, `scripts`, `markup`, `styles` with a `-` in the order you want.
+Format: join the keywords `options`, `scripts`, `markup`, `styles` with a `-` in the order you want; or `none` if you don't want Prettier to reorder anything.
 
 | Default                         | CLI Override                   | API Override                |
 | ------------------------------- | ------------------------------ | --------------------------- |
diff --git a/src/embed.ts b/src/embed.ts
index cdf78b3..a03f60d 100644
--- a/src/embed.ts
+++ b/src/embed.ts
@@ -223,7 +223,7 @@ function embedTag(
     ]);
     let result = groupConcat([openingTag, body, '</', tag, '>']);
 
-    if (isTopLevel) {
+    if (isTopLevel && options.svelteSortOrder !== 'none') {
         // top level embedded nodes have been moved from their normal position in the
         // node tree. if there is a comment referring to it, it must be recreated at
         // the new position.
diff --git a/src/options.ts b/src/options.ts
index eb3cef1..b22877a 100644
--- a/src/options.ts
+++ b/src/options.ts
@@ -48,6 +48,7 @@ export const options: Record<keyof PluginOptions, SupportOption> = {
             makeChoice('markup-scripts-styles-options'),
             makeChoice('styles-markup-scripts-options'),
             makeChoice('styles-scripts-markup-options'),
+            makeChoice('none'),
             // Deprecated, keep in 2.x for backwards-compatibility. svelte:options will be moved to the top
             makeChoice('scripts-markup-styles'),
             makeChoice('scripts-styles-markup'),
@@ -114,6 +115,7 @@ export type SortOrder =
     | 'markup-scripts-styles-options'
     | 'styles-markup-scripts-options'
     | 'styles-scripts-markup-options'
+    | 'none'
     | DeprecatedSortOrder;
 
 export type DeprecatedSortOrder =
@@ -129,6 +131,10 @@ export type SortOrderPart = 'scripts' | 'markup' | 'styles' | 'options';
 const sortOrderSeparator = '-';
 
 export function parseSortOrder(sortOrder: SortOrder): SortOrderPart[] {
+    if (sortOrder === 'none') {
+        return [];
+    }
+
     const order = sortOrder.split(sortOrderSeparator) as SortOrderPart[];
     // For backwards compatibility: Add options to beginning if not present
     if (!order.includes('options')) {
diff --git a/src/print/index.ts b/src/print/index.ts
index 0b8af27..c783c0e 100644
--- a/src/print/index.ts
+++ b/src/print/index.ts
@@ -44,6 +44,7 @@ import {
     isIgnoreStartDirective,
     isIgnoreEndDirective,
     isNodeTopLevelHTML,
+    getChildren,
 } from './node-helpers';
 import {
     ASTNode,
@@ -388,7 +389,10 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
             ]);
         }
         case 'Options':
-            throw new Error('Options tags should have been handled by prepareChildren');
+            if (options.svelteSortOrder !== 'none') {
+                throw new Error('Options tags should have been handled by prepareChildren');
+            }
+        // else fall through to Body
         case 'Body':
             return groupConcat([
                 '<',
@@ -720,6 +724,36 @@ function printTopLevelParts(
     path: FastPath<any>,
     print: PrintFn,
 ): Doc {
+    if (options.svelteSortOrder === 'none') {
+        const topLevelPartsByEnd: Record<number, any> = {};
+
+        if (n.module) {
+            n.module.type = 'Script';
+            n.module.attributes = extractAttributes(getText(n.module, options));
+            topLevelPartsByEnd[n.module.end] = n.module;
+        }
+        if (n.instance) {
+            n.instance.type = 'Script';
+            n.instance.attributes = extractAttributes(getText(n.instance, options));
+            topLevelPartsByEnd[n.instance.end] = n.instance;
+        }
+        if (n.css) {
+            n.css.type = 'Style';
+            n.css.content.type = 'StyleProgram';
+            topLevelPartsByEnd[n.css.end] = n.css;
+        }
+
+        const children = getChildren(n.html);
+        for (let i = 0; i < children.length; i++) {
+            const node = children[i];
+            if (topLevelPartsByEnd[node.start]) {
+                children.splice(i, 0, topLevelPartsByEnd[node.start]);
+                delete topLevelPartsByEnd[node.start];
+            }
+        }
+        return path.call(print, 'html');
+    }
+
     const parts: Record<SortOrderPart, Doc[]> = {
         options: [],
         scripts: [],
@@ -1022,16 +1056,18 @@ function prepareChildren(
             continue;
         }
 
-        if (isCommentFollowedByOptions(currentChild, idx)) {
-            svelteOptionsComment = printComment(currentChild);
-            const nextChild = children[idx + 1];
-            idx += nextChild && isEmptyTextNode(nextChild) ? 1 : 0;
-            continue;
-        }
+        if (options.svelteSortOrder !== 'none') {
+            if (isCommentFollowedByOptions(currentChild, idx)) {
+                svelteOptionsComment = printComment(currentChild);
+                const nextChild = children[idx + 1];
+                idx += nextChild && isEmptyTextNode(nextChild) ? 1 : 0;
+                continue;
+            }
 
-        if (currentChild.type === 'Options') {
-            printSvelteOptions(currentChild, idx, path, print);
-            continue;
+            if (currentChild.type === 'Options') {
+                printSvelteOptions(currentChild, idx, path, print);
+                continue;
+            }
         }
 
         childrenWithoutOptions.push(currentChild);


Test patch:
diff --git a/test/printer/samples/sort-order-none.html b/test/printer/samples/sort-order-none.html
new file mode 100644
index 0000000..ada7724
--- /dev/null
+++ b/test/printer/samples/sort-order-none.html
@@ -0,0 +1,23 @@
+<h1>Hello {name}!</h1>
+
+<svelte:options />
+
+<p>yeah why not</p>
+
+<script>
+    const name = "world";
+</script>
+
+<script context="module">
+    const name1 = "world";
+</script>
+
+<p>I hope noone orders them like this</p>
+
+<style>
+    h1 {
+        color: red;
+    }
+</style>
+
+<p>I really do</p>
diff --git a/test/printer/samples/sort-order-none.options.json b/test/printer/samples/sort-order-none.options.json
new file mode 100644
index 0000000..fd6e47a
--- /dev/null
+++ b/test/printer/samples/sort-order-none.options.json
@@ -0,0 +1,3 @@
+{
+    "svelteSortOrder": "none"
+}
diff --git a/test/printer/samples/sort-order-none2.html b/test/printer/samples/sort-order-none2.html
new file mode 100644
index 0000000..df86335
--- /dev/null
+++ b/test/printer/samples/sort-order-none2.html
@@ -0,0 +1,23 @@
+<script>
+    const name = "world";
+</script>
+
+<h1>Hello {name}!</h1>
+
+<svelte:options />
+
+<p>yeah why not</p>
+<!-- hi -->
+<script context="module">
+    const name1 = "world";
+</script>
+
+<p>I hope noone orders them like this</p>
+
+<style>
+    h1 {
+        color: red;
+    }
+</style>
+
+<p>I really do</p>
diff --git a/test/printer/samples/sort-order-none2.options.json b/test/printer/samples/sort-order-none2.options.json
new file mode 100644
index 0000000..fd6e47a
--- /dev/null
+++ b/test/printer/samples/sort-order-none2.options.json
@@ -0,0 +1,3 @@
+{
+    "svelteSortOrder": "none"
+}