Repository: sveltejs/language-tools
Pull request #1315 (2022-01-05 22:48:10)

Special highlighting for variable props
Was part of my old PR. Quoting myself:
> In detail, normal properties use the `entity.other.attribute-name.svelte` scope, while CSS variables use the `support.type.property-name.svelte` scope. These tend to have slightly different syntax highlighting in most themes, but the way they are highlighted is consistent with CSS properties. I figure that it should be clear - even in highlighting - that these are not really attributes in the normal sense.
> 
> Monokai Dimmed shows this well (My theme, Dark+, and Light+ all highlight those two scopes the same way)
> ![image](https://user-images.githubusercontent.com/34875062/110509683-97dec500-80bf-11eb-87ec-9f2813124be8.png)


Patch:
diff --git a/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml b/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml
index 5a7ebc4f..eb18d0ed 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,
@@ -339,17 +340,26 @@ repository:
   # Matches attribute keyvalues. (and boolean attributes as well)
   # e.g. `class="my-class"`
   attributes-keyvalue:
-    begin: ([_$[:alpha:]][_\-$[:alnum:]]*)
-    beginCaptures: { 1: { name: entity.other.attribute-name.svelte } }
+    begin: ((?:--)?[_$[:alpha:]][_\-$[:alnum:]]*)
+    beginCaptures:
+      0:
+        patterns:
+        # Matches if the key is a `--css-variable` attribute.
+        - match: --.*
+          name: support.type.property-name.svelte
+        # Matches everything else.
+        - match: .*
+          name: entity.other.attribute-name.svelte
     end: (?=\s*+[^=\s])
     name: 'meta.attribute.$1.svelte'
-    patterns: [ include: '#attributes-value' ]
+    patterns:
+    - begin: '='
+      beginCaptures: { 0: { name: punctuation.separator.key-value.svelte } }
+      end: (?<=[^\s=])(?!\s*=)|(?=/?>)
+      patterns: [include: '#attributes-value']
 
-  # The value part of attribute keyvalues. e.g. `="my-class"` in `class="my-class"`
+  # The value part of attribute keyvalues. e.g. `"my-class"` in `class="my-class"`
   attributes-value:
-    begin: '='
-    beginCaptures: { 0: { name: punctuation.separator.key-value.svelte } }
-    end: (?<=[^\s=])(?!\s*=)|(?=/?>)
     patterns:
     # No quotes - just an interpolation expression.
     - include: '#interpolation'
@@ -365,7 +375,7 @@ repository:
     - begin: (['"])
       end: \1
       beginCaptures: { 0: { name: punctuation.definition.string.begin.svelte } }
-      endCaptures: { 0: { name: punctuation.definition.string.end.svelte } }
+      endCaptures:   { 0: { name: punctuation.definition.string.end.svelte } }
       name: string.quoted.svelte
       patterns: [ include: '#interpolation' ]
 
@@ -419,7 +429,10 @@ repository:
     end: (?=\s*+[^=\s])
     name: meta.directive.$1.svelte
     patterns:
-    - include: '#attributes-value'
+    - begin: '='
+      beginCaptures: { 0: { name: punctuation.separator.key-value.svelte } }
+      end: (?<=[^\s=])(?!\s*=)|(?=/?>)
+      patterns: [include: '#attributes-value']
 
   # ------
   #  TAGS
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>