Repository: sveltejs/language-tools
Pull request #864 (2021-03-08 13:40:33)

Svelte2tsx SourceMapping tests rewrite
This PR rewrites `svelte2tsx/test/sourcemaps`

Changed

* Relocated samples to `samples/{name}`. Relocated svelte input to `input.svelte`, relocated tests to `test.jsx`
* Added `source-map-codec` to devDependencies to decode mappings

Added

* Added auto-generating file `mappings.jsx`, it contains the generated output with a comment under each line describing each character's mapping and how they map back to the origin
The intent of that file is to highlight changes on diffchecks [(full example)](https://github.com/sveltejs/language-tools/blob/595c81dc930ab6dc34fea8bd610554b262a87495/packages/svelte2tsx/test/sourcemaps/samples/event-binding/mappings.jsx) 
<img width="914" alt="Code_2021-03-19_22-06-08" src="https://user-images.githubusercontent.com/30108880/111842478-d1a19e00-88ff-11eb-823e-ad6a076921d2.png">

* Added auto-generating untracked file `test.edit.jsx`, it mirrors the ranges tested in `test.jsx` in the form of the generated output with range positions surrounded by triple brackets
Editing this file and running tests will regenerate new tests based on those ranges
<img width="960" alt="Code_2021-03-19_23-33-07" src="https://user-images.githubusercontent.com/30108880/111848518-81c8d400-890b-11eb-9455-74194a75643e.png">


* Added auto-generating untracked file `output.tsx`, It contains the generated output with its inlined sourcemap
That file can be directly uploaded to online sourcemapping tools, e.g. https://evanw.github.io/source-map-visualization/ 
<img width="911" alt="chrome_2021-03-08_13-50-41" src="https://user-images.githubusercontent.com/30108880/110324937-f56b0700-8016-11eb-90dc-7ec64938b889.png">

dummdidumm: Thanks, but this PR will not land until the other PR this is based on is merged. And that PR will not get merged in that form. Please convert that one into three PRs [as requested here](https://github.com/sveltejs/language-tools/pull/845#issuecomment-785786946).

Patch:
diff --git a/.github/workflows/Deploy.yml b/.github/workflows/Deploy.yml
index a6040361..6fbc52fc 100644
--- a/.github/workflows/Deploy.yml
+++ b/.github/workflows/Deploy.yml
@@ -44,5 +44,5 @@ jobs:
           OVSX_TOKEN: ${{ secrets.OVSX_TOKEN }}
         with:
           sort: '["svelte2tsx", "svelte-language-server", "svelte-check", "svelte-vscode-nightly"]'
-          only: '["svelte2tsx", "svelte-language-server", "svelte-vscode-nightly"]'
+          only: '["svelte-vscode-nightly"]'
           install: "true"
diff --git a/.github/workflows/DeployExtensionsProd.yml b/.github/workflows/DeployExtensionsProd.yml
index f71d33c7..0dfa3ac1 100644
--- a/.github/workflows/DeployExtensionsProd.yml
+++ b/.github/workflows/DeployExtensionsProd.yml
@@ -1,4 +1,4 @@
-name: Tagged Production Deploys
+name: Tagged Production Deploys for VS Code
 
 on:
   push:
diff --git a/.github/workflows/DeploySvelte2tsxProd.yml b/.github/workflows/DeploySvelte2tsxProd.yml
new file mode 100644
index 00000000..2611cca5
--- /dev/null
+++ b/.github/workflows/DeploySvelte2tsxProd.yml
@@ -0,0 +1,36 @@
+name: Tagged Production Deploys for svelte2tsx
+
+on:
+  push:
+    tags:
+      - "svelte2tsx-*"
+
+jobs:
+  deploy:
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-node@v1
+        with:
+          node-version: "12.x"
+          registry-url: "https://registry.npmjs.org"
+
+      # Ensure everything is compiling
+      - run: "yarn install"
+      - run: "yarn build"
+
+      # Lets us use one-liner JSON manipulations the package.json files
+      - run: "npm install -g json"
+
+      # Setup the environment
+      - run: 'json -I -f packages/svelte2tsx/package.json -e "this.version=\`${{ github.ref }}\`.split(\`-\`).pop()"'
+
+      # Ship it
+      - run: |
+         cd packages/svelte2tsx
+         npm install
+         npm publish
+
+        env:
+          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/DeploySvelteCheckProd.yml b/.github/workflows/DeploySvelteCheckProd.yml
index 19db7fd1..1404b747 100644
--- a/.github/workflows/DeploySvelteCheckProd.yml
+++ b/.github/workflows/DeploySvelteCheckProd.yml
@@ -1,4 +1,4 @@
-name: Tagged Production Deploys
+name: Tagged Production Deploys for svelte-check
 
 on:
   push:
diff --git a/.github/workflows/DeploySvelteLanguageServerProd.yml b/.github/workflows/DeploySvelteLanguageServerProd.yml
new file mode 100644
index 00000000..20dd9961
--- /dev/null
+++ b/.github/workflows/DeploySvelteLanguageServerProd.yml
@@ -0,0 +1,36 @@
+name: Tagged Production Deploys For svelte-language-server
+
+on:
+  push:
+    tags:
+      - "language-server-*"
+
+jobs:
+  deploy:
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-node@v1
+        with:
+          node-version: "12.x"
+          registry-url: "https://registry.npmjs.org"
+
+      # Ensure everything is compiling
+      - run: "yarn install"
+      - run: "yarn build"
+
+      # Lets us use one-liner JSON manipulations the package.json files
+      - run: "npm install -g json"
+
+      # Setup the environment
+      - run: 'json -I -f packages/language-server/package.json -e "this.version=\`${{ github.ref }}\`.split(\`-\`).pop()"'
+
+      # Ship it
+      - run: |
+         cd packages/language-server
+         npm install
+         npm publish
+
+        env:
+          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.prettierignore b/.prettierignore
index de5ae757..97dae252 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,7 +1,7 @@
 packages/svelte2tsx/*.d.ts
 packages/svelte2tsx/test/*/samples/*/*sx
 packages/svelte2tsx/test/*/samples/*/*.svelte
-packages/svelte2tsx/test/sourcemaps/*.html
+packages/svelte2tsx/test/sourcemaps/samples/*
 packages/language-server/test/**/*.svelte
 packages/language-server/test/**/testfiles/**/*.ts
 packages/svelte-vscode/syntaxes/*.yaml
diff --git a/packages/language-server/CHANGELOG.md b/packages/language-server/CHANGELOG.md
new file mode 100644
index 00000000..c8c4cda6
--- /dev/null
+++ b/packages/language-server/CHANGELOG.md
@@ -0,0 +1,3 @@
+# Changelog
+
+See https://github.com/sveltejs/language-tools/releases
diff --git a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts
index 90ee9d4a..ff9fd2ad 100644
--- a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts
+++ b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts
@@ -112,17 +112,45 @@ export namespace DocumentSnapshot {
     /**
      * Returns a svelte or ts/js snapshot from a file path, depending on the file contents.
      * @param filePath path to the js/ts/svelte file
+     * @param createDocument function that is used to create a document in case it's a Svelte file
      * @param options options that apply in case it's a svelte file
      */
-    export function fromFilePath(filePath: string, options: SvelteSnapshotOptions) {
-        const originalText = ts.sys.readFile(filePath) ?? '';
-
+    export function fromFilePath(
+        filePath: string,
+        createDocument: (filePath: string, text: string) => Document,
+        options: SvelteSnapshotOptions
+    ) {
         if (isSvelteFilePath(filePath)) {
-            return fromDocument(new Document(pathToUrl(filePath), originalText), options);
+            return DocumentSnapshot.fromSvelteFilePath(filePath, createDocument, options);
         } else {
-            return new JSOrTSDocumentSnapshot(INITIAL_VERSION, filePath, originalText);
+            return DocumentSnapshot.fromNonSvelteFilePath(filePath);
         }
     }
+
+    /**
+     * Returns a ts/js snapshot from a file path.
+     * @param filePath path to the js/ts file
+     * @param options options that apply in case it's a svelte file
+     */
+    export function fromNonSvelteFilePath(filePath: string) {
+        const originalText = ts.sys.readFile(filePath) ?? '';
+        return new JSOrTSDocumentSnapshot(INITIAL_VERSION, filePath, originalText);
+    }
+
+    /**
+     * Returns a svelte snapshot from a file path.
+     * @param filePath path to the svelte file
+     * @param createDocument function that is used to create a document
+     * @param options options that apply in case it's a svelte file
+     */
+    export function fromSvelteFilePath(
+        filePath: string,
+        createDocument: (filePath: string, text: string) => Document,
+        options: SvelteSnapshotOptions
+    ) {
+        const originalText = ts.sys.readFile(filePath) ?? '';
+        return fromDocument(createDocument(filePath, originalText), options);
+    }
 }
 
 /**
diff --git a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts
index 44208bcd..7d536bc8 100644
--- a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts
+++ b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts
@@ -75,32 +75,18 @@ export class LSAndTSDocResolver {
             this.workspaceUris,
             this.lsDocumentContext
         );
-        const filePath = document.getFilePath()!;
-        const tsDoc = this.getSnapshot(filePath, document);
+        const tsDoc = this.getSnapshot(document);
         const userPreferences = this.getUserPreferences(tsDoc.scriptKind);
 
         return { tsDoc, lang, userPreferences };
     }
 
-    getSnapshot(filePath: string, document: Document): SvelteDocumentSnapshot;
-    getSnapshot(filePath: string, document?: Document): DocumentSnapshot;
-    getSnapshot(filePath: string, document?: Document) {
+    getSnapshot(document: Document): SvelteDocumentSnapshot;
+    getSnapshot(pathOrDoc: string | Document): DocumentSnapshot;
+    getSnapshot(pathOrDoc: string | Document) {
+        const filePath = typeof pathOrDoc === 'string' ? pathOrDoc : pathOrDoc.getFilePath() || '';
         const tsService = this.getTSService(filePath);
-        const { snapshotManager } = tsService;
-
-        let tsDoc = snapshotManager.get(filePath);
-        if (!tsDoc) {
-            const options = {
-                strictMode: !!tsService.compilerOptions.strict,
-                transformOnTemplateError: this.transformOnTemplateError
-            };
-            tsDoc = document
-                ? DocumentSnapshot.fromDocument(document, options)
-                : DocumentSnapshot.fromFilePath(filePath, options);
-            snapshotManager.set(filePath, tsDoc);
-        }
-
-        return tsDoc;
+        return tsService.updateDocument(pathOrDoc);
     }
 
     updateSnapshotPath(oldPath: string, newPath: string): DocumentSnapshot {
diff --git a/packages/language-server/src/plugins/typescript/SnapshotManager.ts b/packages/language-server/src/plugins/typescript/SnapshotManager.ts
index 6430b52a..2a06b89d 100644
--- a/packages/language-server/src/plugins/typescript/SnapshotManager.ts
+++ b/packages/language-server/src/plugins/typescript/SnapshotManager.ts
@@ -1,9 +1,5 @@
 import ts from 'typescript';
-import {
-    DocumentSnapshot,
-    JSOrTSDocumentSnapshot,
-    SvelteSnapshotOptions
-} from './DocumentSnapshot';
+import { DocumentSnapshot, JSOrTSDocumentSnapshot } from './DocumentSnapshot';
 import { Logger } from '../../logger';
 import { TextDocumentContentChangeEvent } from 'vscode-languageserver';
 
@@ -50,36 +46,26 @@ export class SnapshotManager {
         this.projectFiles = Array.from(new Set([...this.projectFiles, ...projectFiles]));
     }
 
-    updateByFileName(fileName: string, options: SvelteSnapshotOptions) {
-        if (!this.has(fileName)) {
-            return;
-        }
-
-        const newSnapshot = DocumentSnapshot.fromFilePath(fileName, options);
+    updateTsOrJsFile(fileName: string, changes?: TextDocumentContentChangeEvent[]): void {
         const previousSnapshot = this.get(fileName);
 
-        if (previousSnapshot) {
-            newSnapshot.version = previousSnapshot.version + 1;
+        if (changes) {
+            if (!(previousSnapshot instanceof JSOrTSDocumentSnapshot)) {
+                return;
+            }
+            previousSnapshot.update(changes);
         } else {
-            // ensure it's greater than initial version
-            // so that ts server picks up the change
-            newSnapshot.version += 1;
+            const newSnapshot = DocumentSnapshot.fromNonSvelteFilePath(fileName);
+
+            if (previousSnapshot) {
+                newSnapshot.version = previousSnapshot.version + 1;
+            } else {
+                // ensure it's greater than initial version
+                // so that ts server picks up the change
+                newSnapshot.version += 1;
+            }
+            this.set(fileName, newSnapshot);
         }
-
-        this.set(fileName, newSnapshot);
-    }
-
-    updateTsOrJsFile(fileName: string, changes: TextDocumentContentChangeEvent[]): void {
-        if (!this.has(fileName)) {
-            return;
-        }
-
-        const previousSnapshot = this.get(fileName);
-        if (!(previousSnapshot instanceof JSOrTSDocumentSnapshot)) {
-            return;
-        }
-
-        previousSnapshot.update(changes);
     }
 
     has(fileName: string) {
diff --git a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts
index fb66043b..a986141e 100644
--- a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts
+++ b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts
@@ -385,12 +385,7 @@ export class TypeScriptPlugin
                 return;
             }
 
-            // Since the options parameter only applies to svelte snapshots, and this is not
-            // a svelte file, we can just set it to false without having any effect.
-            snapshotManager.updateByFileName(fileName, {
-                strictMode: false,
-                transformOnTemplateError: false
-            });
+            snapshotManager.updateTsOrJsFile(fileName);
         }
     }
 
diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts
index 61ff1137..3471917a 100644
--- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts
@@ -398,8 +398,4 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
     private getLSAndTSDoc(document: Document) {
         return this.lsAndTsDocResolver.getLSAndTSDoc(document);
     }
-
-    private getSnapshot(filePath: string, document?: Document) {
-        return this.lsAndTsDocResolver.getSnapshot(filePath, document);
-    }
 }
diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts
index 998d3b2f..1620a4a2 100644
--- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts
@@ -476,5 +476,11 @@ function isValidCompletion(
     if (!isCompletionInHTMLStartTag) {
         return () => true;
     }
-    return (value) => !completionBlacklist.has(value.name);
+    return (value) =>
+        !completionBlacklist.has(value.name) &&
+        // remove attribues starting with "on" because those are events.
+        // Svelte wants events of the form "on:X", but the suggestions
+        // are of the form "onX". Moreover, they are doubled by the HTML
+        // attribute suggestions. Therefore filter them out.
+        !value.name.startsWith('on');
 }
diff --git a/packages/language-server/src/plugins/typescript/features/HoverProvider.ts b/packages/language-server/src/plugins/typescript/features/HoverProvider.ts
index dc28c470..1f99dfdf 100644
--- a/packages/language-server/src/plugins/typescript/features/HoverProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/HoverProvider.ts
@@ -20,14 +20,28 @@ export class HoverProviderImpl implements HoverProvider {
             return eventHoverInfo;
         }
 
-        const info = lang.getQuickInfoAtPosition(
-            tsDoc.filePath,
-            fragment.offsetAt(fragment.getGeneratedPosition(position))
-        );
+        const offset = fragment.offsetAt(fragment.getGeneratedPosition(position));
+        let info = lang.getQuickInfoAtPosition(tsDoc.filePath, offset);
         if (!info) {
             return null;
         }
 
+        const textSpan = info.textSpan;
+
+        // show docs of $store instead of store if necessary
+        const is$store = fragment.text
+            .substring(0, info.textSpan.start)
+            .endsWith('(__sveltets_store_get(');
+        if (is$store) {
+            const infoFor$store = lang.getQuickInfoAtPosition(
+                tsDoc.filePath,
+                textSpan.start + textSpan.length + 3
+            );
+            if (infoFor$store) {
+                info = infoFor$store;
+            }
+        }
+
         const declaration = ts.displayPartsToString(info.displayParts);
         const documentation = getMarkdownDocumentation(info.documentation, info.tags);
 
@@ -37,7 +51,7 @@ export class HoverProviderImpl implements HoverProvider {
             .join('\n');
 
         return mapObjWithRangeToOriginal(fragment, {
-            range: convertRange(fragment, info.textSpan),
+            range: convertRange(fragment, textSpan),
             contents
         });
     }
diff --git a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts
index 0d917523..e0f807e4 100644
--- a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts
@@ -374,8 +374,8 @@ export class RenameProviderImpl implements RenameProvider {
         return this.lsAndTsDocResolver.getLSAndTSDoc(document);
     }
 
-    private getSnapshot(filePath: string, document?: Document) {
-        return this.lsAndTsDocResolver.getSnapshot(filePath, document);
+    private getSnapshot(filePath: string) {
+        return this.lsAndTsDocResolver.getSnapshot(filePath);
     }
 }
 
diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts
index f2f70e41..2d13e0d8 100644
--- a/packages/language-server/src/plugins/typescript/service.ts
+++ b/packages/language-server/src/plugins/typescript/service.ts
@@ -6,14 +6,14 @@ import { getPackageInfo } from '../../importPackage';
 import { DocumentSnapshot } from './DocumentSnapshot';
 import { createSvelteModuleLoader } from './module-loader';
 import { SnapshotManager } from './SnapshotManager';
-import { ensureRealSvelteFilePath, findTsConfigPath, isSvelteFilePath } from './utils';
+import { ensureRealSvelteFilePath, findTsConfigPath } from './utils';
 
 export interface LanguageServiceContainer {
     readonly tsconfigPath: string;
     readonly compilerOptions: ts.CompilerOptions;
     readonly snapshotManager: SnapshotManager;
     getService(): ts.LanguageService;
-    updateDocument(document: Document): ts.LanguageService;
+    updateDocument(documentOrFilePath: Document | string): DocumentSnapshot;
     deleteDocument(filePath: string): void;
 }
 
@@ -37,9 +37,7 @@ export function getLanguageServiceForDocument(
     workspaceUris: string[],
     docContext: LanguageServiceDocumentContext
 ): ts.LanguageService {
-    return getService(document.getFilePath() || '', workspaceUris, docContext).updateDocument(
-        document
-    );
+    return getLanguageServiceForPath(document.getFilePath() || '', workspaceUris, docContext);
 }
 
 export function getService(
@@ -107,8 +105,7 @@ export function createLanguageService(
         resolveModuleNames: svelteModuleLoader.resolveModuleNames,
         readDirectory: svelteModuleLoader.readDirectory,
         getDirectories: ts.sys.getDirectories,
-        // vscode's uri is all lowercase
-        useCaseSensitiveFileNames: () => false,
+        useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
         getScriptKind: (fileName: string) => getSnapshot(fileName).scriptKind
     };
     let languageService = ts.createLanguageService(host);
@@ -131,23 +128,34 @@ export function createLanguageService(
         snapshotManager.delete(filePath);
     }
 
-    function updateDocument(document: Document): ts.LanguageService {
-        const preSnapshot = snapshotManager.get(document.getFilePath()!);
+    function updateDocument(documentOrFilePath: Document | string): DocumentSnapshot {
+        const filePath =
+            typeof documentOrFilePath === 'string'
+                ? documentOrFilePath
+                : documentOrFilePath.getFilePath() || '';
+        const document = typeof documentOrFilePath === 'string' ? undefined : documentOrFilePath;
+        const prevSnapshot = snapshotManager.get(filePath);
 
         // Don't reinitialize document if no update needed.
-        if (preSnapshot?.version === document.version) {
-            return languageService;
+        if (document && prevSnapshot?.version === document.version) {
+            return prevSnapshot;
         }
 
-        const newSnapshot = DocumentSnapshot.fromDocument(document, transformationConfig);
-        if (preSnapshot && preSnapshot.scriptKind !== newSnapshot.scriptKind) {
+        const newSnapshot = document
+            ? DocumentSnapshot.fromDocument(document, transformationConfig)
+            : DocumentSnapshot.fromFilePath(
+                  filePath,
+                  docContext.createDocument,
+                  transformationConfig
+              );
+        if (prevSnapshot && prevSnapshot.scriptKind !== newSnapshot.scriptKind) {
             // Restart language service as it doesn't handle script kind changes.
             languageService.dispose();
             languageService = ts.createLanguageService(host);
         }
 
-        snapshotManager.set(document.getFilePath()!, newSnapshot);
-        return languageService;
+        snapshotManager.set(filePath, newSnapshot);
+        return newSnapshot;
     }
 
     function getSnapshot(fileName: string): DocumentSnapshot {
@@ -158,16 +166,11 @@ export function createLanguageService(
             return doc;
         }
 
-        if (isSvelteFilePath(fileName)) {
-            const file = ts.sys.readFile(fileName) || '';
-            doc = DocumentSnapshot.fromDocument(
-                docContext.createDocument(fileName, file),
-                transformationConfig
-            );
-        } else {
-            doc = DocumentSnapshot.fromFilePath(fileName, transformationConfig);
-        }
-
+        doc = DocumentSnapshot.fromFilePath(
+            fileName,
+            docContext.createDocument,
+            transformationConfig
+        );
         snapshotManager.set(fileName, doc);
         return doc;
     }
diff --git a/packages/svelte-check/CHANGELOG.md b/packages/svelte-check/CHANGELOG.md
new file mode 100644
index 00000000..c8c4cda6
--- /dev/null
+++ b/packages/svelte-check/CHANGELOG.md
@@ -0,0 +1,3 @@
+# Changelog
+
+See https://github.com/sveltejs/language-tools/releases
diff --git a/packages/svelte-vscode/package.json b/packages/svelte-vscode/package.json
index 9e60d091..314787d8 100644
--- a/packages/svelte-vscode/package.json
+++ b/packages/svelte-vscode/package.json
@@ -49,14 +49,16 @@
             "title": "Svelte",
             "properties": {
                 "svelte.language-server.runtime": {
+                    "scope": "application",
                     "type": "string",
                     "title": "Language Server Runtime",
-                    "description": "- You normally don't need this - Path to the node executable to use to spawn the language server. This is useful when you depend on native modules such as node-sass as without this they will run in the context of vscode, meaning node version mismatch is likely."
+                    "description": "- You normally don't need this - Path to the node executable to use to spawn the language server. This is useful when you depend on native modules such as node-sass as without this they will run in the context of vscode, meaning node version mismatch is likely. This setting can only be changed in user settings for security reasons."
                 },
                 "svelte.language-server.ls-path": {
+                    "scope": "application",
                     "type": "string",
                     "title": "Language Server Path",
-                    "description": "- You normally don't set this - Path to the language server executable. If you installed the \"svelte-language-server\" npm package, it's within there at \"bin/server.js\". Path can be either relative to your workspace root or absolute. Set this only if you want to use a custom version of the language server."
+                    "description": "- You normally don't set this - Path to the language server executable. If you installed the \"svelte-language-server\" npm package, it's within there at \"bin/server.js\". Path can be either relative to your workspace root or absolute. Set this only if you want to use a custom version of the language server. This setting can only be changed in user settings for security reasons."
                 },
                 "svelte.language-server.port": {
                     "type": "number",
diff --git a/packages/svelte2tsx/.gitignore b/packages/svelte2tsx/.gitignore
index 2e1c83f4..0603455f 100644
--- a/packages/svelte2tsx/.gitignore
+++ b/packages/svelte2tsx/.gitignore
@@ -4,4 +4,6 @@ node_modules
 /index.js.map
 /index.mjs
 test/typecheck/samples/**/input.svelte.tsx
-repl/output
\ No newline at end of file
+test/sourcemaps/samples/*/output.tsx
+test/sourcemaps/samples/*/test.edit.jsx
+repl/output
diff --git a/packages/svelte2tsx/CHANGELOG.md b/packages/svelte2tsx/CHANGELOG.md
new file mode 100644
index 00000000..c8c4cda6
--- /dev/null
+++ b/packages/svelte2tsx/CHANGELOG.md
@@ -0,0 +1,3 @@
+# Changelog
+
+See https://github.com/sveltejs/language-tools/releases
diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json
index bcf0f0ea..d400a2be 100644
--- a/packages/svelte2tsx/package.json
+++ b/packages/svelte2tsx/package.json
@@ -8,13 +8,14 @@
         "svelte",
         "typescript"
     ],
-    "homepage": "https://github.com/halfnelson/svelte2tsx",
+    "homepage": "https://github.com/sveltejs/language-tools/tree/master/packages/svelte2tsx",
     "repository": {
         "type": "git",
-        "url": "https://github.com/halfnelson/svelte2tsx.git"
+        "url": "https://github.com/sveltejs/language-tools/tree/master/packages/svelte2tsx"
     },
     "type": "commonjs",
     "main": "index.js",
+    "module": "index.mjs",
     "types": "index.d.ts",
     "devDependencies": {
         "@rollup/plugin-commonjs": "^15.0.0",
@@ -32,6 +33,7 @@
         "rollup-plugin-delete": "^2.0.0",
         "source-map": "^0.6.1",
         "source-map-support": "^0.5.16",
+        "sourcemap-codec": "^1.4.4",
         "svelte": "~3.35.0",
         "tiny-glob": "^0.2.6",
         "tslib": "^1.10.0",
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/exportaccessors.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/exportaccessors.ts
index e7c21e16..a827cbac 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/exportaccessors.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/exportaccessors.ts
@@ -6,8 +6,8 @@ const createClassAccessor = (name: string) =>
 
 export const createClassAccessors = (getters: Set<string>, exportedNames: ExportedNames) => {
     const accessors: string[] = [];
-    for (const [name, value] of exportedNames) {
-        if (getters.has(name)) {
+    for (const value of exportedNames.values()) {
+        if (getters.has(value.identifierText)) {
             continue;
         }
 
diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
index 2c678579..eb50c087 100644
--- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
@@ -142,9 +142,17 @@ export function processInstanceScriptContent(
     };
 
     const handleStore = (ident: ts.Identifier, parent: ts.Node) => {
+        // ignore "typeof $store"
+        if (parent && parent.kind === ts.SyntaxKind.TypeQuery) {
+            return;
+        }
+        // ignore break
+        if (parent && parent.kind === ts.SyntaxKind.BreakStatement) {
+            return;
+        }
+
         const storename = ident.getText().slice(1); // drop the $
         // handle assign to
-        // eslint-disable-next-line max-len
         if (
             parent &&
             ts.isBinaryExpression(parent) &&
@@ -160,10 +168,7 @@ export function processInstanceScriptContent(
             str.appendLeft(parent.end + astOffset, ')');
             return;
         }
-        // ignore break
-        if (parent && parent.kind === ts.SyntaxKind.BreakStatement) {
-            return;
-        }
+
         // handle Assignment operators ($store +=, -=, *=, /=, %=, **=, etc.)
         // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Assignment
         const operators = {


Test patch:
diff --git a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts
index 9fdda750..251ad646 100644
--- a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts
+++ b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts
@@ -340,6 +340,21 @@ describe('CompletionProviderImpl', () => {
         }
     });
 
+    it('provides import completions in file with uppercase directory', async () => {
+        const { completionProvider, document } = setup('UpperCase/dirCasing.svelte');
+
+        const completions = await completionProvider.getCompletions(
+            document,
+            Position.create(1, 22),
+            {
+                triggerKind: CompletionTriggerKind.TriggerCharacter,
+                triggerCharacter: '/'
+            }
+        );
+
+        assert.equal(completions?.items[0].label, 'toImport.ts');
+    });
+
     it('provides import completions for supported files', async () => {
         const sourceFile = 'importcompletions.svelte';
         const { completionProvider, document } = setup(sourceFile);
diff --git a/packages/language-server/test/plugins/typescript/features/HoverProvider.test.ts b/packages/language-server/test/plugins/typescript/features/HoverProvider.test.ts
index 0662ef33..c01837b2 100644
--- a/packages/language-server/test/plugins/typescript/features/HoverProvider.test.ts
+++ b/packages/language-server/test/plugins/typescript/features/HoverProvider.test.ts
@@ -100,4 +100,90 @@ describe('HoverProvider', () => {
             }
         });
     });
+
+    it('provides hover info for $store access', async () => {
+        const { provider, document } = setup('hover-$store.svelte');
+
+        assert.deepStrictEqual(await provider.doHover(document, Position.create(3, 5)), <Hover>{
+            contents: '```typescript\nlet $b: string | {\n    a: boolean | string;\n}\n```',
+            range: {
+                end: {
+                    character: 6,
+                    line: 3
+                },
+                start: {
+                    character: 5,
+                    line: 3
+                }
+            }
+        });
+        assert.deepStrictEqual(await provider.doHover(document, Position.create(5, 9)), <Hover>{
+            contents: '```typescript\nlet $b: string\n```',
+            range: {
+                end: {
+                    character: 10,
+                    line: 5
+                },
+                start: {
+                    character: 9,
+                    line: 5
+                }
+            }
+        });
+        assert.deepStrictEqual(await provider.doHover(document, Position.create(7, 4)), <Hover>{
+            contents:
+                '```typescript\nconst b: Writable<string | {\n    a: boolean | string;\n}>\n```',
+            range: {
+                end: {
+                    character: 5,
+                    line: 7
+                },
+                start: {
+                    character: 4,
+                    line: 7
+                }
+            }
+        });
+
+        assert.deepStrictEqual(await provider.doHover(document, Position.create(10, 2)), <Hover>{
+            contents: '```typescript\nlet $b: string | {\n    a: boolean | string;\n}\n```',
+            range: {
+                end: {
+                    character: 3,
+                    line: 10
+                },
+                start: {
+                    character: 2,
+                    line: 10
+                }
+            }
+        });
+        assert.deepStrictEqual(await provider.doHover(document, Position.create(12, 6)), <Hover>{
+            contents: '```typescript\nlet $b: string\n```',
+            range: {
+                end: {
+                    character: 7,
+                    line: 12
+                },
+                start: {
+                    character: 6,
+                    line: 12
+                }
+            }
+        });
+        assert.deepStrictEqual(await provider.doHover(document, Position.create(14, 1)), <Hover>{
+            contents:
+                '```typescript\nconst b: Writable<string | {\n    a: boolean | string;\n}>\n```',
+            range: {
+                end: {
+                    character: 2,
+                    line: 14
+                },
+                start: {
+                    character: 1,
+                    line: 14
+                }
+            }
+        });
+    });
 });
diff --git a/packages/language-server/test/plugins/typescript/testfiles/completions/UpperCase/dirCasing.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/UpperCase/dirCasing.svelte
new file mode 100644
index 00000000..943c13b0
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/completions/UpperCase/dirCasing.svelte
@@ -0,0 +1,3 @@
+<script>
+    import {} from './'
+</script>
\ No newline at end of file
diff --git a/packages/language-server/test/plugins/typescript/testfiles/completions/UpperCase/toImport.ts b/packages/language-server/test/plugins/typescript/testfiles/completions/UpperCase/toImport.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/language-server/test/plugins/typescript/testfiles/hover/hover-$store.svelte b/packages/language-server/test/plugins/typescript/testfiles/hover/hover-$store.svelte
new file mode 100644
index 00000000..73cf542c
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/hover/hover-$store.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+    import { writable } from 'svelte/store';
+    const b = writable<{a: boolean | string} | string>('')
+    $b
+    if (typeof $b === 'string') {
+        $b;
+    }
+    b;
+</script>
+
+{$b}
+{#if typeof $b === 'string'}
+    {$b}
+{/if}
+{b}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/helpers.ts b/packages/svelte2tsx/test/helpers.ts
index 6678d9eb..96016006 100644
--- a/packages/svelte2tsx/test/helpers.ts
+++ b/packages/svelte2tsx/test/helpers.ts
@@ -5,11 +5,13 @@ import { htmlx2jsx, svelte2tsx } from './build';
 import path from 'path';
 
 let update_count = 0;
+let all_tests_skipped = false;
+
 function can_auto_update() {
-    if (!process.argv.includes('--auto')) {
+    if (!process.argv.includes('--auto') && !all_tests_skipped) {
         if (update_count++ === 0) {
             process.on('exit', () => {
-                const command = color.yellow('npm run test -- --auto');
+                const command = color.yellow('yarn run test --auto');
                 console.log(`  Run ${command} to update ${update_count} files\n`);
             });
         }
@@ -37,7 +39,7 @@ function writeFileSync(path: string, content: string) {
     return fs.writeFileSync(path, normalize(content));
 }
 
-function wildcard(str: string, target: string) {
+function is_same_file(str: string, target: string) {
     if (str[0] === '*') return target.endsWith(str.slice(1));
     return str === target;
 }
@@ -62,13 +64,13 @@ export class Sample {
 
         loop: for (const fileName of this.folder) {
             for (const name of unchecked) {
-                if (wildcard(name, fileName)) {
+                if (is_same_file(name, fileName)) {
                     unchecked.delete(name);
                     continue loop;
                 }
             }
             for (const name of allowed) {
-                if (wildcard(name, fileName)) {
+                if (is_same_file(name, fileName)) {
                     continue loop;
                 }
             }
@@ -138,8 +140,12 @@ export class Sample {
         return this.folder.length === fileNames.length && fileNames.every((f) => this.has(f));
     }
 
-    wildcard(fileName: string) {
-        return this.folder.find((f) => wildcard(fileName, f));
+    /**
+     * Returns first found file that matches the fileName,
+     * or ends with it in case of a wildcard search.
+     */
+    find_file(fileName: string) {
+        return this.folder.find((f) => is_same_file(fileName, f));
     }
 
     at(file: string) {
@@ -158,6 +164,7 @@ export class Sample {
         if (process.env.CI) {
             throw new Error(`Tried to generate ${this.name} dependencies`);
         }
+        all_tests_skipped = true;
         it.only(`${this.name} dependencies`, () => fn(this.generate.bind(this)));
     }
 
@@ -203,7 +210,7 @@ const enum TestError {
 
 export function test_samples(dir: string, transform: TransformSampleFn, jsx: 'jsx' | 'tsx') {
     for (const sample of each_sample(dir)) {
-        const svelteFile = sample.wildcard('*.svelte');
+        const svelteFile = sample.find_file('*.svelte');
         const config = {
             filename: svelteFile,
             sampleName: sample.name,
diff --git a/packages/svelte2tsx/test/sourcemaps/composer.ts b/packages/svelte2tsx/test/sourcemaps/composer.ts
new file mode 100644
index 00000000..22561f6b
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/composer.ts
@@ -0,0 +1,118 @@
+import { get_extra_indent, Segment } from './helpers';
+import { Line } from './parser';
+
+type cmap = string | Segment;
+type CommentOptions = { indent?: number };
+type comment = [cmap, string] | readonly [cmap, string];
+type Section = HorizontalRule | Comment | Line | string;
+
+class Comment {
+    readonly content: [string, string][];
+
+    constructor(gen: Iterable<comment>, readonly options: CommentOptions = {}) {
+        this.content = Array.from(gen, ([cmap, message]): [string, string] => [
+            typeof cmap === 'string' ? cmap : ' '.repeat(cmap.start) + cmap.text,
+            message
+        ]);
+    }
+
+    render(indent: number, col0_width: number, width: number) {
+        col0_width = Math.max(col0_width, ...this.content.map(([map]) => map.length));
+        if ('indent' in this.options) indent = this.options.indent;
+        return this.content
+            .map(([map, comment]) =>
+                (' '.repeat(indent * 4) + map.padEnd(col0_width) + '    ' + comment).padEnd(width)
+            )
+            .join('\n');
+    }
+}
+
+class HorizontalRule {
+    constructor(readonly content?: string, readonly _fill: string = '-') {}
+
+    fill(width: number) {
+        return this.content
+            ? this.content
+                  .padStart(Math.floor((width + this.content.length) / 2), this._fill)
+                  .padEnd(width, this._fill)
+            : this._fill.repeat(width);
+    }
+}
+
+/**
+ * Creates a rule that puts the passed in context in the middle and fills up remaining
+ * width around it with the given fill.
+ */
+function rule(content?: string, fill?: string): HorizontalRule {
+    return new HorizontalRule(content, fill);
+}
+
+/**
+ * Prints given line(s) in a multi-line comment block. (succeeding comments are joined together)
+ */
+function comment(lines: comment | Iterable<comment>, opts?: CommentOptions) {
+    return new Comment(
+        lines instanceof Array && lines.length === 2 && !(lines[0] instanceof Array)
+            ? [lines]
+            : (lines as Iterable<comment>),
+        opts
+    );
+}
+
+function is_inside_comment(section: Section) {
+    return section instanceof HorizontalRule || section instanceof Comment;
+}
+
+export type ComposeHelper = { rule: typeof rule; comment: typeof comment };
+
+export function compose_file(
+    fn: (composer: ComposeHelper) => Iterable<Section>,
+    opts: { width?: number; indent?: number; match_first_column_width?: boolean } = {}
+) {
+    const sections = [...fn({ rule, comment })];
+    const width = opts.width ?? 150;
+    const min_col0_width = opts.match_first_column_width
+        ? Math.max(
+              ...sections.map((section) =>
+                  section instanceof Comment
+                      ? Math.max(...section.content.map((line) => line[0].length))
+                      : 0
+              )
+          )
+        : 0;
+
+    const content: string[] = [];
+    if (sections[0] instanceof HorizontalRule || sections[0] instanceof Comment) {
+        sections.unshift('');
+    }
+
+    for (let i = 0; i < sections.length; i++) {
+        const { [i]: current, [i + 1]: next } = sections;
+        let str = '';
+        if (current instanceof HorizontalRule) {
+            if (next instanceof HorizontalRule) continue;
+            str = current.fill(width);
+            if (!is_inside_comment(next)) {
+                if (content[content.length - 1].includes('//')) {
+                    str = '//' + current.fill(width - 2);
+                } else {
+                    str += comment_end;
+                }
+            }
+        } else if (current instanceof Comment) {
+            str = current.render(opts.indent ?? 0, min_col0_width, width);
+            if (!(next === undefined || is_inside_comment(next))) str += comment_end;
+        } else {
+            str = ('' + current).trimRight().replace(/\t/g, '    ');
+            if (is_inside_comment(next) && !str.includes('//')) {
+                str = str.padEnd(width - get_extra_indent(str)) + comment_start;
+            }
+        }
+        content.push(str);
+    }
+
+    return content.join('\n');
+}
+
+const comment_start = '{/**';
+const comment_end = ' */}';
diff --git a/packages/svelte2tsx/test/sourcemaps/event-binding.html b/packages/svelte2tsx/test/sourcemaps/event-binding.html
deleted file mode 100644
index e0ada3d1..00000000
--- a/packages/svelte2tsx/test/sourcemaps/event-binding.html
+++ /dev/null
@@ -1,15 +0,0 @@
-///<reference types="svelte" />
-<></>;function render() {
-<><Component  />{__sveltets_instanceOf(Component).$on('click', (__sveltets_store_get(check), $check) ? method1 : method2)}
-                                                                                     1====           2================== 
-<button onclick={(__sveltets_store_get(check), $check) ? method1 : method2} >Bla</button></>
-                                       3====           4==================
-return { props: {}, slots: {}, getters: {}, events: {} }}
-
-export default class extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
-}
-!Expected
-<Component on:click={$check ? method1 : method2} />
-                      1==== 2==================
-<button on:click={$check ? method1 : method2} >Bla</button>
-                   3==== 4==================
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/helpers.ts b/packages/svelte2tsx/test/sourcemaps/helpers.ts
new file mode 100644
index 00000000..03b1884c
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/helpers.ts
@@ -0,0 +1,133 @@
+import { GeneratedLine, Line } from './parser';
+
+export type CharMap4 = [
+    generated_charIndex: number,
+    original_fileIndex: number,
+    original_lineIndex: number,
+    original_charIndex: number
+];
+
+export type LineMap = CharMap4[];
+export type Mappings = LineMap[];
+export type Segment = { start: number; text: string };
+export type Range = { start: Position; end: Position };
+export type MappedPosition = { generated: GeneratedPosition; original: Position };
+export type MappedRange = { start: MappedPosition; end: MappedPosition };
+export const MappedKeys: (keyof MappedPosition)[] = ['generated', 'original'];
+
+export interface Position {
+    line: Line;
+    character: number;
+}
+export interface GeneratedPosition extends Position {
+    line: GeneratedLine;
+}
+
+export function print_string(str: string) {
+    return ('' + str)
+        .replace(/ /g, '•')
+        .replace(/[\r\n]/g, '↲')
+        .replace(/\t/g, '╚');
+}
+
+function edit_string(str: string, { start, text }: Segment, insert: boolean) {
+    if (str.length === start) return str + text;
+    else if (str.length < start) return str.padEnd(start) + text;
+    else return str.slice(0, start) + text + str.slice(start + (insert ? 0 : text.length));
+}
+
+/**
+ * Reduces segments into one big string
+ */
+export function reduce_segments<T>(
+    gen: Iterable<T>,
+    fn: (value: T, index: number) => Segment | void,
+    initial = ''
+) {
+    let str = initial;
+    let i = 0;
+    for (const value of gen) {
+        const segment = fn(value, i++);
+        if (segment) str = edit_string(str, segment, false);
+    }
+    return str;
+}
+
+/**
+ * Inserts each segment in the given text
+ */
+export function insert_segments(text: string, segments: Iterable<Segment>) {
+    let str = '';
+    let prev_start: number = undefined;
+    // sort descending
+    for (const segment of [...segments].sort((a, b) => b.start - a.start)) {
+        str = segment.text + text.slice(segment.start, prev_start) + str;
+        prev_start = segment.start;
+    }
+    return text.slice(0, prev_start) + str;
+}
+
+export function fromLineCharToOffset({ line, character }: { line: Line; character: number }) {
+    return line.start + character;
+}
+
+/**
+ * Returns a text span starting with head, ending with tail, and repeating body in the middle.
+ */
+export function span(length: number, [head, body, tail]: string = '#==') {
+    if (length <= 1) return length < 1 ? '' : body === tail ? head : tail;
+    return head + body.repeat(length - 2) + tail;
+}
+
+class Underline {
+    constructor(readonly start: number, readonly text: string) {}
+
+    toString() {
+        return ' '.repeat(this.start) + this.text;
+    }
+}
+
+export function underline(start: number, end: number, style?: string) {
+    return new Underline(start, span(end - start + 1, style));
+}
+
+export function* each_subset(lineMap: LineMap) {
+    let char = lineMap[0];
+    for (let i = 1; i < lineMap.length; i++) {
+        if (char[2] !== lineMap[i][2]) {
+            yield { line: char[2], start: char[0], end: (char = lineMap[i])[0] };
+        }
+    }
+    yield { line: char[2], start: char[0], end: undefined };
+}
+
+export function* each_exec(str: string, re: RegExp) {
+    let arr: RegExpExecArray;
+    while ((arr = re.exec(str))) yield { match: arr[0], index: arr.index };
+}
+
+/**
+ * Returns offset of character at index after accounting for extra space taken by tabs
+ */
+export function tab_aware_index(str: string, index: number) {
+    return index + get_extra_indent(str.slice(0, index + 1));
+}
+
+/**
+ * Returns all extra space taken by tabs in given string
+ */
+export function get_extra_indent(str: string) {
+    return (str.match(/\t/g)?.length ?? 0) * 3;
+}
+
+export function range_for<K extends keyof MappedPosition>(key: K, range: MappedRange) {
+    return { start: range.start[key], end: range.end[key] };
+}
+
+export function hash(str: string): string {
+    str = str.replace(/\r/g, '');
+    let hash = 5381;
+    let i = str.length;
+    while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
+    return (hash >>> 0).toString(36);
+}
diff --git a/packages/svelte2tsx/test/sourcemaps/index.ts b/packages/svelte2tsx/test/sourcemaps/index.ts
index 8c3a89c4..3da87b75 100644
--- a/packages/svelte2tsx/test/sourcemaps/index.ts
+++ b/packages/svelte2tsx/test/sourcemaps/index.ts
@@ -1,178 +1,187 @@
 import { svelte2tsx } from '../build';
-import { htmlx2jsx } from '../build';
-import fs from 'fs';
 import assert from 'assert';
-import sm from 'source-map';
-
-describe('sourcemap', () => {
-    /**
-     *
-     * @param {string} input
-     *
-     * @returns { {source: string, locations: Map<String,{ line: number, start: number, offsets:number[] }}> }
-     */
-    function extractLocations(input) {
-        let lines = input.split('\n');
-        let line;
-        let source_line = 0;
-        let source = [];
-        let locations = new Map();
-        while (lines.length) {
-            line = lines.shift();
-            //are we a range line, we test to see if it starts with whitespace followed by a digit
-            if (/^\s*[\d=]+[\s\d=]*$/.test(line)) {
-                //create the ranges
-                let currentId = null;
-                let offset = 0;
-                let offsets = [];
-                let start = 0;
-                const endSpan = () => {
-                    if (offsets.length) {
-                        locations.set(currentId, {
-                            line: source_line,
-                            start: start,
-                            offsets: offsets
-                        });
-                    }
-                    offset = 0;
-                    offsets = [];
-                };
-
-                for (let char = 0; char < line.length; char++) {
-                    let c = line[char];
-                    let isDigit = /\d/.test(c),
-                        isEquals = /=/.test(c);
-                    if (isDigit) {
-                        endSpan();
-                        currentId = c;
-                        start = char + 1;
-                    }
-                    if (isEquals || isDigit) {
-                        offsets.push(offset);
-                        offset++;
-                    } else {
-                        endSpan();
-                    }
-                }
-                endSpan();
-            } else {
-                //we are a source line
-                source.push(line);
-                source_line++;
-            }
+import { decode } from 'sourcemap-codec';
+import { each_sample, GenerateFn, get_svelte2tsx_config, Sample } from '../helpers';
+import { print_string } from './helpers';
+import {
+    process_transformed_text,
+    is_edit_changed,
+    is_edit_empty,
+    is_edit_from_same_generated,
+    is_test_empty,
+    is_test_from_same_input,
+    validate_edit_file,
+    validate_test_file
+} from './process';
+
+describe('sourcemaps', function () {
+    for (const sample of each_sample(__dirname)) {
+        if (process.env.CI) {
+            sample.checkDirectory({ required: ['*.svelte', 'mappings.jsx', 'test.jsx'] });
+        } else {
+            sample.checkDirectory({
+                required: ['*.svelte'],
+                allowed: ['mappings.jsx', 'test.jsx', 'test.edit.jsx', 'output.tsx']
+            });
+            maybe_generate(sample, regenerate);
+            sample.onError(function (generate, err) {
+                const skip = (err as Error).message.includes('SourceMapping changed');
+                regenerate(generate, skip);
+            });
         }
 
-        return { source: source.join('\n'), locations };
+        sample.it(function () {
+            const parsed = parse(sample);
+
+            parsed.each_test_range(
+                sample.get('test.jsx'),
+                function (actual, expected) {
+                    assert.strictEqual(actual, expected);
+                },
+                function () {
+                    throw new Error(`Invalid test file format`);
+                },
+                function (ranges) {
+                    throw new Error(
+                        `Could not find the following snippets in generated output\n` +
+                            ranges.map((range) => `\t"${print_string(range[2])}"`).join('\n') +
+                            (process.env.CI
+                                ? ''
+                                : `\nTo edit ranges : ${sample.cmd('test.edit.jsx')}`)
+                    );
+                }
+            );
+
+            assert.strictEqual(
+                parsed.print_mappings(),
+                sample.get('mappings.jsx'),
+                `SourceMapping changed, run tests with --auto to update them`
+            );
+        });
+
+        function regenerate(generate: GenerateFn, skip = false) {
+            const parsed = parse(sample);
+            generate_output_and_mappings_file(generate, parsed, skip);
+            if (!sample.has('test.jsx')) generate('test.jsx', parsed.generate_test(), skip);
+            generate('test.edit.jsx', parsed.generate_test_edit(sample.get('test.jsx')), false);
+        }
     }
+});
 
-    fs.readdirSync(`${__dirname}`).forEach((dir) => {
-        if (dir[0] === '.') return;
+/**
+ * Maybe generates test result files depending on the currently existing files.
+ */
+function maybe_generate(sample: Sample, regenerate: (generate: GenerateFn) => void) {
+    const svelteFile = sample.find_file('*.svelte');
 
-        if (!dir.endsWith('.html') && !dir.endsWith('.html.solo')) return;
+    if (sample.hasOnly(svelteFile)) {
+        return sample.generateDeps(regenerate);
+    }
 
-        // add .solo to a sample directory name to only run that test
-        const solo = /\.solo$/.test(dir);
+    if (sample.has('test.edit.jsx')) {
+        const edit = sample.get('test.edit.jsx');
 
-        if (solo && process.env.CI) {
-            throw new Error(`Forgot to remove '.solo' from test parser/samples/${dir}`);
+        try {
+            validate_edit_file(edit);
+        } catch (err) {
+            return sample.generateDeps(function (generate) {
+                generate_output_and_mappings_file(generate, parse(sample));
+                throw err;
+            });
         }
 
-        let showWhitespace = (str) => {
-            return str.replace(/\t/g, '\\t').replace(/\n/g, '\\n\n').replace(/\r/g, '\\r');
-        };
-
-        (solo ? it.only : it)(dir, () => {
-            const testContent = fs
-                .readFileSync(`${__dirname}/${dir}`, 'utf-8')
-                .replace(/\r\n/g, '\n')
-                .replace(/\t/g, '    ');
-
-            let [inputBlock, expectedBlock] = testContent.split(/\n!Expected.*?\n/);
-            let input = extractLocations(inputBlock);
-            let expected = extractLocations(expectedBlock);
-
-            // seems backwards but we don't have an "input" source map, so we generate one from our expected output
-            // but assert that the source it generates matches our input source.
-            //console.log(expected.source)
-            const { map, code } = dir.endsWith('.htmlx.html')
-                ? htmlx2jsx(expected.source)
-                : svelte2tsx(expected.source);
-            assert.equal(
-                showWhitespace(code),
-                showWhitespace(input.source),
-                "Couldn't generate input source map for test"
-            );
+        const edit_changed = is_edit_changed(edit);
 
-            let decoder = new sm.SourceMapConsumer(map);
-
-            for (let [id, span] of input.locations.entries()) {
-                let expectedSpan = expected.locations.get(id);
-
-                //walk our generated span checking it lines up
-                let col = span.start;
-                let input_line = span.line;
-                let expected_line = expectedSpan.line;
-
-                assert.ok(input.source);
-                let error_source = input.source.split('\n')[span.line - 1];
-                assert.ok(error_source);
-                let error_map = new Array(error_source.length).fill(' ');
-
-                let actual_result_line, actual_result_source, actual_result;
-                let errorCount = 0;
-
-                for (var off = 0; off < span.offsets.length; off++) {
-                    let input_col = col + off;
-                    let expected_col = expectedSpan.start + span.offsets[off];
-
-                    //originalPositionFor uses 0 base cols and 1 base lines....
-                    let { line: actual_line, column: decoded_col } = decoder.originalPositionFor({
-                        line: input_line,
-                        column: input_col - 1
-                    });
-                    let actual_col = decoded_col + 1;
-
-                    if (!actual_result) {
-                        actual_result_source = expected.source.split('\n')[actual_line - 1];
-                        actual_result = new Array(actual_result_source.length).fill(' ');
-                        actual_result_line = actual_line;
-                    }
-                    if (actual_line == actual_result_line) {
-                        if (actual_result_line[actual_col - 1] == ' ') {
-                            actual_result[actual_col - 1] = '1';
-                        } else {
-                            //track number of characters mapped to result
-                            actual_result[actual_col - 1] = `${Math.min(
-                                (actual_result[actual_col - 1] << 0) + 1,
-                                9
-                            )}`;
-                        }
-                    } else {
-                        actual_result = actual_result + 'X';
-                    }
-
-                    if (actual_col != expected_col || actual_line != expected_line) {
-                        errorCount++;
-                        error_map[input_col - 1] = 'X';
-                    } else {
-                        error_map[input_col - 1] = '=';
-                    }
+        if (edit_changed || !sample.has('test.jsx')) {
+            return sample.generateDeps(function (generate) {
+                const parsed = parse(sample);
+                if (is_edit_empty(edit)) {
+                    generate('test.jsx', parsed.generate_test());
+                    generate('test.edit.jsx', parsed.generate_test_edit());
+                    generate_output_and_mappings_file(generate, parsed);
+                    return;
                 }
-
-                if (errorCount != 0) {
-                    assert.fail(`
-					Errors on span ${id}
-			
-					Output
-					${actual_result_source}
-					${actual_result.join('').replace(/1/g, '=')}
-
-					Errors
-					${error_source}
-					${error_map.join('')}
-					`);
+                if (is_edit_from_same_generated(edit, parsed.generated)) {
+                    const new_test = parsed.generate_test(edit);
+                    generate('test.jsx', new_test);
+                    generate('test.edit.jsx', parsed.generate_test_edit(new_test));
+                    generate_output_and_mappings_file(generate, parsed);
+                    return;
                 }
+                const err = edit_changed ? 'apply changes made to' : 'generate "test.jsx" from';
+                throw new Error(
+                    '' +
+                        `Failed to ${err} "test.edit.jsx" as it is based on a stale output.\n` +
+                        `\tEither reverse output changes or delete "test.edit.jsx" manually before running tests again\n` +
+                        `\tcmd-click : ${sample.cmd('test.edit.jsx')}\n`
+                );
+            });
+        }
+    }
+
+    if (!sample.has('mappings.jsx') || (!sample.has('test.jsx') && !sample.has('test.edit.jsx'))) {
+        return sample.generateDeps(regenerate);
+    }
+
+    const test = sample.get('test.jsx');
+    try {
+        validate_test_file(test);
+    } catch (err) {
+        return sample.generateDeps(function (generate) {
+            generate_output_and_mappings_file(generate, parse(sample));
+            throw err;
+        });
+    }
+    if (!is_test_from_same_input(test, sample.get(svelteFile))) {
+        return sample.generateDeps(function (generate) {
+            const parsed = parse(sample);
+            generate_output_and_mappings_file(generate, parsed);
+            generate('test.edit.jsx', parsed.generate_test_edit());
+            if (is_test_empty(test)) {
+                generate('test.jsx', parsed.generate_test());
+                return;
             }
+            throw new Error(
+                '' +
+                    `Test input at "${svelteFile}" changed, thus making "test.jsx" invalid.\n` +
+                    `\tEither manually re-select all tested ranges in the newly generated "test.edit.jsx", delete "test.jsx" or undo input changes.\n` +
+                    `\tcmd-click : ${sample.cmd('test.edit.jsx')}\n`
+            );
         });
-    });
-});
+    }
+}
+
+function generate_output_and_mappings_file(generate: GenerateFn, parsed: Parsed, skip = false) {
+    generate('output.tsx', parsed.inline, false);
+    generate('mappings.jsx', parsed.print_mappings(), skip);
+}
+
+type Parsed = ReturnType<typeof process_transformed_text> & { generated: string; inline: string };
+const cache = new WeakMap<Sample, Parsed>();
+
+/**
+ * Generates svelte2tsx-output and mappings and prepares methods for
+ * generating further test output files.
+ */
+function parse(sample: Sample): Parsed {
+    if (!cache.has(sample)) {
+        const filename = sample.find_file('*.svelte');
+        const original = sample.get(filename);
+        const { code, map } = svelte2tsx(
+            original,
+            get_svelte2tsx_config({ filename }, sample.name)
+        );
+
+        map.file = 'output.tsx';
+        map.sources = [filename];
+        map.sourcesContent = [original];
+
+        const mapped = process_transformed_text(original, code, decode(map.mappings) as any);
+        cache.set(sample, {
+            ...mapped,
+            generated: code,
+            inline: code + `\n//# sourceMappingURL=${map.toUrl()}`
+        });
+    }
+    return cache.get(sample);
+}
diff --git a/packages/svelte2tsx/test/sourcemaps/let.html b/packages/svelte2tsx/test/sourcemaps/let.html
deleted file mode 100644
index a6e113a4..00000000
--- a/packages/svelte2tsx/test/sourcemaps/let.html
+++ /dev/null
@@ -1,15 +0,0 @@
-///<reference types="svelte" />
-<></>;function render() {
-
-    let  selected = __sveltets_invalidate(() => lookup.get(slug));
-;
-() => (<>
-</>);
-return { props: {}, slots: {}, getters: {}, events: {} }}
-
-export default class extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
-}
-!Expected
-<script>
-    $: selected = lookup.get(slug);
-</script>
diff --git a/packages/svelte2tsx/test/sourcemaps/parser.ts b/packages/svelte2tsx/test/sourcemaps/parser.ts
new file mode 100644
index 00000000..30c8c49c
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/parser.ts
@@ -0,0 +1,255 @@
+import {
+    CharMap4,
+    each_subset,
+    GeneratedPosition,
+    LineMap,
+    MappedPosition,
+    Mappings,
+    print_string,
+    reduce_segments,
+    span,
+    underline
+} from './helpers';
+
+type LineContext = { index: number; start: number; length: number };
+
+export class SourceText<L extends Line = Line> {
+    readonly lines: L[] = [];
+
+    constructor(readonly text: string, line?: (ctx: LineContext) => L) {
+        this.lines = Array.from(
+            (function* (text: string): Generator<LineContext> {
+                const line = { index: 0, start: 0, length: 0 };
+                for (let i = 0; i < text.length; i++)
+                    switch (text.charCodeAt(i)) {
+                        case 13: // "\r"
+                            if (10 === text.charCodeAt(i + 1)) i++;
+                        case 10: // "\n"
+                            line.length = 1 + i - line.start;
+                            yield line;
+                            line.start += line.length;
+                            line.index++;
+                    }
+                if (line.start === text.length) return;
+                line.length = text.length - line.start;
+                yield line;
+            })(text),
+            line ? line.bind(this) : (ctx) => new Line(this, ctx)
+        );
+    }
+
+    toLineChar(index: number) {
+        let i = this.lines.length - 1;
+        while (index < this.lines[i].start) i--;
+        return { line: this.lines[i], character: index - this.lines[i].start };
+    }
+
+    print_slice(start: number, end?: number) {
+        return print_string(this.text.slice(start, end));
+    }
+}
+
+/**
+ * Wrapper around text that was compiled from another
+ */
+export class GeneratedSourceText extends SourceText<GeneratedLine> {
+    firstMapped: number;
+    lastMapped: number;
+    readonly original: SourceText;
+    readonly mappings: Mappings;
+    private readonly references: Map<Line, GeneratedLine[]>;
+
+    constructor(text: string, original: SourceText, mappings: Mappings) {
+        const references = new Map<Line, GeneratedLine[]>(original.lines.map((line) => [line, []]));
+        super(text, function (this: GeneratedSourceText, line) {
+            if (mappings.length > line.index && mappings[line.index].length > 0) {
+                this.firstMapped ??= line.index;
+                this.lastMapped = line.index;
+                const lineMap = mappings[line.index];
+                const genLine = new GeneratedLine(
+                    this,
+                    line,
+                    lineMap,
+                    [...new Set(lineMap.map((c) => c[2]))].sort().map((i) => original.lines[i])
+                );
+                for (const ogLine of genLine.ogLines) references.get(ogLine).push(genLine);
+                return genLine;
+            }
+            return new GeneratedLine(this, line);
+        });
+        this.references = references;
+        this.original = original;
+        this.mappings = mappings;
+    }
+
+    at(genCharIndex: number): MappedPosition {
+        const { line, character } = super.toLineChar(genCharIndex);
+        return { generated: { line, character }, original: line.getOriginalPosition(character) };
+    }
+
+    from(ogCharIndex: number): MappedPosition[] {
+        const original = this.original.toLineChar(ogCharIndex);
+        const { line: ogLine, character: ogIndex } = original;
+        const matches: GeneratedPosition[] = [];
+        for (const line of this.for(ogLine)) {
+            for (const char of line.lineMap) {
+                if (char[2] === ogLine.index && char[3] === ogIndex) {
+                    matches.push({ line, character: char[0] });
+                }
+            }
+        }
+        return matches.map((generated) => ({ generated, original }));
+    }
+
+    for(ogLine: Line) {
+        return this.references.get(ogLine);
+    }
+}
+
+/**
+ * Wrapper around each line in a SourceText
+ */
+export class Line {
+    readonly index: number;
+    readonly start: number;
+    readonly length: number;
+
+    get end() {
+        return this.start + this.length - 1;
+    }
+
+    constructor(readonly source: SourceText<any>, ctx: LineContext) {
+        ({ index: this.index, start: this.start, length: this.length } = ctx);
+    }
+
+    print_charAt(charIndex: number) {
+        return print_string(this.source.text.charAt(this.start + charIndex));
+    }
+
+    print_slice(start: number, end?: number) {
+        return print_string(
+            start >= 0 && (end === undefined || (start <= end && end <= this.length))
+                ? this.source.text.slice(this.start + start, this.start + (end ?? this.length))
+                : this.toString().slice(start, end)
+        );
+    }
+
+    print() {
+        return this.print_slice(0);
+    }
+
+    toString() {
+        return this.source.text.slice(this.start, this.start + this.length);
+    }
+}
+
+/**
+ * Wrapper around each line in a GeneratedSourceText
+ */
+export class GeneratedLine extends Line {
+    readonly source: GeneratedSourceText;
+    readonly hasOrigin = this.lineMap.length !== 0;
+    readonly hasStartOrigin = this.hasOrigin && this.lineMap[0][0] === 0;
+    readonly isSingleOrigin = this.hasOrigin && this.ogLines.length === 1;
+
+    constructor(
+        source: GeneratedSourceText,
+        ctx: LineContext,
+        readonly lineMap: LineMap = [],
+        readonly ogLines: Line[] = []
+    ) {
+        super(source, ctx);
+    }
+
+    isExact() {
+        return (
+            this.hasOrigin &&
+            this.hasStartOrigin &&
+            this.isSingleOrigin &&
+            this.ogLines[0].print() === this.print() &&
+            this.source.for(this.ogLines[0]).length === 1 &&
+            this.lineMap.every((char) => char[0] === char[3])
+        );
+    }
+
+    getOrderBreaking(ogLine: Line) {
+        let prev: CharMap4 = this.lineMap.find((char) => char[2] === ogLine.index);
+        return reduce_segments(this.lineMap, function (char) {
+            if (char[2] === ogLine.index) {
+                if (prev[3] > char[3]) {
+                    return underline(prev[0], (prev = char)[0] - 1, '#==');
+                }
+                prev = char;
+            }
+        });
+    }
+
+    getUnmappedStart() {
+        return span(this.lineMap[0]?.[0] ?? this.length, '==#');
+    }
+
+    getGeneratedMappingResult(ogLine: Line) {
+        return reduce_segments(this.lineMap, function (char) {
+            if (char[2] === ogLine.index)
+                return { start: char[0], text: ogLine.print_charAt(char[3]) };
+        });
+    }
+
+    getOriginalMappingResult(ogLine: Line) {
+        return reduce_segments(this.lineMap, function (char) {
+            if (char[2] === ogLine.index)
+                return { start: char[3], text: ogLine.print_charAt(char[3]) };
+        });
+    }
+
+    *subsets() {
+        if (!this.hasOrigin) return;
+        if (this.isSingleOrigin)
+            return yield {
+                ogLine: this.ogLines[0],
+                text: this.print_slice(this.lineMap[0][0]),
+                index: 0
+            };
+        const text: { [ogLineIndex: number]: string } = {};
+        for (const { line, start, end } of each_subset(this.lineMap))
+            text[line] = (text[line] ?? '').padEnd(start) + this.print_slice(start, end);
+        for (let index = 0; index < this.ogLines.length; index++) {
+            const ogLine = this.ogLines[index];
+            yield { ogLine, text: text[ogLine.index], index };
+        }
+    }
+
+    getMappingFor(charIndex: number) {
+        if (!this.hasOrigin || (!this.hasStartOrigin && charIndex < this.lineMap[0][0])) return;
+        let i = this.lineMap.length - 1;
+        while (this.lineMap[i][0] > charIndex) i--;
+        return this.lineMap[i];
+    }
+
+    getOriginalPosition(charIndex: number) {
+        const char = this.getMappingFor(charIndex);
+        if (!char) return { line: null, character: -1 };
+        const { 2: line, 3: character } = char;
+        return { line: this.source.original.lines[line], character };
+    }
+}
+
+export type ParsedSource = {
+    original: SourceText;
+    generated: GeneratedSourceText;
+};
+
+/**
+ * Reconciliates the generated text with the original using its mappings
+ * @param original_text original code
+ * @param generated_text transformed code compiled from original
+ * @param mappings mappings from transformed to original
+ */
+export function parse(
+    original_text: string,
+    generated_text: string,
+    mappings: Mappings
+): ParsedSource {
+    const original = new SourceText(original_text);
+    return { original, generated: new GeneratedSourceText(generated_text, original, mappings) };
+}
diff --git a/packages/svelte2tsx/test/sourcemaps/process.ts b/packages/svelte2tsx/test/sourcemaps/process.ts
new file mode 100644
index 00000000..ae7123f8
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/process.ts
@@ -0,0 +1,502 @@
+import { ComposeHelper, compose_file } from './composer';
+import {
+    each_exec,
+    fromLineCharToOffset,
+    get_extra_indent,
+    hash,
+    insert_segments,
+    MappedKeys,
+    MappedPosition,
+    MappedRange,
+    Mappings,
+    Position,
+    Range,
+    range_for,
+    reduce_segments,
+    tab_aware_index,
+    underline
+} from './helpers';
+import {
+    GeneratedLine,
+    GeneratedSourceText,
+    Line,
+    parse,
+    ParsedSource,
+    SourceText
+} from './parser';
+
+/**
+ *
+ * 	SourceMapping Tests
+ *
+ * 		- Original range (expected)		never changes
+ * 		- Generated range (actual)		"always" changes
+ *
+ * 	Tested ranges cannot be stored as simple positions,
+ * 	else they wouldn't map correctly on generated range change
+ *
+ * 	TestedRange format [number, number, string]
+ *
+ * 		[0] : index of the tested range in original
+ * 		[1] : length in original
+ * 		[2] : generated text for the range
+ *
+ * 	To find Tested Ranges between changes,
+ *
+ * 		1) Reverse lookup the generated position for [0]
+ * 	 	2) Find the closest occurence of [2]
+ *
+ */
+type RawTestRange = [ogStart: number, ogLength: number, genText: string];
+type SourceMappingTest = { actual: Range; expected: Range; range: MappedRange };
+
+// inject/retrieve info from raw file
+namespace raw {
+    // test.edit.jsx
+    const EDIT_FILE_START = `/** Surround [[[text]]] with brackets & run tests to add it to this sample's tested ranges */\n`;
+    const EDIT_FILE_END = `\n/** content-hash: $ */`.split('$'); // Hash of own content (used to check if ranges were edited)
+
+    // test.jsx
+    const TEST_FILE_START = '/** tested-ranges: $ */'.split('$'); // RawTestRange[] (what tests are evaluated from)
+    const TEST_FILE_END = '\n/** origin-hash: $ */'.split('$'); // Hash of input.svelte
+
+    /**
+     * Return raw ranges and the hash from a test.jsx input string.
+     */
+    export function fromTestFile(file: string) {
+        if (!file.startsWith(TEST_FILE_START[0]) || !file.includes(TEST_FILE_END[0]))
+            throw new Error('Invalid test file');
+        const length = TEST_FILE_START[0].length;
+        const ranges = JSON.parse(file.slice(length, file.indexOf(TEST_FILE_START[1], length)));
+        const hash = file.slice(
+            file.lastIndexOf(TEST_FILE_END[0]) + TEST_FILE_END[0].length,
+            -TEST_FILE_END[1].length
+        );
+        return { ranges: ranges as RawTestRange[], hash };
+    }
+
+    /**
+     * Returns a string for a test.jsx file
+     */
+    export function toTestFile(origin: string, content: string, ranges: RawTestRange[]) {
+        const raw = JSON.stringify(ranges);
+        if (raw.includes(TEST_FILE_START[1]))
+            throw new Error(`Tested range cannot include "${TEST_FILE_START[1]}"`);
+        let header = TEST_FILE_START[0] + raw + TEST_FILE_START[1];
+        if (ranges.length && /^\s*{\/\*\*/.test(content)) {
+            const width = content.indexOf('{');
+            content = content.slice(Math.min(width, header.length));
+        }
+
+        return header + content + TEST_FILE_END[0] + hash(origin) + TEST_FILE_END[1];
+    }
+
+    export function fromEditFile(file: string) {
+        const start = file.lastIndexOf(EDIT_FILE_START);
+        const end = file.lastIndexOf(EDIT_FILE_END[0]);
+        if (start > 0) throw new Error('Test Edit file invalid start');
+        if (end === -1) throw new Error('Test Edit file is missing its content-hash');
+        return {
+            content: file.slice(start === -1 ? 0 : EDIT_FILE_START.length, end),
+            hash: file.slice(end + EDIT_FILE_END[0].length, file.indexOf(EDIT_FILE_END[1], end))
+        };
+    }
+
+    export function toEditFile(content: string) {
+        return EDIT_FILE_START + content + EDIT_FILE_END[0] + hash(content) + EDIT_FILE_END[1];
+    }
+}
+
+namespace print {
+    /**
+     * Return string for mappings.jsx
+     */
+    export function mappings({ generated }: ParsedSource): string {
+        return compose_file(function* (composer) {
+            for (const line of generated.lines) {
+                if (
+                    line.index < generated.firstMapped ||
+                    line.index > generated.lastMapped ||
+                    line.isExact()
+                ) {
+                    yield line;
+                } else {
+                    yield composer.rule('', '-');
+                    yield line;
+                    yield composer.comment(comment_for(line), { indent: 0 });
+                    yield composer.rule('', '-');
+                }
+            }
+        });
+
+        function* comment_for(line: GeneratedLine) {
+            const text_generated = line.print();
+            for (const { ogLine, text: text_og_subset, index } of line.subsets()) {
+                const mapping_rel_generated = line.getGeneratedMappingResult(ogLine);
+                const mapping_rel_original = line.getOriginalMappingResult(ogLine);
+                const mapping_unordered = line.getOrderBreaking(ogLine);
+                const text_original = ogLine.print();
+                const show_gen_mapping =
+                    mapping_rel_generated !== text_generated &&
+                    mapping_rel_generated !== text_og_subset &&
+                    mapping_rel_generated !== mapping_rel_original;
+
+                let rest = '';
+                const others = line.source.for(ogLine).filter((l) => l !== line);
+                if (others.length !== 0) {
+                    const lines = others.map((line) => line.index + 1).join(', ');
+                    rest = `(rest generated at line${others.length === 1 ? '' : 's'} ${lines})`;
+                }
+                const offset = line.toString().match(/^\t*/)[0].length * 3;
+
+                // prettier-ignore
+                yield* [
+					index !== 0								&& [``,								``												],
+					index === 0 && !line.hasStartOrigin 	&& [line.getUnmappedStart(),		`Originless mappings`							],
+					true 									&& [text_generated,					`[generated] line ${line.index + 1}`			],
+					!line.isSingleOrigin 					&& [text_og_subset,					`[generated] subset`							],
+					show_gen_mapping 						&& [mapping_rel_generated,			``												],
+					!!mapping_unordered 					&& [mapping_unordered,				`Order-breaking mappings`						],
+					mapping_rel_original !== text_original	&& [mapping_rel_original,			``												],
+					true 									&& [text_original,					`[original] line ${ogLine.index + 1} ${rest}`	]
+				].filter(Boolean).map(([map,comment])=>[" ".repeat(offset)+map,comment]) as [string, string][];
+            }
+        }
+    }
+
+    /**
+     * Print string for test.jsx
+     */
+    export function test(test_ranges: RawTestRange[], source: ParsedSource): string {
+        return raw.toTestFile(
+            source.original.text,
+            compose_file(compose_test, { match_first_column_width: true }),
+            test_ranges
+        );
+
+        function* compose_test(composer: ComposeHelper) {
+            const ranges = test_ranges.map((range) => tryEvalTestRange(range, source).range);
+            for (let i = 0; i < ranges.length; i++) {
+                yield composer.rule('', '-');
+                if (is_same_line(ranges[i].start, ranges[i].end)) {
+                    const j = i;
+                    // print ranges that map from same gen line to same og line together
+                    while (i < ranges.length - 1 && can_merge(ranges[i], ranges[i + 1])) i++;
+                    const target = ranges.slice(j, i + 1);
+                    const is_single = j === i;
+                    for (const key of MappedKeys) {
+                        const { line } = target[0].start[key];
+                        if (!line) continue;
+                        yield line;
+                        yield composer.comment([
+                            reduce_segments(
+                                target.map((range) => range_for(key, range)),
+                                (range, i) =>
+                                    underline(
+                                        tab_aware_index(line.toString(), range.start.character),
+                                        tab_aware_index(line.toString(), range.end.character),
+                                        (is_single ? '#' : i + 1) + '=='
+                                    )
+                            ).padEnd(line.length + get_extra_indent(line.toString()) - 3),
+                            `[${key}] line ${line.index + 1}${
+                                key === 'generated' && !target[0].start.original.line
+                                    ? ' => No Mappings !'
+                                    : ''
+                            }`
+                        ]);
+                    }
+                } else {
+                    for (const key of MappedKeys) {
+                        const p0 = ranges[i].start[key];
+                        const p1 = ranges[i].end[key];
+
+                        const lines = source[key].lines.slice(p0.line.index, p1.line.index + 1);
+
+                        yield* lines;
+                        const length = p1.line.start - p0.line.start + p1.character;
+                        const full_underline = underline(p0.character, length, '#=#').toString();
+
+                        yield composer.comment(
+                            (function* (): Generator<[string, string]> {
+                                let off = 0;
+                                const r = lines.map<Range>((line: Line) => ({
+                                    start: { line, character: 0 },
+                                    end: { line, character: line.length - 1 }
+                                }));
+                                const l = r.length - 1;
+                                r[0].start.character = p0.character;
+                                r[l].end.character = p1.character;
+                                for (let i = 0; i < lines.length; i++) {
+                                    const line = lines[i];
+                                    yield [
+                                        ' '.repeat(off) + line.print(),
+                                        `[${key}] line ${line.index + 1}`
+                                    ];
+                                    yield [full_underline.slice(off, off + line.length), ''];
+                                    off += line.length;
+                                }
+                            })(),
+                            { indent: 2 }
+                        );
+                    }
+                }
+
+                yield composer.rule('---');
+            }
+        }
+
+        function can_merge(r1: MappedRange, r2: MappedRange) {
+            return (
+                is_same_line(r1.start, r2.end) &&
+                r2.start.generated.character > r1.end.generated.character &&
+                r2.start.original.character > r1.end.original.character
+            );
+        }
+
+        function is_same_line(p1: MappedPosition, p2: MappedPosition) {
+            return p1.generated.line === p2.generated.line && p1.original.line === p2.original.line;
+        }
+    }
+
+    /**
+     * Print string for test.edit.jsx
+     */
+    export function test_edit(parsed_tests: SourceMappingTest[], source: ParsedSource) {
+        return raw.toEditFile(
+            insert_segments(
+                source.generated.text,
+                (function* () {
+                    for (const test of parsed_tests) {
+                        const { start, end } = range_for('generated', test.range);
+                        yield { start: fromLineCharToOffset(start), text: '[[[' };
+                        yield { start: fromLineCharToOffset(end) + 1, text: ']]]' };
+                    }
+                })()
+            )
+        );
+    }
+}
+
+function tryEvalTestRange(
+    tested_range: RawTestRange,
+    source: ParsedSource
+): SourceMappingTest | null {
+    const { generated, original } = source;
+    const [ogStart, ogLength, genText] = tested_range;
+    if (generated.text.includes(genText)) {
+        const index = tryFindGenPosition(generated, genText, ogStart);
+        const range = { start: generated.at(index), end: generated.at(index + genText.length - 1) };
+        return {
+            range,
+            actual: range_for('original', range),
+            expected: {
+                start: original.toLineChar(ogStart),
+                end: original.toLineChar(ogStart + ogLength - 1)
+            }
+        };
+    }
+}
+
+function tryFindGenPosition(
+    generated: GeneratedSourceText,
+    generated_subset: string,
+    ogStart: number
+) {
+    const matches = generated.from(ogStart);
+    const { generated: cue } = matches.length === 1 ? matches[0] : tryPickMatch(matches);
+    const forward = generated.text.indexOf(generated_subset, fromLineCharToOffset(cue));
+    const backward = generated.text.lastIndexOf(generated_subset, fromLineCharToOffset(cue));
+    const target = forward === backward || forward === -1 ? backward : forward;
+    return target;
+
+    function tryPickMatch(matches: MappedPosition[]) {
+        const exact = matches.filter((match) =>
+            match.generated.line.source.text
+                .slice(fromLineCharToOffset(match.generated))
+                .startsWith(generated_subset)
+        );
+        if (exact.length === 1) return exact[0];
+        const l = exact.length;
+        const m = matches.length;
+        const m_of_them = l === m ? 'all of them' : l === 0 ? 'none' : `${l} out of ${m}`;
+        throw new Error(
+            `Could not find TestRange: Generated text includes ` +
+                `${m} characters mapping back to origin's ${toString(matches[0].original)} and ` +
+                `${m_of_them} start with the tested text "${generated_subset}"` +
+                `\n Matching : ${matches.map((match) => toString(match.generated)).join(',\n')}`
+        );
+    }
+
+    function toString(pos: Position) {
+        return `[${pos.line.index + 1}:${pos.character + 1}]`;
+    }
+}
+
+export function validate_edit_file(text_with_ranges: string) {
+    for (const raw of parse_edit_ranges(text_with_ranges, false)) {
+        if (raw.start === -1 || raw.end === -1) {
+            const missing_start = raw.start === -1;
+            const end = missing_start ? 'start' : 'end';
+            const start = missing_start ? 'end' : 'start';
+            const index = missing_start ? raw.end : raw.start;
+            const { line, character } = new SourceText(text_with_ranges).toLineChar(index);
+            throw new Error(
+                `Line ${line.index + 1} ${start}s a range that has no corresponding ${end}\n\n` +
+                    `\t${line.print()}\n` +
+                    `\t${' '.repeat(character) + '^^^'}\n`
+            );
+        }
+    }
+}
+
+export function validate_test_file(test: string) {
+    raw.fromTestFile(test);
+}
+
+export function is_edit_changed(edit_file: string) {
+    try {
+        const { content, hash: prev } = raw.fromEditFile(edit_file);
+        return hash(content) !== prev;
+    } catch {
+        return true;
+    }
+}
+
+export function is_test_from_same_input(test: string, input: string) {
+    return hash(input) === raw.fromTestFile(test).hash;
+}
+
+export function is_test_empty(test: string) {
+    return raw.fromTestFile(test).ranges.length === 0;
+}
+
+export function is_edit_from_same_generated(test_edit: string, generated: string) {
+    return raw.fromEditFile(test_edit).content.replace(/\[\[\[|\]\]\]/g, '') === generated;
+}
+
+export function is_edit_empty(test_edit: string) {
+    return !/\[\[\[[^\]]*\]\]\]/.test(raw.fromEditFile(test_edit).content);
+}
+
+export function process_transformed_text(
+    original_text: string,
+    generated_text: string,
+    mappings: Mappings
+) {
+    const source = parse(original_text, generated_text, mappings);
+    return {
+        print_mappings: () => print.mappings(source),
+
+        generate_test_edit(test_file: string = '') {
+            return print.test_edit(parse_test_file(test_file, source), source);
+        },
+
+        generate_test(test_edit_file: string = '') {
+            if (test_edit_file) validate_edit_file(test_edit_file);
+            return print.test(parse_edit_file(test_edit_file, source), source);
+        },
+
+        each_test_range(
+            test_file: string,
+            assertStrictEqual: (actual: string, expected: string) => void,
+            invalid_file: () => void,
+            invalid_range: (arr: RawTestRange[]) => void
+        ) {
+            const tested_ranges = parse_test_file(test_file, source, invalid_file, invalid_range);
+            for (const { actual, expected, range } of tested_ranges) {
+                const { start, end } = range_for('generated', range);
+                let gen = '';
+                if (start.line === end.line) {
+                    gen += start.line.print() + '\n';
+                    gen += underline(start.character, end.character);
+                } else {
+                    gen +=
+                        source.generated.print_slice(
+                            start.line.start,
+                            end.line.start + end.line.length
+                        ) + '\n';
+                    gen += underline(start.character, end.line.start + end.character);
+                }
+                const og = source.original.print_slice(
+                    Math.min(actual.start.line.start, expected.start.line.start),
+                    Math.max(actual.end.line.end + 1, expected.start.line.end + 1)
+                );
+                assertStrictEqual(
+                    gen + '\n' + og + '\n' + underline_offset(actual, expected),
+                    gen + '\n' + og + '\n' + underline_offset(expected, actual)
+                );
+            }
+        }
+    };
+
+    function underline_offset(r1: Range, r2: Range) {
+        return underline(
+            r1.start.character + (r1.start.line.start - r2.start.line.start),
+            r1.end.character + (r1.end.line.start - r2.end.line.start),
+            '#=='
+        );
+    }
+}
+
+function parse_test_file(
+    test_file: string,
+    source: ParsedSource,
+    on_invalid_file?: () => void,
+    on_invalid_range?: (ranges: RawTestRange[]) => void
+) {
+    const parsed: SourceMappingTest[] = [];
+    const invalid: RawTestRange[] = [];
+    const test_ranges = (function () {
+        try {
+            return raw.fromTestFile(test_file).ranges;
+        } catch {
+            return on_invalid_file?.(), [];
+        }
+    })();
+    for (const test_range of test_ranges) {
+        const range = tryEvalTestRange(test_range, source);
+        if (range) parsed.push(range);
+        else invalid.push(test_range);
+    }
+    if (invalid.length) on_invalid_range?.(invalid);
+    return parsed;
+}
+
+function parse_edit_file(edit_file: string, { generated }: ParsedSource) {
+    if (!edit_file) return [];
+    return parse_edit_ranges(edit_file, true).map<RawTestRange>(function (raw) {
+        const range = { start: generated.at(raw.start), end: generated.at(raw.end) };
+        const original = range_for('original', range);
+        const { start, end } = range_for('generated', range);
+        const ogStart = original.start.line ? fromLineCharToOffset(original.start) : 0;
+        const ogLength = original.end.line ? fromLineCharToOffset(original.end) - ogStart + 1 : 0;
+        return [
+            ogStart,
+            ogLength,
+            generated.text.slice(fromLineCharToOffset(start), fromLineCharToOffset(end) + 1)
+        ];
+    });
+}
+
+function parse_edit_ranges(text_with_ranges: string, index_relative: boolean) {
+    const ranges: { start: number; end: number }[] = [];
+    const pending: { start: number; end: number }[] = [];
+    const { content } = raw.fromEditFile(text_with_ranges);
+    let offset = 0;
+    for (const { match, index } of each_exec(content, /\[\[\[|\]\]\]/g)) {
+        if (match === '[[[') {
+            const range = { start: index - offset, end: -1 };
+            pending.push(range);
+            ranges.push(range);
+        } else {
+            if (pending.length === 0) {
+                const range = { start: -1, end: -1 }; // error for later
+                ranges.push(range);
+                pending.push(range);
+            }
+            pending.pop().end = index - offset - 1;
+        }
+        if (index_relative) offset += 3;
+    }
+    return ranges;
+}
diff --git a/packages/svelte2tsx/test/sourcemaps/readme.md b/packages/svelte2tsx/test/sourcemaps/readme.md
new file mode 100644
index 00000000..d4ed6ded
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/readme.md
@@ -0,0 +1,21 @@
+# SourceMapping tests
+
+## How to add a new sample
+
+    1) Create a folder with a svelte file (e.g. "input.svelte")
+    2) Run tests
+    3) Surround [[[text]]] with brackets in the newly generated `test.edit.jsx` file to test its mappings
+    4) Run tests
+
+## Tracked Files
+
+    - `*.svelte` : input
+    - `mappings.jsx` : (auto-generated) describes every mapping in detail to highlight changes for code review
+    - `test.jsx` : (auto-generated) asserts mappings of ranges in the generated text
+
+### Untracked Files
+
+    - `test.edit.jsx` : used to edit a sample's tested ranges
+    - `output.tsx` : for debug purposes, a standalone file for sourcemapping tools (e.g. https://evanw.github.io/source-map-visualization/)
+
+Untracked files are only generated for new samples, on test error, mapping change or missing file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/event-binding/input.svelte b/packages/svelte2tsx/test/sourcemaps/samples/event-binding/input.svelte
new file mode 100644
index 00000000..7db74e6a
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/event-binding/input.svelte
@@ -0,0 +1,2 @@
+<Component on:click={$check ? method1 : method2} />
+<button on:click={$check ? method1 : method2} >Bla</button>
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/event-binding/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/event-binding/mappings.jsx
new file mode 100644
index 00000000..d8033362
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/event-binding/mappings.jsx
@@ -0,0 +1,21 @@
+///<reference types="svelte" />
+<></>;function render() {                                                                                                                             {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+<><Component  />{__sveltets_instanceOf(Component).$on('click', (__sveltets_store_get(check), $check) ? method1 : method2)}                            {/**
+=#                                                                                                                             Originless mappings    
+<><Component••/>{__sveltets_instanceOf(Component).$on('click',•(__sveltets_store_get(check),•$check)•?•method1•:•method2)}↲    [generated] line 3     
+  <Component••/>                                   on: click=  $                     check          •?•method1•:•method2} ↲                           
+               #===================================                                                                            Order-breaking mappings
+<Component•on:click= $check•?•method1•:•method2}•/>↲                                                                                                  
+<Component•on:click={$check•?•method1•:•method2}•/>↲                                                                           [original] line 1      
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+<button onclick={(__sveltets_store_get(check), $check) ? method1 : method2} >Bla</button></>                                                          {/**
+<button•onclick={(__sveltets_store_get(check),•$check)•?•method1•:•method2}•>Bla</button></>↲    [generated] line 4                                   
+<button•on:    ={$                     check          •?•method1•:•method2}•>Bla</button>                                                             
+<button•on:     ={$check•?•method1•:•method2}•>Bla</button>                                                                                           
+<button•on:click={$check•?•method1•:•method2}•>Bla</button>                                      [original] line 2                                    
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+return { props: {}, slots: {}, getters: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/event-binding/test.jsx b/packages/svelte2tsx/test/sourcemaps/samples/event-binding/test.jsx
new file mode 100644
index 00000000..89b888f6
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/event-binding/test.jsx
@@ -0,0 +1,13 @@
+/** tested-ranges: [[22,5,"check"],[28,19,"? method1 : method2"],[71,5,"check"],[77,19,"? method1 : method2"]] */                                     {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+<><Component  />{__sveltets_instanceOf(Component).$on('click', (__sveltets_store_get(check), $check) ? method1 : method2)}                            {/**
+                                                                                     1====           2==================    [generated] line 3         */}
+<Component on:click={$check ? method1 : method2} />                                                                                                   {/**
+                      1==== 2==================                                                                             [original] line 1         
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+<button onclick={(__sveltets_store_get(check), $check) ? method1 : method2} >Bla</button></>                                                          {/**
+                                       1====           2==================                                                  [generated] line 4         */}
+<button on:click={$check ? method1 : method2} >Bla</button>                                                                                           {/**
+                   1==== 2==================                                                                                [original] line 2         
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+/** origin-hash: 25dzah */
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/repl.html b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/input.svelte
similarity index 60%
rename from packages/svelte2tsx/test/sourcemaps/repl.html
rename to packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/input.svelte
index 619ad3fe..be596659 100644
--- a/packages/svelte2tsx/test/sourcemaps/repl.html
+++ b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/input.svelte
@@ -1,188 +1,3 @@
-///<reference types="svelte" />
-<></>;
-	export async function preload({ params }) {
-		const res = await this.fetch(`tutorial/${params.slug}.json`);
-
-		if (!res.ok) {
-			return this.redirect(301, `tutorial/basics`);
-		}
-
-		return {
-			slug: params.slug,
-			chapter: await res.json()
-		};
-	}
-;<></>;
-import Repl from '@sveltejs/svelte-repl';
-import { getContext } from 'svelte';
-import ScreenToggle from '../../../components/ScreenToggle.svelte';
-import TableOfContents from './_TableOfContents.svelte';
-import {
-		mapbox_setup, // needed for context API tutorial
-		rollupUrl,
-		svelteUrl
-	} from '../../../config';
-function render() {
-
-	
-	
-
-	
-	
-
-	
-
-	 let slug;
-	 let chapter;
-
-	const { sections } = getContext('tutorial');
-
-	let repl;
-	let prev;
-	let scrollable;
-	const lookup = new Map();
-
-	let width = process.browser ? window.innerWidth : 1000;
-	let offset = 0;
-
-	sections.forEach(section => {
-		section.chapters.forEach(chapter => {
-			const obj = {
-				slug: chapter.slug,
-				section,
-				chapter,
-				prev
-			};
-
-			lookup.set(chapter.slug, obj);
-
-			if (process.browser) { // pending https://github.com/sveltejs/svelte/issues/2135
-				if (prev) prev.next = obj;
-				prev = obj;
-			}
-		});
-	});
-
-    // TODO is there a non-hacky way to trigger scroll when chapter changes?
-    ;() => {$: if (scrollable) chapter, scrollable.scrollTo(0, 0);}
-
-	// TODO: this will need to be changed to the master branch, and probably should be dynamic instead of included
-	//   here statically
-	const tutorial_repo_link = 'https://github.com/sveltejs/svelte/tree/master/site/content/tutorial';
-
-	let  selected = __sveltets_invalidate(() => lookup.get(slug));
-	let  improve_link = __sveltets_invalidate(() => `${tutorial_repo_link}/${selected.chapter.section_dir}/${selected.chapter.chapter_dir}`);
-
-	const clone = file => ({
-		name: file.name,
-		type: file.type,
-		source: file.source
-	});
-
-    ;() => {$: if (repl) {
-		completed = false;
-		repl.set({
-			components: chapter.app_a.map(clone)
-		});
-    }}
-
-	let  mobile = __sveltets_invalidate(() => width < 768);
-
-	function reset() {
-		repl.update({
-			components: chapter.app_a.map(clone)
-		});
-	}
-
-	function complete() {
-		repl.update({
-			components: chapter.app_b.map(clone)
-		});
-	}
-
-	let completed = false;
-
-	function handle_change(event) {
-		completed = event.detail.components.every((file, i) => {
-			const expected = chapter.app_b[i];
-			return expected && (
-				file.name === expected.name &&
-				file.type === expected.type &&
-				file.source.trim().replace(/\s+$/gm, '') === expected.source.trim().replace(/\s+$/gm, '')
-			);
-		});
-	}
-;
-() => (<>
-
-
-
-
-
-<sveltehead>
-	<title>{selected.section.title} / {selected.chapter.title} • Svelte Tutorial</title>
-
-	<meta name="twitter:title" content="Svelte tutorial"/>
-	<meta name="twitter:description" content={`${selected.section.title} / ${selected.chapter.title}`}/>
-	<meta name="Description" content={`${selected.section.title} / ${selected.chapter.title}`}/>
-</sveltehead>
-
-<sveltewindow innerWidth={width}/>
-
-<div class="tutorial-outer">
-	<div class={`viewport offset-${offset}`}>
-		<div class="tutorial-text">
-			<div class="table-of-contents">
-				<TableOfContents sections={sections} slug={slug} selected={selected}/>
-			</div>
-
-			<div class="chapter-markup" {...__sveltets_ensureType(__sveltets_ctorOf(__sveltets_mapElementTag('div')), scrollable)}>
-				{ chapter.html}
-
-				<div class="controls">
-					{(chapter.app_b) ? <>
-						
-						<button class="show" onclick={() => completed ? reset() : complete()}>
-							{completed ? 'Reset' : 'Show me'}
-						</button>
-					</> : <></>}
-
-					{(selected.next) ? <>
-						<a class="next" href={`tutorial/${selected.next.slug}`}>Next</a>
-					</> : <></>}
-				</div>
-
-				<div class="improve-chapter">
-					<a class="no-underline" href={improve_link}>Edit this chapter</a>
-				</div>
-			</div>
-		</div>
-
-		<div class="tutorial-repl">
-			<Repl
-				{...__sveltets_ensureType(Repl, repl)}
-				workersUrl="workers"
-				svelteUrl={svelteUrl}
-				rollupUrl={rollupUrl}
-				orientation={mobile ? 'columns' : 'rows'}
-				fixed={mobile}
-				
-				injectedJS={mapbox_setup}
-				relaxed
-			/>{__sveltets_instanceOf(Repl).$on('change', handle_change)}
-		</div>
-	</div>
-
-	{(mobile) ? <>
-		<ScreenToggle offset={offset} labels={['tutorial', 'input', 'output']}/>
-	</> : <></>}
-</div>
-</>);
-return { props: {slug: slug , chapter: chapter}, slots: {}, getters: {}, events: {} }}
-
-export default class extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
-}
-!Expected
 <script context="module">
 	export async function preload({ params }) {
 		const res = await this.fetch(`tutorial/${params.slug}.json`);
@@ -260,7 +75,6 @@ export default class extends createSvelte2TsxComponent(__sveltets_partial(__svel
 
 	$: if (repl) {
 		completed = false;
-        1========
 		repl.set({
 			components: chapter.app_a.map(clone)
 		});
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx
new file mode 100644
index 00000000..a2d16966
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx
@@ -0,0 +1,474 @@
+///<reference types="svelte" />
+//----------------------------------------------------------------------------------------------------------------------------------------------------
+<></>;                                                                                                                                                {/**
+=#                            Originless mappings                                                                                                     
+<></>;↲                       [generated] line 2                                                                                                      
+  <   ↲                                                                                                                                               
+<                        ↲                                                                                                                            
+<script•context="module">↲    [original] line 1                                                                                                       
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    export async function preload({ params }) {
+        const res = await this.fetch(`tutorial/${params.slug}.json`);
+
+        if (!res.ok) {
+            return this.redirect(301, `tutorial/basics`);
+        }
+
+        return {
+            slug: params.slug,
+            chapter: await res.json()
+        };
+    }                                                                                                                                                 {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+;<></>;                                                                                                                                               {/**
+;<></>;↲      [generated] line 15                                                                                                                     
+;<>           [generated] subset                                                                                                                      
+<                                                                                                                                                     
+</script>↲    [original] line 14 (rest generated at line 116)                                                                                         
+                                                                                                                                                      
+;<></>;↲      [generated] line 15                                                                                                                     
+   </>;↲      [generated] subset                                                                                                                      
+   <                                                                                                                                                  
+<                                                                                                                                                     
+<script>↲     [original] line 16 (rest generated at lines 25, 26)                                                                                     
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+import Repl from '@sveltejs/svelte-repl';                                                                                                             {/**
+import•Repl•from•'@sveltejs/svelte-repl';↲     [generated] line 16                                                                                    
+import•Repl•from•'@sveltejs/svelte-repl';                                                                                                             
+ import•Repl•from•'@sveltejs/svelte-repl';                                                                                                            
+╚import•Repl•from•'@sveltejs/svelte-repl';↲    [original] line 17 (rest generated at line 27)                                                         
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+import { getContext } from 'svelte';                                                                                                                  {/**
+import•{•getContext•}•from•'svelte';↲     [generated] line 17                                                                                         
+import•{•getContext•}•from•'svelte';                                                                                                                  
+ import•{•getContext•}•from•'svelte';                                                                                                                 
+╚import•{•getContext•}•from•'svelte';↲    [original] line 18 (rest generated at line 28)                                                              
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+import ScreenToggle from '../../../components/ScreenToggle.svelte';                                                                                   {/**
+import•ScreenToggle•from•'../../../components/ScreenToggle.svelte';↲     [generated] line 18                                                          
+import•ScreenToggle•from•'../../../components/ScreenToggle.svelte';                                                                                   
+ import•ScreenToggle•from•'../../../components/ScreenToggle.svelte';                                                                                  
+╚import•ScreenToggle•from•'../../../components/ScreenToggle.svelte';↲    [original] line 20 (rest generated at line 30)                               
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+import TableOfContents from './_TableOfContents.svelte';                                                                                              {/**
+import•TableOfContents•from•'./_TableOfContents.svelte';↲     [generated] line 19                                                                     
+import•TableOfContents•from•'./_TableOfContents.svelte';                                                                                              
+ import•TableOfContents•from•'./_TableOfContents.svelte';                                                                                             
+╚import•TableOfContents•from•'./_TableOfContents.svelte';↲    [original] line 21 (rest generated at line 31)                                          
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+import {                                                                                                                                              {/**
+import•{↲     [generated] line 20                                                                                                                     
+ import•{↲                                                                                                                                            
+╚import•{↲    [original] line 23 (rest generated at line 33)                                                                                          
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+        mapbox_setup, // needed for context API tutorial
+        rollupUrl,
+        svelteUrl                                                                                                                                     {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    } from '../../../config';                                                                                                                         {/**
+   ╚}•from•'../../../config';↲    [generated] line 24                                                                                                 
+   ╚}•from•'../../../config';                                                                                                                         
+   ╚}•from•'../../../config';↲    [original] line 27 (rest generated at line 33)                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+function render() {                                                                                                                                   {/**
+function•render()•{↲    [generated] line 25                                                                                                           
+s                                                                                                                                                     
+ s                                                                                                                                                    
+<script>↲               [original] line 16 (rest generated at lines 15, 26)                                                                           
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+↲            [generated] line 26                                                                                                                      
+        ↲                                                                                                                                             
+<script>↲    [original] line 16 (rest generated at lines 15, 25)                                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+   ╚↲                                             [generated] line 27                                                                                 
+   ╚                                         ↲                                                                                                        
+   ╚import•Repl•from•'@sveltejs/svelte-repl';↲    [original] line 17 (rest generated at line 16)                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+   ╚↲                                        [generated] line 28                                                                                      
+   ╚                                    ↲                                                                                                             
+   ╚import•{•getContext•}•from•'svelte';↲    [original] line 18 (rest generated at line 17)                                                           
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+   ╚↲                                                                       [generated] line 30                                                       
+   ╚                                                                   ↲                                                                              
+   ╚import•ScreenToggle•from•'../../../components/ScreenToggle.svelte';↲    [original] line 20 (rest generated at line 18)                            
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+   ╚↲                                                            [generated] line 31                                                                  
+   ╚                                                        ↲                                                                                         
+   ╚import•TableOfContents•from•'./_TableOfContents.svelte';↲    [original] line 21 (rest generated at line 19)                                       
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+   ╚↲                             [generated] line 33                                                                                                 
+   ╚                              [generated] subset                                                                                                  
+   ╚                                                                                                                                                  
+   ╚import•{↲                     [original] line 23 (rest generated at line 20)                                                                      
+                                                                                                                                                      
+   ╚↲                             [generated] line 33                                                                                                 
+    ↲                             [generated] subset                                                                                                  
+                             ↲                                                                                                                        
+   ╚}•from•'../../../config';↲    [original] line 27 (rest generated at line 24)                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+     let slug;                                                                                                                                        {/**
+   ╚•let•slug;↲          [generated] line 35                                                                                                          
+   ╚      •let•slug;↲                                                                                                                                 
+   ╚export•let•slug;↲    [original] line 29                                                                                                           
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+     let chapter;                                                                                                                                     {/**
+   ╚•let•chapter;↲          [generated] line 36                                                                                                       
+   ╚      •let•chapter;↲                                                                                                                              
+   ╚export•let•chapter;↲    [original] line 30                                                                                                        
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+
+    const { sections } = getContext('tutorial');
+
+    let repl;
+    let prev;
+    let scrollable;
+    const lookup = new Map();
+
+    let width = process.browser ? window.innerWidth : 1000;
+    let offset = 0;
+
+    sections.forEach(section => {
+        section.chapters.forEach(chapter => {
+            const obj = {
+                slug: chapter.slug,
+                section,
+                chapter,
+                prev
+            };
+
+            lookup.set(chapter.slug, obj);
+
+            if (process.browser) { // pending https://github.com/sveltejs/svelte/issues/2135
+                if (prev) prev.next = obj;
+                prev = obj;
+            }
+        });
+    });
+
+    // TODO is there a non-hacky way to trigger scroll when chapter changes?
+//----------------------------------------------------------------------------------------------------------------------------------------------------
+    ;() => {$: if (scrollable) chapter, scrollable.scrollTo(0, 0);}                                                                                   {/**
+   ╚;()•=>•{$:•if•(scrollable)•chapter,•scrollable.scrollTo(0,•0);}↲    [generated] line 67                                                           
+   ╚        $:•if•(scrollable)•chapter,•scrollable.scrollTo(0,•0); ↲                                                                                  
+   ╚$:•if•(scrollable)•chapter,•scrollable.scrollTo(0,•0);↲             [original] line 61                                                            
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+
+    // TODO: this will need to be changed to the master branch, and probably should be dynamic instead of included
+    //   here statically
+    const tutorial_repo_link = 'https://github.com/sveltejs/svelte/tree/master/site/content/tutorial';
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    let  selected = __sveltets_invalidate(() => lookup.get(slug));                                                                                    {/**
+   ╚let••selected•=•__sveltets_invalidate(()•=>•lookup.get(slug));↲    [generated] line 73                                                            
+   ╚    •selected•=•                            lookup.get(slug) ;↲                                                                                   
+   ╚  •selected•=•lookup.get(slug);↲                                                                                                                  
+   ╚$:•selected•=•lookup.get(slug);↲                                   [original] line 67                                                             
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    let  improve_link = __sveltets_invalidate(() => `${tutorial_repo_link}/${selected.chapter.section_dir}/${selected.chapter.chapter_dir}`);         {/**
+   ╚let••improve_link•=•__sveltets_invalidate(()•=>•`${tutorial_repo_link}/${selected.chapter.section_dir}/${selected.chapter.chapter_dir}`);↲    [generated] line 74
+   ╚    •improve_link•=•                            `${tutorial_repo_link}/${selected.chapter.section_dir}/${selected.chapter.chapter_dir}` ;↲        
+   ╚  •improve_link•=•`${tutorial_repo_link}/${selected.chapter.section_dir}/${selected.chapter.chapter_dir}`;↲                                       
+   ╚$:•improve_link•=•`${tutorial_repo_link}/${selected.chapter.section_dir}/${selected.chapter.chapter_dir}`;↲                                   [original] line 68 
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+
+    const clone = file => ({
+        name: file.name,
+        type: file.type,
+        source: file.source
+    });
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    ;() => {$: if (repl) {                                                                                                                            {/**
+   ╚;()•=>•{$:•if•(repl)•{↲    [generated] line 82                                                                                                    
+   ╚        $:•if•(repl)•{↲                                                                                                                           
+   ╚$:•if•(repl)•{↲            [original] line 76                                                                                                     
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+        completed = false;
+        repl.set({
+            components: chapter.app_a.map(clone)
+        });                                                                                                                                           {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    }}                                                                                                                                                {/**
+   ╚}}↲    [generated] line 87                                                                                                                        
+   ╚} ↲                                                                                                                                               
+   ╚}↲     [original] line 81                                                                                                                         
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    let  mobile = __sveltets_invalidate(() => width < 768);                                                                                           {/**
+   ╚let••mobile•=•__sveltets_invalidate(()•=>•width•<•768);↲    [generated] line 89                                                                   
+   ╚    •mobile•=•                            width•<•768 ;↲                                                                                          
+   ╚  •mobile•=•width•<•768;↲                                                                                                                         
+   ╚$:•mobile•=•width•<•768;↲                                   [original] line 83                                                                    
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+
+    function reset() {
+        repl.update({
+            components: chapter.app_a.map(clone)
+        });
+    }
+
+    function complete() {
+        repl.update({
+            components: chapter.app_b.map(clone)
+        });
+    }
+
+    let completed = false;
+
+    function handle_change(event) {
+        completed = event.detail.components.every((file, i) => {
+            const expected = chapter.app_b[i];
+            return expected && (
+                file.name === expected.name &&
+                file.type === expected.type &&
+                file.source.trim().replace(/\s+$/gm, '') === expected.source.trim().replace(/\s+$/gm, '')
+            );
+        });
+    }                                                                                                                                                 {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+;                                                                                                                                                     {/**
+;↲            [generated] line 115                                                                                                                    
+<                                                                                                                                                     
+</script>↲    [original] line 109 (rest generated at line 118)                                                                                        
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+() => (<>                                                                                                                                             {/**
+========#     Originless mappings                                                                                                                     
+()•=>•(<>↲    [generated] line 116                                                                                                                    
+         ↲                                                                                                                                            
+</script>↲    [original] line 14 (rest generated at line 15)                                                                                          
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+↲             [generated] line 118                                                                                                                    
+         ↲                                                                                                                                            
+</script>↲    [original] line 109 (rest generated at line 115)                                                                                        
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+↲            [generated] line 120                                                                                                                     
+        ↲                                                                                                                                             
+</style>↲    [original] line 259                                                                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+<sveltehead>                                                                                                                                          {/**
+<sveltehead>↲     [generated] line 122                                                                                                                
+<svelte head>↲                                                                                                                                        
+<svelte:head>↲    [original] line 261                                                                                                                 
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    <title>{selected.section.title} / {selected.chapter.title} • Svelte Tutorial</title>
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    <meta name="twitter:title" content="Svelte tutorial"/>                                                                                            {/**
+   ╚<meta•name="twitter:title"•content="Svelte•tutorial"/>↲    [generated] line 125                                                                   
+   ╚<meta•n   ="twitter:title"•c      ="Svelte•tutorial" >↲                                                                                           
+   ╚<meta•n   ="twitter:title"•c      ="Svelte•tutorial">↲                                                                                            
+   ╚<meta•name="twitter:title"•content="Svelte•tutorial">↲     [original] line 264                                                                    
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    <meta name="twitter:description" content={`${selected.section.title} / ${selected.chapter.title}`}/>                                              {/**
+   ╚<meta•name="twitter:description"•content={`${selected.section.title}•/•${selected.chapter.title}`}/>↲    [generated] line 126                     
+   ╚<meta•n   ="twitter:description"•c      =   {selected.section.title}•/• {selected.chapter.title}"  >↲                                             
+   ╚<meta•n   ="twitter:description"•c      = {selected.section.title}•/•{selected.chapter.title}">↲                                                  
+   ╚<meta•name="twitter:description"•content="{selected.section.title}•/•{selected.chapter.title}">↲         [original] line 265                      
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    <meta name="Description" content={`${selected.section.title} / ${selected.chapter.title}`}/>                                                      {/**
+   ╚<meta•name="Description"•content={`${selected.section.title}•/•${selected.chapter.title}`}/>↲    [generated] line 127                             
+   ╚<meta•n   ="Description"•c      =   {selected.section.title}•/• {selected.chapter.title}"  >↲                                                     
+   ╚<meta•n   ="Description"•c      = {selected.section.title}•/•{selected.chapter.title}">↲                                                          
+   ╚<meta•name="Description"•content="{selected.section.title}•/•{selected.chapter.title}">↲         [original] line 266                              
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+</sveltehead>                                                                                                                                         {/**
+</sveltehead>↲     [generated] line 128                                                                                                               
+</svelte head>↲                                                                                                                                       
+</svelte:head>↲    [original] line 267                                                                                                                
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+<sveltewindow innerWidth={width}/>                                                                                                                    {/**
+<sveltewindow•innerWidth={width}/>↲          [generated] line 130                                                                                     
+<svelte window•     innerWidth={width}/>↲                                                                                                             
+<svelte:window•bind:innerWidth={width}/>↲    [original] line 269                                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+
+<div class="tutorial-outer">                                                                                                                          {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    <div class={`viewport offset-${offset}`}>                                                                                                         {/**
+   ╚<div•class={`viewport•offset-${offset}`}>↲    [generated] line 133                                                                                
+   ╚<div•c    =  viewport•offset- {offset}" >↲                                                                                                        
+   ╚<div•c    = viewport•offset-{offset}">↲                                                                                                           
+   ╚<div•class="viewport•offset-{offset}">↲       [original] line 272                                                                                 
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+        <div class="tutorial-text">
+            <div class="table-of-contents">                                                                                                           {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                <TableOfContents sections={sections} slug={slug} selected={selected}/>                                                                {/**
+            ╚╚╚╚<TableOfContents•sections={sections}•slug={slug}•selected={selected}/>↲    [generated] line 136                                       
+            ╚╚╚╚<TableOfContents•         {sections}•     {slug}•         {selected}/>↲                                                               
+            ╚╚╚╚<TableOfContents•{sections}•{slug}•{selected}/>↲                           [original] line 275                                        
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+            </div>
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+            <div class="chapter-markup" {...__sveltets_ensureType(__sveltets_ctorOf(__sveltets_mapElementTag('div')), scrollable)}>                   {/**
+         ╚╚╚<div•class="chapter-markup"•{...__sveltets_ensureType(__sveltets_ctorOf(__sveltets_mapElementTag('div')),•scrollable)}>↲    [generated] line 139
+         ╚╚╚<div•c    ="chapter-markup"•                                                                              scrollable} >↲                  
+         ╚╚╚<div•c    ="chapter-markup"•           scrollable}>↲                                                                                      
+         ╚╚╚<div•class="chapter-markup"•bind:this={scrollable}>↲                                                                        [original] line 278 
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                { chapter.html}                                                                                                                       {/**
+            ╚╚╚╚{•chapter.html}↲         [generated] line 140                                                                                         
+            ╚╚╚╚{     •chapter.html}↲                                                                                                                 
+            ╚╚╚╚{@html•chapter.html}↲    [original] line 279                                                                                          
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+
+                <div class="controls">                                                                                                                {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                    {(chapter.app_b) ? <>                                                                                                             {/**
+               ╚╚╚╚╚{(chapter.app_b)•?•<>↲    [generated] line 143                                                                                    
+               ╚╚╚╚╚{ chapter.app_b}     ↲                                                                                                            
+               ╚╚╚╚╚{    chapter.app_b}↲                                                                                                              
+               ╚╚╚╚╚{#if•chapter.app_b}↲      [original] line 282                                                                                     
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+                  ╚╚╚╚╚╚↲                                                               [generated] line 144                                          
+                  ╚╚╚╚╚╚                                                                [generated] subset                                            
+                  ╚╚╚╚╚╚                                                                                                                              
+                  ╚╚╚╚╚╚<!--•TODO•disable•this•button•when•the•contents•of•the•REPL↲    [original] line 283                                           
+                                                                                                                                                      
+                  ╚╚╚╚╚╚↲                                                               [generated] line 144                                          
+                        ↲                                                               [generated] subset                                            
+                                                            ↲                                                                                         
+                  ╚╚╚╚╚╚╚matches•the•expected•end•result•-->↲                           [original] line 284                                           
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                        <button class="show" onclick={() => completed ? reset() : complete()}>                                                        {/**
+                  ╚╚╚╚╚╚<button•class="show"•onclick={()•=>•completed•?•reset()•:•complete()}>↲       [generated] line 145                            
+                  ╚╚╚╚╚╚<button•c    ="show"•on:    ={()•=>•completed•?•reset()•:•complete()}>↲                                                       
+                  ╚╚╚╚╚╚<button•c    ="show"•on:     = {()•=>•completed•?•reset()•:•complete()} >↲                                                    
+                  ╚╚╚╚╚╚<button•class="show"•on:click="{()•=>•completed•?•reset()•:•complete()}">↲    [original] line 285                             
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                            {completed ? 'Reset' : 'Show me'}
+                        </button>                                                                                                                     {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                    </> : <></>}                                                                                                                      {/**
+               ╚╚╚╚╚</>•:•<></>}↲    [generated] line 148                                                                                             
+               ╚╚╚╚╚{           ↲                                                                                                                     
+               ╚╚╚╚╚{    ↲                                                                                                                            
+               ╚╚╚╚╚{/if}↲           [original] line 288                                                                                              
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                    {(selected.next) ? <>                                                                                                             {/**
+               ╚╚╚╚╚{(selected.next)•?•<>↲    [generated] line 150                                                                                    
+               ╚╚╚╚╚{ selected.next}     ↲                                                                                                            
+               ╚╚╚╚╚{    selected.next}↲                                                                                                              
+               ╚╚╚╚╚{#if•selected.next}↲      [original] line 290                                                                                     
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                        <a class="next" href={`tutorial/${selected.next.slug}`}>Next</a>                                                              {/**
+                  ╚╚╚╚╚╚<a•class="next"•href={`tutorial/${selected.next.slug}`}>Next</a>↲    [generated] line 151                                     
+                  ╚╚╚╚╚╚<a•c    ="next"•h   =  tutorial/ {selected.next.slug}" >Next</a>↲                                                             
+                  ╚╚╚╚╚╚<a•c    ="next"•h   = tutorial/{selected.next.slug}">Next</a>↲                                                                
+                  ╚╚╚╚╚╚<a•class="next"•href="tutorial/{selected.next.slug}">Next</a>↲       [original] line 291                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                    </> : <></>}                                                                                                                      {/**
+               ╚╚╚╚╚</>•:•<></>}↲    [generated] line 152                                                                                             
+               ╚╚╚╚╚{           ↲                                                                                                                     
+               ╚╚╚╚╚{    ↲                                                                                                                            
+               ╚╚╚╚╚{/if}↲           [original] line 292                                                                                              
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                </div>
+
+                <div class="improve-chapter">
+                    <a class="no-underline" href={improve_link}>Edit this chapter</a>
+                </div>
+            </div>
+        </div>
+
+        <div class="tutorial-repl">
+            <Repl                                                                                                                                     {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                {...__sveltets_ensureType(Repl, repl)}                                                                                                {/**
+            ╚╚╚╚{...__sveltets_ensureType(Repl,•repl)}↲    [generated] line 163                                                                       
+            ╚╚╚╚                                repl} ↲                                                                                               
+            ╚╚╚╚           repl}↲                                                                                                                     
+            ╚╚╚╚bind:this={repl}↲                          [original] line 303                                                                        
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                workersUrl="workers"                                                                                                                  {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                svelteUrl={svelteUrl}                                                                                                                 {/**
+            ╚╚╚╚svelteUrl={svelteUrl}↲    [generated] line 165                                                                                        
+            ╚╚╚╚          {svelteUrl}↲                                                                                                                
+            ╚╚╚╚{svelteUrl}↲              [original] line 305                                                                                         
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                rollupUrl={rollupUrl}                                                                                                                 {/**
+            ╚╚╚╚rollupUrl={rollupUrl}↲    [generated] line 166                                                                                        
+            ╚╚╚╚          {rollupUrl}↲                                                                                                                
+            ╚╚╚╚{rollupUrl}↲              [original] line 306                                                                                         
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                orientation={mobile ? 'columns' : 'rows'}
+                fixed={mobile}                                                                                                                        {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+            ╚╚╚╚↲                             [generated] line 169                                                                                    
+            ╚╚╚╚                         ↲                                                                                                            
+            ╚╚╚╚on:change={handle_change}↲    [original] line 309 (rest generated at line 172)                                                        
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                injectedJS={mapbox_setup}
+                relaxed                                                                                                                               {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+            />{__sveltets_instanceOf(Repl).$on('change', handle_change)}                                                                              {/**
+         ╚╚╚/>{__sveltets_instanceOf(Repl).$on('change',•handle_change)}↲    [generated] line 172                                                     
+                                            on('change',•handle_change)}     [generated] subset                                                       
+                                            on: change=  handle_change}                                                                               
+             on:change= handle_change}                                                                                                                
+         ╚╚╚╚on:change={handle_change}↲                                      [original] line 309 (rest generated at line 169)                         
+                                                                                                                                                      
+         ╚╚╚/>{__sveltets_instanceOf(Repl).$on('change',•handle_change)}↲    [generated] line 172                                                     
+         ╚╚╚/>{__sveltets_instanceOf(Repl).$                            ↲    [generated] subset                                                       
+         ╚╚╚/>                                                          ↲                                                                             
+         ╚╚╚/>↲                                                              [original] line 312                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+        </div>
+    </div>
+                                                                                                                                                      {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    {(mobile) ? <>                                                                                                                                    {/**
+   ╚{(mobile)•?•<>↲    [generated] line 176                                                                                                           
+   ╚{ mobile}     ↲                                                                                                                                   
+   ╚{    mobile}↲                                                                                                                                     
+   ╚{#if•mobile}↲      [original] line 316                                                                                                            
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+        <ScreenToggle offset={offset} labels={['tutorial', 'input', 'output']}/>                                                                      {/**
+      ╚╚<ScreenToggle•offset={offset}•labels={['tutorial',•'input',•'output']}/>↲    [generated] line 177                                             
+      ╚╚<ScreenToggle•        offset •labels={['tutorial',•'input',•'output']}/>↲                                                                     
+      ╚╚<ScreenToggle•     offset•labels={['tutorial',•'input',•'output']}/>↲                                                                         
+      ╚╚<ScreenToggle•bind:offset•labels={['tutorial',•'input',•'output']}/>↲        [original] line 317                                              
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    </> : <></>}                                                                                                                                      {/**
+   ╚</>•:•<></>}↲    [generated] line 178                                                                                                             
+   ╚{           ↲                                                                                                                                     
+   ╚{    ↲                                                                                                                                            
+   ╚{/if}↲           [original] line 318                                                                                                              
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+</div></>);                                                                                                                                           {/**
+</div></>);↲    [generated] line 179                                                                                                                  
+</div>          [original] line 319                                                                                                                   
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+return { props: {slug: slug , chapter: chapter}, slots: {}, getters: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/test.jsx b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/test.jsx
new file mode 100644
index 00000000..f7caad3a
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/test.jsx
@@ -0,0 +1,8 @@
+/** tested-ranges: [[1805,9,"completed"]] */                                                                                                          {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+        completed = false;                                                                                                                            {/**
+        #========           [generated] line 83                                                                                                        */}
+        completed = false;                                                                                                                            {/**
+        #========           [original] line 77                                                                                                        
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+/** origin-hash: 6ddt8g */
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/let/input.svelte b/packages/svelte2tsx/test/sourcemaps/samples/let/input.svelte
new file mode 100644
index 00000000..fc2941c8
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/let/input.svelte
@@ -0,0 +1,3 @@
+<script>
+    $: selected = lookup.get(slug);
+</script>
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/let/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/let/mappings.jsx
new file mode 100644
index 00000000..fa1ec32d
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/let/mappings.jsx
@@ -0,0 +1,30 @@
+///<reference types="svelte" />
+//----------------------------------------------------------------------------------------------------------------------------------------------------
+<></>;function render() {                                                                                                                             {/**
+=#                            Originless mappings                                                                                                     
+<></>;function•render()•{↲    [generated] line 2                                                                                                      
+  <   s                                                                                                                                               
+<s                                                                                                                                                    
+<script>↲                     [original] line 1 (rest generated at line 3)                                                                            
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+                                                                                                                                                      {/**
+↲            [generated] line 3                                                                                                                       
+        ↲                                                                                                                                             
+<script>↲    [original] line 1 (rest generated at line 2)                                                                                             
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    let  selected = __sveltets_invalidate(() => lookup.get(slug));                                                                                    {/**
+••••let••selected•=•__sveltets_invalidate(()•=>•lookup.get(slug));↲    [generated] line 4                                                             
+••••    •selected•=•                            lookup.get(slug) ;↲                                                                                   
+••••  •selected•=•lookup.get(slug);↲                                                                                                                  
+••••$:•selected•=•lookup.get(slug);↲                                   [original] line 2                                                              
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+;                                                                                                                                                     {/**
+;↲           [generated] line 5                                                                                                                       
+<                                                                                                                                                     
+</script>    [original] line 3                                                                                                                        
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+() => (<></>);
+return { props: {}, slots: {}, getters: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/let/test.jsx b/packages/svelte2tsx/test/sourcemaps/samples/let/test.jsx
new file mode 100644
index 00000000..d8ef486a
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/let/test.jsx
@@ -0,0 +1,8 @@
+/** tested-ranges: [[16,8,"selected"],[27,16,"lookup.get(slug)"]] */                                                                                  {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+    let  selected = __sveltets_invalidate(() => lookup.get(slug));                                                                                    {/**
+         1=======                               2===============    [generated] line 4                                                                 */}
+    $: selected = lookup.get(slug);                                                                                                                   {/**
+       1=======   2===============                                  [original] line 2                                                                 
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+/** origin-hash: 1rzn86d */
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/property-shorthand/input.svelte b/packages/svelte2tsx/test/sourcemaps/samples/property-shorthand/input.svelte
new file mode 100644
index 00000000..faa2b267
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/property-shorthand/input.svelte
@@ -0,0 +1,1 @@
+<button {count}>button</button>
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/property-shorthand/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/property-shorthand/mappings.jsx
new file mode 100644
index 00000000..4b38fa16
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/property-shorthand/mappings.jsx
@@ -0,0 +1,13 @@
+///<reference types="svelte" />
+<></>;function render() {                                                                                                                             {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+<><button count={count}>button</button></>                                                                                                            {/**
+=#                                             Originless mappings                                                                                    
+<><button•count={count}>button</button></>↲    [generated] line 3                                                                                     
+  <button•      {count}>button</button>                                                                                                               
+<button•{count}>button</button>                [original] line 1                                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+return { props: {}, slots: {}, getters: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/property-shorthand/test.jsx b/packages/svelte2tsx/test/sourcemaps/samples/property-shorthand/test.jsx
new file mode 100644
index 00000000..d6f1c4f5
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/property-shorthand/test.jsx
@@ -0,0 +1,8 @@
+/** tested-ranges: [[1,6,"button"],[8,7,"{count}"]] */                                                                                                {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+<><button count={count}>button</button></>                                                                                                            {/**
+   1=====       2======                     [generated] line 3                                                                                         */}
+<button {count}>button</button>                                                                                                                       {/**
+ 1===== 2======                             [original] line 1                                                                                         
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+/** origin-hash: 1gpiqzz */
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/simple-element/input.svelte b/packages/svelte2tsx/test/sourcemaps/samples/simple-element/input.svelte
new file mode 100644
index 00000000..9a1eb8d2
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/simple-element/input.svelte
@@ -0,0 +1,1 @@
+<h1>Hello World</h1>
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/simple-element/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/simple-element/mappings.jsx
new file mode 100644
index 00000000..6247d376
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/simple-element/mappings.jsx
@@ -0,0 +1,13 @@
+///<reference types="svelte" />
+<></>;function render() {                                                                                                                             {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+<><h1>Hello World</h1></>                                                                                                                             {/**
+=#                            Originless mappings                                                                                                     
+<><h1>Hello•World</h1></>↲    [generated] line 3                                                                                                      
+  <h1>Hello•World</h1>                                                                                                                                
+<h1>Hello•World</h1>          [original] line 1                                                                                                       
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+return { props: {}, slots: {}, getters: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/simple-element/test.jsx b/packages/svelte2tsx/test/sourcemaps/samples/simple-element/test.jsx
new file mode 100644
index 00000000..08ff0479
--- /dev/null
+++ b/packages/svelte2tsx/test/sourcemaps/samples/simple-element/test.jsx
@@ -0,0 +1,8 @@
+/** tested-ranges: [[0,20,"<h1>Hello World</h1>"]] */                                                                                                 {/**
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+<><h1>Hello World</h1></>                                                                                                                             {/**
+  #===================     [generated] line 3                                                                                                          */}
+<h1>Hello World</h1>                                                                                                                                  {/**
+#===================       [original] line 1                                                                                                          
+------------------------------------------------------------------------------------------------------------------------------------------------------ */}
+/** origin-hash: 1km0uiy */
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/shorthand-prop.htmlx.html b/packages/svelte2tsx/test/sourcemaps/shorthand-prop.htmlx.html
deleted file mode 100644
index 373a5c94..00000000
--- a/packages/svelte2tsx/test/sourcemaps/shorthand-prop.htmlx.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<><button count={count}>button</button></>
-   2=====       1======  
-!Expected
-<button {count}>button</button>
- 2===== 1======
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/simple-element.htmlx.html b/packages/svelte2tsx/test/sourcemaps/simple-element.htmlx.html
deleted file mode 100644
index 5c2e3382..00000000
--- a/packages/svelte2tsx/test/sourcemaps/simple-element.htmlx.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<><h1>Hello World</h1>
-  1===================
-</>
-!Expected
-<h1>Hello World</h1>
-1===================
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/typeof-$store/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/typeof-$store/expected.tsx
new file mode 100644
index 00000000..6e406174
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/typeof-$store/expected.tsx
@@ -0,0 +1,14 @@
+///<reference types="svelte" />
+<></>;
+import {writable} from "svelte/store";
+function render() {
+
+	
+	const foo = writable(1)/*Ωignore_startΩ*/;let $foo = __sveltets_store_get(foo);/*Ωignore_endΩ*/;
+	type Foo = typeof $foo;
+;
+() => (<></>);
+return { props: {}, slots: {}, getters: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/typeof-$store/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/typeof-$store/input.svelte
new file mode 100644
index 00000000..5bf160f8
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/typeof-$store/input.svelte
@@ -0,0 +1,5 @@
+<script lang="ts">
+	import {writable} from "svelte/store";
+	const foo = writable(1);
+	type Foo = typeof $foo;
+</script>
\ No newline at end of file