Skip to content

Commit 5280d23

Browse files
author
Andy
authored
importNameCodeFix: consistently put fixes to use existing imports before fixes for existing imports (microsoft#23663)
1 parent 1f59e6f commit 5280d23

File tree

2 files changed

+51
-12
lines changed

2 files changed

+51
-12
lines changed

src/services/codefixes/importFixes.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ namespace ts.codefix {
119119
}
120120

121121
function getCodeActionsForImport(exportInfos: ReadonlyArray<SymbolExportInfo>, context: ImportCodeFixContext): CodeFixAction[] {
122+
const result: CodeFixAction[] = [];
123+
getCodeActionsForImport_separateExistingAndNew(exportInfos, context, result, result);
124+
return result;
125+
}
126+
127+
function getCodeActionsForImport_separateExistingAndNew(exportInfos: ReadonlyArray<SymbolExportInfo>, context: ImportCodeFixContext, useExisting: Push<CodeFixAction>, addNew: Push<CodeFixAction>): void {
122128
const existingImports = flatMap(exportInfos, info =>
123129
getImportDeclarations(info, context.checker, context.sourceFile, context.cachedImportDeclarations));
124130
// It is possible that multiple import statements with the same specifier exist in the file.
@@ -133,16 +139,18 @@ namespace ts.codefix {
133139
// 1. change "member3" to "ns.member3"
134140
// 2. add "member3" to the second import statement's import list
135141
// and it is up to the user to decide which one fits best.
136-
const useExistingImportActions = !context.symbolToken || !isIdentifier(context.symbolToken) ? emptyArray : mapDefined(existingImports, ({ declaration }) => {
137-
const namespace = getNamespaceImportName(declaration);
138-
if (namespace) {
139-
const moduleSymbol = context.checker.getAliasedSymbol(context.checker.getSymbolAtLocation(namespace));
140-
if (moduleSymbol && moduleSymbol.exports.has(escapeLeadingUnderscores(context.symbolName))) {
141-
return getCodeActionForUseExistingNamespaceImport(namespace.text, context, context.symbolToken as Identifier);
142+
if (context.symbolToken && isIdentifier(context.symbolToken)) {
143+
for (const { declaration } of existingImports) {
144+
const namespace = getNamespaceImportName(declaration);
145+
if (namespace) {
146+
const moduleSymbol = context.checker.getAliasedSymbol(context.checker.getSymbolAtLocation(namespace));
147+
if (moduleSymbol && moduleSymbol.exports.has(escapeLeadingUnderscores(context.symbolName))) {
148+
useExisting.push(getCodeActionForUseExistingNamespaceImport(namespace.text, context, context.symbolToken));
149+
}
142150
}
143151
}
144-
});
145-
return [...useExistingImportActions, ...getCodeActionsForAddImport(exportInfos, context, existingImports)];
152+
}
153+
getCodeActionsForAddImport(exportInfos, context, existingImports, useExisting, addNew);
146154
}
147155

148156
function getNamespaceImportName(declaration: AnyImportSyntax): Identifier | undefined {
@@ -551,7 +559,9 @@ namespace ts.codefix {
551559
exportInfos: ReadonlyArray<SymbolExportInfo>,
552560
ctx: ImportCodeFixContext,
553561
existingImports: ReadonlyArray<ExistingImportInfo>,
554-
): CodeFixAction[] {
562+
useExisting: Push<CodeFixAction>,
563+
addNew: Push<CodeFixAction>,
564+
): void {
555565
const fromExistingImport = firstDefined(existingImports, ({ declaration, importKind }) => {
556566
if (declaration.kind === SyntaxKind.ImportDeclaration && declaration.importClause) {
557567
const changes = tryUpdateExistingImport(ctx, isImportClause(declaration.importClause) && declaration.importClause || undefined, importKind);
@@ -562,14 +572,17 @@ namespace ts.codefix {
562572
}
563573
});
564574
if (fromExistingImport) {
565-
return [fromExistingImport];
575+
useExisting.push(fromExistingImport);
576+
return;
566577
}
567578

568579
const existingDeclaration = firstDefined(existingImports, newImportInfoFromExistingSpecifier);
569580
const newImportInfos = existingDeclaration
570581
? [existingDeclaration]
571582
: getNewImportInfos(ctx.program, ctx.sourceFile, exportInfos, ctx.compilerOptions, ctx.getCanonicalFileName, ctx.host, ctx.preferences);
572-
return newImportInfos.map(info => getCodeActionForNewImport(ctx, info));
583+
for (const info of newImportInfos) {
584+
addNew.push(getCodeActionForNewImport(ctx, info));
585+
}
573586
}
574587

575588
function newImportInfoFromExistingSpecifier({ declaration, importKind }: ExistingImportInfo): NewImportInfo | undefined {
@@ -760,7 +773,12 @@ namespace ts.codefix {
760773
}
761774
});
762775

763-
return arrayFrom(flatMapIterator(originalSymbolToExportInfos.values(), exportInfos => getCodeActionsForImport(exportInfos, convertToImportCodeFixContext(context, symbolToken, symbolName))));
776+
const addToExistingDeclaration: CodeFixAction[] = [];
777+
const addNewDeclaration: CodeFixAction[] = [];
778+
originalSymbolToExportInfos.forEach(exportInfos => {
779+
getCodeActionsForImport_separateExistingAndNew(exportInfos, convertToImportCodeFixContext(context, symbolToken, symbolName), addToExistingDeclaration, addNewDeclaration);
780+
});
781+
return [...addToExistingDeclaration, ...addNewDeclaration];
764782
}
765783

766784
function checkSymbolHasMeaning({ declarations }: Symbol, meaning: SemanticMeaning): boolean {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @Filename: /a.ts
4+
////export const foo: number;
5+
6+
// @Filename: /b.ts
7+
////export const foo: number;
8+
////export const bar: number;
9+
10+
// @Filename: /c.ts
11+
////[|import { bar } from "./b";
12+
////foo;|]
13+
14+
goTo.file("/c.ts");
15+
verify.importFixAtPosition([
16+
`import { bar, foo } from "./b";
17+
foo;`,
18+
`import { bar } from "./b";
19+
import { foo } from "./a";
20+
foo;`,
21+
]);

0 commit comments

Comments
 (0)