Repository: sveltejs/language-tools
Pull request #1939 (2023-03-15 10:16:25)

(feat) fix-all quick fix
#1885. I might still do some refactoring later. But I made many changes, so opening it as a draft for now as a heads-up for potential conflict. 

There are two parts to this PR. First is supporting the combined quick for TypeScript. And Second part is to add our "import component" and "import store" fix on top of it. Handler is skipped since TypeScript 5.0 should have full support, and our custom code would only be backup for non-VSCode users still using < 5.0.

~~About the custom quick fix. I reuse the logic in the usual quick fix. Except we ask TypeScript for the full diagnostics first as the source. We take the first one from every name. Then remove the empty line in between, and wrap the script tag if needed. The caveat is that the "which is first" logic Typescript used is way more complex, our custom imports need to be sorted, and those from the same module need to be combined into one import. This logic is very complex, so I am leaning toward not doing anything if we can't find a workaround to let TypeScript do its magic.~~

Edit:
The implementation of the customs import fix has changed. Now I create a copy of the file with the declared name of the store and component variables at the end of the script tags. This way, we don't have to deal with the "which is first" and import organization problems. Performance-wise, It's sometimes slower than the custom solution because TypeScript will rebuild and invalidate the cache. But it's more consistent. The original method might be very slow if there are a lot of missing imports.  


Patch:
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 5f22dba6..a71fed5b 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -12,7 +12,8 @@
             "outFiles": ["${workspaceRoot}/packages/svelte-vscode/dist/**/*.js"],
             "preLaunchTask": "npm: watch",
             "env": {
-                "TSS_DEBUG": "5859"
+                "TSS_DEBUG": "5859",
+                "TSS_REMOTE_DEBUG": "5859"
             }
         },
         {
diff --git a/packages/language-server/src/plugins/PluginHost.ts b/packages/language-server/src/plugins/PluginHost.ts
index 22f121a4..c0bededf 100644
--- a/packages/language-server/src/plugins/PluginHost.ts
+++ b/packages/language-server/src/plugins/PluginHost.ts
@@ -349,6 +349,23 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
         );
     }
 
+    async resolveCodeAction(
+        textDocument: TextDocumentIdentifier,
+        codeAction: CodeAction,
+        cancellationToken: CancellationToken
+    ): Promise<CodeAction> {
+        const document = this.getDocument(textDocument.uri);
+
+        const result = await this.execute<CodeAction>(
+            'resolveCodeAction',
+            [document, codeAction, cancellationToken],
+            ExecuteMode.FirstNonNull,
+            'high'
+        );
+
+        return result ?? codeAction;
+    }
+
     async updateImports(fileRename: FileRename): Promise<WorkspaceEdit | null> {
         return await this.execute<WorkspaceEdit>(
             'updateImports',
diff --git a/packages/language-server/src/plugins/html/dataProvider.ts b/packages/language-server/src/plugins/html/dataProvider.ts
index 4f681901..46f360dd 100644
--- a/packages/language-server/src/plugins/html/dataProvider.ts
+++ b/packages/language-server/src/plugins/html/dataProvider.ts
@@ -43,6 +43,10 @@ const svelteAttributes: IAttributeData[] = [
         name: 'bind:textContent',
         description: 'Available when contenteditable=true'
     },
+    {
+        name: 'bind:innerText',
+        description: 'Available when contenteditable=true'
+    },
     {
         name: 'bind:clientWidth',
         description: 'Available for block level elements. (read-only)'
@@ -303,6 +307,9 @@ const mediaAttributes: IAttributeData[] = [
     },
     {
         name: 'bind:muted'
+    },
+    {
+        name: 'bind:readyState'
     }
 ];
 const videoAttributes: IAttributeData[] = [
@@ -331,6 +338,7 @@ const addAttributes: Record<string, IAttributeData[]> = {
         indeterminateAttribute,
         { ...indeterminateAttribute, name: 'bind:indeterminate' }
     ],
+    img: [{ name: 'bind:naturalWidth' }, { name: 'bind:naturalHeight' }],
     textarea: [{ name: 'bind:value' }],
     video: [...mediaAttributes, ...videoAttributes],
     audio: [...mediaAttributes],
diff --git a/packages/language-server/src/plugins/interfaces.ts b/packages/language-server/src/plugins/interfaces.ts
index be384cd5..b95836c6 100644
--- a/packages/language-server/src/plugins/interfaces.ts
+++ b/packages/language-server/src/plugins/interfaces.ts
@@ -119,6 +119,12 @@ export interface CodeActionsProvider {
         command: string,
         args?: any[]
     ): Resolvable<WorkspaceEdit | string | null>;
+
+    resolveCodeAction?(
+        document: Document,
+        codeAction: CodeAction,
+        cancellationToken?: CancellationToken
+    ): Resolvable<CodeAction>;
 }
 
 export interface FileRename {
diff --git a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts
index 9e530c3c..5dbdf527 100644
--- a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts
+++ b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts
@@ -434,6 +434,14 @@ export class TypeScriptPlugin
         return this.codeActionsProvider.getCodeActions(document, range, context, cancellationToken);
     }
 
+    async resolveCodeAction(
+        document: Document,
+        codeAction: CodeAction,
+        cancellationToken?: CancellationToken | undefined
+    ): Promise<CodeAction> {
+        return this.codeActionsProvider.resolveCodeAction(document, codeAction, cancellationToken);
+    }
+
     async executeCommand(
         document: Document,
         command: string,
diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts
index c2c34600..d987c3a0 100644
--- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts
@@ -5,10 +5,12 @@ import {
     CodeActionContext,
     CodeActionKind,
     Diagnostic,
+    LSPAny,
     OptionalVersionedTextDocumentIdentifier,
     Position,
     Range,
     TextDocumentEdit,
+    TextDocumentIdentifier,
     TextEdit,
     WorkspaceEdit
 } from 'vscode-languageserver';
@@ -25,23 +27,34 @@ import {
     flatten,
     getIndent,
     isNotNullOrUndefined,
+    memoize,
     modifyLines,
+    normalizePath,
     pathToUrl,
     possiblyComponent
 } from '../../../utils';
 import { CodeActionsProvider } from '../../interfaces';
 import { DocumentSnapshot, SvelteDocumentSnapshot } from '../DocumentSnapshot';
 import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
-import { changeSvelteComponentName, convertRange, toGeneratedSvelteComponentName } from '../utils';
+import {
+    changeSvelteComponentName,
+    convertRange,
+    isInScript,
+    toGeneratedSvelteComponentName
+} from '../utils';
 import { CompletionsProviderImpl } from './CompletionProvider';
 import {
+    findClosestContainingNode,
     findContainingNode,
     FormatCodeBasis,
     getFormatCodeBasis,
+    getNewScriptStartTag,
     getQuotePreference,
     isTextSpanInGeneratedCode,
     SnapshotMap
 } from './utils';
+import { DiagnosticCode } from './DiagnosticsProvider';
+import { createGetCanonicalFileName } from '../../../utils';
 
 /**
  * TODO change this to protocol constant if it's part of the protocol
@@ -55,6 +68,32 @@ interface RefactorArgs {
     originalRange: Range;
 }
 
+interface CustomFixCannotFindNameInfo extends ts.CodeFixAction {
+    position: Position;
+}
+
+interface QuickFixConversionOptions {
+    fix: ts.CodeFixAction | CustomFixCannotFindNameInfo;
+    snapshots: SnapshotMap;
+    document: Document;
+    formatCodeSettings: ts.FormatCodeSettings;
+    formatCodeBasis: FormatCodeBasis;
+    getDiagnostics: () => Diagnostic[];
+    skipAddScriptTag?: boolean;
+}
+
+type FixId = NonNullable<ts.CodeFixAction['fixId']>;
+
+interface QuickFixAllResolveInfo extends TextDocumentIdentifier {
+    fixId: FixId;
+    fixName: string;
+}
+
+const FIX_IMPORT_FIX_NAME = 'import';
+const FIX_IMPORT_FIX_ID = 'fixMissingImport';
+const FIX_IMPORT_FIX_DESCRIPTION = 'Add all missing imports';
+const nonIdentifierRegex = /[\`\~\!\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>/\?\s]/;
+
 export class CodeActionsProviderImpl implements CodeActionsProvider {
     constructor(
         private readonly lsAndTsDocResolver: LSAndTSDocResolver,
@@ -107,6 +146,243 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
         return [];
     }
 
+    async resolveCodeAction(
+        document: Document,
+        codeAction: CodeAction,
+        cancellationToken?: CancellationToken | undefined
+    ): Promise<CodeAction> {
+        if (!this.isQuickFixAllResolveInfo(codeAction.data)) {
+            return codeAction;
+        }
+
+        const { lang, tsDoc, userPreferences } = await this.lsAndTsDocResolver.getLSAndTSDoc(
+            document
+        );
+        if (cancellationToken?.isCancellationRequested) {
+            return codeAction;
+        }
+
+        const formatCodeSettings = await this.configManager.getFormatCodeSettingsForFile(
+            document,
+            tsDoc.scriptKind
+        );
+        const formatCodeBasis = getFormatCodeBasis(formatCodeSettings);
+
+        const getDiagnostics = memoize(() =>
+            lang.getSemanticDiagnostics(tsDoc.filePath).map(
+                (dia): Diagnostic => ({
+                    range: mapRangeToOriginal(tsDoc, convertRange(tsDoc, dia)),
+                    message: '',
+                    code: dia.code
+                })
+            )
+        );
+
+        const isImportFix = codeAction.data.fixName === FIX_IMPORT_FIX_NAME;
+        const virtualDocInfo = isImportFix
+            ? await this.createVirtualDocumentForCombinedImportCodeFix(
+                  document,
+                  getDiagnostics(),
+                  tsDoc,
+                  lang
+              )
+            : undefined;
+
+        const fix = lang.getCombinedCodeFix(
+            {
+                type: 'file',
+                fileName: (virtualDocInfo?.virtualDoc ?? document).getFilePath()!
+            },
+            codeAction.data.fixId,
+            formatCodeSettings,
+            userPreferences
+        );
+
+        if (virtualDocInfo) {
+            const getCanonicalFileName = createGetCanonicalFileName(
+                ts.sys.useCaseSensitiveFileNames
+            );
+
+            const virtualDocPath = getCanonicalFileName(
+                normalizePath(virtualDocInfo.virtualDoc.getFilePath()!)
+            );
+
+            for (const change of fix.changes) {
+                if (getCanonicalFileName(normalizePath(change.fileName)) === virtualDocPath) {
+                    change.fileName = tsDoc.filePath;
+
+                    this.removeDuplicatedComponentImport(virtualDocInfo.insertedNames, change);
+                }
+            }
+
+            await this.lsAndTsDocResolver.deleteSnapshot(virtualDocPath);
+        }
+
+        const snapshots = new SnapshotMap(this.lsAndTsDocResolver);
+        const fixActions: ts.CodeFixAction[] = [
+            {
+                fixName: codeAction.data.fixName,
+                changes: Array.from(fix.changes),
+                description: ''
+            }
+        ];
+
+        const documentChangesPromises = fixActions.map((fix) =>
+            this.convertAndFixCodeFixAction({
+                document,
+                fix,
+                formatCodeBasis,
+                formatCodeSettings,
+                getDiagnostics,
+                snapshots,
+                skipAddScriptTag: true
+            })
+        );
+        const documentChanges = (await Promise.all(documentChangesPromises)).flat();
+
+        if (cancellationToken?.isCancellationRequested) {
+            return codeAction;
+        }
+
+        if (isImportFix) {
+            this.fixCombinedImportQuickFix(documentChanges, document, formatCodeBasis);
+        }
+
+        codeAction.edit = {
+            documentChanges
+        };
+
+        return codeAction;
+    }
+
+    /**
+     * Do not use this in regular code action
+     * This'll cause TypeScript to rebuild and invalidate caches every time. It'll be slow
+     */
+    private async createVirtualDocumentForCombinedImportCodeFix(
+        document: Document,
+        diagnostics: Diagnostic[],
+        tsDoc: DocumentSnapshot,
+        lang: ts.LanguageService
+    ) {
+        const virtualUri = document.uri + '.__virtual__.svelte';
+        const names = new Set<string>();
+        const sourceFile = lang.getProgram()?.getSourceFile(tsDoc.filePath);
+        if (!sourceFile) {
+            return undefined;
+        }
+
+        for (const diagnostic of diagnostics) {
+            if (
+                diagnostic.range.start.line < 0 ||
+                diagnostic.range.end.line < 0 ||
+                diagnostic.code !== DiagnosticCode.CANNOT_FIND_NAME
+            ) {
+                continue;
+            }
+            const identifier = this.findIdentifierForDiagnostic(tsDoc, diagnostic, sourceFile);
+            const name = identifier?.text;
+            if (!name || names.has(name)) {
+                continue;
+            }
+
+            if (name.startsWith('$')) {
+                names.add(name.slice(1));
+            } else if (!isInScript(diagnostic.range.start, document)) {
+                if (this.isComponentStartTag(identifier)) {
+                    names.add(toGeneratedSvelteComponentName(name));
+                }
+            }
+        }
+
+        if (!names.size) {
+            return undefined;
+        }
+
+        const inserts = Array.from(names.values())
+            .map((name) => name + ';')
+            .join('');
+
+        // assumption: imports are always at the top of the script tag
+        // so these appends won't change the position of the edits
+        const text = document.getText();
+        const newText = document.scriptInfo
+            ? text.slice(0, document.scriptInfo.end) + inserts + text.slice(document.scriptInfo.end)
+            : `${document.getText()}<script>${inserts}</script>`;
+
+        const virtualDoc = new Document(virtualUri, newText);
+        // let typescript know about the virtual document
+        await this.lsAndTsDocResolver.getSnapshot(virtualDoc);
+
+        return {
+            virtualDoc: new Document(virtualUri, newText),
+            insertedNames: names
+        };
+    }
+
+    /**
+     * Remove component default import if there is a named import with the same name
+     * Usually happens with reexport or inheritance of component library
+     */
+    private removeDuplicatedComponentImport(
+        insertedNames: Set<string>,
+        change: ts.FileTextChanges
+    ) {
+        for (const name of insertedNames) {
+            const unSuffixedNames = changeSvelteComponentName(name);
+            const matchRegex = unSuffixedNames != name && this.toImportMemberRegex(unSuffixedNames);
+            if (
+                !matchRegex ||
+                !change.textChanges.some((textChange) => textChange.newText.match(matchRegex))
+            ) {
+                continue;
+            }
+
+            const importRegex = new RegExp(`\\s+import ${name} from ('|")(.*)('|");?\r?\n?`);
+            change.textChanges = change.textChanges
+                .map((textChange) => ({
+                    ...textChange,
+                    newText: textChange.newText.replace(importRegex, '')
+                }))
+                // in case there are replacements
+                .filter((change) => change.span.length || change.newText);
+        }
+    }
+
+    private fixCombinedImportQuickFix(
+        documentChanges: TextDocumentEdit[],
+        document: Document,
+        formatCodeBasis: FormatCodeBasis
+    ) {
+        if (!documentChanges.length || document.scriptInfo || document.moduleScriptInfo) {
+            return;
+        }
+
+        const editForThisFile = documentChanges.find(
+            (change) => change.textDocument.uri === document.uri
+        );
+
+        if (editForThisFile?.edits.length) {
+            const [first] = editForThisFile.edits;
+            first.newText =
+                getNewScriptStartTag(this.configManager.getConfig()) +
+                formatCodeBasis.baseIndent +
+                first.newText.trimStart();
+
+            const last = editForThisFile.edits[editForThisFile.edits.length - 1];
+            last.newText = last.newText + '</script>' + formatCodeBasis.newLine;
+        }
+    }
+
+    private toImportMemberRegex(name: string) {
+        return new RegExp(`${name}($| |,)`);
+    }
+
+    private isQuickFixAllResolveInfo(data: LSPAny): data is QuickFixAllResolveInfo {
+        const asserted = data as QuickFixAllResolveInfo | undefined;
+        return asserted?.fixId != undefined && typeof asserted.fixName === 'string';
+    }
+
     private async organizeImports(
         document: Document,
         cancellationToken: CancellationToken | undefined,
@@ -264,9 +540,9 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
         const start = tsDoc.offsetAt(tsDoc.getGeneratedPosition(range.start));
         const end = tsDoc.offsetAt(tsDoc.getGeneratedPosition(range.end));
         const errorCodes: number[] = context.diagnostics.map((diag) => Number(diag.code));
-        const cannotFoundNameDiagnostic = context.diagnostics.filter(
-            (diagnostic) => diagnostic.code === 2304
-        ); // "Cannot find name '...'."
+        const cannotFindNameDiagnostic = context.diagnostics.filter(
+            (diagnostic) => diagnostic.code === DiagnosticCode.CANNOT_FIND_NAME
+        );
 
         const formatCodeSettings = await this.configManager.getFormatCodeSettingsForFile(
             document,
@@ -274,269 +550,447 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
         );
         const formatCodeBasis = getFormatCodeBasis(formatCodeSettings);
 
-        let codeFixes = cannotFoundNameDiagnostic.length
-            ? this.getComponentImportQuickFix(
-                  start,
-                  end,
-                  lang,
-                  tsDoc,
-                  userPreferences,
-                  cannotFoundNameDiagnostic,
-                  formatCodeSettings
-              )
-            : undefined;
+        let codeFixes: Array<CustomFixCannotFindNameInfo | ts.CodeFixAction> | undefined =
+            cannotFindNameDiagnostic.length
+                ? this.getComponentImportQuickFix(
+                      document,
+                      lang,
+                      tsDoc,
+                      userPreferences,
+                      cannotFindNameDiagnostic,
+                      formatCodeSettings
+                  )
+                : undefined;
         codeFixes =
             // either-or situation
-            codeFixes ||
-            lang
-                .getCodeFixesAtPosition(
+            codeFixes || [
+                ...lang.getCodeFixesAtPosition(
                     tsDoc.filePath,
                     start,
                     end,
                     errorCodes,
                     formatCodeSettings,
                     userPreferences
+                ),
+                ...this.getSvelteQuickFixes(
+                    lang,
+                    document,
+                    cannotFindNameDiagnostic,
+                    tsDoc,
+                    formatCodeBasis,
+                    userPreferences,
+                    formatCodeSettings
                 )
-                .concat(
-                    await this.getSvelteQuickFixes(
-                        lang,
-                        document,
-                        cannotFoundNameDiagnostic,
-                        tsDoc,
-                        formatCodeBasis,
-                        userPreferences
-                    )
-                );
+            ];
 
         const snapshots = new SnapshotMap(this.lsAndTsDocResolver);
         snapshots.set(tsDoc.filePath, tsDoc);
 
         const codeActionsPromises = codeFixes.map(async (fix) => {
-            const documentChangesPromises = fix.changes.map(async (change) => {
-                const snapshot = await snapshots.retrieve(change.fileName);
-                return TextDocumentEdit.create(
-                    OptionalVersionedTextDocumentIdentifier.create(
-                        pathToUrl(change.fileName),
-                        null
-                    ),
-                    change.textChanges
-                        .map((edit) => {
-                            if (
-                                fix.fixName === 'import' &&
-                                snapshot instanceof SvelteDocumentSnapshot
-                            ) {
-                                return this.completionProvider.codeActionChangeToTextEdit(
-                                    document,
-                                    snapshot,
-                                    edit,
-                                    true,
-                                    range.start
-                                );
-                            }
+            const documentChanges = await this.convertAndFixCodeFixAction({
+                fix,
+                snapshots,
+                document,
+                formatCodeSettings,
+                formatCodeBasis,
+                getDiagnostics: () => context.diagnostics
+            });
 
-                            if (isTextSpanInGeneratedCode(snapshot.getFullText(), edit.span)) {
-                                return undefined;
-                            }
+            const codeAction = CodeAction.create(
+                fix.description,
+                {
+                    documentChanges
+                },
+                CodeActionKind.QuickFix
+            );
+
+            return {
+                fix,
+                codeAction
+            };
+        });
+
+        const identifier: TextDocumentIdentifier = {
+            uri: document.uri
+        };
+
+        const codeActions = await Promise.all(codeActionsPromises);
+        if (cancellationToken?.isCancellationRequested) {
+            return [];
+        }
+
+        const codeActionsNotFilteredOut = codeActions.filter(({ codeAction }) =>
+            codeAction.edit?.documentChanges?.every(
+                (change) => (<TextDocumentEdit>change).edits.length > 0
+            )
+        );
 
-                            let originalRange = mapRangeToOriginal(
+        const fixAllActions = this.getFixAllActions(
+            codeActionsNotFilteredOut.map(({ fix }) => fix),
+            identifier,
+            tsDoc.filePath,
+            lang
+        );
+
+        // filter out empty code action
+        return codeActionsNotFilteredOut.map(({ codeAction }) => codeAction).concat(fixAllActions);
+    }
+
+    private async convertAndFixCodeFixAction({
+        fix,
+        snapshots,
+        document,
+        formatCodeSettings,
+        formatCodeBasis,
+        getDiagnostics,
+        skipAddScriptTag
+    }: QuickFixConversionOptions) {
+        const documentChangesPromises = fix.changes.map(async (change) => {
+            const snapshot = await snapshots.retrieve(change.fileName);
+            return TextDocumentEdit.create(
+                OptionalVersionedTextDocumentIdentifier.create(pathToUrl(change.fileName), null),
+                change.textChanges
+                    .map((edit) => {
+                        if (
+                            fix.fixName === FIX_IMPORT_FIX_NAME &&
+                            snapshot instanceof SvelteDocumentSnapshot
+                        ) {
+                            const namePosition = 'position' in fix ? fix.position : undefined;
+                            const startPos =
+                                namePosition ??
+                                this.findDiagnosticForImportFix(document, edit, getDiagnostics())
+                                    ?.range?.start ??
+                                Position.create(0, 0);
+
+                            return this.completionProvider.codeActionChangeToTextEdit(
+                                document,
                                 snapshot,
-                                convertRange(snapshot, edit.span)
+                                edit,
+                                true,
+                                startPos,
+                                formatCodeBasis.newLine,
+                                undefined,
+                                skipAddScriptTag
                             );
+                        }
 
-                            if (fix.fixName === 'unusedIdentifier') {
-                                originalRange = this.checkRemoveImportCodeActionRange(
-                                    edit,
-                                    snapshot,
-                                    originalRange
-                                );
-                            }
+                        if (isTextSpanInGeneratedCode(snapshot.getFullText(), edit.span)) {
+                            return undefined;
+                        }
 
-                            if (fix.fixName === 'fixMissingFunctionDeclaration') {
-                                originalRange = this.checkEndOfFileCodeInsert(
-                                    originalRange,
-                                    range,
-                                    document
-                                );
-
-                                // ts doesn't add base indent to the first line
-                                if (formatCodeSettings.baseIndentSize) {
-                                    const emptyLine = formatCodeBasis.newLine.repeat(2);
-                                    edit.newText =
-                                        emptyLine +
-                                        formatCodeBasis.baseIndent +
-                                        edit.newText.trimLeft();
-                                }
-                            }
+                        let originalRange = mapRangeToOriginal(
+                            snapshot,
+                            convertRange(snapshot, edit.span)
+                        );
 
-                            if (fix.fixName === 'disableJsDiagnostics') {
-                                if (edit.newText.includes('ts-nocheck')) {
-                                    return this.checkTsNoCheckCodeInsert(document, edit);
-                                }
+                        if (fix.fixName === 'unusedIdentifier') {
+                            originalRange = this.checkRemoveImportCodeActionRange(
+                                edit,
+                                snapshot,
+                                originalRange
+                            );
+                        }
+
+                        if (fix.fixName === 'fixMissingFunctionDeclaration') {
+                            const position = 'position' in fix ? fix.position : undefined;
+                            const checkRange = position
+                                ? Range.create(position, position)
+                                : this.findDiagnosticForQuickFix(
+                                      document,
+                                      DiagnosticCode.CANNOT_FIND_NAME,
+                                      getDiagnostics(),
+                                      (possiblyIdentifier) => {
+                                          return edit.newText.includes(
+                                              'function ' + possiblyIdentifier + '('
+                                          );
+                                      }
+                                  )?.range;
+
+                            originalRange = this.checkEndOfFileCodeInsert(
+                                originalRange,
+                                checkRange,
+                                document
+                            );
 
-                                return this.checkDisableJsDiagnosticsCodeInsert(
-                                    originalRange,
-                                    document,
-                                    edit
-                                );
+                            // ts doesn't add base indent to the first line
+                            if (formatCodeSettings.baseIndentSize) {
+                                const emptyLine = formatCodeBasis.newLine.repeat(2);
+                                edit.newText =
+                                    emptyLine +
+                                    formatCodeBasis.baseIndent +
+                                    edit.newText.trimLeft();
                             }
+                        }
 
-                            if (fix.fixName === 'inferFromUsage') {
-                                originalRange = this.checkAddJsDocCodeActionRange(
-                                    snapshot,
-                                    originalRange,
-                                    document
-                                );
+                        if (fix.fixName === 'disableJsDiagnostics') {
+                            if (edit.newText.includes('ts-nocheck')) {
+                                return this.checkTsNoCheckCodeInsert(document, edit);
                             }
 
-                            if (fix.fixName === 'fixConvertConstToLet') {
-                                const offset = document.offsetAt(originalRange.start);
-                                const constOffset = document.getText().indexOf('const', offset);
-                                if (constOffset < 0) {
-                                    return undefined;
-                                }
-                                const beforeConst = document.getText().slice(0, constOffset);
-                                if (
-                                    beforeConst[beforeConst.length - 1] === '@' &&
-                                    beforeConst
-                                        .slice(0, beforeConst.length - 1)
-                                        .trimEnd()
-                                        .endsWith('{')
-                                ) {
-                                    return undefined;
-                                }
-                            }
+                            return this.checkDisableJsDiagnosticsCodeInsert(
+                                originalRange,
+                                document,
+                                edit
+                            );
+                        }
+
+                        if (fix.fixName === 'inferFromUsage') {
+                            originalRange = this.checkAddJsDocCodeActionRange(
+                                snapshot,
+                                originalRange,
+                                document
+                            );
+                        }
 
-                            if (originalRange.start.line < 0 || originalRange.end.line < 0) {
+                        if (fix.fixName === 'fixConvertConstToLet') {
+                            const offset = document.offsetAt(originalRange.start);
+                            const constOffset = document.getText().indexOf('const', offset);
+                            if (constOffset < 0) {
+                                return undefined;
+                            }
+                            const beforeConst = document.getText().slice(0, constOffset);
+                            if (
+                                beforeConst[beforeConst.length - 1] === '@' &&
+                                beforeConst
+                                    .slice(0, beforeConst.length - 1)
+                                    .trimEnd()
+                                    .endsWith('{')
+                            ) {
                                 return undefined;
                             }
+                        }
 
-                            return TextEdit.replace(originalRange, edit.newText);
-                        })
-                        .filter(isNotNullOrUndefined)
-                );
-            });
-            const documentChanges = await Promise.all(documentChangesPromises);
-            return CodeAction.create(
-                fix.description,
-                {
-                    documentChanges
-                },
-                CodeActionKind.QuickFix
+                        if (originalRange.start.line < 0 || originalRange.end.line < 0) {
+                            return undefined;
+                        }
+
+                        return TextEdit.replace(originalRange, edit.newText);
+                    })
+                    .filter(isNotNullOrUndefined)
             );
         });
+        const documentChanges = await Promise.all(documentChangesPromises);
+        return documentChanges;
+    }
 
-        const codeActions = await Promise.all(codeActionsPromises);
-
-        // filter out empty code action
-        return codeActions.filter((codeAction) =>
-            codeAction.edit?.documentChanges?.every(
-                (change) => (<TextDocumentEdit>change).edits.length > 0
-            )
+    private findDiagnosticForImportFix(
+        document: Document,
+        edit: ts.TextChange,
+        diagnostics: Diagnostic[]
+    ) {
+        return this.findDiagnosticForQuickFix(
+            document,
+            DiagnosticCode.CANNOT_FIND_NAME,
+            diagnostics,
+            (possibleIdentifier) =>
+                !nonIdentifierRegex.test(possibleIdentifier) &&
+                this.toImportMemberRegex(possibleIdentifier).test(edit.newText)
         );
     }
 
+    private findDiagnosticForQuickFix(
+        document: Document,
+        targetCode: number,
+        diagnostics: Diagnostic[],
+        match: (identifier: string) => boolean
+    ) {
+        const diagnostic = diagnostics.find((diagnostic) => {
+            if (diagnostic.code !== targetCode) {
+                return false;
+            }
+
+            const possibleIdentifier = document.getText(diagnostic.range);
+            if (possibleIdentifier) {
+                return match(possibleIdentifier);
+            }
+
+            return false;
+        });
+
+        return diagnostic;
+    }
+
+    private getFixAllActions(
+        codeFixes: readonly ts.CodeFixAction[],
+        identifier: TextDocumentIdentifier,
+        fileName: string,
+        lang: ts.LanguageService
+    ) {
+        const checkedFixIds = new Set<FixId>();
+        const fixAll: CodeAction[] = [];
+
+        for (const codeFix of codeFixes) {
+            if (!codeFix.fixId || !codeFix.fixAllDescription || checkedFixIds.has(codeFix.fixId)) {
+                continue;
+            }
+
+            // we have custom fix for import
+            // check it again if fix-all might be necessary
+            if (codeFix.fixName === FIX_IMPORT_FIX_NAME) {
+                const allCannotFindNameDiagnostics = lang
+                    .getSemanticDiagnostics(fileName)
+                    .filter((diagnostic) => diagnostic.code === DiagnosticCode.CANNOT_FIND_NAME);
+
+                if (allCannotFindNameDiagnostics.length < 2) {
+                    checkedFixIds.add(codeFix.fixId);
+                    continue;
+                }
+            }
+
+            const codeAction = CodeAction.create(
+                codeFix.fixAllDescription,
+                CodeActionKind.QuickFix
+            );
+
+            const data: QuickFixAllResolveInfo = {
+                ...identifier,
+                fixName: codeFix.fixName,
+                fixId: codeFix.fixId
+            };
+
+            codeAction.data = data;
+            checkedFixIds.add(codeFix.fixId);
+            fixAll.push(codeAction);
+        }
+
+        return fixAll;
+    }
+
     /**
      * import quick fix requires the symbol name to be the same as where it's defined.
      * But we have suffix on component default export to prevent conflict with
      * a local variable. So we use auto-import completion as a workaround here.
      */
     private getComponentImportQuickFix(
-        start: number,
-        end: number,
+        document: Document,
         lang: ts.LanguageService,
         tsDoc: DocumentSnapshot,
         userPreferences: ts.UserPreferences,
         diagnostics: Diagnostic[],
         formatCodeSetting: ts.FormatCodeSettings
-    ): readonly ts.CodeFixAction[] | undefined {
+    ): CustomFixCannotFindNameInfo[] | undefined {
         const sourceFile = lang.getProgram()?.getSourceFile(tsDoc.filePath);
 
         if (!sourceFile) {
             return;
         }
 
-        const node = findContainingNode(
-            sourceFile,
-            {
-                start,
-                length: end - start
-            },
-            (node): node is ts.JsxOpeningLikeElement | ts.JsxClosingElement | ts.Identifier =>
-                ts.isCallExpression(node.parent) &&
-                ts.isIdentifier(node.parent.expression) &&
-                node.parent.expression.text === '__sveltets_2_ensureComponent' &&
-                ts.isIdentifier(node)
-        );
+        const nameToPosition = new Map<string, number>();
 
-        if (!node) {
-            return;
-        }
+        for (const diagnostic of diagnostics) {
+            if (isInScript(diagnostic.range.start, document)) {
+                continue;
+            }
+            const possibleIdentifier = document.getText(diagnostic.range);
+            if (
+                !possibleIdentifier ||
+                !possiblyComponent(possibleIdentifier) ||
+                nameToPosition.has(possibleIdentifier)
+            ) {
+                continue;
+            }
 
-        const tagName = ts.isIdentifier(node) ? node : node.tagName;
-        const tagNameEnd = tagName.getEnd();
-        const tagNameEndOriginalPosition = tsDoc.offsetAt(
-            tsDoc.getOriginalPosition(tsDoc.positionAt(tagNameEnd))
-        );
-        const name = tagName.getText();
-        if (!possiblyComponent(name)) {
-            return;
-        }
+            const node = this.findIdentifierForDiagnostic(tsDoc, diagnostic, sourceFile);
+            if (!node || !this.isComponentStartTag(node)) {
+                return;
+            }
 
-        const hasDiagnosticForTag = diagnostics.some(
-            ({ range }) =>
-                tsDoc.offsetAt(range.start) <= tagNameEndOriginalPosition &&
-                tagNameEndOriginalPosition <= tsDoc.offsetAt(range.end)
-        );
+            const tagNameEnd = node.getEnd();
+            const name = node.getText();
 
-        if (!hasDiagnosticForTag) {
-            return;
+            if (possiblyComponent(name)) {
+                nameToPosition.set(name, tagNameEnd);
+            }
         }
 
-        const completion = lang.getCompletionsAtPosition(
-            tsDoc.filePath,
-            tagNameEnd,
-            userPreferences,
-            formatCodeSetting
-        );
-
-        if (!completion) {
+        if (!nameToPosition.size) {
             return;
         }
 
-        const suffixedName = toGeneratedSvelteComponentName(name);
-        const errorPreventingUserPreferences =
-            this.completionProvider.fixUserPreferencesForSvelteComponentImport(userPreferences);
+        const result: CustomFixCannotFindNameInfo[] = [];
+        for (const [name, position] of nameToPosition) {
+            const errorPreventingUserPreferences =
+                this.completionProvider.fixUserPreferencesForSvelteComponentImport(userPreferences);
 
-        const toFix = (c: ts.CompletionEntry) =>
-            lang
-                .getCompletionEntryDetails(
+            const resolvedCompletion = (c: ts.CompletionEntry) =>
+                lang.getCompletionEntryDetails(
                     tsDoc.filePath,
-                    end,
+                    position,
                     c.name,
                     formatCodeSetting,
                     c.source,
                     errorPreventingUserPreferences,
                     c.data
+                );
+
+            const toFix = (c: ts.CompletionEntryDetails) =>
+                c.codeActions?.map(
+                    (a): CustomFixCannotFindNameInfo => ({
+                        ...a,
+                        description: changeSvelteComponentName(a.description),
+                        fixName: FIX_IMPORT_FIX_NAME,
+                        fixId: FIX_IMPORT_FIX_ID,
+                        fixAllDescription: FIX_IMPORT_FIX_DESCRIPTION,
+                        position: originalPosition
+                    })
+                ) ?? [];
+
+            const completion = lang.getCompletionsAtPosition(
+                tsDoc.filePath,
+                position,
+                userPreferences,
+                formatCodeSetting
+            );
+
+            const entries = completion?.entries
+                .filter((c) => c.name === name || c.name === toGeneratedSvelteComponentName(name))
+                .map(resolvedCompletion)
+                .sort(
+                    (a, b) =>
+                        this.numberOfDirectorySeparators(
+                            ts.displayPartsToString(a?.sourceDisplay ?? [])
+                        ) -
+                        this.numberOfDirectorySeparators(
+                            ts.displayPartsToString(b?.sourceDisplay ?? [])
+                        )
                 )
-                ?.codeActions?.map((a) => ({
-                    ...a,
-                    description: changeSvelteComponentName(a.description),
-                    fixName: 'import'
-                })) ?? [];
+                .filter(isNotNullOrUndefined);
 
-        return flatten(
-            completion.entries.filter((c) => c.name === name || c.name === suffixedName).map(toFix)
+            if (!entries?.length) {
+                continue;
+            }
+
+            const originalPosition = tsDoc.getOriginalPosition(tsDoc.positionAt(position));
+            const resultForName = entries.flatMap(toFix);
+
+            result.push(...resultForName);
+        }
+
+        return result;
+    }
+
+    private isComponentStartTag(node: ts.Identifier) {
+        return (
+            ts.isCallExpression(node.parent) &&
+            ts.isIdentifier(node.parent.expression) &&
+            node.parent.expression.text === '__sveltets_2_ensureComponent' &&
+            ts.isIdentifier(node)
         );
     }
 
-    private async getSvelteQuickFixes(
+    private numberOfDirectorySeparators(path: string) {
+        return path.split('/').length - 1;
+    }
+
+    private getSvelteQuickFixes(
         lang: ts.LanguageService,
         document: Document,
-        diagnostics: Diagnostic[],
+        cannotFindNameDiagnostics: Diagnostic[],
         tsDoc: DocumentSnapshot,
         formatCodeBasis: FormatCodeBasis,
-        userPreferences: ts.UserPreferences
-    ): Promise<ts.CodeFixAction[]> {
+        userPreferences: ts.UserPreferences,
+        formatCodeSettings: ts.FormatCodeSettings
+    ): CustomFixCannotFindNameInfo[] {
         const program = lang.getProgram();
         const sourceFile = program?.getSourceFile(tsDoc.filePath);
         if (!program || !sourceFile) {
@@ -544,44 +998,41 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
         }
 
         const typeChecker = program.getTypeChecker();
-        const results: ts.CodeFixAction[] = [];
+        const results: CustomFixCannotFindNameInfo[] = [];
         const quote = getQuotePreference(sourceFile, userPreferences);
+        const getGlobalCompletion = memoize(() =>
+            lang.getCompletionsAtPosition(tsDoc.filePath, 0, userPreferences, formatCodeSettings)
+        );
 
-        for (const diagnostic of diagnostics) {
-            const start = tsDoc.offsetAt(tsDoc.getGeneratedPosition(diagnostic.range.start));
-            const end = tsDoc.offsetAt(tsDoc.getGeneratedPosition(diagnostic.range.end));
-
-            const identifier = findContainingNode(
-                sourceFile,
-                { start, length: end - start },
-                ts.isIdentifier
-            );
+        for (const diagnostic of cannotFindNameDiagnostics) {
+            const identifier = this.findIdentifierForDiagnostic(tsDoc, diagnostic, sourceFile);
 
             if (!identifier) {
                 continue;
             }
 
-            const isQuickFixTargetTargetStore =
-                identifier?.escapedText.toString().startsWith('$') && diagnostic.code === 2304;
+            const isQuickFixTargetTargetStore = identifier?.escapedText.toString().startsWith('$');
             const isQuickFixTargetEventHandler = this.isQuickFixForEventHandler(
                 document,
                 diagnostic
             );
 
+            const fixes: ts.CodeFixAction[] = [];
             if (isQuickFixTargetTargetStore) {
-                results.push(
-                    ...(await this.getSvelteStoreQuickFixes(
+                fixes.push(
+                    ...this.getSvelteStoreQuickFixes(
                         identifier,
                         lang,
-                        document,
                         tsDoc,
-                        userPreferences
-                    ))
+                        userPreferences,
+                        formatCodeSettings,
+                        getGlobalCompletion
+                    )
                 );
             }
 
             if (isQuickFixTargetEventHandler) {
-                results.push(
+                fixes.push(
                     ...this.getEventHandlerQuickFixes(
                         identifier,
                         tsDoc,
@@ -591,29 +1042,51 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
                     )
                 );
             }
+
+            if (!fixes.length) {
+                continue;
+            }
+
+            const originalPosition = tsDoc.getOriginalPosition(tsDoc.positionAt(identifier.pos));
+            results.push(
+                ...fixes.map((fix) => ({
+                    name: identifier.getText(),
+                    position: originalPosition,
+                    ...fix
+                }))
+            );
         }
 
         return results;
     }
 
-    private async getSvelteStoreQuickFixes(
+    private findIdentifierForDiagnostic(
+        tsDoc: DocumentSnapshot,
+        diagnostic: Diagnostic,
+        sourceFile: ts.SourceFile
+    ) {
+        const start = tsDoc.offsetAt(tsDoc.getGeneratedPosition(diagnostic.range.start));
+        const end = tsDoc.offsetAt(tsDoc.getGeneratedPosition(diagnostic.range.end));
+
+        const identifier = findClosestContainingNode(
+            sourceFile,
+            { start, length: end - start },
+            ts.isIdentifier
+        );
+
+        return identifier;
+    }
+
+    private getSvelteStoreQuickFixes(
         identifier: ts.Identifier,
         lang: ts.LanguageService,
-        document: Document,
         tsDoc: DocumentSnapshot,
-        userPreferences: ts.UserPreferences
-    ): Promise<ts.CodeFixAction[]> {
+        userPreferences: ts.UserPreferences,
+        formatCodeSettings: ts.FormatCodeSettings,
+        getCompletions: () => ts.CompletionInfo | undefined
+    ): ts.CodeFixAction[] {
         const storeIdentifier = identifier.escapedText.toString().substring(1);
-        const formatCodeSettings = await this.configManager.getFormatCodeSettingsForFile(
-            document,
-            tsDoc.scriptKind
-        );
-        const completion = lang.getCompletionsAtPosition(
-            tsDoc.filePath,
-            0,
-            userPreferences,
-            formatCodeSettings
-        );
+        const completion = getCompletions();
 
         if (!completion) {
             return [];
@@ -644,7 +1117,9 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
                             })
                         };
                     }),
-                    fixName: 'import'
+                    fixName: FIX_IMPORT_FIX_NAME,
+                    fixId: FIX_IMPORT_FIX_ID,
+                    fixAllDescription: FIX_IMPORT_FIX_DESCRIPTION
                 })) ?? [];
 
         return flatten(completion.entries.filter((c) => c.name === storeIdentifier).map(toFix));
@@ -901,9 +1376,16 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
      * Some refactorings place the new code at the end of svelte2tsx' render function,
      *  which is unmapped. In this case, add it to the end of the script tag ourselves.
      */
-    private checkEndOfFileCodeInsert(resultRange: Range, targetRange: Range, document: Document) {
+    private checkEndOfFileCodeInsert(
+        resultRange: Range,
+        targetRange: Range | undefined,
+        document: Document
+    ) {
         if (resultRange.start.line < 0 || resultRange.end.line < 0) {
-            if (isRangeInTag(targetRange, document.moduleScriptInfo)) {
+            if (
+                document.moduleScriptInfo &&
+                (!targetRange || isRangeInTag(targetRange, document.moduleScriptInfo))
+            ) {
                 return Range.create(
                     document.moduleScriptInfo.endPos,
                     document.moduleScriptInfo.endPos
@@ -915,6 +1397,9 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
             }
         }
 
+        // don't add script tag here because the code action is calculated
+        // when the file is treated as js
+        // but user might want a ts version of the code action
         return resultRange;
     }
 
diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts
index 8d46276e..bdfdd3f5 100644
--- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts
@@ -27,7 +27,13 @@ import {
 } from '../../../lib/documents';
 import { AttributeContext, getAttributeContextAtPosition } from '../../../lib/documents/parseHtml';
 import { LSConfigManager } from '../../../ls-config';
-import { flatten, getRegExpMatches, isNotNullOrUndefined, pathToUrl } from '../../../utils';
+import {
+    flatten,
+    getRegExpMatches,
+    isNotNullOrUndefined,
+    modifyLines,
+    pathToUrl
+} from '../../../utils';
 import { AppCompletionItem, AppCompletionList, CompletionsProvider } from '../../interfaces';
 import { ComponentInfoProvider, ComponentPartInfo } from '../ComponentInfoProvider';
 import { SvelteDocumentSnapshot } from '../DocumentSnapshot';
@@ -42,8 +48,9 @@ import {
 } from '../utils';
 import { getJsDocTemplateCompletion } from './getJsDocTemplateCompletion';
 import {
-    findContainingNode,
     getComponentAtPosition,
+    getFormatCodeBasis,
+    getNewScriptStartTag,
     isKitTypePath,
     isPartOfImportStatement
 } from './utils';
@@ -674,6 +681,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
         if (actions) {
             const edit: TextEdit[] = [];
 
+            const formatCodeBasis = getFormatCodeBasis(formatCodeOptions);
             for (const action of actions) {
                 for (const change of action.changes) {
                     edit.push(
@@ -683,6 +691,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
                             change,
                             isImport,
                             comp.position,
+                            formatCodeBasis.newLine,
                             is$typeImport
                         )
                     );
@@ -729,6 +738,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
         changes: ts.FileTextChanges,
         isImport: boolean,
         originalTriggerPosition: Position,
+        newLine: string,
         is$typeImport?: boolean
     ): TextEdit[] {
         return changes.textChanges.map((change) =>
@@ -738,6 +748,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
                 change,
                 isImport,
                 originalTriggerPosition,
+                newLine,
                 is$typeImport
             )
         );
@@ -749,23 +760,35 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
         change: ts.TextChange,
         isImport: boolean,
         originalTriggerPosition: Position,
-        is$typeImport?: boolean
+        newLine: string,
+        is$typeImport?: boolean,
+        isCombinedCodeAction?: boolean
     ): TextEdit {
-        change.newText = this.changeComponentImport(
-            change.newText,
-            isInScript(originalTriggerPosition, doc),
-            is$typeImport
-        );
+        change.newText = isCombinedCodeAction
+            ? modifyLines(change.newText, (line) =>
+                  this.fixImportNewText(
+                      line,
+                      isInScript(originalTriggerPosition, doc),
+                      is$typeImport
+                  )
+              )
+            : this.fixImportNewText(
+                  change.newText,
+                  isInScript(originalTriggerPosition, doc),
+                  is$typeImport
+              );
 
         const scriptTagInfo = snapshot.scriptInfo || snapshot.moduleScriptInfo;
+        // no script tag defined yet, add it.
         if (!scriptTagInfo) {
-            // no script tag defined yet, add it.
-            const lang = this.configManager.getConfig().svelte.defaultScriptLanguage;
-            const scriptLang = lang === 'none' ? '' : ` lang="${lang}"`;
+            if (isCombinedCodeAction) {
+                return TextEdit.insert(Position.create(0, 0), change.newText);
+            }
 
+            const config = this.configManager.getConfig();
             return TextEdit.replace(
                 beginOfDocumentRange,
-                `<script${scriptLang}>${ts.sys.newLine}${change.newText}</script>${ts.sys.newLine}`
+                `${getNewScriptStartTag(config)}${change.newText}</script>${newLine}`
             );
         }
 
@@ -809,7 +832,14 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
             !change.newText.startsWith('\r\n') &&
             !change.newText.startsWith('\n')
         ) {
-            change.newText = ts.sys.newLine + change.newText;
+            change.newText = newLine + change.newText;
+        }
+
+        const after = doc.getText().slice(doc.offsetAt(range.end));
+        // typescript add empty line after import when the generated ts file
+        // doesn't have new line at the start of the file
+        if (after.startsWith('\r\n') || after.startsWith('\n')) {
+            change.newText = change.newText.trimEnd() + newLine;
         }
 
         return TextEdit.replace(range, change.newText);
@@ -828,7 +858,7 @@ export class CompletionsProviderImpl implements CompletionsProvider<CompletionEn
         );
     }
 
-    private changeComponentImport(
+    private fixImportNewText(
         importText: string,
         actionTriggeredInScript: boolean,
         is$typeImport?: boolean
diff --git a/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts b/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts
index 17c6de64..b6986498 100644
--- a/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts
@@ -25,7 +25,7 @@ import { not, flatten, passMap, swapRangeStartEndIfNecessary, memoize } from '..
 import { LSConfigManager } from '../../../ls-config';
 import { isAttributeName, isEventHandler } from '../svelte-ast-utils';
 
-enum DiagnosticCode {
+export enum DiagnosticCode {
     MODIFIERS_CANNOT_APPEAR_HERE = 1184, // "Modifiers cannot appear here."
     USED_BEFORE_ASSIGNED = 2454, // "Variable '{0}' is used before being assigned."
     JSX_ELEMENT_DOES_NOT_SUPPORT_ATTRIBUTES = 2607, // "JSX element class does not support attributes because it does not have a '{0}' property."
@@ -40,7 +40,8 @@ enum DiagnosticCode {
     TYPE_X_NOT_ASSIGNABLE_TO_TYPE_Y = 2345, // "Argument of type '..' is not assignable to parameter of type '..'."
     MISSING_PROPS = 2739, // "Type '...' is missing the following properties from type '..': ..."
     MISSING_PROP = 2741, // "Property '..' is missing in type '..' but required in type '..'."
-    NO_OVERLOAD_MATCHES_CALL = 2769 // "No overload matches this call"
+    NO_OVERLOAD_MATCHES_CALL = 2769, // "No overload matches this call"
+    CANNOT_FIND_NAME = 2304 // "Cannot find name 'xxx'"
 }
 
 export class DiagnosticsProviderImpl implements DiagnosticsProvider {
diff --git a/packages/language-server/src/plugins/typescript/features/utils.ts b/packages/language-server/src/plugins/typescript/features/utils.ts
index 495de510..93f7270c 100644
--- a/packages/language-server/src/plugins/typescript/features/utils.ts
+++ b/packages/language-server/src/plugins/typescript/features/utils.ts
@@ -11,6 +11,7 @@ import { DocumentSnapshot, SvelteDocumentSnapshot } from '../DocumentSnapshot';
 import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
 import { or } from '../../../utils';
 import { FileMap } from '../../../lib/documents/fileCollection';
+import { LSConfig } from '../../../ls-config';
 
 type NodePredicate = (node: ts.Node) => boolean;
 
@@ -402,3 +403,9 @@ export function findChildOfKind(node: ts.Node, kind: ts.SyntaxKind): ts.Node | u
         }
     }
 }
+
+export function getNewScriptStartTag(lsConfig: Readonly<LSConfig>) {
+    const lang = lsConfig.svelte.defaultScriptLanguage;
+    const scriptLang = lang === 'none' ? '' : ` lang="${lang}"`;
+    return `<script${scriptLang}>${ts.sys.newLine}`;
+}
diff --git a/packages/language-server/src/plugins/typescript/svelte-sys.ts b/packages/language-server/src/plugins/typescript/svelte-sys.ts
index 2ff39c71..c615fc3f 100644
--- a/packages/language-server/src/plugins/typescript/svelte-sys.ts
+++ b/packages/language-server/src/plugins/typescript/svelte-sys.ts
@@ -25,7 +25,7 @@ export function createSvelteSys(
             return snapshot.getText(0, snapshot.getLength());
         },
         readDirectory(path, extensions, exclude, include, depth) {
-            const extensionsWithSvelte = (extensions ?? []).concat('.svelte');
+            const extensionsWithSvelte = extensions ? [...extensions, '.svelte'] : undefined;
 
             return tsSystem.readDirectory(path, extensionsWithSvelte, exclude, include, depth);
         },
diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts
index 1b09e530..7b82e581 100644
--- a/packages/language-server/src/server.ts
+++ b/packages/language-server/src/server.ts
@@ -253,7 +253,8 @@ export function startServer(options?: LSOptions) {
                                   evt.initializationOptions?.shouldFilterCodeActionKind
                                   ? (kind) => clientSupportedCodeActionKinds.includes(kind)
                                   : () => true
-                          )
+                          ),
+                          resolveProvider: true
                       }
                     : true,
                 executeCommandProvider: clientSupportApplyEditCommand
@@ -378,6 +379,10 @@ export function startServer(options?: LSOptions) {
             });
         }
     });
+    connection.onCodeActionResolve((codeAction, cancellationToken) => {
+        const data = codeAction.data as TextDocumentIdentifier;
+        return pluginHost.resolveCodeAction(data, codeAction, cancellationToken);
+    });
 
     connection.onCompletionResolve((completionItem, cancellationToken) => {
         const data = (completionItem as AppCompletionItem).data as TextDocumentIdentifier;
diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json
index 3511838b..4f6ed6d4 100644
--- a/packages/svelte2tsx/package.json
+++ b/packages/svelte2tsx/package.json
@@ -41,7 +41,7 @@
     },
     "peerDependencies": {
         "svelte": "^3.55",
-        "typescript": "^4.9.4"
+        "typescript": "^4.9.4 || ^5.0.0"
     },
     "scripts": {
         "build": "rollup -c",
diff --git a/packages/svelte2tsx/rollup.config.mjs b/packages/svelte2tsx/rollup.config.mjs
index 1105a86b..ccd67376 100644
--- a/packages/svelte2tsx/rollup.config.mjs
+++ b/packages/svelte2tsx/rollup.config.mjs
@@ -6,10 +6,15 @@ import builtins from 'builtin-modules';
 import fs from 'fs';
 import path from 'path';
 import { decode } from '@jridgewell/sourcemap-codec';
+import { createRequire } from 'module';
+import { fileURLToPath } from 'url'
 
 const DEV = !!process.env.ROLLUP_WATCH;
 
 function repl() {
+    const require = createRequire(import.meta.url);
+    const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
     require('ts-node').register({
         project: 'test/tsconfig.json',
         transpileOnly: true
diff --git a/packages/svelte2tsx/src/emitDts.ts b/packages/svelte2tsx/src/emitDts.ts
index 2c4e5360..98148825 100644
--- a/packages/svelte2tsx/src/emitDts.ts
+++ b/packages/svelte2tsx/src/emitDts.ts
@@ -176,6 +176,23 @@ async function createTsCompilerHost(options: any, svelteMap: SvelteMap) {
             return resolveModuleName(moduleName, containingFile, compilerOptions);
         });
     };
+    // @ts-expect-error remove once we bump dev dep to TS 5
+    host.resolveModuleNameLiterals = (
+        moduleLiterals,
+        containingFile,
+        _redirectedReference,
+        compilerOptions
+    ) => {
+        return moduleLiterals.map((moduleLiteral) => {
+            return {
+                resolvedModule: resolveModuleName(
+                    moduleLiteral.text,
+                    containingFile,
+                    compilerOptions
+                )
+            };
+        });
+    };
 
     function resolveModuleName(name: string, containingFile: string, compilerOptions: any) {
         // Delegate to the TS resolver first.
diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts
index 45bd5a7c..7f5d04f4 100644
--- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts
+++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts
@@ -14,7 +14,10 @@ const oneWayBindingAttributes: Set<string> = new Set([
     'seekable',
     'seeking',
     'played',
-    'ended'
+    'ended',
+    'readyState',
+    'naturalWidth',
+    'naturalHeight'
 ]);
 /**
  * List of all binding names that are transformed to sth like `binding = variable`.
diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
index d7939d19..be3c447e 100644
--- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
@@ -1,6 +1,6 @@
 import MagicString from 'magic-string';
 import { Node } from 'estree-walker';
-import * as ts from 'typescript';
+import ts from 'typescript';
 import { getBinaryAssignmentExpr, isNotPropertyNameOfImport, moveNode } from './utils/tsAst';
 import { ExportedNames, is$$PropsDeclaration } from './nodes/ExportedNames';
 import { ImplicitTopLevelNames } from './nodes/ImplicitTopLevelNames';
@@ -158,16 +158,13 @@ export function processInstanceScriptContent(
 
         if (ts.isFunctionDeclaration(node)) {
             exportedNames.handleExportFunctionOrClass(node);
-
-            pushScope();
-            onLeaveCallbacks.push(() => popScope());
         }
 
         if (ts.isClassDeclaration(node)) {
             exportedNames.handleExportFunctionOrClass(node);
         }
 
-        if (ts.isBlock(node) || ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
+        if (ts.isBlock(node) || ts.isFunctionLike(node)) {
             pushScope();
             onLeaveCallbacks.push(() => popScope());
         }
diff --git a/packages/typescript-plugin/src/language-service/host.ts b/packages/typescript-plugin/src/language-service/host.ts
new file mode 100644
index 00000000..03c74ed6
--- /dev/null
+++ b/packages/typescript-plugin/src/language-service/host.ts
@@ -0,0 +1,12 @@
+import type ts from 'typescript/lib/tsserverlibrary';
+
+export function decorateLanguageServiceHost(host: ts.LanguageServiceHost) {
+    const originalReadDirectory = host.readDirectory?.bind(host);
+    host.readDirectory = originalReadDirectory
+        ? (path, extensions, exclude, include, depth) => {
+              const extensionsWithSvelte = extensions ? [...extensions, '.svelte'] : undefined;
+
+              return originalReadDirectory(path, extensionsWithSvelte, exclude, include, depth);
+          }
+        : undefined;
+}
diff --git a/packages/typescript-plugin/src/language-service/index.ts b/packages/typescript-plugin/src/language-service/index.ts
index 9f7ab4f6..9ebc80d0 100644
--- a/packages/typescript-plugin/src/language-service/index.ts
+++ b/packages/typescript-plugin/src/language-service/index.ts
@@ -13,6 +13,7 @@ import { decorateGetImplementation } from './implementation';
 import { decorateInlayHints } from './inlay-hints';
 import { decorateRename } from './rename';
 import { decorateUpdateImports } from './update-imports';
+import { decorateLanguageServiceHost } from './host';
 
 const sveltePluginPatchSymbol = Symbol('sveltePluginPatchSymbol');
 
@@ -31,7 +32,9 @@ export function decorateLanguageService(
     // Decorate using a proxy so we can dynamically enable/disable method
     // patches depending on the enabled state of our config
     const proxy = new Proxy(ls, createProxyHandler(configManager));
+    decorateLanguageServiceHost(info.languageServiceHost);
     decorateLanguageServiceInner(proxy, snapshotManager, logger, info, typescript);
+
     return proxy;
 }
 
diff --git a/packages/typescript-plugin/src/language-service/sveltekit.ts b/packages/typescript-plugin/src/language-service/sveltekit.ts
index 2c6839cb..4d65b577 100644
--- a/packages/typescript-plugin/src/language-service/sveltekit.ts
+++ b/packages/typescript-plugin/src/language-service/sveltekit.ts
@@ -607,11 +607,14 @@ function getProxiedLanguageService(info: ts.server.PluginCreateInfo, ts: _ts, lo
 
         // needed for path auto completions
         readDirectory = originalLanguageServiceHost.readDirectory
-            ? (...args: any[]) => {
-                  return originalLanguageServiceHost.readDirectory!(
-                      // @ts-ignore
-                      ...args
-                  );
+            ? (...args: Parameters<NonNullable<ts.LanguageServiceHost['readDirectory']>>) => {
+                  return originalLanguageServiceHost.readDirectory!(...args);
+              }
+            : undefined;
+
+        getDirectories = originalLanguageServiceHost.getDirectories
+            ? (...args: Parameters<NonNullable<ts.LanguageServiceHost['getDirectories']>>) => {
+                  return originalLanguageServiceHost.getDirectories!(...args);
               }
             : undefined;
 
@@ -628,6 +631,23 @@ function getProxiedLanguageService(info: ts.server.PluginCreateInfo, ts: _ts, lo
                 originalLanguageServiceHost.fileExists(fileName)
             );
         }
+
+        getCancellationToken = originalLanguageServiceHost.getCancellationToken
+            ? () => originalLanguageServiceHost.getCancellationToken!()
+            : undefined;
+
+        getNewLine = originalLanguageServiceHost.getNewLine
+            ? () => originalLanguageServiceHost.getNewLine!()
+            : undefined;
+
+        useCaseSensitiveFileNames = originalLanguageServiceHost.useCaseSensitiveFileNames
+            ? () => originalLanguageServiceHost.useCaseSensitiveFileNames!()
+            : undefined;
+
+        realpath = originalLanguageServiceHost.realpath
+            ? (...args: Parameters<NonNullable<ts.LanguageServiceHost['realpath']>>) =>
+                  originalLanguageServiceHost.realpath!(...args)
+            : undefined;
     }
 
     // Ideally we'd create a full Proxy of the language service, but that seems to have cache issues


Test patch:
diff --git a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts
index 566f2919..252935c3 100644
--- a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts
+++ b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts
@@ -108,6 +108,15 @@ describe('CodeActionsProvider', () => {
                 },
                 kind: CodeActionKind.QuickFix,
                 title: "Remove unused declaration for: 'a'"
+            },
+            {
+                data: {
+                    fixId: 'unusedIdentifier_delete',
+                    fixName: 'unusedIdentifier',
+                    uri: getUri('codeactions.svelte')
+                },
+                kind: 'quickfix',
+                title: 'Delete all unused declarations'
             }
         ]);
     });
@@ -211,6 +220,15 @@ describe('CodeActionsProvider', () => {
                 },
                 kind: CodeActionKind.QuickFix,
                 title: "Add missing function declaration 'handleClick'"
+            },
+            {
+                data: {
+                    fixId: 'fixMissingFunctionDeclaration',
+                    fixName: 'fixMissingFunctionDeclaration',
+                    uri: getUri('codeactions.svelte')
+                },
+                kind: 'quickfix',
+                title: 'Add all missing function declarations'
             }
         ]);
     });
@@ -311,6 +329,15 @@ describe('CodeActionsProvider', () => {
                 },
                 kind: CodeActionKind.QuickFix,
                 title: "Add missing function declaration 'abc'"
+            },
+            {
+                data: {
+                    fixId: 'fixMissingFunctionDeclaration',
+                    fixName: 'fixMissingFunctionDeclaration',
+                    uri: getUri('codeactions.svelte')
+                },
+                kind: 'quickfix',
+                title: 'Add all missing function declarations'
             }
         ]);
     }
@@ -346,7 +373,7 @@ describe('CodeActionsProvider', () => {
                         {
                             edits: [
                                 {
-                                    newText: `\n${indent}import { blubb } from "../definitions";\n\n`,
+                                    newText: `\n${indent}import { blubb } from "../definitions";\n`,
                                     range: Range.create(
                                         Position.create(0, 8),
                                         Position.create(0, 8)
@@ -469,6 +496,15 @@ describe('CodeActionsProvider', () => {
 
                 kind: 'quickfix',
                 title: 'Disable checking for this file'
+            },
+            {
+                data: {
+                    fixId: 'disableJsDiagnostics',
+                    fixName: 'disableJsDiagnostics',
+                    uri: getUri('codeaction-checkJs-module.svelte')
+                },
+                kind: 'quickfix',
+                title: "Add '@ts-ignore' to all error messages"
             }
         ]);
     });
@@ -605,6 +641,15 @@ describe('CodeActionsProvider', () => {
                 },
                 kind: 'quickfix',
                 title: 'Add import from "../empty.svelte"'
+            },
+            {
+                data: {
+                    fixId: 'fixMissingImport',
+                    fixName: 'import',
+                    uri: getUri('codeactions.svelte')
+                },
+                kind: 'quickfix',
+                title: 'Add all missing imports'
             }
         ]);
     });
@@ -703,6 +748,15 @@ describe('CodeActionsProvider', () => {
                 },
                 kind: 'quickfix',
                 title: "Convert 'const' to 'let'"
+            },
+            {
+                data: {
+                    fixId: 'fixConvertConstToLet',
+                    fixName: 'fixConvertConstToLet',
+                    uri: getUri('codeaction-const-reassign.svelte')
+                },
+                kind: 'quickfix',
+                title: "Convert all 'const' to 'let'"
             }
         ]);
     });
@@ -729,6 +783,342 @@ describe('CodeActionsProvider', () => {
         assert.deepStrictEqual(codeActions, []);
     });
 
+    it('provide quick fix to fix all errors when possible', async () => {
+        const { provider, document } = setup('codeactions.svelte');
+
+        const codeActions = await provider.getCodeActions(
+            document,
+            Range.create(Position.create(9, 0), Position.create(9, 3)),
+            {
+                diagnostics: [
+                    {
+                        code: 2304,
+                        message: "Cannot find name 'abc'.",
+                        range: Range.create(Position.create(9, 0), Position.create(9, 3)),
+                        source: 'ts'
+                    }
+                ],
+                only: [CodeActionKind.QuickFix]
+            }
+        );
+
+        const fixAll = codeActions.find((action) => action.data);
+        const resolvedFixAll = await provider.resolveCodeAction(document, fixAll!);
+
+        (<TextDocumentEdit>resolvedFixAll?.edit?.documentChanges?.[0])?.edits.forEach(
+            (edit) => (edit.newText = harmonizeNewLines(edit.newText))
+        );
+
+        assert.deepStrictEqual(resolvedFixAll.edit, {
+            documentChanges: [
+                {
+                    edits: [
+                        {
+                            newText:
+                                `\n\n${indent}function abc() {\n` +
+                                `${indent}${indent}throw new Error('Function not implemented.');\n` +
+                                `${indent}}\n`,
+                            range: {
+                                start: {
+                                    character: 0,
+                                    line: 10
+                                },
+                                end: {
+                                    character: 0,
+                                    line: 10
+                                }
+                            }
+                        },
+                        {
+                            newText:
+                                `\n\n${indent}function handleClick(e: MouseEvent & { currentTarget: EventTarget & HTMLButtonElement; }): any {\n` +
+                                `${indent}${indent}throw new Error('Function not implemented.');\n` +
+                                `${indent}}\n`,
+                            range: {
+                                start: {
+                                    character: 0,
+                                    line: 10
+                                },
+                                end: {
+                                    character: 0,
+                                    line: 10
+                                }
+                            }
+                        }
+                    ],
+                    textDocument: {
+                        uri: getUri('codeactions.svelte'),
+                        version: null
+                    }
+                }
+            ]
+        });
+    });
+
+    it('provide quick fix to fix all missing import component', async () => {
+        const { provider, document } = setup('codeaction-custom-fix-all-component.svelte');
+
+        const range = Range.create(Position.create(4, 1), Position.create(4, 15));
+        const codeActions = await provider.getCodeActions(document, range, {
+            diagnostics: [
+                {
+                    code: 2304,
+                    message: "Cannot find name 'FixAllImported'.",
+                    range: range,
+                    source: 'ts'
+                }
+            ],
+            only: [CodeActionKind.QuickFix]
+        });
+
+        const fixAll = codeActions.find((action) => action.data);
+        const resolvedFixAll = await provider.resolveCodeAction(document, fixAll!);
+
+        (<TextDocumentEdit>resolvedFixAll?.edit?.documentChanges?.[0])?.edits.forEach(
+            (edit) => (edit.newText = harmonizeNewLines(edit.newText))
+        );
+
+        assert.deepStrictEqual(resolvedFixAll.edit, {
+            documentChanges: [
+                {
+                    edits: [
+                        {
+                            newText:
+                                `\n${indent}import FixAllImported from \"./importing/FixAllImported.svelte\";\n` +
+                                `${indent}import FixAllImported2 from \"./importing/FixAllImported2.svelte\";\n`,
+                            range: {
+                                start: {
+                                    character: 18,
+                                    line: 0
+                                },
+                                end: {
+                                    character: 18,
+                                    line: 0
+                                }
+                            }
+                        }
+                    ],
+                    textDocument: {
+                        uri: getUri('codeaction-custom-fix-all-component.svelte'),
+                        version: null
+                    }
+                }
+            ]
+        });
+    });
+
+    it('provide quick fix to fix all missing import component without duplicate (script)', async () => {
+        const { provider, document } = setup('codeaction-custom-fix-all-component2.svelte');
+
+        const range = Range.create(Position.create(2, 4), Position.create(2, 19));
+        const codeActions = await provider.getCodeActions(document, range, {
+            diagnostics: [
+                {
+                    code: 2304,
+                    message: "Cannot find name 'FixAllImported3'.",
+                    range: range,
+                    source: 'ts'
+                }
+            ],
+            only: [CodeActionKind.QuickFix]
+        });
+
+        const fixAll = codeActions.find((action) => action.data);
+        const resolvedFixAll = await provider.resolveCodeAction(document, fixAll!);
+
+        (<TextDocumentEdit>resolvedFixAll?.edit?.documentChanges?.[0])?.edits.forEach(
+            (edit) => (edit.newText = harmonizeNewLines(edit.newText))
+        );
+
+        assert.deepStrictEqual(resolvedFixAll.edit, {
+            documentChanges: [
+                {
+                    edits: [
+                        {
+                            newText:
+                                `\n${indent}import { FixAllImported3 } from \"./importing/c\";\n` +
+                                `${indent}import FixAllImported2 from \"./importing/FixAllImported2.svelte\";\n`,
+                            range: {
+                                start: {
+                                    character: 18,
+                                    line: 0
+                                },
+                                end: {
+                                    character: 18,
+                                    line: 0
+                                }
+                            }
+                        }
+                    ],
+                    textDocument: {
+                        uri: getUri('codeaction-custom-fix-all-component2.svelte'),
+                        version: null
+                    }
+                }
+            ]
+        });
+    });
+
+    it('provide quick fix to fix all missing import component without duplicate (template)', async () => {
+        const { provider, document } = setup('codeaction-custom-fix-all-component3.svelte');
+
+        const range = Range.create(Position.create(4, 1), Position.create(4, 16));
+        const codeActions = await provider.getCodeActions(document, range, {
+            diagnostics: [
+                {
+                    code: 2304,
+                    message: "Cannot find name 'FixAllImported3'.",
+                    range: range,
+                    source: 'ts'
+                }
+            ],
+            only: [CodeActionKind.QuickFix]
+        });
+
+        const fixAll = codeActions.find((action) => action.data);
+        const resolvedFixAll = await provider.resolveCodeAction(document, fixAll!);
+
+        (<TextDocumentEdit>resolvedFixAll?.edit?.documentChanges?.[0])?.edits.forEach(
+            (edit) => (edit.newText = harmonizeNewLines(edit.newText))
+        );
+
+        assert.deepStrictEqual(resolvedFixAll.edit, {
+            documentChanges: [
+                {
+                    edits: [
+                        {
+                            newText:
+                                `\n${indent}import { FixAllImported3 } from \"./importing/c\";\n` +
+                                `${indent}import FixAllImported2 from \"./importing/FixAllImported2.svelte\";\n`,
+                            range: {
+                                start: {
+                                    character: 18,
+                                    line: 0
+                                },
+                                end: {
+                                    character: 18,
+                                    line: 0
+                                }
+                            }
+                        }
+                    ],
+                    textDocument: {
+                        uri: getUri('codeaction-custom-fix-all-component3.svelte'),
+                        version: null
+                    }
+                }
+            ]
+        });
+    });
+
+    it('provide quick fix to fix all missing import stores', async () => {
+        const { provider, document } = setup('codeaction-custom-fix-all-store.svelte');
+
+        const range = Range.create(Position.create(1, 4), Position.create(1, 19));
+        const codeActions = await provider.getCodeActions(document, range, {
+            diagnostics: [
+                {
+                    code: 2304,
+                    message: "Cannot find name '$someOtherStore'.",
+                    range: range,
+                    source: 'ts'
+                }
+            ],
+            only: [CodeActionKind.QuickFix]
+        });
+
+        const fixAll = codeActions.find((action) => action.data);
+        const resolvedFixAll = await provider.resolveCodeAction(document, fixAll!);
+
+        (<TextDocumentEdit>resolvedFixAll?.edit?.documentChanges?.[0])?.edits.forEach(
+            (edit) => (edit.newText = harmonizeNewLines(edit.newText))
+        );
+
+        assert.deepStrictEqual(resolvedFixAll.edit, {
+            documentChanges: [
+                {
+                    edits: [
+                        {
+                            newText:
+                                `\n${indent}import { someStore } from \"./importing/a\";\n` +
+                                `${indent}import { someOtherStore } from \"./importing/b\";\n`,
+                            range: {
+                                start: {
+                                    character: 18,
+                                    line: 0
+                                },
+                                end: {
+                                    character: 18,
+                                    line: 0
+                                }
+                            }
+                        }
+                    ],
+                    textDocument: {
+                        uri: getUri('codeaction-custom-fix-all-store.svelte'),
+                        version: null
+                    }
+                }
+            ]
+        });
+    });
+
+    it('provide quick fix to fix all missing import component (without script tag)', async () => {
+        const { provider, document } = setup(
+            'check-js/codeaction-custom-fix-all-component3.svelte'
+        );
+
+        const range = Range.create(Position.create(0, 1), Position.create(0, 15));
+        const codeActions = await provider.getCodeActions(document, range, {
+            diagnostics: [
+                {
+                    code: 2304,
+                    message: "Cannot find name 'FixAllImported'.",
+                    range: range,
+                    source: 'js'
+                }
+            ],
+            only: [CodeActionKind.QuickFix]
+        });
+
+        const fixAll = codeActions.find((action) => action.data);
+        const resolvedFixAll = await provider.resolveCodeAction(document, fixAll!);
+
+        (<TextDocumentEdit>resolvedFixAll?.edit?.documentChanges?.[0])?.edits.forEach(
+            (edit) => (edit.newText = harmonizeNewLines(edit.newText))
+        );
+
+        assert.deepStrictEqual(resolvedFixAll.edit, {
+            documentChanges: [
+                {
+                    edits: [
+                        {
+                            newText:
+                                '<script>\n' +
+                                `${indent}import FixAllImported from \"./importing/FixAllImported.svelte\";\n` +
+                                `${indent}import FixAllImported2 from \"./importing/FixAllImported2.svelte\";\n\n` +
+                                '</script>\n',
+                            range: {
+                                start: {
+                                    character: 0,
+                                    line: 0
+                                },
+                                end: {
+                                    character: 0,
+                                    line: 0
+                                }
+                            }
+                        }
+                    ],
+                    textDocument: {
+                        uri: getUri('check-js/codeaction-custom-fix-all-component3.svelte'),
+                        version: null
+                    }
+                }
+            ]
+        });
+    });
+
     it('organizes imports', async () => {
         const { provider, document } = setup('codeactions.svelte');
 
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 a27b7f30..d0b8122a 100644
--- a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts
+++ b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts
@@ -628,7 +628,7 @@ describe('CompletionProviderImpl', () => {
         assert.strictEqual(
             harmonizeNewLines(additionalTextEdits![0]?.newText),
             // " instead of ' because VSCode uses " by default when there are no other imports indicating otherwise
-            `${newLine}${indent}import { blubb } from "../definitions";${newLine}${newLine}`
+            `${newLine}${indent}import { blubb } from "../definitions";${newLine}`
         );
 
         assert.deepEqual(
@@ -748,7 +748,7 @@ describe('CompletionProviderImpl', () => {
         assert.strictEqual(
             harmonizeNewLines(additionalTextEdits![0]?.newText),
             // " instead of ' because VSCode uses " by default when there are no other imports indicating otherwise
-            `${newLine}${indent}import { onMount } from "svelte";${newLine}${newLine}`
+            `${newLine}${indent}import { onMount } from "svelte";${newLine}`
         );
 
         assert.deepEqual(
@@ -772,7 +772,7 @@ describe('CompletionProviderImpl', () => {
         assert.strictEqual(
             harmonizeNewLines(additionalTextEdits![0]?.newText),
             // " instead of ' because VSCode uses " by default when there are no other imports indicating otherwise
-            `${newLine}${indent}import { onMount } from "svelte";${newLine}${newLine}`
+            `${newLine}${indent}import { onMount } from "svelte";${newLine}`
         );
 
         assert.deepEqual(
@@ -1284,7 +1284,7 @@ describe('CompletionProviderImpl', () => {
 
         assert.strictEqual(
             additionalTextEdits?.[0].newText,
-            `${newLine}${indent}import { ScndImport } from "./to-import";${newLine}${newLine}`
+            `${newLine}${indent}import { ScndImport } from "./to-import";${newLine}`
         );
     });
 
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/check-js/codeaction-custom-fix-all-component3.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/check-js/codeaction-custom-fix-all-component3.svelte
new file mode 100644
index 00000000..494c9766
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/check-js/codeaction-custom-fix-all-component3.svelte
@@ -0,0 +1,2 @@
+<FixAllImported />
+<FixAllImported2 />
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/check-js/importing/FixAllImported.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/check-js/importing/FixAllImported.svelte
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/check-js/importing/FixAllImported2.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/check-js/importing/FixAllImported2.svelte
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/check-js/tsconfig.json b/packages/language-server/test/plugins/typescript/testfiles/code-actions/check-js/tsconfig.json
new file mode 100644
index 00000000..c069b2e8
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/check-js/tsconfig.json
@@ -0,0 +1,10 @@
+{
+    "compilerOptions": {
+        /**
+          This is actually not needed, but makes the tests faster
+          because TS does not look up other types.
+        */
+        "types": ["svelte"],
+        "checkJs": true
+    }
+}
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component.svelte
new file mode 100644
index 00000000..fb0e383c
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component.svelte
@@ -0,0 +1,6 @@
+<script lang="ts">
+
+</script>
+
+<FixAllImported />
+<FixAllImported2 />
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component2.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component2.svelte
new file mode 100644
index 00000000..896a44d4
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component2.svelte
@@ -0,0 +1,6 @@
+<script lang="ts">
+
+    FixAllImported3
+</script>
+
+<FixAllImported2 />
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component3.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component3.svelte
new file mode 100644
index 00000000..6d52321b
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-component3.svelte
@@ -0,0 +1,6 @@
+<script lang="ts">
+
+</script>
+
+<FixAllImported2 />
+<FixAllImported3 ></FixAllImported3>
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-store.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-store.svelte
new file mode 100644
index 00000000..7cf50219
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-custom-fix-all-store.svelte
@@ -0,0 +1,4 @@
+<script lang="ts">
+    $someOtherStore;
+    $someStore;
+</script>
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/FixAllImported.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/FixAllImported.svelte
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/FixAllImported2.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/FixAllImported2.svelte
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/FixAllImported3.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/FixAllImported3.svelte
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/Nest/FixAllImported.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/Nest/FixAllImported.svelte
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/a.ts b/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/a.ts
new file mode 100644
index 00000000..cd7bf375
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/a.ts
@@ -0,0 +1,3 @@
+import { writable } from 'svelte/store';
+
+export const someStore = writable(1);
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/b.ts b/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/b.ts
new file mode 100644
index 00000000..dd8f011d
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/b.ts
@@ -0,0 +1,4 @@
+import { readable, writable } from 'svelte/store';
+
+export const someStore = readable(1);
+export const someOtherStore = writable(1);
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/c.ts b/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/c.ts
new file mode 100644
index 00000000..fe879ee7
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/importing/c.ts
@@ -0,0 +1,3 @@
+import { SvelteComponentTyped } from 'svelte';
+
+export class FixAllImported3 extends SvelteComponentTyped {}
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/function-scope/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/function-scope/expectedv2.ts
new file mode 100644
index 00000000..44b3cfd0
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/function-scope/expectedv2.ts
@@ -0,0 +1,19 @@
+///<reference types="svelte" />
+;function render() {
+
+    let callback = (id) => undefined;
+    class B {
+        constructor(id) { }
+
+        method(id) { }
+
+        set id(id) { }
+    }
+
+    let  { id } = __sveltets_2_invalidate(() => ({ id: '' }));
+;
+async () => {};
+return { props: /** @type {Record<string, never>} */ ({}), slots: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/function-scope/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/function-scope/input.svelte
new file mode 100644
index 00000000..8637ec30
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/function-scope/input.svelte
@@ -0,0 +1,12 @@
+<script>
+    let callback = (id) => undefined;
+    class B {
+        constructor(id) { }
+
+        method(id) { }
+
+        set id(id) { }
+    }
+
+    $: ({ id } = { id: '' });
+</script>
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-function-type-scope/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-function-type-scope/expectedv2.ts
new file mode 100644
index 00000000..e2f14bf2
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-function-type-scope/expectedv2.ts
@@ -0,0 +1,15 @@
+///<reference types="svelte" />
+;function render() {
+
+    let callback: (id: string) => void;
+    let callback2: {( id: string): void}
+    let constructor1: new (id: string) => { id: string };
+    let obj: { hi(id: string): void }
+
+    let  { id } = __sveltets_2_invalidate(() => ({ id: '' }));
+;
+async () => {};
+return { props: {} as Record<string, never>, slots: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_with_any_event(render())) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-function-type-scope/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-function-type-scope/input.svelte
new file mode 100644
index 00000000..80dd60d2
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-function-type-scope/input.svelte
@@ -0,0 +1,8 @@
+<script lang="ts">
+    let callback: (id: string) => void;
+    let callback2: {( id: string): void}
+    let constructor1: new (id: string) => { id: string };
+    let obj: { hi(id: string): void }
+
+    $: ({ id } = { id: '' });
+</script>