From bd5b867662a3de4f59e7052cababe615dceb919a Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Mon, 3 Feb 2020 12:09:26 -0800
Subject: [PATCH 1/8] Refactor fix-all-missing-imports to be reusable by other
 codefixes

---
 src/services/codefixes/importFixes.ts | 194 +++++++++++++++-----------
 src/services/completions.ts           |  20 +--
 src/services/utilities.ts             |   8 ++
 3 files changed, 124 insertions(+), 98 deletions(-)

diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts
index 64c9c93189842..2b8648f928309 100644
--- a/src/services/codefixes/importFixes.ts
+++ b/src/services/codefixes/importFixes.ts
@@ -24,90 +24,114 @@ namespace ts.codefix {
         },
         fixIds: [importFixId],
         getAllCodeActions: context => {
-            const { sourceFile, preferences } = context;
-
-            // Namespace fixes don't conflict, so just build a list.
-            const addToNamespace: FixUseNamespaceImport[] = [];
-            const importType: FixUseImportType[] = [];
-            // Keys are import clause node IDs.
-            const addToExisting = createMap<{ readonly importClause: ImportClause, defaultImport: string | undefined; readonly namedImports: string[], canUseTypeOnlyImport: boolean }>();
-            const newImports = createMap<Mutable<ImportsCollection>>();
-            let lastModuleSpecifier: string | undefined;
-
-            eachDiagnostic(context, errorCodes, diag => {
-                const info = getFixesInfo(context, diag.code, diag.start);
-                if (!info || !info.fixes.length) return;
-                const { fixes, symbolName } = info;
-
-                const fix = first(fixes);
-                switch (fix.kind) {
-                    case ImportFixKind.UseNamespace:
-                        addToNamespace.push(fix);
-                        break;
-                    case ImportFixKind.ImportType:
-                        importType.push(fix);
-                        break;
-                    case ImportFixKind.AddToExisting: {
-                        const { importClause, importKind, canUseTypeOnlyImport } = fix;
-                        const key = String(getNodeId(importClause));
-                        let entry = addToExisting.get(key);
-                        if (!entry) {
-                            addToExisting.set(key, entry = { importClause, defaultImport: undefined, namedImports: [], canUseTypeOnlyImport });
-                        }
-                        if (importKind === ImportKind.Named) {
-                            pushIfUnique(entry.namedImports, symbolName);
-                        }
-                        else {
-                            Debug.assert(entry.defaultImport === undefined || entry.defaultImport === symbolName, "(Add to Existing) Default import should be missing or match symbolName");
-                            entry.defaultImport = symbolName;
-                        }
-                        break;
+            const { sourceFile, program, preferences, host } = context;
+            const importAdder = createImportAdder(sourceFile, program, preferences, host);
+            eachDiagnostic(context, errorCodes, diag => importAdder.addImportFromDiagnostic(diag, context));
+            return createCombinedCodeActions(textChanges.ChangeTracker.with(context, importAdder.writeFixes));
+        },
+    });
+
+    export interface ImportAdder {
+        addImportFromDiagnostic: (diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) => void;
+        addImportFromExportedSymbol: (exportedSymbol: Symbol) => void;
+        writeFixes: (changeTracker: textChanges.ChangeTracker) => void;
+    }
+
+    export function createImportAdder(sourceFile: SourceFile, program: Program, preferences: UserPreferences, host: LanguageServiceHost): ImportAdder {
+        const compilerOptions = program.getCompilerOptions();
+        // Namespace fixes don't conflict, so just build a list.
+        const addToNamespace: FixUseNamespaceImport[] = [];
+        const importType: FixUseImportType[] = [];
+        // Keys are import clause node IDs.
+        const addToExisting = createMap<{ readonly importClause: ImportClause, defaultImport: string | undefined; readonly namedImports: string[], canUseTypeOnlyImport: boolean }>();
+        const newImports = createMap<Mutable<ImportsCollection>>();
+        let lastModuleSpecifier: string | undefined;
+        return { addImportFromDiagnostic, addImportFromExportedSymbol, writeFixes };
+
+        function addImportFromDiagnostic(diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) {
+            const info = getFixesInfo(context, diagnostic.code, diagnostic.start);
+            if (!info || !info.fixes.length) return;
+            addImport(info);
+        }
+
+        function addImportFromExportedSymbol(exportedSymbol: Symbol) {
+            const moduleSymbol = Debug.assertDefined(exportedSymbol.parent);
+            const symbolName = getNameForExportedSymbol(exportedSymbol, getEmitScriptTarget(compilerOptions));
+            const exportInfos = getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, sourceFile, compilerOptions, program.getTypeChecker(), program.getSourceFiles());
+            const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, host, preferences);
+            addImport({ fixes: [fix], symbolName });
+        }
+
+        function addImport(info: FixesInfo) {
+            const { fixes, symbolName } = info;
+            const fix = first(fixes);
+            switch (fix.kind) {
+                case ImportFixKind.UseNamespace:
+                    addToNamespace.push(fix);
+                    break;
+                case ImportFixKind.ImportType:
+                    importType.push(fix);
+                    break;
+                case ImportFixKind.AddToExisting: {
+                    const { importClause, importKind, canUseTypeOnlyImport } = fix;
+                    const key = String(getNodeId(importClause));
+                    let entry = addToExisting.get(key);
+                    if (!entry) {
+                        addToExisting.set(key, entry = { importClause, defaultImport: undefined, namedImports: [], canUseTypeOnlyImport });
                     }
-                    case ImportFixKind.AddNew: {
-                        const { moduleSpecifier, importKind } = fix;
-                        let entry = newImports.get(moduleSpecifier);
-                        if (!entry) {
-                            newImports.set(moduleSpecifier, entry = { defaultImport: undefined, namedImports: [], namespaceLikeImport: undefined });
-                            lastModuleSpecifier = moduleSpecifier;
-                        }
-                        switch (importKind) {
-                            case ImportKind.Default:
-                                Debug.assert(entry.defaultImport === undefined || entry.defaultImport === symbolName, "(Add new) Default import should be missing or match symbolName");
-                                entry.defaultImport = symbolName;
-                                break;
-                            case ImportKind.Named:
-                                pushIfUnique(entry.namedImports, symbolName);
-                                break;
-                            case ImportKind.Equals:
-                            case ImportKind.Namespace:
-                                Debug.assert(entry.namespaceLikeImport === undefined || entry.namespaceLikeImport.name === symbolName, "Namespacelike import shoudl be missing or match symbolName");
-                                entry.namespaceLikeImport = { importKind, name: symbolName };
-                                break;
-                        }
-                        break;
+                    if (importKind === ImportKind.Named) {
+                        pushIfUnique(entry.namedImports, symbolName);
                     }
-                    default:
-                        Debug.assertNever(fix, `fix wasn't never - got kind ${(fix as ImportFix).kind}`);
-                }
-            });
-
-            return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => {
-                const quotePreference = getQuotePreference(sourceFile, preferences);
-                for (const fix of addToNamespace) {
-                    addNamespaceQualifier(changes, sourceFile, fix);
+                    else {
+                        Debug.assert(entry.defaultImport === undefined || entry.defaultImport === symbolName, "(Add to Existing) Default import should be missing or match symbolName");
+                        entry.defaultImport = symbolName;
+                    }
+                    break;
                 }
-                for (const fix of importType) {
-                    addImportType(changes, sourceFile, fix, quotePreference);
+                case ImportFixKind.AddNew: {
+                    const { moduleSpecifier, importKind } = fix;
+                    let entry = newImports.get(moduleSpecifier);
+                    if (!entry) {
+                        newImports.set(moduleSpecifier, entry = { defaultImport: undefined, namedImports: [], namespaceLikeImport: undefined });
+                        lastModuleSpecifier = moduleSpecifier;
+                    }
+                    switch (importKind) {
+                        case ImportKind.Default:
+                            Debug.assert(entry.defaultImport === undefined || entry.defaultImport === symbolName, "(Add new) Default import should be missing or match symbolName");
+                            entry.defaultImport = symbolName;
+                            break;
+                        case ImportKind.Named:
+                            pushIfUnique(entry.namedImports, symbolName);
+                            break;
+                        case ImportKind.Equals:
+                        case ImportKind.Namespace:
+                            Debug.assert(entry.namespaceLikeImport === undefined || entry.namespaceLikeImport.name === symbolName, "Namespacelike import shoudl be missing or match symbolName");
+                            entry.namespaceLikeImport = { importKind, name: symbolName };
+                            break;
+                    }
+                    break;
                 }
-                addToExisting.forEach(({ importClause, defaultImport, namedImports, canUseTypeOnlyImport }) => {
-                    doAddExistingFix(changes, sourceFile, importClause, defaultImport, namedImports, canUseTypeOnlyImport);
-                });
-                newImports.forEach((imports, moduleSpecifier) => {
-                    addNewImports(changes, sourceFile, moduleSpecifier, quotePreference, imports, /*blankLineBetween*/ lastModuleSpecifier === moduleSpecifier);
-                });
-            }));
-        },
-    });
+                default:
+                    Debug.assertNever(fix, `fix wasn't never - got kind ${(fix as ImportFix).kind}`);
+            }
+        }
+
+        function writeFixes(changeTracker: textChanges.ChangeTracker) {
+            const quotePreference = getQuotePreference(sourceFile, preferences);
+            for (const fix of addToNamespace) {
+                addNamespaceQualifier(changeTracker, sourceFile, fix);
+            }
+            for (const fix of importType) {
+                addImportType(changeTracker, sourceFile, fix, quotePreference);
+            }
+            addToExisting.forEach(({ importClause, defaultImport, namedImports, canUseTypeOnlyImport }) => {
+                doAddExistingFix(changeTracker, sourceFile, importClause, defaultImport, namedImports, canUseTypeOnlyImport);
+            });
+            newImports.forEach((imports, moduleSpecifier) => {
+                addNewImports(changeTracker, sourceFile, moduleSpecifier, quotePreference, imports, /*blankLineBetween*/ lastModuleSpecifier === moduleSpecifier);
+            });
+        }
+    }
 
     // Sorted with the preferred fix coming first.
     const enum ImportFixKind { UseNamespace, ImportType, AddToExisting, AddNew }
@@ -168,13 +192,17 @@ namespace ts.codefix {
         preferences: UserPreferences,
     ): { readonly moduleSpecifier: string, readonly codeAction: CodeAction } {
         const exportInfos = getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, sourceFile, program.getCompilerOptions(), program.getTypeChecker(), program.getSourceFiles());
-        Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol), "Some exportInfo should match the specified moduleSymbol");
-        // We sort the best codefixes first, so taking `first` is best for completions.
         const moduleSpecifier = first(getNewImportInfos(program, sourceFile, position, exportInfos, host, preferences)).moduleSpecifier;
-        const fix = first(getFixForImport(exportInfos, symbolName, position, program, sourceFile, host, preferences));
+        const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, host, preferences);
         return { moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) };
     }
 
+    function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, symbolName: string, program: Program, position: number | undefined, host: LanguageServiceHost, preferences: UserPreferences) {
+        Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol), "Some exportInfo should match the specified moduleSymbol");
+        // We sort the best codefixes first, so taking `first` is best.
+        return first(getFixForImport(exportInfos, symbolName, position, program, sourceFile, host, preferences));
+    }
+
     function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction {
         return { description, changes, commands };
     }
diff --git a/src/services/completions.ts b/src/services/completions.ts
index fdbc01ce503df..e7e06cb7dfc71 100644
--- a/src/services/completions.ts
+++ b/src/services/completions.ts
@@ -40,8 +40,8 @@ namespace ts.Completions {
         return !!(origin.kind & SymbolOriginInfoKind.SymbolMember);
     }
 
-    function originIsExport(origin: SymbolOriginInfo): origin is SymbolOriginInfoExport {
-        return !!(origin.kind & SymbolOriginInfoKind.Export);
+    function originIsExport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport {
+        return !!(origin && origin.kind & SymbolOriginInfoKind.Export);
     }
 
     function originIsPromise(origin: SymbolOriginInfo): boolean {
@@ -559,16 +559,6 @@ namespace ts.Completions {
         }) || { type: "none" };
     }
 
-    function getSymbolName(symbol: Symbol, origin: SymbolOriginInfo | undefined, target: ScriptTarget): string {
-        return origin && originIsExport(origin) && (
-            (origin.isDefaultExport && symbol.escapedName === InternalSymbolName.Default) ||
-            (symbol.escapedName === InternalSymbolName.ExportEquals))
-            // Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase.
-            ? firstDefined(symbol.declarations, d => isExportAssignment(d) && isIdentifier(d.expression) ? d.expression.text : undefined)
-            || codefix.moduleSymbolToValidIdentifier(origin.moduleSymbol, target)
-            : symbol.name;
-    }
-
     export interface CompletionEntryIdentifier {
         name: string;
         source?: string;
@@ -671,7 +661,7 @@ namespace ts.Completions {
             exportedSymbol,
             moduleSymbol,
             sourceFile,
-            getSymbolName(symbol, symbolOriginInfo, compilerOptions.target!),
+            getNameForExportedSymbol(symbol, compilerOptions.target!),
             host,
             program,
             formatContext,
@@ -1632,7 +1622,7 @@ namespace ts.Completions {
                 const origin: SymbolOriginInfoExport = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport };
                 results.push({
                     symbol,
-                    symbolName: getSymbolName(symbol, origin, target),
+                    symbolName: getNameForExportedSymbol(symbol, target),
                     origin,
                     skipFilter,
                 });
@@ -2392,7 +2382,7 @@ namespace ts.Completions {
         origin: SymbolOriginInfo | undefined,
         kind: CompletionKind,
     ): CompletionEntryDisplayNameForSymbol | undefined {
-        const name = getSymbolName(symbol, origin, target);
+        const name = originIsExport(origin) ? getNameForExportedSymbol(symbol, target) : symbol.name;
         if (name === undefined
             // If the symbol is external module, don't show it in the completion list
             // (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there)
diff --git a/src/services/utilities.ts b/src/services/utilities.ts
index 655d6d890bad5..afde7fb6fead5 100644
--- a/src/services/utilities.ts
+++ b/src/services/utilities.ts
@@ -2748,5 +2748,13 @@ namespace ts {
         return isArray(valueOrArray) ? first(valueOrArray) : valueOrArray;
     }
 
+    export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget) {
+        if (symbol.escapedName === InternalSymbolName.ExportEquals || symbol.escapedName === InternalSymbolName.Default) {
+            return firstDefined(symbol.declarations, d => isExportAssignment(d) && isIdentifier(d.expression) ? d.expression.text : undefined)
+                || codefix.moduleSymbolToValidIdentifier(Debug.assertDefined(symbol.parent), scriptTarget);
+        }
+        return symbol.name;
+    }
+
     // #endregion
 }

From 1fa2a194ffe1bddcb12f56641d58b2def2670295 Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Mon, 3 Feb 2020 14:06:30 -0800
Subject: [PATCH 2/8] Migrate infer-from-usage to use ImportAdder

---
 src/services/codefixes/inferFromUsage.ts | 90 +++++++++++-------------
 1 file changed, 41 insertions(+), 49 deletions(-)

diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts
index 5718f592cb82a..6c637dd667a28 100644
--- a/src/services/codefixes/inferFromUsage.ts
+++ b/src/services/codefixes/inferFromUsage.ts
@@ -49,21 +49,21 @@ namespace ts.codefix {
     registerCodeFix({
         errorCodes,
         getCodeActions(context) {
-            const { sourceFile, program, span: { start }, errorCode, cancellationToken, host, formatContext, preferences } = context;
+            const { sourceFile, program, span: { start }, errorCode, cancellationToken, host, preferences } = context;
 
             const token = getTokenAtPosition(sourceFile, start);
-            let declaration!: Declaration | undefined;
-            const changes = textChanges.ChangeTracker.with(context, changes => { declaration = doChange(changes, sourceFile, token, errorCode, program, cancellationToken, /*markSeen*/ returnTrue, host, formatContext, preferences); });
+            let declaration: Declaration | undefined;
+            const changes = textChanges.ChangeTracker.with(context, changes => { declaration = doChange(changes, sourceFile, token, errorCode, program, cancellationToken, /*markSeen*/ returnTrue, host, preferences); });
             const name = declaration && getNameOfDeclaration(declaration);
             return !name || changes.length === 0 ? undefined
                 : [createCodeFixAction(fixId, changes, [getDiagnostic(errorCode, token), name.getText(sourceFile)], fixId, Diagnostics.Infer_all_types_from_usage)];
         },
         fixIds: [fixId],
         getAllCodeActions(context) {
-            const { sourceFile, program, cancellationToken, host, formatContext, preferences } = context;
+            const { sourceFile, program, cancellationToken, host, preferences } = context;
             const markSeen = nodeSeenTracker();
             return codeFixAll(context, errorCodes, (changes, err) => {
-                doChange(changes, sourceFile, getTokenAtPosition(err.file, err.start), err.code, program, cancellationToken, markSeen, host, formatContext, preferences);
+                doChange(changes, sourceFile, getTokenAtPosition(err.file, err.start), err.code, program, cancellationToken, markSeen, host, preferences);
             });
         },
     });
@@ -106,19 +106,21 @@ namespace ts.codefix {
         return errorCode;
     }
 
-    function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker, host: LanguageServiceHost, formatContext: formatting.FormatContext, preferences: UserPreferences): Declaration | undefined {
+    function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker, host: LanguageServiceHost, preferences: UserPreferences): Declaration | undefined {
         if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken && token.kind !== SyntaxKind.ThisKeyword) {
             return undefined;
         }
 
         const { parent } = token;
+        const importAdder = createImportAdder(sourceFile, program, preferences, host);
         errorCode = mapSuggestionDiagnostic(errorCode);
         switch (errorCode) {
             // Variable and Property declarations
             case Diagnostics.Member_0_implicitly_has_an_1_type.code:
             case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code:
                 if ((isVariableDeclaration(parent) && markSeen(parent)) || isPropertyDeclaration(parent) || isPropertySignature(parent)) { // handle bad location
-                    annotateVariableDeclaration(changes, sourceFile, parent, program, host, cancellationToken, formatContext, preferences);
+                    annotateVariableDeclaration(changes, importAdder, sourceFile, parent, program, host, cancellationToken);
+                    importAdder.writeFixes(changes);
                     return parent;
                 }
                 if (isPropertyAccessExpression(parent)) {
@@ -129,6 +131,7 @@ namespace ts.codefix {
                         const typeTag = createJSDocTypeTag(createJSDocTypeExpression(typeNode), /*comment*/ "");
                         addJSDocTags(changes, sourceFile, cast(parent.parent.parent, isExpressionStatement), [typeTag]);
                     }
+                    importAdder.writeFixes(changes);
                     return parent;
                 }
                 return undefined;
@@ -136,7 +139,8 @@ namespace ts.codefix {
             case Diagnostics.Variable_0_implicitly_has_an_1_type.code: {
                 const symbol = program.getTypeChecker().getSymbolAtLocation(token);
                 if (symbol && symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && markSeen(symbol.valueDeclaration)) {
-                    annotateVariableDeclaration(changes, sourceFile, symbol.valueDeclaration, program, host, cancellationToken, formatContext, preferences);
+                    annotateVariableDeclaration(changes, importAdder, sourceFile, symbol.valueDeclaration, program, host, cancellationToken);
+                    importAdder.writeFixes(changes);
                     return symbol.valueDeclaration;
                 }
                 return undefined;
@@ -148,77 +152,80 @@ namespace ts.codefix {
             return undefined;
         }
 
+        let declaration: Declaration | undefined;
         switch (errorCode) {
             // Parameter declarations
             case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
                 if (isSetAccessorDeclaration(containingFunction)) {
-                    annotateSetAccessor(changes, sourceFile, containingFunction, program, host, cancellationToken, formatContext, preferences);
-                    return containingFunction;
+                    annotateSetAccessor(changes, importAdder, sourceFile, containingFunction, program, host, cancellationToken);
+                    declaration = containingFunction;
+                    break;
                 }
                 // falls through
             case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
                 if (markSeen(containingFunction)) {
                     const param = cast(parent, isParameter);
-                    annotateParameters(changes, sourceFile, param, containingFunction, program, host, cancellationToken, formatContext, preferences);
-                    return param;
+                    annotateParameters(changes, importAdder, sourceFile, param, containingFunction, program, host, cancellationToken);
+                    declaration = param;
                 }
-                return undefined;
+                break;
 
             // Get Accessor declarations
             case Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code:
             case Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code:
                 if (isGetAccessorDeclaration(containingFunction) && isIdentifier(containingFunction.name)) {
-                    annotate(changes, sourceFile, containingFunction, inferTypeForVariableFromUsage(containingFunction.name, program, cancellationToken), program, host, formatContext, preferences);
-                    return containingFunction;
+                    annotate(changes, importAdder, sourceFile, containingFunction, inferTypeForVariableFromUsage(containingFunction.name, program, cancellationToken), program, host);
+                    declaration = containingFunction;
                 }
-                return undefined;
+                break;
 
             // Set Accessor declarations
             case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code:
                 if (isSetAccessorDeclaration(containingFunction)) {
-                    annotateSetAccessor(changes, sourceFile, containingFunction, program, host, cancellationToken, formatContext, preferences);
-                    return containingFunction;
+                    annotateSetAccessor(changes, importAdder, sourceFile, containingFunction, program, host, cancellationToken);
+                    declaration = containingFunction;
                 }
-                return undefined;
+                break;
 
             // Function 'this'
             case Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation.code:
                 if (textChanges.isThisTypeAnnotatable(containingFunction) && markSeen(containingFunction)) {
                     annotateThis(changes, sourceFile, containingFunction, program, host, cancellationToken);
-                    return containingFunction;
+                    declaration = containingFunction;
                 }
-                return undefined;
+                break;
 
             default:
                 return Debug.fail(String(errorCode));
         }
+
+        importAdder.writeFixes(changes);
+        return declaration;
     }
 
     function annotateVariableDeclaration(
         changes: textChanges.ChangeTracker,
+        importAdder: ImportAdder,
         sourceFile: SourceFile,
         declaration: VariableDeclaration | PropertyDeclaration | PropertySignature,
         program: Program,
         host: LanguageServiceHost,
         cancellationToken: CancellationToken,
-        formatContext: formatting.FormatContext,
-        preferences: UserPreferences,
     ): void {
         if (isIdentifier(declaration.name)) {
-            annotate(changes, sourceFile, declaration, inferTypeForVariableFromUsage(declaration.name, program, cancellationToken), program, host, formatContext, preferences);
+            annotate(changes, importAdder, sourceFile, declaration, inferTypeForVariableFromUsage(declaration.name, program, cancellationToken), program, host);
         }
     }
 
     function annotateParameters(
         changes: textChanges.ChangeTracker,
+        importAdder: ImportAdder,
         sourceFile: SourceFile,
         parameterDeclaration: ParameterDeclaration,
         containingFunction: FunctionLike,
         program: Program,
         host: LanguageServiceHost,
         cancellationToken: CancellationToken,
-        formatContext: formatting.FormatContext,
-        preferences: UserPreferences,
     ): void {
         if (!isIdentifier(parameterDeclaration.name)) {
             return;
@@ -235,7 +242,7 @@ namespace ts.codefix {
             if (needParens) changes.insertNodeBefore(sourceFile, first(containingFunction.parameters), createToken(SyntaxKind.OpenParenToken));
             for (const { declaration, type } of parameterInferences) {
                 if (declaration && !declaration.type && !declaration.initializer) {
-                    annotate(changes, sourceFile, declaration, type, program, host, formatContext, preferences);
+                    annotate(changes, importAdder, sourceFile, declaration, type, program, host);
                 }
             }
             if (needParens) changes.insertNodeAfter(sourceFile, last(containingFunction.parameters), createToken(SyntaxKind.CloseParenToken));
@@ -269,13 +276,12 @@ namespace ts.codefix {
 
     function annotateSetAccessor(
         changes: textChanges.ChangeTracker,
+        importAdder: ImportAdder,
         sourceFile: SourceFile,
         setAccessorDeclaration: SetAccessorDeclaration,
         program: Program,
         host: LanguageServiceHost,
         cancellationToken: CancellationToken,
-        formatContext: formatting.FormatContext,
-        preferences: UserPreferences,
     ): void {
         const param = firstOrUndefined(setAccessorDeclaration.parameters);
         if (param && isIdentifier(setAccessorDeclaration.name) && isIdentifier(param.name)) {
@@ -287,12 +293,12 @@ namespace ts.codefix {
                 annotateJSDocParameters(changes, sourceFile, [{ declaration: param, type }], program, host);
             }
             else {
-                annotate(changes, sourceFile, param, type, program, host, formatContext, preferences);
+                annotate(changes, importAdder, sourceFile, param, type, program, host);
             }
         }
     }
 
-    function annotate(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type, program: Program, host: LanguageServiceHost, formatContext: formatting.FormatContext, preferences: UserPreferences): void {
+    function annotate(changes: textChanges.ChangeTracker, importAdder: ImportAdder, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type, program: Program, host: LanguageServiceHost): void {
         const typeNode = getTypeNodeIfAccessible(type, declaration, program, host);
         if (typeNode) {
             if (isInJSFile(sourceFile) && declaration.kind !== SyntaxKind.PropertySignature) {
@@ -304,35 +310,21 @@ namespace ts.codefix {
                 const typeTag = isGetAccessorDeclaration(declaration) ? createJSDocReturnTag(typeExpression, "") : createJSDocTypeTag(typeExpression, "");
                 addJSDocTags(changes, sourceFile, parent, [typeTag]);
             }
-            else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, changes, sourceFile, declaration, type, program, host, formatContext, preferences)) {
+            else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, changes, importAdder, sourceFile, declaration, type)) {
                 changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode);
             }
         }
     }
 
-    function tryReplaceImportTypeNodeWithAutoImport(typeNode: TypeNode, changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type, program: Program, host: LanguageServiceHost, formatContext: formatting.FormatContext, preferences: UserPreferences): boolean {
+    function tryReplaceImportTypeNodeWithAutoImport(typeNode: TypeNode, changes: textChanges.ChangeTracker, importAdder: ImportAdder, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type): boolean {
         if (isLiteralImportTypeNode(typeNode) && typeNode.qualifier && type.symbol) {
             // Replace 'import("./a").SomeType' with 'SomeType' and an actual import if possible
             const moduleSymbol = find(type.symbol.declarations, d => !!d.getSourceFile().externalModuleIndicator)?.getSourceFile().symbol;
             // Symbol for the left-most thing after the dot
             if (moduleSymbol) {
                 const symbol = getFirstIdentifier(typeNode.qualifier).symbol;
-                const action = getImportCompletionAction(
-                    symbol,
-                    moduleSymbol,
-                    sourceFile,
-                    symbol.name,
-                    host,
-                    program,
-                    formatContext,
-                    declaration.pos,
-                    preferences,
-                );
-                if (action.codeAction.changes.length && changes.tryInsertTypeAnnotation(sourceFile, declaration, createTypeReferenceNode(typeNode.qualifier, typeNode.typeArguments))) {
-                    for (const change of action.codeAction.changes) {
-                        const file = sourceFile.fileName === change.fileName ? sourceFile : Debug.assertDefined(program.getSourceFile(change.fileName));
-                        changes.pushRaw(file, change);
-                    }
+                if (changes.tryInsertTypeAnnotation(sourceFile, declaration, createTypeReferenceNode(typeNode.qualifier, typeNode.typeArguments))) {
+                    importAdder.addImportFromExportedSymbol(symbol);
                     return true;
                 }
             }

From 4ddfac6f4d4943ecc64669ee39cd5de4618551a3 Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Mon, 3 Feb 2020 14:10:47 -0800
Subject: [PATCH 3/8] Add infer from usage test importing more than one thing
 in a single fix

---
 .../codeFixInferFromUsageContextualImport4.ts | 30 +++++++++++++++++++
 1 file changed, 30 insertions(+)
 create mode 100644 tests/cases/fourslash/codeFixInferFromUsageContextualImport4.ts

diff --git a/tests/cases/fourslash/codeFixInferFromUsageContextualImport4.ts b/tests/cases/fourslash/codeFixInferFromUsageContextualImport4.ts
new file mode 100644
index 0000000000000..fb97c99ef314d
--- /dev/null
+++ b/tests/cases/fourslash/codeFixInferFromUsageContextualImport4.ts
@@ -0,0 +1,30 @@
+/// <reference path="fourslash.ts" />
+
+// @strict: true
+// @noImplicitAny: true
+// @noLib: true
+
+// @Filename: /types.d.ts
+////declare function getEmail(user: import('./a').User, settings: import('./a').Settings): string;
+
+// @Filename: /a.ts
+////export interface User {}
+////export interface Settings {}
+
+// @Filename: /b.ts
+////export function f([|user|], settings) {
+////    getEmail(user, settings);
+////}
+
+goTo.file("/b.ts");
+
+verify.codeFix({
+  index: 0,
+  description: "Infer parameter types from usage",
+  newFileContent:
+`import { User, Settings } from "./a";
+
+export function f(user: User, settings: Settings) {
+    getEmail(user, settings);
+}`
+});

From 3f44f02d01230ba225e964fab7aa5c40b760a9d8 Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Mon, 3 Feb 2020 17:32:26 -0800
Subject: [PATCH 4/8] Migrate implement interface / abstract members fixes to
 use ImportAdder

---
 ...sDoesntImplementInheritedAbstractMember.ts |   4 +-
 .../fixClassIncorrectlyImplementsInterface.ts |   4 +-
 src/services/codefixes/helpers.ts             | 124 +++++++++++++++---
 src/services/codefixes/importFixes.ts         |   4 +-
 src/services/codefixes/inferFromUsage.ts      |  19 +--
 ...eFixClassImplementInterfaceAutoImports1.ts |  40 ++++++
 6 files changed, 164 insertions(+), 31 deletions(-)
 create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports1.ts

diff --git a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts
index d18bc50639a85..a27ded9d403f8 100644
--- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts
+++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts
@@ -41,7 +41,9 @@ namespace ts.codefix {
         // so duplicates cannot occur.
         const abstractAndNonPrivateExtendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType).filter(symbolPointsToNonPrivateAndAbstractMember);
 
-        createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, context, preferences, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member));
+        const importAdder = createImportAdder(sourceFile, context.program, preferences, context.host);
+        createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, context, preferences, importAdder, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member));
+        importAdder.writeFixes(changeTracker);
     }
 
     function symbolPointsToNonPrivateAndAbstractMember(symbol: Symbol): boolean {
diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts
index 0735e85bc7004..58e5a137c6b5e 100644
--- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts
+++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts
@@ -63,7 +63,9 @@ namespace ts.codefix {
             createMissingIndexSignatureDeclaration(implementedType, IndexKind.String);
         }
 
-        createMissingMemberNodes(classDeclaration, nonPrivateAndNotExistedInHeritageClauseMembers, context, preferences, member => insertInterfaceMemberNode(sourceFile, classDeclaration, member));
+        const importAdder = createImportAdder(sourceFile, context.program, preferences, context.host);
+        createMissingMemberNodes(classDeclaration, nonPrivateAndNotExistedInHeritageClauseMembers, context, preferences, importAdder, member => insertInterfaceMemberNode(sourceFile, classDeclaration, member));
+        importAdder.writeFixes(changeTracker);
 
         function createMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind): void {
             const indexInfoOfKind = checker.getIndexInfoOfType(type, kind);
diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts
index b1893f2e93c24..36614a162a8ae 100644
--- a/src/services/codefixes/helpers.ts
+++ b/src/services/codefixes/helpers.ts
@@ -4,13 +4,14 @@ namespace ts.codefix {
      * Finds members of the resolved type that are missing in the class pointed to by class decl
      * and generates source code for the missing members.
      * @param possiblyMissingSymbols The collection of symbols to filter and then get insertions for.
+     * @param importAdder If provided, type annotations will use identifier type references instead of ImportTypeNodes, and the missing imports will be added to the importAdder.
      * @returns Empty string iff there are no member insertions.
      */
-    export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: readonly Symbol[], context: TypeConstructionContext, preferences: UserPreferences, out: (node: ClassElement) => void): void {
+    export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: readonly Symbol[], context: TypeConstructionContext, preferences: UserPreferences, importAdder: ImportAdder | undefined, addClassElement: (node: ClassElement) => void): void {
         const classMembers = classDeclaration.symbol.members!;
         for (const symbol of possiblyMissingSymbols) {
             if (!classMembers.has(symbol.escapedName)) {
-                addNewNodeForMemberSymbol(symbol, classDeclaration, context, preferences, out);
+                addNewNodeForMemberSymbol(symbol, classDeclaration, context, preferences, importAdder, addClassElement);
             }
         }
     }
@@ -19,7 +20,7 @@ namespace ts.codefix {
         return {
             directoryExists: context.host.directoryExists ? d => context.host.directoryExists!(d) : undefined,
             fileExists: context.host.fileExists ? f => context.host.fileExists!(f) : undefined,
-            getCurrentDirectory: context.host.getCurrentDirectory ? () => context.host.getCurrentDirectory!() : undefined,
+            getCurrentDirectory: context.host.getCurrentDirectory ? () => context.host.getCurrentDirectory() : undefined,
             readFile: context.host.readFile ? f => context.host.readFile!(f) : undefined,
             useCaseSensitiveFileNames: context.host.useCaseSensitiveFileNames ? () => context.host.useCaseSensitiveFileNames!() : undefined,
             getSourceFiles: () => context.program.getSourceFiles(),
@@ -36,19 +37,19 @@ namespace ts.codefix {
 
     export interface TypeConstructionContext {
         program: Program;
-        host: ModuleSpecifierResolutionHost;
+        host: LanguageServiceHost;
     }
 
     /**
      * @returns Empty string iff there we can't figure out a representation for `symbol` in `enclosingDeclaration`.
      */
-    function addNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, context: TypeConstructionContext, preferences: UserPreferences, out: (node: Node) => void): void {
+    function addNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, context: TypeConstructionContext, preferences: UserPreferences, importAdder: ImportAdder | undefined, addClassElement: (node: Node) => void): void {
         const declarations = symbol.getDeclarations();
         if (!(declarations && declarations.length)) {
             return undefined;
         }
         const checker = context.program.getTypeChecker();
-
+        const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions());
         const declaration = declarations[0];
         const name = getSynthesizedDeepClone(getNameOfDeclaration(declaration), /*includeTrivia*/ false) as PropertyName;
         const visibilityModifier = createVisibilityModifier(getModifierFlags(declaration));
@@ -61,8 +62,15 @@ namespace ts.codefix {
             case SyntaxKind.PropertySignature:
             case SyntaxKind.PropertyDeclaration:
                 const flags = preferences.quotePreference === "single" ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : undefined;
-                const typeNode = checker.typeToTypeNode(type, enclosingDeclaration, flags, getNoopSymbolTrackerWithResolver(context));
-                out(createProperty(
+                let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, flags, getNoopSymbolTrackerWithResolver(context));
+                if (importAdder) {
+                    const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget);
+                    if (importableReference) {
+                        typeNode = importableReference.typeReference;
+                        forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+                    }
+                }
+                addClassElement(createProperty(
                     /*decorators*/undefined,
                     modifiers,
                     name,
@@ -72,14 +80,21 @@ namespace ts.codefix {
                 break;
             case SyntaxKind.GetAccessor:
             case SyntaxKind.SetAccessor: {
+                let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context));
                 const allAccessors = getAllAccessorDeclarations(declarations, declaration as AccessorDeclaration);
-                const typeNode = checker.typeToTypeNode(type, enclosingDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context));
                 const orderedAccessors = allAccessors.secondAccessor
                     ? [allAccessors.firstAccessor, allAccessors.secondAccessor]
                     : [allAccessors.firstAccessor];
+                if (importAdder) {
+                    const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget);
+                    if (importableReference) {
+                        typeNode = importableReference.typeReference;
+                        forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+                    }
+                }
                 for (const accessor of orderedAccessors) {
                     if (isGetAccessorDeclaration(accessor)) {
-                        out(createGetAccessor(
+                        addClassElement(createGetAccessor(
                             /*decorators*/ undefined,
                             modifiers,
                             name,
@@ -91,7 +106,7 @@ namespace ts.codefix {
                         Debug.assertNode(accessor, isSetAccessorDeclaration, "The counterpart to a getter should be a setter");
                         const parameter = getSetAccessorValueParameter(accessor);
                         const parameterName = parameter && isIdentifier(parameter.name) ? idText(parameter.name) : undefined;
-                        out(createSetAccessor(
+                        addClassElement(createSetAccessor(
                             /*decorators*/ undefined,
                             modifiers,
                             name,
@@ -134,15 +149,15 @@ namespace ts.codefix {
                     }
                     else {
                         Debug.assert(declarations.length === signatures.length, "Declarations and signatures should match count");
-                        out(createMethodImplementingSignatures(signatures, name, optional, modifiers, preferences));
+                        addClassElement(createMethodImplementingSignatures(signatures, name, optional, modifiers, preferences));
                     }
                 }
                 break;
         }
 
         function outputMethod(signature: Signature, modifiers: NodeArray<Modifier> | undefined, name: PropertyName, body?: Block): void {
-            const method = signatureToMethodDeclaration(context, signature, enclosingDeclaration, modifiers, name, optional, body);
-            if (method) out(method);
+            const method = signatureToMethodDeclaration(context, signature, enclosingDeclaration, modifiers, name, optional, body, importAdder);
+            if (method) addClassElement(method);
         }
     }
 
@@ -154,13 +169,53 @@ namespace ts.codefix {
         name: PropertyName,
         optional: boolean,
         body: Block | undefined,
+        importAdder: ImportAdder | undefined,
     ): MethodDeclaration | undefined {
         const program = context.program;
-        const signatureDeclaration = <MethodDeclaration>program.getTypeChecker().signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.NoTruncation | NodeBuilderFlags.SuppressAnyReturnType, getNoopSymbolTrackerWithResolver(context));
+        const checker = program.getTypeChecker();
+        const scriptTarget = getEmitScriptTarget(program.getCompilerOptions());
+        const signatureDeclaration = <MethodDeclaration>checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.NoTruncation | NodeBuilderFlags.SuppressAnyReturnType, getNoopSymbolTrackerWithResolver(context));
         if (!signatureDeclaration) {
             return undefined;
         }
 
+        if (importAdder) {
+            if (signatureDeclaration.typeParameters) {
+                forEach(signatureDeclaration.typeParameters, (typeParameterDecl, i) => {
+                    const typeParameter = signature.typeParameters![i];
+                    if (typeParameterDecl.constraint) {
+                        const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeParameterDecl.constraint, typeParameter.constraint, scriptTarget);
+                        if (importableReference) {
+                            typeParameterDecl.constraint = importableReference.typeReference;
+                            forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+                        }
+                    }
+                    if (typeParameterDecl.default) {
+                        const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeParameterDecl.default, typeParameter.default, scriptTarget);
+                        if (importableReference) {
+                            typeParameterDecl.default = importableReference.typeReference;
+                            forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+                        }
+                    }
+                });
+            }
+            forEach(signatureDeclaration.parameters, (parameterDecl, i) => {
+                const parameter = signature.parameters[i];
+                const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(parameterDecl.type, checker.getTypeAtLocation(parameter.valueDeclaration), scriptTarget);
+                if (importableReference) {
+                    parameterDecl.type = importableReference.typeReference;
+                    forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+                }
+            });
+            if (signatureDeclaration.type) {
+                const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(signatureDeclaration.type, signature.resolvedReturnType, scriptTarget);
+                if (importableReference) {
+                    signatureDeclaration.type = importableReference.typeReference;
+                    forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+                }
+            }
+        }
+
         signatureDeclaration.decorators = undefined;
         signatureDeclaration.modifiers = modifiers;
         signatureDeclaration.name = name;
@@ -359,4 +414,43 @@ namespace ts.codefix {
     export function findJsonProperty(obj: ObjectLiteralExpression, name: string): PropertyAssignment | undefined {
         return find(obj.properties, (p): p is PropertyAssignment => isPropertyAssignment(p) && !!p.name && isStringLiteral(p.name) && p.name.text === name);
     }
+
+    export function tryGetAutoImportableReferenceFromImportTypeNode(importTypeNode: TypeNode | undefined, type: Type | undefined, scriptTarget: ScriptTarget) {
+        if (importTypeNode && isLiteralImportTypeNode(importTypeNode) && importTypeNode.qualifier && (!type || type.symbol)) {
+            // Replace 'import("./a").SomeType' with 'SomeType' and an actual import if possible
+            // Symbol for the left-most thing after the dot
+            const firstIdentifier = getFirstIdentifier(importTypeNode.qualifier);
+            const name = getNameForExportedSymbol(firstIdentifier.symbol, scriptTarget);
+            const qualifier = name !== firstIdentifier.text
+                ? replaceFirstIdentifierOfEntityName(importTypeNode.qualifier, createIdentifier(name))
+                : importTypeNode.qualifier;
+
+            const symbols = [firstIdentifier.symbol];
+            const typeArguments: TypeNode[] = [];
+            if (importTypeNode.typeArguments) {
+                importTypeNode.typeArguments.forEach(arg => {
+                    const ref = tryGetAutoImportableReferenceFromImportTypeNode(arg, /*undefined*/ type, scriptTarget);
+                    if (ref) {
+                        symbols.push(...ref.symbols);
+                        typeArguments.push(ref.typeReference);
+                    }
+                    else {
+                        typeArguments.push(arg);
+                    }
+                });
+            }
+
+            return {
+                symbols,
+                typeReference: createTypeReferenceNode(qualifier, typeArguments)
+            };
+        }
+    }
+
+    function replaceFirstIdentifierOfEntityName(name: EntityName, newIdentifier: Identifier): EntityName {
+        if (name.kind === SyntaxKind.Identifier) {
+            return newIdentifier;
+        }
+        return createQualifiedName(replaceFirstIdentifierOfEntityName(name.left, newIdentifier), name.right);
+    }
 }
diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts
index 2b8648f928309..8d1647502d844 100644
--- a/src/services/codefixes/importFixes.ts
+++ b/src/services/codefixes/importFixes.ts
@@ -57,7 +57,9 @@ namespace ts.codefix {
         function addImportFromExportedSymbol(exportedSymbol: Symbol) {
             const moduleSymbol = Debug.assertDefined(exportedSymbol.parent);
             const symbolName = getNameForExportedSymbol(exportedSymbol, getEmitScriptTarget(compilerOptions));
-            const exportInfos = getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, sourceFile, compilerOptions, program.getTypeChecker(), program.getSourceFiles());
+            const checker = program.getTypeChecker();
+            const symbol = checker.getMergedSymbol(skipAlias(exportedSymbol, checker));
+            const exportInfos = getAllReExportingModules(sourceFile, symbol, moduleSymbol, symbolName, sourceFile, compilerOptions, checker, program.getSourceFiles());
             const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, host, preferences);
             addImport({ fixes: [fix], symbolName });
         }
diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts
index 6c637dd667a28..fb9e885302aea 100644
--- a/src/services/codefixes/inferFromUsage.ts
+++ b/src/services/codefixes/inferFromUsage.ts
@@ -310,24 +310,17 @@ namespace ts.codefix {
                 const typeTag = isGetAccessorDeclaration(declaration) ? createJSDocReturnTag(typeExpression, "") : createJSDocTypeTag(typeExpression, "");
                 addJSDocTags(changes, sourceFile, parent, [typeTag]);
             }
-            else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, changes, importAdder, sourceFile, declaration, type)) {
+            else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, changes, importAdder, sourceFile, declaration, type, getEmitScriptTarget(program.getCompilerOptions()))) {
                 changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode);
             }
         }
     }
 
-    function tryReplaceImportTypeNodeWithAutoImport(typeNode: TypeNode, changes: textChanges.ChangeTracker, importAdder: ImportAdder, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type): boolean {
-        if (isLiteralImportTypeNode(typeNode) && typeNode.qualifier && type.symbol) {
-            // Replace 'import("./a").SomeType' with 'SomeType' and an actual import if possible
-            const moduleSymbol = find(type.symbol.declarations, d => !!d.getSourceFile().externalModuleIndicator)?.getSourceFile().symbol;
-            // Symbol for the left-most thing after the dot
-            if (moduleSymbol) {
-                const symbol = getFirstIdentifier(typeNode.qualifier).symbol;
-                if (changes.tryInsertTypeAnnotation(sourceFile, declaration, createTypeReferenceNode(typeNode.qualifier, typeNode.typeArguments))) {
-                    importAdder.addImportFromExportedSymbol(symbol);
-                    return true;
-                }
-            }
+    function tryReplaceImportTypeNodeWithAutoImport(typeNode: TypeNode, changes: textChanges.ChangeTracker, importAdder: ImportAdder, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type, scriptTarget: ScriptTarget): boolean {
+        const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget);
+        if (importableReference && changes.tryInsertTypeAnnotation(sourceFile, declaration, importableReference.typeReference)) {
+            forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+            return true;
         }
         return false;
     }
diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports1.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports1.ts
new file mode 100644
index 0000000000000..3a08ffa5e5a2a
--- /dev/null
+++ b/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports1.ts
@@ -0,0 +1,40 @@
+/// <reference path='fourslash.ts' />
+
+// @Filename: types1.ts
+////type A = {};
+////export default A;
+
+// @Filename: types2.ts
+////export type B = {};
+////export type C = {};
+////export type D<T> = {};
+
+// @Filename: interface.ts
+////import A from './types1';
+////import { B, C, D } from './types2';
+////
+////export interface Base {
+////  a: A;
+////  b<T extends B = B>(p1: C): D<C>;
+////}
+
+// @Filename: index.ts
+////import { Base } from './interface';
+////
+////export class C implements Base {[| |]}
+
+goTo.file('index.ts');
+verify.codeFix({
+  description: "Implement interface 'Base'",
+  newFileContent:
+`import { Base } from './interface';
+import A from './types1';
+import { B, C, D } from './types2';
+
+export class C implements Base {
+    a: A;
+    b<T extends B = B>(p1: C): D<C> {
+        throw new Error("Method not implemented.");
+    }
+}`,
+});

From 9d9f846905410764725c02bc64e18738cd2b2dfe Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Tue, 4 Feb 2020 10:28:43 -0800
Subject: [PATCH 5/8] Update old tests

---
 .../codeFixClassImplementInterface_typeInOtherFile.ts       | 6 +++---
 ...ixImplementInterfaceUnreachableTypeUsesRelativeImport.ts | 6 ++++--
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/tests/cases/fourslash/codeFixClassImplementInterface_typeInOtherFile.ts b/tests/cases/fourslash/codeFixClassImplementInterface_typeInOtherFile.ts
index ed5d29cd767aa..f1fb7d5f7ca03 100644
--- a/tests/cases/fourslash/codeFixClassImplementInterface_typeInOtherFile.ts
+++ b/tests/cases/fourslash/codeFixClassImplementInterface_typeInOtherFile.ts
@@ -15,10 +15,10 @@ goTo.file("/C.ts");
 verify.codeFix({
     description: "Implement interface 'I'",
     newFileContent:
-`import { I } from "./I";
+`import { I, J } from "./I";
 export class C implements I {
-    x: import("./I").J;
-    m(): import("./I").J {
+    x: J;
+    m(): J {
         throw new Error("Method not implemented.");
     }
 }`,
diff --git a/tests/cases/fourslash/quickfixImplementInterfaceUnreachableTypeUsesRelativeImport.ts b/tests/cases/fourslash/quickfixImplementInterfaceUnreachableTypeUsesRelativeImport.ts
index ba6cd702ea00f..5db9047035513 100644
--- a/tests/cases/fourslash/quickfixImplementInterfaceUnreachableTypeUsesRelativeImport.ts
+++ b/tests/cases/fourslash/quickfixImplementInterfaceUnreachableTypeUsesRelativeImport.ts
@@ -17,10 +17,12 @@ verify.codeFix({
     index: 0,
     description: "Implement interface 'Foo'",
     newFileContent: {
-        "/tests/cases/fourslash/index.ts": `import { Foo } from './interface';
+        "/tests/cases/fourslash/index.ts":
+`import { Foo } from './interface';
+import { Class } from './class';
 
 class X implements Foo {
-    x: import("./class").Class;
+    x: Class;
 }`
     }
 });

From 79d7068942c25b965825525e98d3fb62d8602abb Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Tue, 4 Feb 2020 12:05:43 -0800
Subject: [PATCH 6/8] Use type-only imports when it would be an error not to

---
 src/services/codefixes/helpers.ts             | 16 +++--
 src/services/codefixes/importFixes.ts         | 63 +++++++++++--------
 src/services/codefixes/inferFromUsage.ts      |  2 +-
 ...eFixClassImplementInterfaceAutoImports2.ts | 42 +++++++++++++
 .../fourslash/importNameCodeFix_typeOnly.ts   | 16 +++++
 5 files changed, 107 insertions(+), 32 deletions(-)
 create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports2.ts
 create mode 100644 tests/cases/fourslash/importNameCodeFix_typeOnly.ts

diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts
index 36614a162a8ae..73c67009c3e64 100644
--- a/src/services/codefixes/helpers.ts
+++ b/src/services/codefixes/helpers.ts
@@ -67,7 +67,7 @@ namespace ts.codefix {
                     const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget);
                     if (importableReference) {
                         typeNode = importableReference.typeReference;
-                        forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+                        importSymbols(importAdder, importableReference.symbols);
                     }
                 }
                 addClassElement(createProperty(
@@ -89,7 +89,7 @@ namespace ts.codefix {
                     const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget);
                     if (importableReference) {
                         typeNode = importableReference.typeReference;
-                        forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+                        importSymbols(importAdder, importableReference.symbols);
                     }
                 }
                 for (const accessor of orderedAccessors) {
@@ -187,14 +187,14 @@ namespace ts.codefix {
                         const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeParameterDecl.constraint, typeParameter.constraint, scriptTarget);
                         if (importableReference) {
                             typeParameterDecl.constraint = importableReference.typeReference;
-                            forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+                            importSymbols(importAdder, importableReference.symbols);
                         }
                     }
                     if (typeParameterDecl.default) {
                         const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeParameterDecl.default, typeParameter.default, scriptTarget);
                         if (importableReference) {
                             typeParameterDecl.default = importableReference.typeReference;
-                            forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+                            importSymbols(importAdder, importableReference.symbols);
                         }
                     }
                 });
@@ -204,14 +204,14 @@ namespace ts.codefix {
                 const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(parameterDecl.type, checker.getTypeAtLocation(parameter.valueDeclaration), scriptTarget);
                 if (importableReference) {
                     parameterDecl.type = importableReference.typeReference;
-                    forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+                    importSymbols(importAdder, importableReference.symbols);
                 }
             });
             if (signatureDeclaration.type) {
                 const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(signatureDeclaration.type, signature.resolvedReturnType, scriptTarget);
                 if (importableReference) {
                     signatureDeclaration.type = importableReference.typeReference;
-                    forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+                    importSymbols(importAdder, importableReference.symbols);
                 }
             }
         }
@@ -453,4 +453,8 @@ namespace ts.codefix {
         }
         return createQualifiedName(replaceFirstIdentifierOfEntityName(name.left, newIdentifier), name.right);
     }
+
+    function importSymbols(importAdder: ImportAdder, symbols: readonly Symbol[]) {
+        symbols.forEach(s => importAdder.addImportFromExportedSymbol(s, /*usageIsTypeOnly*/ true));
+    }
 }
diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts
index 8d1647502d844..d66bc414fa750 100644
--- a/src/services/codefixes/importFixes.ts
+++ b/src/services/codefixes/importFixes.ts
@@ -33,7 +33,7 @@ namespace ts.codefix {
 
     export interface ImportAdder {
         addImportFromDiagnostic: (diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) => void;
-        addImportFromExportedSymbol: (exportedSymbol: Symbol) => void;
+        addImportFromExportedSymbol: (exportedSymbol: Symbol, usageIsTypeOnly?: boolean) => void;
         writeFixes: (changeTracker: textChanges.ChangeTracker) => void;
     }
 
@@ -54,13 +54,14 @@ namespace ts.codefix {
             addImport(info);
         }
 
-        function addImportFromExportedSymbol(exportedSymbol: Symbol) {
+        function addImportFromExportedSymbol(exportedSymbol: Symbol, usageIsTypeOnly?: boolean) {
             const moduleSymbol = Debug.assertDefined(exportedSymbol.parent);
             const symbolName = getNameForExportedSymbol(exportedSymbol, getEmitScriptTarget(compilerOptions));
             const checker = program.getTypeChecker();
             const symbol = checker.getMergedSymbol(skipAlias(exportedSymbol, checker));
             const exportInfos = getAllReExportingModules(sourceFile, symbol, moduleSymbol, symbolName, sourceFile, compilerOptions, checker, program.getSourceFiles());
-            const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, host, preferences);
+            const preferTypeOnlyImport = !!usageIsTypeOnly && compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error;
+            const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, preferTypeOnlyImport, host, preferences);
             addImport({ fixes: [fix], symbolName });
         }
 
@@ -91,12 +92,16 @@ namespace ts.codefix {
                     break;
                 }
                 case ImportFixKind.AddNew: {
-                    const { moduleSpecifier, importKind } = fix;
+                    const { moduleSpecifier, importKind, typeOnly } = fix;
                     let entry = newImports.get(moduleSpecifier);
                     if (!entry) {
-                        newImports.set(moduleSpecifier, entry = { defaultImport: undefined, namedImports: [], namespaceLikeImport: undefined });
+                        newImports.set(moduleSpecifier, entry = { defaultImport: undefined, namedImports: [], namespaceLikeImport: undefined, typeOnly });
                         lastModuleSpecifier = moduleSpecifier;
                     }
+                    else {
+                        // An import clause can only be type-only if every import fix contributing to it can be type-only.
+                        entry.typeOnly = entry.typeOnly && typeOnly;
+                    }
                     switch (importKind) {
                         case ImportKind.Default:
                             Debug.assert(entry.defaultImport === undefined || entry.defaultImport === symbolName, "(Add new) Default import should be missing or match symbolName");
@@ -158,6 +163,7 @@ namespace ts.codefix {
         readonly kind: ImportFixKind.AddNew;
         readonly moduleSpecifier: string;
         readonly importKind: ImportKind;
+        readonly typeOnly: boolean;
     }
 
     const enum ImportKind {
@@ -193,16 +199,18 @@ namespace ts.codefix {
         position: number,
         preferences: UserPreferences,
     ): { readonly moduleSpecifier: string, readonly codeAction: CodeAction } {
-        const exportInfos = getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, sourceFile, program.getCompilerOptions(), program.getTypeChecker(), program.getSourceFiles());
-        const moduleSpecifier = first(getNewImportInfos(program, sourceFile, position, exportInfos, host, preferences)).moduleSpecifier;
-        const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, host, preferences);
+        const compilerOptions = program.getCompilerOptions();
+        const exportInfos = getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, sourceFile, compilerOptions, program.getTypeChecker(), program.getSourceFiles());
+        const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
+        const moduleSpecifier = first(getNewImportInfos(program, sourceFile, position, preferTypeOnlyImport, exportInfos, host, preferences)).moduleSpecifier;
+        const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, preferTypeOnlyImport, host, preferences);
         return { moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) };
     }
 
-    function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, symbolName: string, program: Program, position: number | undefined, host: LanguageServiceHost, preferences: UserPreferences) {
+    function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, symbolName: string, program: Program, position: number | undefined, preferTypeOnlyImport: boolean, host: LanguageServiceHost, preferences: UserPreferences) {
         Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol), "Some exportInfo should match the specified moduleSymbol");
         // We sort the best codefixes first, so taking `first` is best.
-        return first(getFixForImport(exportInfos, symbolName, position, program, sourceFile, host, preferences));
+        return first(getFixForImport(exportInfos, symbolName, position, preferTypeOnlyImport, program, sourceFile, host, preferences));
     }
 
     function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction {
@@ -244,6 +252,7 @@ namespace ts.codefix {
         symbolName: string,
         /** undefined only for missing JSX namespace */
         position: number | undefined,
+        preferTypeOnlyImport: boolean,
         program: Program,
         sourceFile: SourceFile,
         host: LanguageServiceHost,
@@ -254,7 +263,7 @@ namespace ts.codefix {
         const useNamespace = position === undefined ? undefined : tryUseExistingNamespaceImport(existingImports, symbolName, position, checker);
         const addToExisting = tryAddToExistingImport(existingImports, position !== undefined && isTypeOnlyPosition(sourceFile, position));
         // Don't bother providing an action to add a new import if we can add to an existing one.
-        const addImport = addToExisting ? [addToExisting] : getFixesForAddImport(exportInfos, existingImports, program, sourceFile, position, host, preferences);
+        const addImport = addToExisting ? [addToExisting] : getFixesForAddImport(exportInfos, existingImports, program, sourceFile, position, preferTypeOnlyImport, host, preferences);
         return [...(useNamespace ? [useNamespace] : emptyArray), ...addImport];
     }
 
@@ -317,6 +326,7 @@ namespace ts.codefix {
         program: Program,
         sourceFile: SourceFile,
         position: number | undefined,
+        preferTypeOnlyImport: boolean,
         moduleSymbols: readonly SymbolExportInfo[],
         host: LanguageServiceHost,
         preferences: UserPreferences,
@@ -330,7 +340,7 @@ namespace ts.codefix {
                     // `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types.
                     exportedSymbolIsTypeOnly && isJs
                         ? { kind: ImportFixKind.ImportType, moduleSpecifier, position: Debug.assertDefined(position, "position should be defined") }
-                        : { kind: ImportFixKind.AddNew, moduleSpecifier, importKind }));
+                        : { kind: ImportFixKind.AddNew, moduleSpecifier, importKind, typeOnly: preferTypeOnlyImport }));
 
         // Sort by presence in package.json, then shortest paths first
         return sort(choicesForEachExportingModule, (a, b) => {
@@ -352,21 +362,22 @@ namespace ts.codefix {
         program: Program,
         sourceFile: SourceFile,
         position: number | undefined,
+        preferTypeOnlyImport: boolean,
         host: LanguageServiceHost,
         preferences: UserPreferences,
     ): readonly (FixAddNewImport | FixUseImportType)[] {
-        const existingDeclaration = firstDefined(existingImports, newImportInfoFromExistingSpecifier);
-        return existingDeclaration ? [existingDeclaration] : getNewImportInfos(program, sourceFile, position, exportInfos, host, preferences);
+        const existingDeclaration = firstDefined(existingImports, info => newImportInfoFromExistingSpecifier(info, preferTypeOnlyImport));
+        return existingDeclaration ? [existingDeclaration] : getNewImportInfos(program, sourceFile, position, preferTypeOnlyImport, exportInfos, host, preferences);
     }
 
-    function newImportInfoFromExistingSpecifier({ declaration, importKind }: FixAddToExistingImportInfo): FixAddNewImport | undefined {
+    function newImportInfoFromExistingSpecifier({ declaration, importKind }: FixAddToExistingImportInfo, preferTypeOnlyImport: boolean): FixAddNewImport | undefined {
         const expression = declaration.kind === SyntaxKind.ImportDeclaration
             ? declaration.moduleSpecifier
             : declaration.moduleReference.kind === SyntaxKind.ExternalModuleReference
                 ? declaration.moduleReference.expression
                 : undefined;
         return expression && isStringLiteral(expression)
-            ? { kind: ImportFixKind.AddNew, moduleSpecifier: expression.text, importKind }
+            ? { kind: ImportFixKind.AddNew, moduleSpecifier: expression.text, importKind, typeOnly: preferTypeOnlyImport }
             : undefined;
     }
 
@@ -386,7 +397,7 @@ namespace ts.codefix {
         const symbol = checker.getAliasedSymbol(umdSymbol);
         const symbolName = umdSymbol.name;
         const exportInfos: readonly SymbolExportInfo[] = [{ moduleSymbol: symbol, importKind: getUmdImportKind(sourceFile, program.getCompilerOptions()), exportedSymbolIsTypeOnly: false }];
-        const fixes = getFixForImport(exportInfos, symbolName, isIdentifier(token) ? token.getStart(sourceFile) : undefined, program, sourceFile, host, preferences);
+        const fixes = getFixForImport(exportInfos, symbolName, isIdentifier(token) ? token.getStart(sourceFile) : undefined, /*preferTypeOnlyImport*/ false, program, sourceFile, host, preferences);
         return { fixes, symbolName };
     }
     function getUmdSymbol(token: Node, checker: TypeChecker): Symbol | undefined {
@@ -440,9 +451,10 @@ namespace ts.codefix {
         // "default" is a keyword and not a legal identifier for the import, so we don't expect it here
         Debug.assert(symbolName !== InternalSymbolName.Default, "'default' isn't a legal identifier and couldn't occur here");
 
+        const preferTypeOnlyImport = program.getCompilerOptions().importsNotUsedAsValues === ImportsNotUsedAsValues.Error && isValidTypeOnlyAliasUseSite(symbolToken);
         const exportInfos = getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, checker, program, host);
         const fixes = arrayFrom(flatMapIterator(exportInfos.entries(), ([_, exportInfos]) =>
-            getFixForImport(exportInfos, symbolName, symbolToken.getStart(sourceFile), program, sourceFile, host, preferences)));
+            getFixForImport(exportInfos, symbolName, symbolToken.getStart(sourceFile), preferTypeOnlyImport, program, sourceFile, host, preferences)));
         return { fixes, symbolName };
     }
 
@@ -576,10 +588,10 @@ namespace ts.codefix {
                 return [importKind === ImportKind.Default ? Diagnostics.Add_default_import_0_to_existing_import_declaration_from_1 : Diagnostics.Add_0_to_existing_import_declaration_from_1, symbolName, moduleSpecifierWithoutQuotes]; // you too!
             }
             case ImportFixKind.AddNew: {
-                const { importKind, moduleSpecifier } = fix;
-                addNewImports(changes, sourceFile, moduleSpecifier, quotePreference, importKind === ImportKind.Default ? { defaultImport: symbolName, namedImports: emptyArray, namespaceLikeImport: undefined }
-                    : importKind === ImportKind.Named ? { defaultImport: undefined, namedImports: [symbolName], namespaceLikeImport: undefined }
-                    : { defaultImport: undefined, namedImports: emptyArray, namespaceLikeImport: { importKind, name: symbolName } }, /*blankLineBetween*/ true);
+                const { importKind, moduleSpecifier, typeOnly } = fix;
+                addNewImports(changes, sourceFile, moduleSpecifier, quotePreference, importKind === ImportKind.Default ? { defaultImport: symbolName, namedImports: emptyArray, namespaceLikeImport: undefined, typeOnly }
+                    : importKind === ImportKind.Named ? { defaultImport: undefined, namedImports: [symbolName], namespaceLikeImport: undefined, typeOnly }
+                    : { defaultImport: undefined, namedImports: emptyArray, namespaceLikeImport: { importKind, name: symbolName }, typeOnly }, /*blankLineBetween*/ true);
                 return [importKind === ImportKind.Default ? Diagnostics.Import_default_0_from_module_1 : Diagnostics.Import_0_from_module_1, symbolName, moduleSpecifier];
             }
             default:
@@ -633,6 +645,7 @@ namespace ts.codefix {
     }
 
     interface ImportsCollection {
+        readonly typeOnly: boolean;
         readonly defaultImport: string | undefined;
         readonly namedImports: string[];
         readonly namespaceLikeImport: {
@@ -640,13 +653,13 @@ namespace ts.codefix {
             readonly name: string;
         } | undefined;
     }
-    function addNewImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, moduleSpecifier: string, quotePreference: QuotePreference, { defaultImport, namedImports, namespaceLikeImport }: ImportsCollection, blankLineBetween: boolean): void {
+    function addNewImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, moduleSpecifier: string, quotePreference: QuotePreference, { defaultImport, namedImports, namespaceLikeImport, typeOnly }: ImportsCollection, blankLineBetween: boolean): void {
         const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference);
         if (defaultImport !== undefined || namedImports.length) {
             insertImport(changes, sourceFile,
                 makeImport(
                     defaultImport === undefined ? undefined : createIdentifier(defaultImport),
-                    namedImports.map(n => createImportSpecifier(/*propertyName*/ undefined, createIdentifier(n))), moduleSpecifier, quotePreference), /*blankLineBetween*/ blankLineBetween);
+                    namedImports.map(n => createImportSpecifier(/*propertyName*/ undefined, createIdentifier(n))), moduleSpecifier, quotePreference, typeOnly), /*blankLineBetween*/ blankLineBetween);
         }
         if (namespaceLikeImport) {
             insertImport(
@@ -654,7 +667,7 @@ namespace ts.codefix {
                 sourceFile,
                 namespaceLikeImport.importKind === ImportKind.Equals ? createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createIdentifier(namespaceLikeImport.name), createExternalModuleReference(quotedModuleSpecifier)) :
                 namespaceLikeImport.importKind === ImportKind.ConstEquals ? createConstEqualsRequireDeclaration(namespaceLikeImport.name, quotedModuleSpecifier) :
-                createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createImportClause(/*name*/ undefined, createNamespaceImport(createIdentifier(namespaceLikeImport.name))), quotedModuleSpecifier), /*blankLineBetween*/ blankLineBetween);
+                createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createImportClause(/*name*/ undefined, createNamespaceImport(createIdentifier(namespaceLikeImport.name)), typeOnly), quotedModuleSpecifier), /*blankLineBetween*/ blankLineBetween);
         }
     }
 
diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts
index fb9e885302aea..b98bef72332c9 100644
--- a/src/services/codefixes/inferFromUsage.ts
+++ b/src/services/codefixes/inferFromUsage.ts
@@ -319,7 +319,7 @@ namespace ts.codefix {
     function tryReplaceImportTypeNodeWithAutoImport(typeNode: TypeNode, changes: textChanges.ChangeTracker, importAdder: ImportAdder, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type, scriptTarget: ScriptTarget): boolean {
         const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget);
         if (importableReference && changes.tryInsertTypeAnnotation(sourceFile, declaration, importableReference.typeReference)) {
-            forEach(importableReference.symbols, importAdder.addImportFromExportedSymbol);
+            forEach(importableReference.symbols, s => importAdder.addImportFromExportedSymbol(s, /*usageIsTypeOnly*/ true));
             return true;
         }
         return false;
diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports2.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports2.ts
new file mode 100644
index 0000000000000..9f261a72c2aed
--- /dev/null
+++ b/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports2.ts
@@ -0,0 +1,42 @@
+/// <reference path='fourslash.ts' />
+
+// @importsNotUsedAsValues: error
+
+// @Filename: types1.ts
+////type A = {};
+////export default A;
+
+// @Filename: types2.ts
+////export type B = {};
+////export type C = {};
+////export type D<T> = {};
+
+// @Filename: interface.ts
+////import type A from './types1';
+////import type { B, C, D } from './types2';
+////
+////export interface Base {
+////  a: A;
+////  b<T extends B = B>(p1: C): D<C>;
+////}
+
+// @Filename: index.ts
+////import type { Base } from './interface';
+////
+////export class C implements Base {[| |]}
+
+goTo.file('index.ts');
+verify.codeFix({
+  description: "Implement interface 'Base'",
+  newFileContent:
+`import type { Base } from './interface';
+import type A from './types1';
+import type { B, C, D } from './types2';
+
+export class C implements Base {
+    a: A;
+    b<T extends B = B>(p1: C): D<C> {
+        throw new Error("Method not implemented.");
+    }
+}`,
+});
diff --git a/tests/cases/fourslash/importNameCodeFix_typeOnly.ts b/tests/cases/fourslash/importNameCodeFix_typeOnly.ts
new file mode 100644
index 0000000000000..ad16ea4203229
--- /dev/null
+++ b/tests/cases/fourslash/importNameCodeFix_typeOnly.ts
@@ -0,0 +1,16 @@
+/// <reference path="fourslash.ts" />
+
+// @importsNotUsedAsValues: error
+
+// @Filename: types.ts
+////export class A {}
+
+// @Filename: index.ts
+////const a: /**/A
+
+goTo.marker("");
+verify.importFixAtPosition([
+`import type { A } from "./types";
+
+const a: A`
+]);

From 0354402ca50a0a623fb77b7d602bc52e0122569c Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Tue, 4 Feb 2020 14:36:15 -0800
Subject: [PATCH 7/8] Add another test

---
 .../fourslash/importNameCodeFix_typeOnly2.ts  | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
 create mode 100644 tests/cases/fourslash/importNameCodeFix_typeOnly2.ts

diff --git a/tests/cases/fourslash/importNameCodeFix_typeOnly2.ts b/tests/cases/fourslash/importNameCodeFix_typeOnly2.ts
new file mode 100644
index 0000000000000..534275fd6e3c2
--- /dev/null
+++ b/tests/cases/fourslash/importNameCodeFix_typeOnly2.ts
@@ -0,0 +1,19 @@
+/// <reference path="fourslash.ts" />
+
+// @importsNotUsedAsValues: error
+
+// @Filename: types.ts
+////export class A {}
+
+// @Filename: index.ts
+////const a: A = new A();
+
+goTo.file("index.ts");
+verify.codeFixAll({
+  fixAllDescription: ts.Diagnostics.Add_all_missing_imports.message,
+  fixId: "fixMissingImport",
+  newFileContent:
+`import { A } from "./types";
+
+const a: A = new A();`
+});

From 7de6c0346270caf46607cc5978e197651bca7b5a Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Mon, 10 Feb 2020 13:46:01 -0800
Subject: [PATCH 8/8] Rename stuff

---
 src/services/codefixes/helpers.ts                    |  6 +++++-
 src/services/codefixes/inferFromUsage.ts             | 12 ++++++++++--
 src/services/utilities.ts                            |  1 +
 ... => codeFixClassImplementInterfaceAutoImports.ts} |  0
 ...ixClassImplementInterfaceAutoImports_typeOnly.ts} |  0
 .../codeFixInferFromUsageContextualImport4.ts        | 10 +++++++---
 6 files changed, 23 insertions(+), 6 deletions(-)
 rename tests/cases/fourslash/{codeFixClassImplementInterfaceAutoImports1.ts => codeFixClassImplementInterfaceAutoImports.ts} (100%)
 rename tests/cases/fourslash/{codeFixClassImplementInterfaceAutoImports2.ts => codeFixClassImplementInterfaceAutoImports_typeOnly.ts} (100%)

diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts
index 73c67009c3e64..b2e05fab06c3f 100644
--- a/src/services/codefixes/helpers.ts
+++ b/src/services/codefixes/helpers.ts
@@ -415,9 +415,13 @@ namespace ts.codefix {
         return find(obj.properties, (p): p is PropertyAssignment => isPropertyAssignment(p) && !!p.name && isStringLiteral(p.name) && p.name.text === name);
     }
 
+    /**
+     * Given an ImportTypeNode 'import("./a").SomeType<import("./b").OtherType<...>>',
+     * returns an equivalent type reference node with any nested ImportTypeNodes also replaced
+     * with type references, and a list of symbols that must be imported to use the type reference.
+     */
     export function tryGetAutoImportableReferenceFromImportTypeNode(importTypeNode: TypeNode | undefined, type: Type | undefined, scriptTarget: ScriptTarget) {
         if (importTypeNode && isLiteralImportTypeNode(importTypeNode) && importTypeNode.qualifier && (!type || type.symbol)) {
-            // Replace 'import("./a").SomeType' with 'SomeType' and an actual import if possible
             // Symbol for the left-most thing after the dot
             const firstIdentifier = getFirstIdentifier(importTypeNode.qualifier);
             const name = getNameForExportedSymbol(firstIdentifier.symbol, scriptTarget);
diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts
index b98bef72332c9..888128834046c 100644
--- a/src/services/codefixes/inferFromUsage.ts
+++ b/src/services/codefixes/inferFromUsage.ts
@@ -310,13 +310,21 @@ namespace ts.codefix {
                 const typeTag = isGetAccessorDeclaration(declaration) ? createJSDocReturnTag(typeExpression, "") : createJSDocTypeTag(typeExpression, "");
                 addJSDocTags(changes, sourceFile, parent, [typeTag]);
             }
-            else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, changes, importAdder, sourceFile, declaration, type, getEmitScriptTarget(program.getCompilerOptions()))) {
+            else if (!tryReplaceImportTypeNodeWithAutoImport(typeNode, declaration, type, sourceFile, changes, importAdder, getEmitScriptTarget(program.getCompilerOptions()))) {
                 changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode);
             }
         }
     }
 
-    function tryReplaceImportTypeNodeWithAutoImport(typeNode: TypeNode, changes: textChanges.ChangeTracker, importAdder: ImportAdder, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type, scriptTarget: ScriptTarget): boolean {
+    function tryReplaceImportTypeNodeWithAutoImport(
+        typeNode: TypeNode,
+        declaration: textChanges.TypeAnnotatable,
+        type: Type,
+        sourceFile: SourceFile,
+        changes: textChanges.ChangeTracker,
+        importAdder: ImportAdder,
+        scriptTarget: ScriptTarget
+    ): boolean {
         const importableReference = tryGetAutoImportableReferenceFromImportTypeNode(typeNode, type, scriptTarget);
         if (importableReference && changes.tryInsertTypeAnnotation(sourceFile, declaration, importableReference.typeReference)) {
             forEach(importableReference.symbols, s => importAdder.addImportFromExportedSymbol(s, /*usageIsTypeOnly*/ true));
diff --git a/src/services/utilities.ts b/src/services/utilities.ts
index afde7fb6fead5..d94106486446d 100644
--- a/src/services/utilities.ts
+++ b/src/services/utilities.ts
@@ -2750,6 +2750,7 @@ namespace ts {
 
     export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget) {
         if (symbol.escapedName === InternalSymbolName.ExportEquals || symbol.escapedName === InternalSymbolName.Default) {
+            // Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase.
             return firstDefined(symbol.declarations, d => isExportAssignment(d) && isIdentifier(d.expression) ? d.expression.text : undefined)
                 || codefix.moduleSymbolToValidIdentifier(Debug.assertDefined(symbol.parent), scriptTarget);
         }
diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports1.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports.ts
similarity index 100%
rename from tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports1.ts
rename to tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports.ts
diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports2.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports_typeOnly.ts
similarity index 100%
rename from tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports2.ts
rename to tests/cases/fourslash/codeFixClassImplementInterfaceAutoImports_typeOnly.ts
diff --git a/tests/cases/fourslash/codeFixInferFromUsageContextualImport4.ts b/tests/cases/fourslash/codeFixInferFromUsageContextualImport4.ts
index fb97c99ef314d..fbb6336a869b0 100644
--- a/tests/cases/fourslash/codeFixInferFromUsageContextualImport4.ts
+++ b/tests/cases/fourslash/codeFixInferFromUsageContextualImport4.ts
@@ -4,14 +4,17 @@
 // @noImplicitAny: true
 // @noLib: true
 
-// @Filename: /types.d.ts
-////declare function getEmail(user: import('./a').User, settings: import('./a').Settings): string;
+// @Filename: /getEmail.ts
+////import { User, Settings } from './a';
+////export declare function getEmail(user: User, settings: Settings): string;
 
 // @Filename: /a.ts
 ////export interface User {}
 ////export interface Settings {}
 
 // @Filename: /b.ts
+////import { getEmail } from './getEmail';
+////
 ////export function f([|user|], settings) {
 ////    getEmail(user, settings);
 ////}
@@ -22,7 +25,8 @@ verify.codeFix({
   index: 0,
   description: "Infer parameter types from usage",
   newFileContent:
-`import { User, Settings } from "./a";
+`import { getEmail } from './getEmail';
+import { User, Settings } from './a';
 
 export function f(user: User, settings: Settings) {
     getEmail(user, settings);