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

Issue:
Style content erased
I'm seeing again the disappearing styles issue described in https://github.com/sveltejs/prettier-plugin-svelte/issues/255: in some cases, the content of the <style> tag is erased by prettier.

Repro: 
```
$ npm init svelte@next my-app
Need to install the following packages:
  create-svelte@next
Ok to proceed? (y) 
(...)
✔ Which Svelte app template? › Skeleton project
✔ Use TypeScript? … No 
✔ Add ESLint for code linting? … No 
✔ Add Prettier for code formatting? … Yes
```

At this point the dev dependencies are:
```
  "devDependencies": {
    "@sveltejs/kit": "next",
    "prettier": "^2.4.1",
    "prettier-plugin-svelte": "^2.4.0",
    "svelte": "^3.42.6"
  },
```

After npm install:
```
$ npm list
my-app@0.0.1 /Users/cr/Dora/src/my-app
├── @sveltejs/kit@1.0.0-next.196
├── prettier-plugin-svelte@2.5.0
├── prettier@2.4.1
└── svelte@3.44.1
```

I then add the following file to src/routes/test.svelte:
```
<script>
  import MyFunction from "./this_is_a_long_import_path/this_is_a_long_import_name.svelte";

  // a long, and useless comment
</script>

<style>
  iframe {
    width: 100%;
  }
</style>

<svelte:head>
  <script></script>
</svelte:head>
```

Now both `npx prettier --plugin-search-dir=. src/routes/test.svelte` or vscode `format document` will reformat the file like this, losing the style content:
```
<script>
        import MyFunction from './this_is_a_long_import_path/this_is_a_long_import_name.svelte';

        // a long, and useless comment
</script>

<svelte:head>
        <script></script>
</svelte:head>

<style></style>
```

If i downgrade prettier-plugin-svelte to 2.4.0, by removing the leading version caret in package.json, followed by npm install, I then get:
```
$ npm list
my-app@0.0.1 /Users/cr/Dora/src/my-app
├── @sveltejs/kit@1.0.0-next.196
├── prettier-plugin-svelte@2.4.0
├── prettier@2.4.1
└── svelte@3.44.1
```

And this time formatting via either the cli or vscode option works as expected.

_Originally posted by @cedricr in https://github.com/sveltejs/prettier-plugin-svelte/issues/255#issuecomment-970174634_

Pull request:
(fix) adjust snipTagContent
Correctly deal with changing spans
Fixes #261

Patch:
diff --git a/src/lib/snipTagContent.ts b/src/lib/snipTagContent.ts
index c9fb96f..df28679 100644
--- a/src/lib/snipTagContent.ts
+++ b/src/lib/snipTagContent.ts
@@ -1,8 +1,8 @@
 export const snippedTagContentAttribute = '✂prettier:content✂';
 
 export function snipScriptAndStyleTagContent(source: string): string {
-    const scriptMatchSpans = getMatchIndexes('script');
-    const styleMatchSpans = getMatchIndexes('style');
+    let scriptMatchSpans = getMatchIndexes('script');
+    let styleMatchSpans = getMatchIndexes('style');
 
     return snipTagContent(
         snipTagContent(source, 'script', '{}', styleMatchSpans),
@@ -29,32 +29,54 @@ export function snipScriptAndStyleTagContent(source: string): string {
         placeholder: string,
         otherSpans: [number, number][],
     ) {
-        // Replace valid matches
         const regex = getRegexp(tagName);
+        let newScriptMatchSpans = scriptMatchSpans;
+        let newStyleMatchSpans = styleMatchSpans;
+        // Replace valid matches
         const newSource = _source.replace(regex, (match, attributes, content, index) => {
             if (match.startsWith('<!--') || withinOtherSpan(index)) {
                 return match;
             }
             const encodedContent = Buffer.from(content).toString('base64');
-            return `<${tagName}${attributes} ${snippedTagContentAttribute}="${encodedContent}">${placeholder}</${tagName}>`;
+            const newContent = `<${tagName}${attributes} ${snippedTagContentAttribute}="${encodedContent}">${placeholder}</${tagName}>`;
+
+            // Adjust the spans because the source now has a different content length
+            const lengthDiff = match.length - newContent.length;
+            newScriptMatchSpans = adjustSpans(scriptMatchSpans, newScriptMatchSpans);
+            newStyleMatchSpans = adjustSpans(styleMatchSpans, newStyleMatchSpans);
+            function adjustSpans(
+                oldSpans: [number, number][],
+                newSpans: [number, number][],
+            ): [number, number][] {
+                return oldSpans.map((oldSpan, idx) => {
+                    const newSpan = newSpans[idx];
+                    // Do the check using the old spans because the replace function works
+                    // on the old spans. Replace oldSpans with newSpans afterwards.
+                    if (oldSpan[0] > index) {
+                        // span is after the match -> adjust start and end
+                        return [newSpan[0] - lengthDiff, newSpan[1] - lengthDiff];
+                    } else if (oldSpan[0] === index) {
+                        // span is the match -> adjust end only
+                        return [newSpan[0], newSpan[1] - lengthDiff];
+                    } else {
+                        // span is before the match -> nothing to adjust
+                        return newSpan;
+                    }
+                });
+            }
+
+            return newContent;
         });
 
-        // Adjust the spans because the source now has a different content length
-        adjustSpans(scriptMatchSpans);
-        adjustSpans(styleMatchSpans);
+        // Now that the replacement function ran, we can adjust the spans for the next run
+        scriptMatchSpans = newScriptMatchSpans;
+        styleMatchSpans = newStyleMatchSpans;
 
         return newSource;
 
         function withinOtherSpan(idx: number) {
             return otherSpans.some((otherSpan) => idx > otherSpan[0] && idx < otherSpan[1]);
         }
-        function adjustSpans(spans: [number, number][]) {
-            const lengthDiff = _source.length - newSource.length;
-            spans.forEach((span) => {
-                span[0] -= lengthDiff;
-                span[1] -= lengthDiff;
-            });
-        }
     }
 
     function getRegexp(tagName: string) {


Test patch:
diff --git a/test/printer/samples/script-style-script-snipping.html b/test/printer/samples/script-style-script-snipping.html
new file mode 100644
index 0000000..4f82d0b
--- /dev/null
+++ b/test/printer/samples/script-style-script-snipping.html
@@ -0,0 +1,15 @@
+<script>
+    import MyFunction from "./this_is_a_long_import_path/this_is_a_long_import_name.svelte";
+
+    // a long, and useless comment
+</script>
+
+<style>
+    iframe {
+        width: 100%;
+    }
+</style>
+
+<svelte:head>
+    <script></script>
+</svelte:head>
diff --git a/test/printer/samples/script-style-script-snipping.options.json b/test/printer/samples/script-style-script-snipping.options.json
new file mode 100644
index 0000000..2ff9eac
--- /dev/null
+++ b/test/printer/samples/script-style-script-snipping.options.json
@@ -0,0 +1,3 @@
+{
+    "svelteSortOrder": "options-scripts-styles-markup"
+}