Repository: sveltejs/language-tools (ts)

Issue:
Add syntax higlighting and Typescript intellisense for `<style lang="ts">` tags
**Is your feature request related to a problem? Please describe.**
New CSS frameworks like vanilla-extract use Typescript as a CSS preprocessor. That's quite amazing because it lets you to reuse javascript variables in your style as well as in your script.

As of today vanilla-extract recommend to define your "typescript style" into separate `*.css.ts` files. That hurts the spirit of Svelte's components architecture, that is to put the content of any css preprocessor into the `<style>` tag rather than into a separate file.

**Describe the solution you'd like**
Add syntax highlighting and intellisense support for `<style lang="ts">` tags. The Typescript code inside the style tag should be completely independent of the main script tag. As a css preprocessor it will be processed into valid css that can be later parsed.

This feature would be awesome for the Svelte + Vanilla-extract users as well as the other Typescript-oriented CSS frameworks that I think will be coming in the next few years.

Pull request description:
Add support for highlighting JS/TS/CoffeeScript in `<style>` blocks
Partially deals with #1248 

This doesn't add anything like autocompletion or semantic highlighting, but if there are people using these kind of libraries with Svelte, at least the highlighting, indentation, etc. will be present.

Screenie:
![image](https://user-images.githubusercontent.com/34875062/148303607-fccfd5af-2871-4475-bc6f-4fc2b476ce5d.png)


Patch:
diff --git a/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml b/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml
index 5a7ebc4f..0127892a 100644
--- a/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml
+++ b/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml
@@ -13,22 +13,23 @@ injections:
   #     patterns: [{begin: '(?<=>)(?!</)', end: '(?=</)', name: meta.embedded.block.svelte,
   #     contentName: source.<lang>, patterns: [{ include: source.<lang> }]}]
 
-  # Script Languages
+  # Style/Script Languages
   # JavaScript | 'javascript' | 'source.js'
-  'L:meta.script.svelte meta.lang.javascript - (meta source)':
+  'L:(meta.script.svelte | meta.style.svelte) (meta.lang.js | meta.lang.javascript) - (meta source)':
     patterns: [{begin: '(?<=>)(?!</)', end: '(?=</)', name: meta.embedded.block.svelte,
     contentName: source.js, patterns: [{ include: source.js }]}]
 
   # TypeScript | 'ts' 'typescript' | 'source.ts'
-  'L:meta.script.svelte (meta.lang.ts | meta.lang.typescript) - (meta source)':
+  'L:(meta.script.svelte | meta.style.svelte) (meta.lang.ts | meta.lang.typescript) - (meta source)':
     patterns: [{begin: '(?<=>)(?!</)', end: '(?=</)', name: meta.embedded.block.svelte,
     contentName: source.ts, patterns: [{ include: source.ts }]}]
 
   # CoffeeScript | 'coffee' | 'source.coffee'
-  'L:meta.script.svelte meta.lang.coffee - (meta source)':
+  'L:(meta.script.svelte | meta.style.svelte) meta.lang.coffee - (meta source)':
     patterns: [{begin: '(?<=>)(?!</)', end: '(?=</)', name: meta.embedded.block.svelte,
     contentName: source.coffee, patterns: [{ include: source.coffee }]}]
 
+  # Script Languages
   # Default (JavaScript)
   'L:meta.script.svelte - meta.lang - (meta source)':
     patterns: [{begin: '(?<=>)(?!</)', end: '(?=</)', name: meta.embedded.block.svelte,
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts
new file mode 100644
index 00000000..258f7ca3
--- /dev/null
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts
@@ -0,0 +1,27 @@
+import MagicString from 'magic-string';
+import ts from 'typescript';
+
+/**
+ * move imports to top of script so they appear outside our render function
+ */
+export function handleImportDeclaration(
+    node: ts.ImportDeclaration,
+    str: MagicString,
+    astOffset: number,
+    scriptStart: number
+) {
+    const comments = ts.getLeadingCommentRanges(node.getFullText(), 0) ?? [];
+    for (const comment of comments) {
+        const commentEnd = node.pos + comment.end + astOffset;
+        str.move(node.pos + comment.pos + astOffset, commentEnd, scriptStart + 1);
+
+        if (comment.hasTrailingNewLine) {
+            str.overwrite(commentEnd - 1, commentEnd, str.original[commentEnd - 1] + '\n');
+        }
+    }
+
+    str.move(node.getStart() + astOffset, node.end + astOffset, scriptStart + 1);
+    //add in a \n
+    const originalEndChar = str.original[node.end + astOffset - 1];
+    str.overwrite(node.end + astOffset - 1, node.end + astOffset, originalEndChar + '\n');
+}
diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
index 7fd86f76..75583f3b 100644
--- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
@@ -15,6 +15,7 @@ import { ImplicitStoreValues } from './nodes/ImplicitStoreValues';
 import { Generics } from './nodes/Generics';
 import { is$$SlotsDeclaration } from './nodes/slot';
 import { preprendStr } from '../utils/magic-string';
+import { handleImportDeclaration } from './nodes/handleImportDeclaration';
 
 export interface InstanceScriptProcessResult {
     exportedNames: ExportedNames;
@@ -283,11 +284,8 @@ export function processInstanceScriptContent(
         }
 
         if (ts.isImportDeclaration(node)) {
-            //move imports to top of script so they appear outside our render function
-            str.move(node.getStart() + astOffset, node.end + astOffset, script.start + 1);
-            //add in a \n
-            const originalEndChar = str.original[node.end + astOffset - 1];
-            str.overwrite(node.end + astOffset - 1, node.end + astOffset, originalEndChar + '\n');
+            handleImportDeclaration(node, str, astOffset, script.start);
+
             // Check if import is the event dispatcher
             events.checkIfImportIsEventDispatcher(node);
         }


Test patch:
diff --git a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts
index 158dad5d..51da7b55 100644
--- a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts
+++ b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts
@@ -695,6 +695,70 @@ describe('CodeActionsProvider', () => {
         ]);
     });
 
+    it('organizes imports and not remove the leading comment', async () => {
+        const { provider, document } = setup('organize-imports-leading-comment.svelte');
+
+        const codeActions = await provider.getCodeActions(
+            document,
+            Range.create(Position.create(1, 4), Position.create(1, 5)), // irrelevant
+            {
+                diagnostics: [],
+                only: [CodeActionKind.SourceOrganizeImports]
+            }
+        );
+        (<TextDocumentEdit>codeActions[0]?.edit?.documentChanges?.[0])?.edits.forEach(
+            (edit) => (edit.newText = harmonizeNewLines(edit.newText))
+        );
+
+        assert.deepStrictEqual(codeActions, [
+            {
+                edit: {
+                    documentChanges: [
+                        {
+                            edits: [
+                                {
+                                    newText:
+                                        '// @ts-ignore\n' +
+                                        "    import { } from './somepng.png';\n" +
+                                        "    import { } from './t.png';\n",
+                                    range: {
+                                        end: {
+                                            character: 4,
+                                            line: 2
+                                        },
+                                        start: {
+                                            character: 4,
+                                            line: 1
+                                        }
+                                    }
+                                },
+                                {
+                                    newText: '',
+                                    range: {
+                                        end: {
+                                            character: 0,
+                                            line: 4
+                                        },
+                                        start: {
+                                            character: 4,
+                                            line: 2
+                                        }
+                                    }
+                                }
+                            ],
+                            textDocument: {
+                                uri: getUri('organize-imports-leading-comment.svelte'),
+                                version: null
+                            }
+                        }
+                    ]
+                },
+                kind: 'source.organizeImports',
+                title: 'Organize Imports'
+            }
+        ]);
+    });
+
     it('should do extract into const refactor', async () => {
         const { provider, document } = setup('codeactions.svelte');
 
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-imports-leading-comment.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-imports-leading-comment.svelte
new file mode 100644
index 00000000..27ae0f3e
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-imports-leading-comment.svelte
@@ -0,0 +1,5 @@
+<script>
+    import { } from './t.png';
+    // @ts-ignore
+    import { } from './somepng.png';
+</script>
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/import-leading-comment/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/import-leading-comment/expected.tsx
new file mode 100644
index 00000000..5f3f21cc
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/import-leading-comment/expected.tsx
@@ -0,0 +1,18 @@
+///<reference types="svelte" />
+<></>;
+import A from './a.svelte';
+// @ts-ignore
+import B from './b.svelte';
+/*hi*/import C from './c.svelte';
+function render() {
+
+    
+    
+    
+    
+;
+() => (<></>);
+return { props: {}, slots: {}, getters: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends __sveltets_1_createSvelte2TsxComponent(__sveltets_1_partial(__sveltets_1_with_any_event(render()))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/import-leading-comment/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/import-leading-comment/input.svelte
new file mode 100644
index 00000000..faeafc35
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/import-leading-comment/input.svelte
@@ -0,0 +1,6 @@
+<script lang="ts">
+    import A from './a.svelte';
+    // @ts-ignore
+    import B from './b.svelte';
+    /*hi*/import C from './c.svelte';
+</script>