Repository: sveltejs/language-tools
Pull request #1319 (2022-01-06 00:00:18)

Add highlighting for `@const` tags
Helps with #1067

Due to me trying to make this as reliable as possible, the actual TS highlighting begins at the `=` character. Trying to get TS to highlight `const` itself probably would open up a bunch of edge cases.

![image](https://user-images.githubusercontent.com/34875062/148306696-9ecaf02a-ae10-45de-a633-0131d2511d11.png)



Patch:
diff --git a/packages/language-server/package.json b/packages/language-server/package.json
index df7ffa29..bd5d9973 100644
--- a/packages/language-server/package.json
+++ b/packages/language-server/package.json
@@ -63,6 +63,7 @@
         "vscode-emmet-helper": "~2.6.0",
         "vscode-html-languageservice": "~4.1.0",
         "vscode-languageserver": "7.1.0-next.4",
+        "vscode-languageserver-protocol": "3.16.0",
         "vscode-languageserver-types": "3.16.0",
         "vscode-uri": "~3.0.0"
     }
diff --git a/packages/language-server/src/lib/documents/Document.ts b/packages/language-server/src/lib/documents/Document.ts
index e7728fa7..0c64674b 100644
--- a/packages/language-server/src/lib/documents/Document.ts
+++ b/packages/language-server/src/lib/documents/Document.ts
@@ -17,6 +17,11 @@ export class Document extends WritableDocument {
     configPromise: Promise<SvelteConfig | undefined>;
     config?: SvelteConfig;
     html!: HTMLDocument;
+    /**
+     * Compute and cache directly because of performance reasons
+     * and it will be called anyway.
+     */
+    private path = urlToPath(this.url);
 
     constructor(public url: string, public content: string) {
         super();
@@ -77,7 +82,7 @@ export class Document extends WritableDocument {
      * Returns the file path if the url scheme is file
      */
     getFilePath(): string | null {
-        return urlToPath(this.url);
+        return this.path;
     }
 
     /**
diff --git a/packages/language-server/src/ls-config.ts b/packages/language-server/src/ls-config.ts
index 2e8b8ad8..33e54a56 100644
--- a/packages/language-server/src/ls-config.ts
+++ b/packages/language-server/src/ls-config.ts
@@ -20,7 +20,8 @@ const defaultLSConfig: LSConfig = {
         selectionRange: { enable: true },
         signatureHelp: { enable: true },
         semanticTokens: { enable: true },
-        implementation: { enable: true }
+        implementation: { enable: true },
+        typeDefinition: { enable: true }
     },
     css: {
         enable: true,
@@ -116,6 +117,9 @@ export interface LSTypescriptConfig {
     implementation: {
         enable: boolean;
     };
+    typeDefinition: {
+        enable: boolean;
+    };
 }
 
 export interface LSCSSConfig {
diff --git a/packages/language-server/src/plugins/PluginHost.ts b/packages/language-server/src/plugins/PluginHost.ts
index f8108923..d506c1cd 100644
--- a/packages/language-server/src/plugins/PluginHost.ts
+++ b/packages/language-server/src/plugins/PluginHost.ts
@@ -446,6 +446,20 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
         );
     }
 
+    getTypeDefinition(
+        textDocument: TextDocumentIdentifier,
+        position: Position
+    ): Promise<Location[] | null> {
+        const document = this.getDocument(textDocument.uri);
+
+        return this.execute<Location[] | null>(
+            'getTypeDefinition',
+            [document, position],
+            ExecuteMode.FirstNonNull,
+            'high'
+        );
+    }
+
     onWatchFileChanges(onWatchFileChangesParas: OnWatchFileChangesPara[]): void {
         for (const support of this.plugins) {
             support.onWatchFileChanges?.(onWatchFileChangesParas);
diff --git a/packages/language-server/src/plugins/interfaces.ts b/packages/language-server/src/plugins/interfaces.ts
index aed3e8f6..005e9724 100644
--- a/packages/language-server/src/plugins/interfaces.ts
+++ b/packages/language-server/src/plugins/interfaces.ts
@@ -171,6 +171,10 @@ export interface ImplementationProvider {
     getImplementation(document: Document, position: Position): Resolvable<Location[] | null>;
 }
 
+export interface TypeDefinitionProvider {
+    getTypeDefinition(document: Document, position: Position): Resolvable<Location[] | null>;
+}
+
 export interface OnWatchFileChangesPara {
     fileName: string;
     changeType: FileChangeType;
@@ -199,7 +203,8 @@ type ProviderBase = DiagnosticsProvider &
     SignatureHelpProvider &
     SemanticTokensProvider &
     LinkedEditingRangesProvider &
-    ImplementationProvider;
+    ImplementationProvider &
+    TypeDefinitionProvider;
 
 export type LSProvider = ProviderBase & BackwardsCompatibleDefinitionsProvider;
 
diff --git a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts
index b879b02c..02be420c 100644
--- a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts
+++ b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts
@@ -36,14 +36,15 @@ import {
     FileRename,
     FindReferencesProvider,
     HoverProvider,
+    ImplementationProvider,
     OnWatchFileChanges,
     OnWatchFileChangesPara,
     RenameProvider,
     SelectionRangeProvider,
     SemanticTokensProvider,
     SignatureHelpProvider,
+    TypeDefinitionProvider,
     UpdateImportsProvider,
-    ImplementationProvider,
     UpdateTsOrJsFile
 } from '../interfaces';
 import { CodeActionsProviderImpl } from './features/CodeActionsProvider';
@@ -60,6 +61,7 @@ import { RenameProviderImpl } from './features/RenameProvider';
 import { SelectionRangeProviderImpl } from './features/SelectionRangeProvider';
 import { SemanticTokensProviderImpl } from './features/SemanticTokensProvider';
 import { SignatureHelpProviderImpl } from './features/SignatureHelpProvider';
+import { TypeDefinitionProviderImpl } from './features/TypeDefinitionProvider';
 import { UpdateImportsProviderImpl } from './features/UpdateImportsProvider';
 import { isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './features/utils';
 import { LSAndTSDocResolver } from './LSAndTSDocResolver';
@@ -80,6 +82,7 @@ export class TypeScriptPlugin
         SignatureHelpProvider,
         SemanticTokensProvider,
         ImplementationProvider,
+        TypeDefinitionProvider,
         OnWatchFileChanges,
         CompletionsProvider<CompletionEntryWithIdentifer>,
         UpdateTsOrJsFile
@@ -97,6 +100,7 @@ export class TypeScriptPlugin
     private readonly signatureHelpProvider: SignatureHelpProviderImpl;
     private readonly semanticTokensProvider: SemanticTokensProviderImpl;
     private readonly implementationProvider: ImplementationProviderImpl;
+    private readonly typeDefinitionProvider: TypeDefinitionProviderImpl;
 
     constructor(configManager: LSConfigManager, lsAndTsDocResolver: LSAndTSDocResolver) {
         this.configManager = configManager;
@@ -119,6 +123,7 @@ export class TypeScriptPlugin
         this.signatureHelpProvider = new SignatureHelpProviderImpl(this.lsAndTsDocResolver);
         this.semanticTokensProvider = new SemanticTokensProviderImpl(this.lsAndTsDocResolver);
         this.implementationProvider = new ImplementationProviderImpl(this.lsAndTsDocResolver);
+        this.typeDefinitionProvider = new TypeDefinitionProviderImpl(this.lsAndTsDocResolver);
     }
 
     async getDiagnostics(
@@ -467,6 +472,14 @@ export class TypeScriptPlugin
         return this.implementationProvider.getImplementation(document, position);
     }
 
+    async getTypeDefinition(document: Document, position: Position): Promise<Location[] | null> {
+        if (!this.featureEnabled('typeDefinition')) {
+            return null;
+        }
+
+        return this.typeDefinitionProvider.getTypeDefinition(document, position);
+    }
+
     private async getLSAndTSDoc(document: Document) {
         return this.lsAndTsDocResolver.getLSAndTSDoc(document);
     }
diff --git a/packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts b/packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts
index a72b13c1..fc330db0 100644
--- a/packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts
@@ -37,7 +37,7 @@ export class ImplementationProviderImpl implements ImplementationProvider {
                     convertRange(fragment, implementation.textSpan)
                 );
 
-                if (range.start.line > 0 && range.end.line > 0) {
+                if (range.start.line >= 0 && range.end.line >= 0) {
                     return Location.create(pathToUrl(implementation.fileName), range);
                 }
             })
diff --git a/packages/language-server/src/plugins/typescript/features/TypeDefinitionProvider.ts b/packages/language-server/src/plugins/typescript/features/TypeDefinitionProvider.ts
new file mode 100644
index 00000000..fad6fde4
--- /dev/null
+++ b/packages/language-server/src/plugins/typescript/features/TypeDefinitionProvider.ts
@@ -0,0 +1,48 @@
+import { Position, Location } from 'vscode-languageserver-protocol';
+import { Document, mapRangeToOriginal } from '../../../lib/documents';
+import { pathToUrl, isNotNullOrUndefined } from '../../../utils';
+import { TypeDefinitionProvider } from '../../interfaces';
+import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
+import { convertRange } from '../utils';
+import { isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './utils';
+
+export class TypeDefinitionProviderImpl implements TypeDefinitionProvider {
+    constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {}
+
+    async getTypeDefinition(document: Document, position: Position): Promise<Location[] | null> {
+        const { tsDoc, lang } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
+
+        const mainFragment = await tsDoc.getFragment();
+        const offset = mainFragment.offsetAt(mainFragment.getGeneratedPosition(position));
+
+        const typeDefs = lang.getTypeDefinitionAtPosition(tsDoc.filePath, offset);
+
+        const docs = new SnapshotFragmentMap(this.lsAndTsDocResolver);
+        docs.set(tsDoc.filePath, { fragment: mainFragment, snapshot: tsDoc });
+
+        if (!typeDefs) {
+            return null;
+        }
+
+        const result = await Promise.all(
+            typeDefs.map(async (typeDef) => {
+                const { fragment, snapshot } = await docs.retrieve(typeDef.fileName);
+
+                if (!isNoTextSpanInGeneratedCode(snapshot.getFullText(), typeDef.textSpan)) {
+                    return;
+                }
+
+                const range = mapRangeToOriginal(
+                    fragment,
+                    convertRange(fragment, typeDef.textSpan)
+                );
+
+                if (range.start.line >= 0 && range.end.line >= 0) {
+                    return Location.create(pathToUrl(typeDef.fileName), range);
+                }
+            })
+        );
+
+        return result.filter(isNotNullOrUndefined);
+    }
+}
diff --git a/packages/language-server/src/plugins/typescript/module-loader.ts b/packages/language-server/src/plugins/typescript/module-loader.ts
index b023318b..1fcac077 100644
--- a/packages/language-server/src/plugins/typescript/module-loader.ts
+++ b/packages/language-server/src/plugins/typescript/module-loader.ts
@@ -90,9 +90,14 @@ export function createSvelteModuleLoader(
         fileExists: svelteSys.fileExists,
         readFile: svelteSys.readFile,
         readDirectory: svelteSys.readDirectory,
-        deleteFromModuleCache: (path: string) => moduleCache.delete(path),
-        deleteUnresolvedResolutionsFromCache: (path: string) =>
-            moduleCache.deleteUnresolvedResolutionsFromCache(path),
+        deleteFromModuleCache: (path: string) => {
+            svelteSys.deleteFromCache(path);
+            moduleCache.delete(path);
+        },
+        deleteUnresolvedResolutionsFromCache: (path: string) => {
+            svelteSys.deleteFromCache(path);
+            moduleCache.deleteUnresolvedResolutionsFromCache(path);
+        },
         resolveModuleNames
     };
 
diff --git a/packages/language-server/src/plugins/typescript/svelte-sys.ts b/packages/language-server/src/plugins/typescript/svelte-sys.ts
index 6393c0c3..64a3c840 100644
--- a/packages/language-server/src/plugins/typescript/svelte-sys.ts
+++ b/packages/language-server/src/plugins/typescript/svelte-sys.ts
@@ -6,10 +6,15 @@ import { ensureRealSvelteFilePath, isVirtualSvelteFilePath, toRealSvelteFilePath
  * This should only be accessed by TS svelte module resolution.
  */
 export function createSvelteSys(getSnapshot: (fileName: string) => DocumentSnapshot) {
-    const svelteSys: ts.System = {
+    const fileExistsCache = new Map<string, boolean>();
+
+    const svelteSys: ts.System & { deleteFromCache: (path: string) => void } = {
         ...ts.sys,
         fileExists(path: string) {
-            return ts.sys.fileExists(ensureRealSvelteFilePath(path));
+            path = ensureRealSvelteFilePath(path);
+            const exists = fileExistsCache.get(path) ?? ts.sys.fileExists(path);
+            fileExistsCache.set(path, exists);
+            return exists;
         },
         readFile(path: string) {
             const snapshot = getSnapshot(path);
@@ -19,6 +24,13 @@ export function createSvelteSys(getSnapshot: (fileName: string) => DocumentSnaps
             const extensionsWithSvelte = (extensions ?? []).concat('.svelte');
 
             return ts.sys.readDirectory(path, extensionsWithSvelte, exclude, include, depth);
+        },
+        deleteFile(path) {
+            fileExistsCache.delete(ensureRealSvelteFilePath(path));
+            return ts.sys.deleteFile?.(path);
+        },
+        deleteFromCache(path) {
+            fileExistsCache.delete(ensureRealSvelteFilePath(path));
         }
     };
 
diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts
index b473c0b6..acfe572d 100644
--- a/packages/language-server/src/server.ts
+++ b/packages/language-server/src/server.ts
@@ -246,7 +246,8 @@ export function startServer(options?: LSOptions) {
                     full: true
                 },
                 linkedEditingRangeProvider: true,
-                implementationProvider: true
+                implementationProvider: true,
+                typeDefinitionProvider: true
             }
         };
     });
@@ -354,6 +355,10 @@ export function startServer(options?: LSOptions) {
         pluginHost.getImplementation(evt.textDocument, evt.position)
     );
 
+    connection.onTypeDefinition((evt) =>
+        pluginHost.getTypeDefinition(evt.textDocument, evt.position)
+    );
+
     const diagnosticsManager = new DiagnosticsManager(
         connection.sendDiagnostics,
         docManager,
diff --git a/packages/svelte-vscode/package.json b/packages/svelte-vscode/package.json
index 59ccae74..75666c31 100644
--- a/packages/svelte-vscode/package.json
+++ b/packages/svelte-vscode/package.json
@@ -178,6 +178,12 @@
                     "title": "Typescript: Go to Implementation",
                     "description": "Enable go to implementation for Typescript"
                 },
+                "svelte.plugin.typescript.typeDefinition.enable": {
+                    "type": "boolean",
+                    "default": true,
+                    "title": "Typescript: Go to Type Definition",
+                    "description": "Enable go to Type Definition for Typescript"
+                },
                 "svelte.plugin.css.enable": {
                     "type": "boolean",
                     "default": true,
diff --git a/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml b/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml
index 5a7ebc4f..62cf699a 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,
@@ -214,7 +215,8 @@ repository:
          { match: each|key,          name: keyword.control.svelte             },
          { match: await|then|catch,  name: keyword.control.flow.svelte        },
          { match: html,              name: keyword.other.svelte               },
-         { match: debug,             name: keyword.other.debugger.svelte      }]}
+         { match: debug,             name: keyword.other.debugger.svelte      },
+         { match: const,             name: storage.type.svelte                }]}
 
   # Scopes special tag _block start nodes_ depending on what type they are, such as `#if` or `#await` blocks.
   special-tags-modes:
@@ -225,6 +227,19 @@ repository:
       name: meta.embedded.expression.svelte source.ts
       patterns: [ include: source.ts ]
 
+    # Const.
+    - begin: (?<=const.*?)\G
+      end: (?=})
+      patterns:
+      # Variable.
+      - begin: \G\s*([_$[:alpha:]][_$[:alnum:]]+)\s*
+        end: (?=\=)
+        beginCaptures: { 1: { name: variable.other.constant.svelte } }
+      # Expression (starting with "=").
+      - begin: (?=\=)
+        end: (?=})
+        patterns: [ include: source.ts#variable-initializer ]
+
     # Each.
     - begin: (?<=each.*?)\G
       end: (?=})
@@ -339,24 +354,36 @@ 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'
     # Units, meaning digit characters and an optional unit string. e.g. `15px`
-    - match: ([0-9._]+[\w]{,4})(?=\s|/?>)
-      name: constant.numeric.decimal.svelte
-      patterns: [ include: '#interpolation' ]
+    - match: (?:(['"])([0-9._]+[\w%]{,4})(\1))|(?:([0-9._]+[\w%]{,4})(?=\s|/?>))
+      captures:
+        1: { name: punctuation.definition.string.begin.svelte }
+        2: { name: constant.numeric.decimal.svelte }
+        3: { name: punctuation.definition.string.end.svelte }
+        4: { name: constant.numeric.decimal.svelte }
     # Unquoted strings.
     - match: ([^\s"'=<>`/]|/(?!>))+
       name: string.unquoted.svelte
@@ -365,7 +392,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' ]
 
@@ -377,7 +404,7 @@ repository:
     - { match: on|use|bind,               name: keyword.control.svelte             }
     - { match: transition|in|out|animate, name: keyword.other.animation.svelte     }
     - { match: let,                       name: storage.type.svelte                }
-    - { match: class,                     name: entity.other.attribute-name.svelte }
+    - { match: class|style,               name: entity.other.attribute-name.svelte }
 
   # For Svelte element directives. Scopes the 'click' part in `on:click`.
   # The scope of the variable should represent what data type it generally is.
@@ -387,7 +414,7 @@ repository:
     - { match: '(?<=(on):).*$',                            name: entity.name.type.svelte   }
     - { match: '(?<=(bind):).*$',                          name: variable.parameter.svelte }
     - { match: '(?<=(use|transition|in|out|animate):).*$', name: variable.function.svelte  }
-    - { match: '(?<=(let|class):).*$',                     name: variable.parameter.svelte }
+    - { match: '(?<=(let|class|style):).*$',               name: variable.parameter.svelte }
 
   # For directives that may be better suited using different scopes if assigned to something.
   # e.g. `class:var={var}` is different to `class:var`.
@@ -398,15 +425,16 @@ repository:
     # Makes 'bind:this' display like `this` would in JS.
     - { match: '(?<=(bind):)this$', name: variable.language.svelte }
     # If other keywords are patched in in the future, they can easily be added here.
-    - { match: '(?<=(bind):).*$',  name: entity.name.type.svelte                  }
-    - { match: '(?<=(class):).*$', name: entity.other.attribute-name.class.svelte }
+    - { match: '(?<=(bind):).*$',   name: entity.name.type.svelte                  }
+    - { match: '(?<=(class):).*$',  name: entity.other.attribute-name.class.svelte }
+    - { match: '(?<=(style):).*$',  name: support.type.property-name.svelte        }
     # Defaults to the non-assignment version for everything else.
     - include: '#attributes-directives-types'
 
   # Matches Svelte element directives, e.g. `on:click|preventDefault={var}`
   attributes-directives:
     # If something is added to attributes-directives-keywords, it must be added to the begin-regex, too.
-    begin: (?<!<)(on|use|bind|transition|in|out|animate|let|class)(:)(?:([_$[:alpha:]][_\-$[:alnum:]]*(?=\s*=))|([_$[:alpha:]][_\-$[:alnum:]]*))((\|\w+)*)
+    begin: (?<!<)(on|use|bind|transition|in|out|animate|let|class|style)(:)(?:((?:--)?[_$[:alpha:]][_\-$[:alnum:]]*(?=\s*=))|((?:--)?[_$[:alpha:]][_\-$[:alnum:]]*))((\|\w+)*)
     beginCaptures:
       1: { patterns: [ include: '#attributes-directives-keywords' ] }
       2: { name: punctuation.definition.keyword.svelte }
@@ -419,7 +447,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..fa5d3732 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;
@@ -169,7 +170,12 @@ export function processInstanceScriptContent(
         // - in order to get ts errors if store is not assignable to SvelteStore
         // - use $store variable defined above to get ts flow control
         const dollar = str.original.indexOf('$', ident.getStart() + astOffset);
-        const getPrefix = isSafeToPrefixWithSemicolon(ident) ? ';' : '';
+        const getPrefix = isSafeToPrefixWithSemicolon(ident)
+            ? ';'
+            : ts.isShorthandPropertyAssignment(parent)
+            ? // { $store } --> { $store: __sveltets_1_store_get(..)}
+              ident.text + ': '
+            : '';
         str.overwrite(dollar, dollar + 1, getPrefix + '(__sveltets_1_store_get(');
         str.prependLeft(ident.end + astOffset, `), $${storename})`);
     };
@@ -283,11 +289,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/features/ImplemenationProvider.test.ts b/packages/language-server/test/plugins/typescript/features/ImplemenationProvider.test.ts
index 3f05419b..cc4dccac 100644
--- a/packages/language-server/test/plugins/typescript/features/ImplemenationProvider.test.ts
+++ b/packages/language-server/test/plugins/typescript/features/ImplemenationProvider.test.ts
@@ -10,7 +10,7 @@ import { Location } from 'vscode-languageserver-protocol';
 
 const testDir = path.join(__dirname, '..');
 
-describe('FindReferencesProvider', () => {
+describe('ImplementationProvider', () => {
     function getFullPath(filename: string) {
         return path.join(testDir, 'testfiles', 'implementation', filename);
     }
diff --git a/packages/language-server/test/plugins/typescript/features/TypeDefinitionProvider.test.ts b/packages/language-server/test/plugins/typescript/features/TypeDefinitionProvider.test.ts
new file mode 100644
index 00000000..c410901d
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/features/TypeDefinitionProvider.test.ts
@@ -0,0 +1,89 @@
+import assert from 'assert';
+import path from 'path';
+import ts from 'typescript';
+import { Location } from 'vscode-languageserver-protocol';
+import { Document, DocumentManager } from '../../../../src/lib/documents';
+import { LSConfigManager } from '../../../../src/ls-config';
+import { LSAndTSDocResolver } from '../../../../src/plugins';
+import { TypeDefinitionProviderImpl } from '../../../../src/plugins/typescript/features/TypeDefinitionProvider';
+import { pathToUrl } from '../../../../src/utils';
+
+const testDir = path.join(__dirname, '..');
+
+describe('TypeDefinitionProvider', () => {
+    function getFullPath(filename: string) {
+        return path.join(testDir, 'testfiles', 'typedefinition', filename);
+    }
+
+    function getUri(filename: string) {
+        return pathToUrl(getFullPath(filename));
+    }
+
+    function setup(filename: string) {
+        const docManager = new DocumentManager(
+            (textDocument) => new Document(textDocument.uri, textDocument.text)
+        );
+        const lsAndTsDocResolver = new LSAndTSDocResolver(
+            docManager,
+            [testDir],
+            new LSConfigManager()
+        );
+        const provider = new TypeDefinitionProviderImpl(lsAndTsDocResolver);
+        const filePath = getFullPath(filename);
+        const document = docManager.openDocument(<any>{
+            uri: pathToUrl(filePath),
+            text: ts.sys.readFile(filePath) || ''
+        });
+        return { provider, document };
+    }
+
+    it('find type definition in TS file', async () => {
+        const { document, provider } = setup('typedefinition.svelte');
+
+        const typeDefs = await provider.getTypeDefinition(document, {
+            line: 5,
+            character: 15
+        });
+
+        assert.deepStrictEqual(typeDefs, <Location[]>[
+            {
+                range: {
+                    start: {
+                        line: 0,
+                        character: 13
+                    },
+                    end: {
+                        line: 0,
+                        character: 30
+                    }
+                },
+                uri: getUri('some-class.ts')
+            }
+        ]);
+    });
+
+    it('find type definition in same Svelte file', async () => {
+        const { document, provider } = setup('typedefinition.svelte');
+
+        const typeDefs = await provider.getTypeDefinition(document, {
+            line: 6,
+            character: 20
+        });
+
+        assert.deepStrictEqual(typeDefs, <Location[]>[
+            {
+                range: {
+                    start: {
+                        line: 3,
+                        character: 10
+                    },
+                    end: {
+                        line: 3,
+                        character: 19
+                    }
+                },
+                uri: getUri('typedefinition.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/language-server/test/plugins/typescript/testfiles/typedefinition/some-class.ts b/packages/language-server/test/plugins/typescript/testfiles/typedefinition/some-class.ts
new file mode 100644
index 00000000..7d076030
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/typedefinition/some-class.ts
@@ -0,0 +1,1 @@
+export class SomeImportedClass {}
\ No newline at end of file
diff --git a/packages/language-server/test/plugins/typescript/testfiles/typedefinition/typedefinition.svelte b/packages/language-server/test/plugins/typescript/testfiles/typedefinition/typedefinition.svelte
new file mode 100644
index 00000000..b5dca5a1
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/typedefinition/typedefinition.svelte
@@ -0,0 +1,8 @@
+<script lang="ts">
+    import { SomeImportedClass } from './some-class';
+
+    class SomeClass {}
+
+    let someClassInstance1 = new SomeImportedClass();
+    let someClassInstance2 = new SomeClass();
+</script>
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/$store-prop-init/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/$store-prop-init/expected.tsx
new file mode 100644
index 00000000..bf227624
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/$store-prop-init/expected.tsx
@@ -0,0 +1,12 @@
+///<reference types="svelte" />
+<></>;function render() {
+
+    let store = null/*Ωignore_startΩ*/;let $store = __sveltets_1_store_get(store);/*Ωignore_endΩ*/;
+    const foo = { $store: (__sveltets_1_store_get(store), $store) };
+    const bar = { $store: (__sveltets_1_store_get(store), $store) };
+;
+() => (<></>);
+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/$store-prop-init/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/$store-prop-init/input.svelte
new file mode 100644
index 00000000..e14a1f8b
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/$store-prop-init/input.svelte
@@ -0,0 +1,5 @@
+<script lang="ts">
+    let store = null;
+    const foo = { $store };
+    const bar = { $store: $store };
+</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>